https://kotlinlang.org logo
#compiler
Title
# compiler
o

Olaf Gottschalk

03/24/2023, 6:21 PM
So, I am running into a problem with extension function because of Jvm name clashes. In my particular case I need to offer two similar functions operation on either a non-nullable type X and on a nullable type X.
Copy code
infix fun <T> String.blah(processor: (String)->T): T

infix fun <T> String?.blah(processor: (String)->T): T?
Normally, such problems can be fixed by adding a specific
@JvmName
annotation to at least one of the two methods - but in this case you get an error, stating "'@JvmName' annotation is not applicable to this declaration". This is quite a downer.. why is that and how can I make this compile? I thought an extension function is nothing special at all, in my case a function with two arguments... Any chance I might get this working? Thanks!
y

Youssef Shoaib [MOD]

03/24/2023, 6:35 PM
Are you inside an interface? Also, you could add a
unit: Unit = Unit
to the nullable function (so that even though the non-null is now higher priority, the nullable will still be called for nullable arguments)
o

Olaf Gottschalk

03/24/2023, 6:43 PM
Yes, I am in an interface with default implementation. I just left out the implementation, you can put = TODO() there to see the error. But the problem is also there outside of interfaces / classes. I do not really get what you mean by add
unit: Unit = Unit
... can you put that in context, please. Does that fix my problem?
y

Youssef Shoaib [MOD]

03/24/2023, 10:06 PM
This works:
Copy code
interface Test {
    infix fun <T> String.blah(processor: (String)-> T): T = processor(this)

    @Suppress("INAPPLICABLE_INFIX_MODIFIER")
    infix fun <T> String?.blah(processor: (String)-> T, unit: Unit = Unit): T? = this?.let(processor)
}
class TestImpl: Test
fun main(){
    val nullable: String? = null
	val test = with(TestImpl()) { "hello" blah { it } }
	val test2 = with(TestImpl()) { nullable blah { it } }
    print(test)
    print(test2)
}
the suppress is needed since the unit parameter makes the function have 3 params. I don't think there's a way around that, but thankfully the suppress is sufficient to not cause any issues
o

Olaf Gottschalk

03/25/2023, 10:40 AM
That's a cool idea. I tried adding a parameter, but didn't know I could suppress the error that this is not an infix fun anymore. In the meantime, I worked around my problem with a
@JvmInline
class as a wrapper around my nullable Receiver - technically this does exactly what I wanted, when you look at the bytecode there's a function with a name
blah_something
for my nullable Receiver. What I still would like to understand from the makers of the Kotlin compiler is, why I cannot just use
@JvmName
on my second infix fun...
y

Youssef Shoaib [MOD]

03/25/2023, 11:30 AM
It's because it's an interface, and there's no way currently to force interface inheritors to use the correct
JvmName
annotation.
o

Olaf Gottschalk

03/25/2023, 11:36 AM
Cool, makes sense. Thanks!
y

Youssef Shoaib [MOD]

03/25/2023, 1:16 PM
This also works:
Copy code
interface Test {
    infix fun <T> String.blah(processor: (String)-> T): T = processor(this)

    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("blahNullable")
    infix fun <T> String?.blah(processor: (String)-> T): T? = this?.let(processor)
}
and if you don't wanna use Suppress, but are comfortable with using context receivers:
Copy code
interface Test

context(Test)
infix fun <T> String.blah(processor: (String)-> T): T = processor(this)

context(Test) @JvmName("blahNullable")
infix fun <T> String?.blah(processor: (String)-> T): T? = this?.let(processor)
(You can adapt this one to use inheritance by having 2 methods with different names named something other than blah within Test, and then have the contextual blah extensions call out to each apt method).
o

Olaf Gottschalk

03/25/2023, 1:33 PM
What I really sometimes find hard to understand is that you can suppress an error and still "hope" for it to work as expected. Seems weird. Like as if I opt out of a guarantee. And: some errors are errors because it simply cannot work and other seem to be nothing more than a good advice, but you can ignore them. Which one is which is not really clear...
I also think the IDE IntelliJ does not really offer the suppression as a quick fix in my situation and that's why I didn't come up with that myself.
y

Youssef Shoaib [MOD]

03/25/2023, 4:30 PM
Some errors exist because the language isn't "intended" to do certain things. They're there to enforce the spec of the language, and hence to allow future changes without breaking any supported use case. Other errors are genuinely compiler-breaking. When you suppress an error, you basically give the compiler a "Shut up, I know what I'm doing" signal, and if it breaks later on, then it's your fault. Hence, IDE will never recommend a suppression for an error. The way I find out these suppression magical strings is by turning on internal mode in intellij, which shows the error name next to its description
o

Olaf Gottschalk

03/27/2023, 9:00 AM
Thanks again @Youssef Shoaib [MOD], all of your answers were more than helpful! One follow up regarding Context Receivers, which I really cannot wait to get released for good! My DSL where my blah infix function is at home uses the Kotlin Script compiler on user Kotlin snippets. I wouldn't actually know how to enable experimental features like Context Receivers on this Script Engine at all... any hints?
y

Youssef Shoaib [MOD]

03/27/2023, 12:13 PM
I'm not sure if the scripting compiler has support just yet for that. I'd suggest you instead use Kscript, which uses the jvm compiler under the hood, and allows you to configure compiler options
30 Views