Hi everyone! I have a question about the resolving...
# arrow-meta
a
Hi everyone! I have a question about the resolving step in the kotlin compiler plugin. I try to use arrow meta to develop a plugin, but I am not sure how can I get a fully qualified name. I have found a good example in addition to the hello world example for getting started, but if I right understand it works during the parsing step and I can not get the fully qualified name like
io.arrowkt.example.MyObject
instead of a short one like
MyObject
. I use
dotQualifiedExpression
in meta param. My final goal is to find a dotQualifiedExpression like
MyObject.myFunction
and get all children if it is possible. For example, if I found
MyObject.myFunction
and originally it is 
MyObject.myFunction.children
, I would like to get all children and I would like to be sure it is MyObject from
io.arrowkt.example.MyObject
. Could you help me, please?
i
I hope this mini tutorial helps to navigate through your question. I did not fully understand what you meant with children. 😅 One way to get the FqName of an psiElement in the compiler is that all subtypes of
KtNamedDeclaration
have a member
fqName
, which is the Full qualified name. With that goal in mind, now one can observe the KtDotQualifiedExpression. A screenshot is below - check out the PsiViewer IdePlugin if you’re not using it, yet. It helps to navigate through. From there one can see that a KtDotQualifiedExpression consists of an referenceExpression and a callExpression in the API one can access them like this :
Copy code
quote(this, yourPredicate) { expression: KtDotQualifiedExpression ->
           val referenceExpression = expression.receiverExpression
val callExpression = 
expression.selectorExpression
}
from here there are multiple ways one can go to any Subtype
A
of KtNamedDeclaration: That is, for instance in this code example
Copy code
myProperty().value
Let’s say
myProperty
is indeed a
KtProperty
- but we don’t know that when we write the quote. Then we can get the fqName (in a rather quick and dirty fashion like this)
Copy code
expression.receiverExpression.safeAs<KtCallExpression>()?.calleeExpression.containingDeclarationForPseudocode?.safeAs<KtNamedDeclaration>()?.fqName
When we write the quote we don’t know for sure if it is a KtProperty or something else, but once we get to the Declaration we are assured. If you take a small code snippet and run it in debug-mode that should help, also. Setting a debug point in your quote. Don’t take the example above as a good solution it’s just what I came up in 5 min, so there are for sure other ways of getting to the Declaration or using other Services that can resolve the Declaration, e.g.: BindingContext etc.
🆒 2
Later in the evening I can send in a better way to do this with the BindingContext, if you want.
🔥 1
Analogously, one can do the same with the callExpression 😄
a
Wow, thanks for the detailed reply! Yes, it would be great (I am about BindingContext)
i
Your welcome 😄 theoretically once you obtain the BindingContext or BindingTrace, you can use one of the Slices to get the Descriptor. Here is a list.
👍 1
r
@Anastasia Birillo [JB] There is also support for typed quotes that @bloder contributed recently and should allow you to access type-checked info in analysis at the same time you are given the chance to gen new sources or transform the tree in place. It uses the RetryWithAdditionalRoots return type for the analysis phase to rewind analysis before codegen and will be adapted to FIR once the multiphase API @dmitriy.novozhilov team and the compiler team is working on. If there is anything we can do to help you on your plugin let us know 🙂
🔥 1
b
👍 1
1
a
Thanks a lot! I'll try to implement what I need. I think these links will be very helpful for me. Happy to keep in touch!
I hope the PR to be in the master branch. And now probably I have to find a good way to get the
bindingContex
from the Kotlin compiler...
Maybe does someone know how can I get
bindingContex
in arrow-meta? Is it possible? @raulraja @bloder
I have found this message: https://kotlinlang.slack.com/archives/CJ699L62W/p1588795437130200 I think it can help me to get the information about types
r
Hi @Anastasia Birillo [JB], we usually just use the BindincContext or BindingTrace when we intercept in
analisys
in
doAnalysis
and
analisisCompleted
in AnalysysSyntax.kt. You may be able to get a hold of them in other places depending on the kind of descriptors you are using. For example LazyClassDescriptors in the compiler hold a reference to the binding trace but it’s not something you can generally rely on and AFAIK and @dmitriy.novozhilov may be able to confirm with the new FIR push all that frontend including the context and trace may be replaced and exposed differently.
d
New fir based api won't be released in a year at least, so it's ok to use binding context now
👍 2
r
thanks for comfirming @dmitriy.novozhilov!
a
Thanks, I'll try to use it!
Sorry for my next question, but maybe @raulraja could you answer on it? I implemented two different `CliPlugin`s:
A
and
B
. The
A
contains analysis step: I try to save
BindingTrace
and the
B
contains only transformations, which use the gotten in the
A
BindingTrace
. My
intercept
method looks like this:
Copy code
override fun intercept(ctx: CompilerContext): List<CliPlugin> =
        listOf(
            A,
            B
        )
But I don't quite understand in what order they will be called, because it seems to me that the stage of analysis is always called last after all transformations. Is this so or am I just use the analysis incorrect?
r
The rules for plugins processing order are… 1. Plugins get called in the order they are registered. This is in consonance with what the compiler does when you register a callback in a extension phase. 2. Regardless of how many plugins you have each plugin is connected to multiple phases which respects the compiler order: Parsing -> Analysys / Resolution / Codegen. If a plugin interacts with more than one phase this plugin is called as the compiler advances naturally.
👍 1
A plugin in meta is a data structure that is folded in the internal registry to take all user code and register each function as a compiler extension that otherwise would have been a class impl if it was interacting with the compiler directly. This gives us the ability to interpret the user plugin and register it how we want. Registration to the compiler phases in a place like the CLI for example happens here https://github.com/arrow-kt/arrow-meta/blob/6a758274131075bd44f56cc131385d67fa279a10/compiler-plugin/src/main/kotlin/arrow/meta/internal/registry/InternalRegistry.kt#L164-L195
Being value based and a data structure it can also be manipulated and intercepted and interpreted by other plugins that don’t need to be in the hierarchy of subtyping and we designed it this way so we can do meta of meta, that is create plugins that can intercept and patch other plugins down the road for bincompat checks and other use cases we have.
a
It means I cannot use the
BindingContext
for transformations?
r
yes you can use it but using a service like that is a global change meaning that your changes are definetly gonna impact other plugins, same as if you do
enableIr
The binding context is global for the entire compile session in the CLI AFAIK, so using it for reading is safe but mutating it probably is not
a
I would like to use the binding context only for getting the information about types and use it to get necessary functions for transformations
r
if you use meta the best strategies currently for transformations is quotes and changes to new files. If you make changes in place transforming the AST of the same file then IDEA users would need the meta plugin if this is a plugin that is ment to be used also in IDEA while developing. I’m not sure what you are working on.
Do your transformations need to generate new code or can those transformations happen just in the codegen IR phase?
type information is already there if you just need to perform an IR transformation and you are not introducing anything new to type checking
(new declarations etc)
a
I would like to do the transformation like this:
emptyList() -> listOf(A, B)
But type of the list elements is not changed. I would like to find all elements by special rules and use the information from the binding context to get all nessesary elements (in the example there are
A
and
B
)
For example, I had the following code:
val a = emptyList<Type>()
and
A
and
B
has type
Type
But I can not understand how can I do it, because the analyzing stage is the last
r
I’m not sure I understood. This is what I got: 1. You want to gather expressions typed as
Type
in a given context for example a function body. 2. Replace all instances of the
emptyList
object value singleton and replace them for a list where the expressions are placed to fill the list. something like: input:
Copy code
val a: Int = 0
val b: Int = 1
val result: List<Int> = emptyList()
becomes:
Copy code
val a: Int = 0
val b: Int = 1
val result: List<Int> = listOf(a, b)
If that’s not the case do you have a code example with the expected transformation? sounds to me like we re discussing runtime changes which implies you don’t need analysis, just IR codegen
a
Yes, something like this. But the necessary variables can be in other files and so I need to get the binding context. I don't know how can I check the variable's type it it is in another file. Cause from my understanding I'll have to use references and references are resolved via binding context
r
Does that also imply binaries from third party libs or just files in the local module? The IR phase has type access to all that happened in typechecking and still gives you the chance to transform before it codegens the binaries. Let me find a couple of links
that is the API of all IR nodes that can be intercepted and transformed. That phase also has access to the trace and binding context https://github.com/arrow-kt/arrow-meta/blob/bac9e291ad9aed088d5f2d14491ebcc654b0d4b0/compiler-plugin/src/main/kotlin/arrow/meta/phases/codegen/ir/IRGeneration.kt
🔥 1
The meta proofs plugin makes extensive use of IR to implement the proof system over Kotlin https://github.com/arrow-kt/arrow-meta/blob/master/compiler-plugin/src/main/kotlin/arrow/meta/plugins/proofs/ProofsPlugin.kt#L34-L39
Mostly it intercepts calls and rewrites them recursively but you also have access to all the files analyzed in the PluginContext which is available in all ir functions
a
IR looks like the best way to solve my tasks I think
r
and literally you can intercept any node that has already been typechecked. Does not look like you are altering typechecking or expanding the AST in place so for this kind of plugin I would start with IR only and see if you need type checking at all
it can always be added to the plugin since it will run before IR no matter if its declared before or after in the meta scope and that is where order does not matter since it respects the compiler phase approach
meta { irStuff(), analysis() } == meta { analysis(), irStuff() }
a
It looks really well! I have to find how can I use it because I don't clearly understand it now. Unfortunately, I have not seen the examples of it 😞 But I hope I would be able to understand it. Thanks a lot!
r
no problem, let me know if there is anything we can do to help!
👍 1
🆒 1