Hi, TLDR; how we can properly bind generated in F...
# k2-adopters
r
Hi, TLDR; how we can properly bind generated in FirDeclarationGenerationExtension property to newly added in FirTransformer constructor parameter to make the property available inside init {} ? We have a K2 compiler plugin used for our internal framework support. The plugin transforms FIR and inserts new declarations. We transform source code from this:
class Test () {
fun test() {
val a: List<Person> = someFunction()
}
}
into:
class Test(private val someContext: SomeContext) {
fun test() {
val a: List<Person> = someFunction(SOME_ID, someContext.someField)
}
}
We're using an FirTransformer to transform constructors and function's callsites and we are using FirDeclarationGenerationExtension to create the property and to bind it's initializer to the constructor's parameter. The property generation part looks like:
val property = createMemberProperty(...)
property.replaceInitializer(buildPropertyAccessExpression(...))
The transformations look fine until we get a function call inside an init block:
init {
val a: List<Person> = someFunction()
}
In the case when the function is called inside an init block, the property is not initialized yet and it's null. Is there a way we can define order of initialization for the property to make sure it's initialized before the init block?
j
Why do you need to do those transformations in FIR instead of in IR?
r
Because in the actual source code we pass less parameters to the functions, so the compiler won't get to IR stage due to failing FirCheckers.
j
It is a bit weird, you cannot suppress FIR errors so the user will see red code on the IDE, no? Generating in FIR a function with less parameter and then fixing it later in IR maybe works
r
What's the problem with fully transforming it in FIR? Would IR transformations make it easier? Would IR give the same level of details in errors if a transformation fails?
j
Adding a parameter in IR to a call is straightforward. AFAIK transformations on FIR do not have a "public API" for them.
r
FIR transformations are also kind of straightforward. The main problem I'm facing - in FIR constructor parameter's are already de-sugared into properties, so adding a new constructor parameter I need to: 1. add a parameter 2. add a property 3. bind the property to the parameter. I don't think IR can make it simpler. But the only way to bind the property I figured out so far - is via an initializer. It works fine until the property is used in init block, because init block seem to be executed before the initializer.
j
You can try to ask on #C7L3JB43G
d
Such transformations of the code are not supported at the FIR level by design The fact that it's technically possible in some case is a coincidence So I suggest to redesign your approach to the plugin
I can help with it if you describe what do you want in details
r
@dmitriy.novozhilov I described it in the question. So we are transforming code. We transform source code from this:
class Test () {
fun test() {
val a: List<Person> = someFunction()
}
}
into:
class Test(private val someContext: SomeContext) {
fun test() {
val a: List<Person> = someFunction(SOME_ID, someContext.someField)
}
}
We are adding a parameter to the constructor, a property to the class and transforming a function's callsite to use the property and an integer.
d
And why you cannot perform this transformation in IR?
r
Because the function with the used signature in the source code doesn't exist.
d
Sorry, but I don't understand what function you mean Could you please write a full-contained example with what user writes and what is the output of transformation? Like this:
Copy code
// compose
// user writes
@Composable fun foo() {}

@Composable fun bar() {
    foo()
}

// transformed code
fun foo(c: Composer) {}

fun bar(c: Composer) {
    foo(c)
}
Copy code
// serialization
// user writes
@Serializable
class Some

fun foo(s: Some) {
    Some.serialize(s)
}

// plugin generates
@Serializable
class Some {
    companion object {
        fun serialize(s: Some)
    }
}
r
So we have a users code:
class Test () {
fun test() {
val a: List<Person> = someFunction()
}
}
And we have framework defining the function:
someFunction(a: Int, context: SomeContext)
So the transformed code looks like this:
class Test(private val someContext: SomeContext) {
fun test() {
val a: List<Person> = someFunction(SOME_ID, someContext.someField)
}
}
So as you can see, we transformed:
class Test ()
into:
class Test(private val someContext: SomeContext) {
And:
someFunction()
into
someFunction(SOME_ID, someContext.someField)
d
If you introduce a noop function
fun someFunction()
in the framework, all transformation could be moved to the IR
r
We can consider changing framework's API implementation or adding the function in a compiler plugin as a long term solution. But is there a way to quick fix the existing implementation based on FIR transformers to work inside init {} blocks as a short term solution?
d
There isn't