How do I implement a `FirAdditionalCheckersExtensi...
# compiler
m
How do I implement a
FirAdditionalCheckersExtension
?
m
Copy code
class 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.
d
You need to add something like this to the
MyFirAdditionalCheckersExtension
.
Copy code
override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
    override val regularClassCheckers: Set<FirRegularClassChecker>
        get() = setOf(MyFirRegularClassChecker)
}
Why do you need a companion object with
new
function? The
MppCheckerKind
is a property of each specific checker, not something passed from above. You can read more about it here.
m
MyFirAdditionalCheckersExtension.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?
d
This would be enough
Copy code
class MyFirAdditionalCheckersExtension(session: FirSession) : FirAdditionalCheckersExtension(session) {
    override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
        override val regularClassCheckers: Set<FirRegularClassChecker>
            get() = setOf(MyFirRegularClassChecker)
    }
}
m
Huh? Somebody has to pass me a
MppCheckerKind
, don’t they?
Copy code
private class MyFirRegularClassChecker(mppKind: MppCheckerKind)
        : FirRegularClassChecker(mppKind)
I cannot extend
FirRegularClassChecker
without providing an instance, can I?
d
Yes, and that's you who decides what kind to pass
Copy code
object MyFirRegularClassChecker
 : FirRegularClassChecker(MppCheckerKind.Common) {...}
// or
object MyFirRegularClassChecker
 : FirRegularClassChecker(MppCheckerKind.Platform) {...}
m
So for declarations (
expect
) with multiple definitions (
actual
) that are each specific to a certain target, I can use
MppCheckerKind.Platform
to inspect each
actual
definition?
Wouldn't you always want that? When would you use
MppCheckerKind.Common
? When that is positively really not necessary, for performance gains?
d
No, it's a more compilcated. Any checker would be run on every declaration regardless of its kind. The question is with which session it would run:
Copy code
// 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
.
For you, I think, it would be fine just to stick with
Platform
and don't bother about it much.
m
Copy code
class 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!
👍 2
👍🏾 1