min
09/20/2026, 10:05 AMclass ExtScope {
fun Int.f() {}
}
fun extScope(block: (ExtScope) -> Unit) {/* ... */}
extScope { scope ->
// How to reference `Int::f` here?
}
How do I reference the f extension method on Int in the block argument without using with(scope) and the like?min
09/20/2026, 10:10 AMoverride fun ExtensionRegistrarContext.configurePlugin() {
(::MyFirAdditionalCheckersExtension).unaryPlus()
(::MyFirDeclarationGenerationExtension).unaryPlus()
this.registerDiagnosticContainers(MyFirAdditionalCheckersExtension.MyDiagnostics)
}
I want to replace this with the following
override fun ExtensionRegistrarContext.configurePlugin() {
val self = this@MyFirExtensionRegistrar
val context = this@configurePlugin
self.configurePluginImpl(context)
}
private fun configurePluginImpl(context: ExtensionRegistrarContext) {
context.unaryPlus(::MyFirAdditionalCheckersExtension)
context.unaryPlus(::MyFirDeclarationGenerationExtension)
context.registerDiagnosticContainers(MyFirAdditionalCheckersExtension.MyDiagnostics)
}Youssef Shoaib [MOD]
09/20/2026, 10:11 AM::), it's currently impossible sadly. Relevant issue
Context parameters were a blocker for this, so now that they're here, it's at least possible design-wiseYoussef Shoaib [MOD]
09/20/2026, 10:19 AMfun ExtensionRegistrarContext.register(factory: (FirSession) -> FirAdditionalCheckersExtension) { +factory }
fun ExtensionRegistrarContext.register(factory: (FirSession) -> FirDeclarationGenerationExtension) { +factory }
// which allows you to write
private fun configurePluginImpl(context: ExtensionRegistrarContext) {
context.register(::MyFirAdditionalCheckersExtension)
context.register(::MyFirDeclarationGenerationExtension)
context.registerDiagnosticContainers(MyFirAdditionalCheckersExtension.MyDiagnostics)
}
but you quickly run into another hurdle: you now need to duplicate all the couple dozen unaryPlus methods for each extension type because the original DSL intentionally has overloads for each of them.
It's not the end of the world, but maybe a sign that you should try and embrace the implicitness of Kotlin and see where it leads you!min
09/20/2026, 10:53 AMFirExtensionRegistrar declared and let subclasses inherit the following final method:
fun register(extension: (FirSession) -> FirDeclarationGenerationExtension) {
this.registerExtension(
FirDeclarationGenerationExtension::class,
FirDeclarationGenerationExtension.Factory(extension),
)
}
My override fun configurePlugin could have used it like so:
class MyFirExtensionRegistrar: FirExtensionRegistrar() {
override fun configurePlugin() {
this.register(::MyFirDeclarationGenerationExtension)
}
}
Which I personally find has a cleaner signature, is better at explaining the semantics than +::MyFirDeclarationGenerationExtension, and involves fewer layers of indirection and more straightforward name resolution.Youssef Shoaib [MOD]
09/20/2026, 11:06 AMExtensionRegistrarContext is there to limit any such calls to being in configurePlugin only. This limits the mutable state to the context as well, meaning that configurePlugin can thus be reused (e.g. in IDE to recreate your extensions when user changes happen). In reality, it actually gets cached as a lazy value. Regardless, the point is to limit these calls to that one method. It also means that if another plugin somehow gets its hands on your FirExtensionRegistrar, it can't somehow attribute its additions to you, and similarly it means that you can't add things to your registrar after the configurePlugin call happens.
2- Choosing unaryPlus instead of register or some other name: It's a matter of personal taste, but the point is to reduce visual noise so that your extensions are the most visible thing in the code
Using unaryPlus for registration or "adding things to a container" is a Kotlin idiom, and has been for a while.min
09/20/2026, 11:11 AMxs.sum() / xs.size to {(+⌿⍵)÷≢⍵}) but the choices are interesting.Joffrey
09/21/2026, 9:41 AMxs.sum().div(xs.size) then? (I'm not weighing in on the general topic here, but I just found it funny that in your methods example, you actually use an operator)min
09/21/2026, 9:50 AMoperator functions by name, especially to handle nullability (eg x?.plus(y)).
2. It’s ignorant at best and disingenuous at worst to equate
a. The use of / for division,
b. And the use of + on a (FirSession) -> FirDeclarationGenerationExtension which is enabled because the block receiver is an instance of inner class ExtensionRegistrarContext that eventually registers the factory with the registrar, which is not as standard as 6 / 2 == 3 and +x == x.
I do not appreciate that you chimed in just to be snide and not add anything else to the general topic.Joffrey
09/26/2026, 6:38 PMPath.div is usually clear to most people, and I think it's a gain to use myDir / "foo.txt" over myDir.resolve("foo.txt").min
09/27/2026, 9:22 AMBut if we're being serious about it, I don't think you should say you prefer methods to operators as a blanket statement, then, since you seem to define classes of cases where you find operators better (as far as I can tell).Why continue this semantic debate though? Do you also think that in order for somebody to prefer tea to coffee, it’s not enough to drink 10 times or even 100 times as much tea, because they should simply never have any coffee at all? The line I draw is BODMAS (PEMDAS) and some de facto standard notation such as collection access (
map[key]) versus everything else. I reckon that’s enough for me to say I prefer methods to operators!
I think it's a gain to useI personally findovermyDir / "foo.txt".myDir.resolve("foo.txt")
Path#dir not worth bothering about. It requires parentheses in call chains ((x.f().g() / y).h()) which I find unergonomic. In places where that’s not an issue, it still doesn’t enable anything #resolve doesn’t, and only opens the door for unproductive pointless arguments about Clean Code such as this one. For what it’s worth, I’m of the opinion that there’s a clear difference between generalising division to your own types for example, and coopting the / symbol in disjoint semantic contexts.