Suppose I have the following Java interface and Kotlin extensions:
public interface Foo {
void scale(int x, int y, int z);
void translate(int x, int y, int z);
}
fun Foo.scale(x: Int, y: Int, z: Int) = scale(x, y, z)
fun Foo.translate(x: Int = 0, y: Int = 0, z: Int = 0) = translate(x, y, z)
I noticed inconsistent compiler behavior across different Kotlin versions regarding the shadowing warning.
Kotlin 1.9.25: I receive the following warnings:
Extension is shadowed by a member: public abstract fun scale(x: Int, y: Int, z: Int): Unit Extension is shadowed by a member: public abstract fun translate(x: Int, y: Int, z: Int): Unit
Kotlin 2.1.0: I receive similar warnings:
This extension is shadowed by a member: 'fun scale(x: Int, y: Int, z: Int): Unit' defined in '/Foo'. This extension is shadowed by a member: 'fun translate(x: Int, y: Int, z: Int): Unit' defined in '/Foo'.
Kotlin 2.0.0 and 2.2.0: No warnings are generated.
I have investigated this issue and formed a hypothesis. I would like to know if my understanding is correct.
1. Regarding Kotlin 2.0.0 It seems the absence of the warning in 2.0.0 was likely due to a missing feature or an implementation gap in the initial K2 compiler release.
2. Regarding Kotlin 2.2.0 For 2.2.0, it appears the suppression of the warning is intentional. It seems to reflect a design decision that even if an extension function's signature matches a Java member, the extension is still distinct and functionally useful because of Kotlin features like named arguments and default parameters.
For example:
// This calls the Java member method
foo.translate(1, 1, 1)
// These call the Kotlin extension function
foo.translate(1)
foo.translate(y = 1)
foo.translate(x = 1, y = 1, z = 1)
Is my assessment of the reasons behind the behavior in versions 2.0.0 and 2.2.0 correct?
I don't know about version 2.0.0 (and it doesn't really matter since it's pretty old by now), but the observed behavior from version 2.2.0 onwards is the intended behavior, basically for the reasons you mentioned.
This was introduced by KT-76485 and the warning is now only issued when the extension function really cannot be accessed due to the shadowing. That isn't the case in your example, so no warning is issued because you can do the following:
foo.scale(x = 1, 1, 1)
foo.translate(1, 1)
Foo.scale can be accessed because the named parameter x was used. Java has no named parameters, so this unambiguously points to the extension function.
Foo.translate can be accessed because the last parameter is omitted, falling back to the default of z = 0. The default value is only available on the extension function, so it is unambiguous what needs to be called.
Note that the latter case is independent of the Java interop, so this also applies to a Kotlin interface:
interface Bar {
fun scale(x: Int, y: Int, z: Int)
fun translate(x: Int, y: Int, z: Int)
}
fun Bar.scale(x: Int, y: Int, z: Int) = scale(x, y, z)
fun Bar.translate(x: Int = 0, y: Int = 0, z: Int = 0) = translate(x, y, z)
bar.translate(1, 1) can only be resolved to the extension function (due to the missing z parameter), so no warning is issued. But Bar.scale is now completely shadowed by the interface, why you get the following warning here:
This extension is shadowed by a member: 'fun scale(x: Int, y: Int, z: Int): Unit' defined in '/Bar'.