https://kotlinlang.org logo
#getting-started
Title
# getting-started
v

Vampire

03/12/2024, 3:09 PM
Can you have two methods with exact same signature, just being different in parameter names, so that when calling from Kotlin with named parameter syntax it knows which method to use, annotating them with
@JvmName
to be able to produce valid bytecode?
y

Youssef Shoaib [MOD]

03/12/2024, 3:24 PM
As long as you have dummy parameters:
Copy code
fun foo(bar: Int)
fun foo(baz: Int, unit: Unit = Unit)
🤯 1
h

hfhbd

03/12/2024, 3:25 PM
Nope, while I think it should be valid JVM code, the Kotlin compiler can’t distinguish between the functions because parameter names aren’t enforced.
e

ephemient

03/12/2024, 3:36 PM
with
@JvmName
and sufficient
@Suppress
you can
Copy code
@JvmName("fooX")
@Suppress("CONFLICTING_OVERLOADS")
fun foo(x: Int) { println("foo(x = $x)") }

@JvmName("fooY")
@Suppress("CONFLICTING_OVERLOADS")
fun foo(y: Int) { println("foo(y = $y)")}

foo(x = 1)
foo(y = 2)
v

Vampire

03/12/2024, 4:18 PM
Hmmm, I wonder whether that is a good idea 😄 But at least I know now it would work, thanks 🙂
Actually that's also a second way to force users to use named arguments besides adding a dummy first argument
vararg pleaseUseNamedArguments: Unit,
😄
l

Landry Norris

03/12/2024, 8:15 PM
I'd imagine the JVM name + suppressions won't work in common code, unless using CName for native or JSName if that exists.
e

ephemient

03/12/2024, 11:14 PM
those do exist
d

dmitriy.novozhilov

03/13/2024, 10:34 AM
The fact that suppression of error is working is just an accident and you can't rely on it
v

Vampire

03/13/2024, 12:18 PM
Why not? As long as the compiler is happy, what could happen? In the bytecode it then refers to the
JvmName
.
d

dmitriy.novozhilov

03/13/2024, 12:23 PM
> As long as the compiler is happy This is the key point There are no guarantees on the compiler behavior in case of suppressed error And actually the compiler is unhappy in a first place. It reports you about it using error
v

Vampire

03/13/2024, 12:42 PM
Well, it shouldn't be unhappy though, the named arguments makes it clear which method to call. 😄
d

dmitriy.novozhilov

03/13/2024, 12:47 PM
Kotlin language does not support overloading of functions by names of arguments (as by return types) The fact that it can work is just a coincidence
v

Vampire

03/13/2024, 12:47 PM
Well, you are wrong as I just found out. 🙂
Now I think it is a bug.
https://kotlinlang.org/spec/overload-resolution.html#call-with-named-parameters clearly says in "11.2.6 Call with named parameters": > Calls in Kotlin may use named parameters in call expressions, e.g.,
f(a = 2)
, where
a
is a parameter specified in the declaration of
f
. Such calls are treated the same way as normal calls, but the overload resolution sets are filtered to only contain callables which have matching formal parameter names for all named parameters from the call. > > Important: this filtering is done before we perform selection of the overload candidate set w.r.t. rules for the respective type of call.
Or do you interpret this differently?
d

dmitriy.novozhilov

03/13/2024, 12:53 PM
Don't mix rules of overload resolution and rules of overloads declaration On the call site you easily can observe to different functions with exact same signature
Copy code
// FILE: a.kt
package a
fun foo() {} // (1)

// FILE: b.kt
package b
fun foo() {} // (2)

// FILE: c.kt
import a.*
import b.*

fun test() {
    foo() // ambiguity between (1) and (2)
}
But you can't declare two functions with same signature in one namespace (package/class), as it produce conflict of overloads
Copy code
// FILE: a.kt

fun foo() {} // conflicting
fun foo() {} // overloads
v

Vampire

03/13/2024, 12:57 PM
But why, if the binary name is different and the resolution rules at call-site throw out the non-matching overloads by parameter name, there shouldn't be any ambiguity
(Besides that your example would fail with "symbol not found" 😄)
d

dmitriy.novozhilov

03/13/2024, 12:59 PM
Because you can't change the binary name on any platform except JVM, until
@BinarySignatureName
proposal will be implemented
v

Vampire

03/13/2024, 1:00 PM
But I am in JVM-only code. 🙂
I mean, you probably know better than me as Kotlin Compiler developer, but to me it seems like an arbitrary restriction that should not be necessary. 🙂
d

dmitriy.novozhilov

03/13/2024, 1:02 PM
Kotlin is a single language compilable to different platforms, not multiple similar languages per each platform There are some differences between platforms indeed (like flexible types in jvm or
dynamic
in js), but we are trying to minimize their number to keep the language unified
> it seems like an arbitrary restriction that should not be necessary This can be said about anything, actually E.g. about ternary operator 🧌
😂 1
y

Youssef Shoaib [MOD]

03/13/2024, 1:05 PM
Btw, here's a perfectly legal way to do what you want in Kotlin while ensuring that no overload is preferred when no named parameter is used:
Copy code
fun main() {
    println(foo(a = 0))
    println(foo(b = 0))
    // println(foo(0)) errors
}

fun foo(a: Int, unit: Unit1 = UnitImpl) = a
fun foo(b: Int, unit: Unit2 = UnitImpl) = b + 42

sealed interface Unit1
sealed interface Unit2
private object UnitImpl: Unit1, Unit2
(Using
Unitx
because naming is hard) Note that
UnitImpl
is private so that it can never be specified, and hence one can't discriminate between the overloads except by using the names. You might choose to make it public so that one can avoid using named parameters and instead doing
UnitImpl as Unit1
👍 1
v

Vampire

03/13/2024, 1:05 PM
This can be said about anything, actually
E.g. about ternary operator
No, that's just a very missed feature you refuse to implement. 😄
Btw, here's a perfectly legal way to do what you want in Kotlin while ensuring that no overload is preferred when no named parameter is used
How is it, compared to your other proposal at the beginning of the thread? 🙂
y

Youssef Shoaib [MOD]

03/13/2024, 1:09 PM
The earlier proposal would resolve
foo(0)
to the unit-less overload, this one instead ensures that there's no possible disambiguation without simply specifying the parameter names
👌 1
v

Vampire

03/13/2024, 1:10 PM
Also, it would support more than two overloads, without needing one more parameter per overload
y

Youssef Shoaib [MOD]

03/13/2024, 1:12 PM
Yep, just by creating more interfaces and making
UnitImpl
implement them. I'm using one object because old Android habits. I think this solution is "good enough".
v

Vampire

03/13/2024, 1:14 PM
Argh, no sealed interface here. Language version 1.4 as it is for a Gradle 7.6 plugin
Ah, no wait, it does not need to be compatible with older Gradle versions, so I probably just change the language version to 1.7.
y

Youssef Shoaib [MOD]

03/13/2024, 1:17 PM
Just use sealed classes or a class with private constructor. The point is that no one can create a valid object, and hence no way to choose based on
Unit1
vs
Unit2
v

Vampire

03/13/2024, 2:11 PM
I actually don't care how they choose. I just want that choosing by parameter name is sufficient.
e

ephemient

03/13/2024, 2:29 PM
as a variation of that, I suppose you could also have one fewer object
Copy code
sealed class Nothing1
sealed class Nothing2
fun foo(x: Int, unused: Nothing1? = null) {}
fun foo(y: Int, unused: Nothing2? = null) {}
y

Youssef Shoaib [MOD]

03/13/2024, 2:30 PM
Then you could just do
object Unit1
and
object Unit2
v

Vampire

03/13/2024, 2:35 PM
§%$/§%$§ Upgrading language and api version to 1.7 now causes an internal compiler error
NullPointerException
at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerialInfoImplJvmIrGenerator.getImplClass(SerialInfoImplJvmIrGenerator.kt:57) >:-D
y

Youssef Shoaib [MOD]

03/13/2024, 2:36 PM
Weird. There's no real need for
sealed
btw, it was just meant to prevent someone from implementing it and thus choosing an overload in a convoluted way
v

Vampire

03/13/2024, 2:36 PM
Yeah, but now that I realized I do not need the old language and api version, I want to have the new one 😄
@ephemient in your variant, why would I use "sealed"? If the class is not
open
it should not be necessary, should it?
Thanks for all the input. What I ended up with now (unless again someone sees some problem with it :-D) is:
Copy code
fun foo(bar: Int, @Suppress("UNUSED_PARAMETER") overloadSelector: ByBar? = null) = ...
fun foo(baz: Int, @Suppress("UNUSED_PARAMETER") overloadSelector: ByBaz? = null) = ...
class ByBar private constructor()
class ByBaz private constructor()
Now I can from Kotlin consumer do
Copy code
foo(bar = 1)
foo(baz = 2)
And for example from Groovy consumer
Copy code
foo(1, null as ByBar)
foo(2, null as ByBaz)
or from Java consumer
Copy code
foo(1, (ByBar) null)
foo(2, (ByBaz) null)
K 1