Can you have two methods with exact same signature...
# getting-started
v
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
As long as you have dummy parameters:
Copy code
fun foo(bar: Int)
fun foo(baz: Int, unit: Unit = Unit)
๐Ÿคฏ 1
h
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
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
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
I'd imagine the JVM name + suppressions won't work in common code, unless using CName for native or JSName if that exists.
e
those do exist
d
The fact that suppression of error is working is just an accident and you can't rely on it
v
Why not? As long as the compiler is happy, what could happen? In the bytecode it then refers to the
JvmName
.
d
> 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
Well, it shouldn't be unhappy though, the named arguments makes it clear which method to call. ๐Ÿ˜„
d
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
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
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
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
Because you can't change the binary name on any platform except JVM, until
@BinarySignatureName
proposal will be implemented
v
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
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
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
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
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
Also, it would support more than two overloads, without needing one more parameter per overload
y
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
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
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
I actually don't care how they choose. I just want that choosing by parameter name is sufficient.
e
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
Then you could just do
object Unit1
and
object Unit2
v
ยง%$/ยง%$ยง 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
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
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