This message was deleted.
# compiler
s
This message was deleted.
y
I believe so. With an IR plugin, you could likely find every annotated call and add a call to
id
For your end goal, though, I'm not sure how realistic it is. You can try and do some hacky stuff in FIR, but I don't think there's a "proper" way to do it
Ahh, I see! The compiler is split into 2 phases: frontend and backend. The frontend is where all compiler errors get detected, and it's where call resolution happens and stuff. You interact with it in plugins thru FIR (frontend intermediate representation). Then there's the backend. It takes what the frontend produced, and turns it into IR (intermediate representation), runs some transformations on it, then eventually turns it into platform specific code. So the problem is that FIR plugins are very limited in what they can do. In IR, you can do any transformation that you want! There are, however, hacks you can employ. If you look at the call refinement extension, it lets you intercept all function calls, so while not officially supported, you can technically edit the function call to add the
id
call. A better idea would be to have some
@GenerateArgumentOverload
annotation that you can annotate
hof
with, and simply generate an overload that takes the other function type (you might also have to add some dummy arguments
unit: Unit = Unit
to stop it from complaining about ambiguity and platform declaration clashes) To get started, you can use the compiler plugin template
Sadly, I don't think that works... I would probably go for the hacky FIR solution then. It does exactly what you suggested at first (adding an
id
call)
Sadly, you can't annotate imports. You can annotate individual calls, but I don't think FIR plugins can collect that ahead of time and generate the right overloads
does all Kotlin code be syntactically valid before and after applying a plugin?
Yes, so that wouldn't work...
Something to think about is whether there are normal Kotlin solutions to your problem. My understanding is that you don't like the ambiguity of calling functions on an implicit receiver. You can always make it explicit:
Copy code
class MyClass {
    fun f(arg: MyArg) {
        arg.build {
            this@MyClass.f()
            this@build.g()
        }
    }
}
You can even add a label to lambdas, which effectively lets you name the receiver:
Copy code
class MyClass {
    fun f(arg: MyArg) {
        arg.build builder@{
            this@MyClass.f()
            this@builder.g()
        }
    }
}
You can also get receivers by their type:
Copy code
class MyClass {
    fun f(arg: MyArg) {
        arg.build {
            contextOf<MyClass>().f()
            contextOf<BuilderType>().g()
        }
    }
}
Finally, you can create "default names" for receiver types you care about by using extension properties:
Copy code
val MyClass.myClass get() = this
val MyBuilder.builder get() = this
class MyClass {
    fun f(arg: MyArg) {
        arg.build {
            myClass.f()
            builder.g()
        }
    }
}
You can also just use local variables to give names to your receivers (but they'll still remain receivers):
Copy code
class MyClass {
    fun f(arg: MyArg) {
        val self = this
        arg.build {
            val builder = this
            self.f()
            builder.g()
        }
    }
}
Btw,
with(Unit)
wouldn't get rid of the receiver. I gotta think on this for a bit. I do think there's hacky ways to do this. The fundamental issue, ultimately, is that
arg.build { builder, x, y -> ... }
will be rejected by the compiler. FIR plugins don't really have ways to prevent that, except with hacky workarounds that could eventually stop working.
Oh apologies, yes it does replace what
this
refers to, but it leaves the implicit receiver, so you can still do:
Copy code
class MyClass {
    fun f() {}
    fun g() {
        return with(Unit) {
            f() // uses this@MyClass implicitly
        }
    }
}
Another breadcrumb: maybe
FirSamConversionTransformerExtension
can help here? It's what's used in Gradle to let you call functions that take
Action
with an extension lambda instead, so maybe you could do the opposite. It might be a deadend though. It seems it only works for SAM interfaces (i.e. `fun interface`s), so I don't think it'd let you intercept regular function types
FWIW, the call refinement extension I talked about before (that can let you do the
id
trick) is called
FirFunctionCallRefinementExtension
. Inside its
intercept
method, you can call
callInfo.argumentList.transformArguments
to change the arguments given to the call, so you can use that to add the
id
to the last argument if it's a lambda.
Apologies that this code is a mess, but here's a somewhat similar plugin I wrote a while back. The important part is that it finds all the lambdas (but in your case, you can just focus on the last one), and adds code inside their body. For your case, you can just add the
id({ ... })
calls as previously discussed But you can also take an approach similar to mine, and instead add the
val builder = this
line at the start of the lambda. Hopefully my code can serve as a good starting point! This fits your thoughts from above for transforming:
Copy code
arg.build { @This builder, x, y ->
    builder.g()
}
into:
Copy code
arg.build { x, y ->
    val builder = this
    with(Unit) { builder.g() }
}
Obviously, it doesn't prevent access to
this@build
, and it even doesn't prevent implicit access to just
g()
, but it still lets you be explicit when you want