utopia-rise / godot-kotlin-jvm

Godot Kotlin JVM Module
MIT License
646 stars 45 forks source link

Look into K2's FIR #326

Open chippmann opened 2 years ago

chippmann commented 2 years ago

This is a documentation issue for evaluating and researching the changes we need to make to our godot-kotlin-symbol-processor and godot-intellij-plugin to make them ready for the new kotlin kompiler named K2. We need to do this ASAP because as of kotlin version 1.7.0 the new kompiler is already in preview phase.

If we don't have this ready by the time the new compiler becomes stable and the default, our default value extraction for properties will most probably not work anymore as they heavily rely on PSI (Program Structure Interface) for extracting the initialization expression's from the users source code.

K2 will use FIR (Frontend Intermediate Representation) when parsing the source code and no PSI anymore. Hence this might be a good starting point for our investigations.

Disclaimer: I'm not yet 100% sure if FIR really is a replacement for PSI or just the new intermediate representation for compiler plugins which run in the frontend stages. Either way: we need to look into it in more detail.

Some links for the topic: [Blog Post] Road to the K2 compiler PSI Documentation [Youtrack Issue] Prototype the IDE plugin with the new compiler frontend [Youtrack Issue] Stabilize the K2 Compiler Plugin API [Youtrack Issue] Release K2 Beta

chippmann commented 2 years ago

@raniejade Maybe you have some insights here as well?

raniejade commented 2 years ago

From what I can dig up, we can see the preview to the new compiler plugin api around 1.8. They are building a prototype of the serialization plugin using the new API and it is targeted for 1.8.0.

chippmann commented 1 year ago

Probably no longer necessary after the merge of https://github.com/utopia-rise/godot-kotlin-jvm/pull/437.

Still we need to look into this as the signal parameter names are still extracted with PSI. But they are at least not critical

marcopennekamp commented 1 year ago

Hey. FIR isn't a replacement for PSI, rather the Kotlin compiler first parses the source code to PSI with a parser that's common for K1 and K2, and then the FIR tree gets built from the PSI. FIR is the new frontend intermediate representation used by the compiler frontend as the main AST.

What's being removed in K2 are descriptors, binding contexts, etc., basically the old semantic mapping of K1. If you're only doing syntax analysis with the PSI, maybe porting your code might not need much work.

If you need to do semantic analysis, for K2 we're introducing the Analysis API: https://github.com/JetBrains/kotlin/blob/master/docs/analysis/analysis-api/analysis-api.md

If you point me to the files that might be affected, I can take a look.

chippmann commented 1 year ago

Hey @marcopennekamp Thanks a lot for the quick sum up. I think we're then no longer really affected by this change in regard to our symbol processor. There since #437 we only extract some parameter names of our signal property delegates in PsiProvider.

The IDE plugin checks for core type copy assignment on the other hand, will probably require some rework then. Assuming these changes also affect IDE plugin annotators dealing with type resolving on the psi classes?

Thanks a lot for your insights and offer to have a look at the snippets in question. I'll have a look at this new analysis api then soon. Thanks for that link. This new analysis api will also be part of the new ide plugin system in the sense that it will be as easily accessible as psi from within, let's say, an annotator i presume?

marcopennekamp commented 1 year ago

I think we're then no longer really affected by this change in regard to our symbol processor. There since https://github.com/utopia-rise/godot-kotlin-jvm/pull/437 we only extract some parameter names of our signal property delegates in PsiProvider.

Yep, looks like it will work with K2 as well. 👍

The IDE plugin checks for core type copy assignment on the other hand, will probably require some rework then.

Yes, specifically K1 analyze (org.jetbrains.kotlin.idea.caches.resolve.analyze) calls will have to be ported, and any time that BindingContext is used. The Analysis API equivalent is also called analyze.

For example, to replace resolveTypeSafe from CopyModificationAnnotator, you can use the getKtType function for expressions. You'll have to work with KtTypes instead of KotlinTypes then, of course. isCoreTypeCopyAssignment can be ported to something like:

private val dictionaryClassId = ClassId(godotCorePackage, GodotTypes.dictionary)
private val variantArrayClassId = ClassId(godotCorePackage, GodotKotlinJvmTypes.variantArray)
private val coreTypeHelperAnnotationClassId = ClassId(godotAnnotationPackage, GodotKotlinJvmTypes.Annotations.coreTypeHelper)

private fun isCoreTypeAssignment(element: PsiElement): Boolean = when (element) {
    is KtBinaryExpression -> TODO()
    is KtPostfixExpression -> TODO()
    is KtDotQualifiedExpression -> {
        analyze(element) {
            val receiverType = element.receiverExpression.getKtType() ?: return false
            val classId = (receiverType as? KtNonErrorClassType)?.classId ?: return false

            if (classId == dictionaryClassId || classId == variantArrayClassId || !classId.isCoreType()) {
                return false
            }

            val calleeSymbol =
                ((element.selectorExpression as? KtCallExpression)?.calleeExpression as? KtReferenceExpression)?.mainReference
                    ?.resolveToSymbol()
                    as? KtAnnotatedSymbol
                    ?: return false

            return calleeSymbol.hasAnnotation(coreTypeHelperAnnotationClassId)
        }
    }
}

private fun ClassId.isCoreType() = coreTypes.contains(asFqNameString())

It's important to note that KtSymbols and KtTypes (and other lifetime owners) shouldn't leak from the analyze call, so something like this would be wrong:

private fun KtExpression.resolveTypeSafe(): KtType? = analyze(this) { getKtType() }

Rather, the KtAnalysisSession should be passed around as a context receiver:

context(KtAnalysisSession)
private fun KtExpression.resolveTypeSafe(): KtType? = getKtType()

The Analysis API has an implementation for the K1 compiler, so you might actually try to port your code soon if you're intrigued, especially if your resolution needs are relatively simple. Probably setting it up in your existing plugin would be the biggest hurdle, but I'm not sure about the amount of setup required for third-party plugins. The Analysis API analyze call might even work out of the box. (We are using the Analysis API in a very limited capacity in the K1 plugin.)

That said, the API is not stable yet and we have to improve the documentation as well, so we might break your code in the future. I don't think it'll be a big issue, but this should be considered before deciding to move to the Analysis API.

Assuming these changes also affect IDE plugin annotators dealing with type resolving on the psi classes?

Yep, anything that uses the old analyze, bindings contexts, descriptors, etc. will be affected.

This new analysis api will also be part of the new ide plugin system in the sense that it will be as easily accessible as psi from within, let's say, an annotator i presume?

It should be. Let me know if you run into any issues though.

Thanks a lot for your insights and offer to have a look at the snippets in question.

My pleasure! I was actually interested in trying out Godot with Kotlin, so I'm happy to add my expertise.

Btw, if you decide to check out the Analysis API and encounter any usability issues, please let me know. Getting feedback from someone with a fresh perspective is always great. 😄