min
09/17/2026, 8:13 AMFirAdditionalCheckersExtension?min
09/17/2026, 8:15 AMclass MyFirAdditionalCheckersExtension private constructor(
session: FirSession,
override val declarationCheckers: DeclarationCheckers,
): FirAdditionalCheckersExtension(session) {
companion object {
fun new(
session: FirSession,
mppKind: MppCheckerKind,
): MyFirAdditionalCheckersExtension {
return MyFirAdditionalCheckersExtension(
session = session,
declarationCheckers = object: DeclarationCheckers() {
override val regularClassCheckers = setOf(
MyFirRegularClassChecker(mppKind),
)
},
)
}
}
private class MyFirRegularClassChecker(mppKind: MppCheckerKind)
: FirRegularClassChecker(mppKind)
{
context(context: CheckerContext, reporter: DiagnosticReporter)
override fun check(declaration: FirRegularClass) {
when (val error = MyAnnotation.on(
maybeMydClass = declaration.symbol,
session = context.session,
)
.asBar() ?: return
) {
is MyAnnotation.FactoryError.ClassNotAnnotated -> reporter.reportOn(
error.decl.source,
MyDiagnosticsContainer.CLASS_NOT_ANNOTATED,
context,
)
is MyAnnotation.FactoryError.ArgumentMissing -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnosticsContainer.ARGUMENT_MISSING,
context,
)
is MyAnnotation.FactoryError.ArgumentInvalid -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnosticsContainer.ARGUMENT_INVALID,
context,
)
is MyAnnotation.FactoryError.PrimaryConstructorMissing -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnosticsContainer.PRIMARY_CONSTRUCTOR_MISSING,
context,
)
is MyAnnotation.FactoryError.PrimaryConstructorExposed -> reporter.reportOn(
error.primaryConstructor.source,
MyDiagnosticsContainer.PRIMARY_CONSTRUCTOR_EXPOSED,
context,
)
}
}
}
private object MyDiagnosticsContainer: KtDiagnosticsContainer() {
val CLASS_NOT_ANNOTATED by error0<KtElement>()
val ARGUMENT_MISSING by error0<KtElement>()
val ARGUMENT_INVALID by error0<KtElement>()
val PRIMARY_CONSTRUCTOR_MISSING by error0<KtElement>()
val PRIMARY_CONSTRUCTOR_EXPOSED by error0<KtElement>()
override fun getRendererFactory(): BaseDiagnosticRendererFactory {
return MyDiagnosticsContainer.GetRendererFactoryOutput
}
object GetRendererFactoryOutput: BaseDiagnosticRendererFactory() {
override val MAP: KtDiagnosticFactoryToRendererMap
by KtDiagnosticFactoryToRendererMap("MyPlugin") { map ->
map.put(MyDiagnosticsContainer.CLASS_NOT_MyD, message = "asdf")
map.put(MyDiagnosticsContainer.ARGUMENT_MISSING, message = "asdf")
map.put(MyDiagnosticsContainer.ARGUMENT_INVALID, message = "asdf")
map.put(MyDiagnosticsContainer.PRIMARY_CONSTRUCTOR_MISSING, message = "asdf")
map.put(MyDiagnosticsContainer.PRIMARY_CONSTRUCTOR_EXPOSED, message = "asdf")
}
}
}
}
I have implemented this much.dmitriy.novozhilov
09/17/2026, 8:17 AMMyFirAdditionalCheckersExtension.
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val regularClassCheckers: Set<FirRegularClassChecker>
get() = setOf(MyFirRegularClassChecker)
}dmitriy.novozhilov
09/17/2026, 8:18 AMnew function?
The MppCheckerKind is a property of each specific checker, not something passed from above. You can read more about it here.min
09/17/2026, 8:18 AMMyFirAdditionalCheckersExtension.Companion.new is the only way in the public API to instantiate the type and it does initialise the declarationCheckers field. What do I do now?dmitriy.novozhilov
09/17/2026, 8:19 AMclass MyFirAdditionalCheckersExtension(session: FirSession) : FirAdditionalCheckersExtension(session) {
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val regularClassCheckers: Set<FirRegularClassChecker>
get() = setOf(MyFirRegularClassChecker)
}
}min
09/17/2026, 8:21 AMMppCheckerKind, don’t they?
private class MyFirRegularClassChecker(mppKind: MppCheckerKind)
: FirRegularClassChecker(mppKind)
I cannot extend FirRegularClassChecker without providing an instance, can I?dmitriy.novozhilov
09/17/2026, 8:22 AMobject MyFirRegularClassChecker
: FirRegularClassChecker(MppCheckerKind.Common) {...}
// or
object MyFirRegularClassChecker
: FirRegularClassChecker(MppCheckerKind.Platform) {...}min
09/17/2026, 8:55 AMexpect) with multiple definitions (actual) that are each specific to a certain target, I can use MppCheckerKind.Platform to inspect each actual definition?min
09/17/2026, 8:56 AMMppCheckerKind.Common? When that is positively really not necessary, for performance gains?dmitriy.novozhilov
09/17/2026, 8:59 AM// common
expect class A
fun foo(x: A) {} // <----
// platform
actual typealias A = String
Imagine you have a simple function checkers, which inspects the function foo.
If it's a Common checker, then it runs with declaration-site session of function foo (so with a session of module common). And if you try to get the class for type A, you'll get a symbol of expect class A.
If it's a Platform checker, then it runs with the session of the leaf module (platform). And for the same request (class symbol for type A) you'll get the actual typealias A which would be expanded to String.dmitriy.novozhilov
09/17/2026, 9:00 AMPlatform and don't bother about it much.min
09/17/2026, 9:17 AMclass MyFirExtensionRegistrar: FirExtensionRegistrar() {
override fun ExtensionRegistrarContext.configurePlugin() {
(::MyFirDeclarationGenerationExtension).unaryPlus()
(MyFirAdditionalCheckersExtension::new).unaryPlus()
}
}
class MyFirAdditionalCheckersExtension private constructor(
session: FirSession,
override val declarationCheckers: DeclarationCheckers,
): FirAdditionalCheckersExtension(session) {
companion object {
fun new(session: FirSession): MyFirAdditionalCheckersExtension {
return MyFirAdditionalCheckersExtension(
session = session,
declarationCheckers = object: DeclarationCheckers() {
override val regularClassCheckers = setOf(
MyFirRegularClassChecker,
)
},
)
}
}
private object MyFirRegularClassChecker
: FirRegularClassChecker(MppCheckerKind.Platform)
{
context(context: CheckerContext, reporter: DiagnosticReporter)
override fun check(declaration: FirRegularClass) {
when (val output = MyAnnotation.on(
maybeAnnotatedClass = declaration.symbol,
session = context.session,
)) {
is Either.Foo -> return
is Either.Bar -> MyDiagnostics.report(
error = output.bar,
reporter = reporter,
context = context,
)
}
}
}
private object MyDiagnostics: KtDiagnosticsContainer() {
val CLASS_NOT_ANNOTATED by error0<KtElement>()
val ARGUMENT_MISSING by error0<KtElement>()
val ARGUMENT_INVALID by error0<KtElement>()
val PRIMARY_CONSTRUCTOR_MISSING by error0<KtElement>()
val PRIMARY_CONSTRUCTOR_EXPOSED by error0<KtElement>()
override fun getRendererFactory(): BaseDiagnosticRendererFactory {
return MyDiagnostics.GetRendererFactoryOutput
}
object GetRendererFactoryOutput: BaseDiagnosticRendererFactory() {
override val MAP: KtDiagnosticFactoryToRendererMap
by KtDiagnosticFactoryToRendererMap("My") { map ->
map.put(MyDiagnostics.CLASS_NOT_ANNOTATED, message = "asdf")
map.put(MyDiagnostics.ARGUMENT_MISSING, message = "asdf")
map.put(MyDiagnostics.ARGUMENT_INVALID, message = "asdf")
map.put(MyDiagnostics.PRIMARY_CONSTRUCTOR_MISSING, message = "asdf")
map.put(MyDiagnostics.PRIMARY_CONSTRUCTOR_EXPOSED, message = "asdf")
}
}
fun report(
error: MyAnnotation.FactoryError,
reporter: DiagnosticReporter,
context: CheckerContext,
) {
when (error) {
is MyAnnotation.FactoryError.ClassNotAnnotated -> reporter.reportOn(
error.decl.source,
MyDiagnostics.CLASS_NOT_ANNOTATED,
context,
)
is MyAnnotation.FactoryError.ArgumentMissing -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnostics.ARGUMENT_MISSING,
context,
)
is MyAnnotation.FactoryError.ArgumentInvalid -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnostics.ARGUMENT_INVALID,
context,
)
is MyAnnotation.FactoryError.PrimaryConstructorMissing -> reporter.reportOn(
error.myAnnotatedClass.source,
MyDiagnostics.PRIMARY_CONSTRUCTOR_MISSING,
context,
)
is MyAnnotation.FactoryError.PrimaryConstructorExposed -> reporter.reportOn(
error.primaryConstructor.source,
MyDiagnostics.PRIMARY_CONSTRUCTOR_EXPOSED,
context,
)
}
}
}
}
Thanks for helping me put this together!