K2: Avoid false-positive overload resolution ambiguity with smart casts

The idea is that when we have successful candidates both from smart cast
and original type, we should discriminate in the favor of former ones.

While this problem (see kt55722.kt) existed before this branch is merged,
initially it was recognized on FP Ultimate when we stopped assuming
captured types from the same projections as equal (see kt55722Initial.kt).

^KT-55722 Fixed
^KT-55024 Fixed
^KT-56283 Related
^KT-56310 Related
This commit is contained in:
Denis.Zharkov
2023-01-02 12:00:14 +01:00
committed by Space Team
parent 7b6c6fceb6
commit b6b132a9a3
27 changed files with 1034 additions and 35 deletions
@@ -0,0 +1,18 @@
FILE: kt55722.kt
public abstract interface A<T> : R|kotlin/Any| {
public open fun foo(x: R|T?|): R|kotlin/Unit| {
}
}
public abstract interface B<F> : R|A<F>| {
public abstract override fun foo(x: R|F?|): R|kotlin/Unit|
}
public final fun <T> bar(x: R|A<in T>|): R|kotlin/Unit| {
when () {
(R|<local>/x| is R|B<in T>|) -> {
R|<local>/x|.R|SubstitutionOverride</B.foo: R|kotlin/Unit|>|(Null(null))
}
}
}
@@ -0,0 +1,17 @@
// FIR_IDENTICAL
// FIR_DUMP
// SKIP_TXT
interface A<T> {
fun foo(x: T?) {}
}
interface B<F> : A<F> {
override fun foo(x: F?)
}
fun <T> bar(x: A<in T>) {
if (x is B) {
x.foo(null) // Shouldn't be OVERLOAD_RESOLUTION_AMBIGUITY
}
}
@@ -0,0 +1,14 @@
FILE: kt55722Initial.kt
public abstract interface MyConsumer<T> : R|kotlin/Any| {
public open fun consume(x: R|T|): R|kotlin/Unit| {
}
}
public final fun foo(x: R|MyConsumer<in kotlin/CharSequence>?|, v: R|kotlin/CharSequence|): R|kotlin/Unit| {
when () {
!=(R|<local>/x|, Null(null)) -> {
R|<local>/x|.R|SubstitutionOverride</MyConsumer.consume: R|kotlin/Unit|>|(R|<local>/v|)
}
}
}
@@ -0,0 +1,13 @@
// FIR_IDENTICAL
// FIR_DUMP
// SKIP_TXT
interface MyConsumer<T> {
fun consume(x: T) {}
}
fun foo(x: MyConsumer<in CharSequence>?, v: CharSequence) {
if (x != null) {
x.consume(v)
}
}
@@ -0,0 +1,26 @@
// SKIP_TXT
// FIR_DUMP
open class Base
class Derived : Base()
open class A(protected open val foo: Base) {
protected open fun bar(): Base = Base()
fun f(other: A) {
other.foo // OK in K1 and K2
other.bar() // OK in K1 and K2
when (other) {
is B -> {
// OK in K2, INVISIBLE_MEMBER (B::foo) in K1
other.foo
other.bar()
}
}
}
}
class B(override val foo: Derived): A(foo) {
override fun bar(): Derived = Derived()
}
@@ -0,0 +1,51 @@
FILE: kt56283.fir.kt
public open class Base : R|kotlin/Any| {
public constructor(): R|Base| {
super<R|kotlin/Any|>()
}
}
public final class Derived : R|Base| {
public constructor(): R|Derived| {
super<R|Base|>()
}
}
public open class A : R|kotlin/Any| {
public constructor(foo: R|Base|): R|A| {
super<R|kotlin/Any|>()
}
protected open val foo: R|Base| = R|<local>/foo|
protected get(): R|Base|
protected open fun bar(): R|Base| {
^bar R|/Base.Base|()
}
public final fun f(other: R|A|): R|kotlin/Unit| {
R|<local>/other|.R|/A.foo|
R|<local>/other|.R|/A.bar|()
when (R|<local>/other|) {
($subj$ is R|B|) -> {
R|<local>/other|.R|/A.foo|
R|<local>/other|.R|/A.bar|()
}
}
}
}
public final class B : R|A| {
public constructor(foo: R|Derived|): R|B| {
super<R|A|>(R|<local>/foo|)
}
protected open override val foo: R|Derived| = R|<local>/foo|
protected get(): R|Derived|
protected open override fun bar(): R|Derived| {
^bar R|/Derived.Derived|()
}
}
@@ -0,0 +1,26 @@
// SKIP_TXT
// FIR_DUMP
open class Base
class Derived : Base()
open class A(protected open val foo: Base) {
protected open fun bar(): Base = Base()
fun f(other: A) {
other.foo // OK in K1 and K2
other.bar() // OK in K1 and K2
when (other) {
is B -> {
// OK in K2, INVISIBLE_MEMBER (B::foo) in K1
<!DEBUG_INFO_SMARTCAST!>other<!>.<!INVISIBLE_MEMBER!>foo<!>
<!DEBUG_INFO_SMARTCAST!>other<!>.<!INVISIBLE_MEMBER!>bar<!>()
}
}
}
}
class B(override val foo: Derived): A(foo) {
override fun bar(): Derived = Derived()
}
@@ -0,0 +1,106 @@
// SKIP_TXT
// FIR_DUMP
// CHECK_TYPE
open class Base
class Derived : Base()
interface M1
interface M2
interface M2Sub : M2
interface M3
interface M3Sub : M3
interface M4
interface M5
interface M5Sub : M5
interface M5SubSub : M5Sub
interface M6
open class A {
public fun foo(a1: Base, a2: Base): M1 = TODO()
protected open fun foo(a1: Base, a2: Derived): M2 = TODO()
protected open fun foo(a1: Derived, a2: Derived): M3 = TODO()
public fun baz(a1: Base, a2: Base): M4 = TODO()
protected open fun baz(a1: Derived, a2: Derived): M5 = TODO()
}
open class B : A() {
public val fromB: Any = Any()
override fun foo(a1: Base, a2: Derived): M2Sub = TODO()
override fun baz(a1: Derived, a2: Derived): M5Sub = TODO()
fun f(a: A, b: B, c: C, d: Derived) {
// Only visible is M1
a.foo(d, d) checkType { _<M1>() }
// We may call M3 and it's the most specific by its parameters
b.foo(d, d) checkType { _<M3>() }
// We can't call M3 (and M3Sub) as it's protected overriden in C, so it's invisible because
// we only allow to call protected members on dispatch receiver values that are subtypes of a dispatch receiver parameter
// So, the next visible and specific member is M2Sub
c.foo(d, d) checkType { _<M2Sub>() }
// Only visible is M4
a.baz(d, d) checkType { _<M4>() }
// M5Sub is more specific and visible for `b` receiver
b.baz(d, d) checkType { _<M5Sub>() }
// M5SubSub is invisible because it's protected in something that is not our super-class
// M6 and M1 are visible, but M6 is more specific
c.baz(d, d) checkType { _<M6>() }
when (a) {
is C -> {
// Make sure smart cast works
a.fromC
// The same logic as for `c.foo`
a.foo(d, d) checkType { _<M2Sub>() }
// The same logic as for `c.baz`
a.baz(d, d) checkType { _<M6>() }
}
is B -> {
// Make sure smart cast works
a.fromB
// The same logic as `b.foo`
a.foo(d, d) checkType { _<M3>() }
// The same logic as for `b.baz`
a.baz(d, d) checkType { _<M5Sub>() }
}
}
when (b) {
is C -> {
b.fromC
// In K1, it works just as `c.foo`
b.foo(d, d) checkType { <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>_<!><M2Sub>() }
// In K2, M3Sub is invisible, but we have candidate M3 from original receiver and we choose it
// Unlike the case of `c.foo` when we choose M2Sub because we don't have more special M3 there in the scope of C
// (in the meaning of overload comparison by the value parameter types)
b.foo(d, d) checkType { _<M3>() }
// In K1, it works just as `c.foo`
b.baz(d, d) checkType { <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>_<!><M6>() }
// In K2, M5SubSub is invisible, but we have candidate M5Sub from original receiver and we choose it
// Unlike the case of `c.baz` when we choose M6 because we don't have more special M5Sub there in the scope of C
// (in the meaning of overload comparison by the value parameter types)
b.baz(d, d) checkType { _<M5Sub>() }
}
}
}
}
class C : B() {
public val fromC: Any = Any()
override fun foo(a1: Derived, a2: Derived): M3Sub = TODO()
override fun baz(a1: Derived, a2: Derived): M5SubSub = TODO()
// public fallback
public fun baz(a1: Derived, a2: Base): M6 = TODO()
}
@@ -0,0 +1,171 @@
FILE: moreSpecificProtected.fir.kt
public open class Base : R|kotlin/Any| {
public constructor(): R|Base| {
super<R|kotlin/Any|>()
}
}
public final class Derived : R|Base| {
public constructor(): R|Derived| {
super<R|Base|>()
}
}
public abstract interface M1 : R|kotlin/Any| {
}
public abstract interface M2 : R|kotlin/Any| {
}
public abstract interface M2Sub : R|M2| {
}
public abstract interface M3 : R|kotlin/Any| {
}
public abstract interface M3Sub : R|M3| {
}
public abstract interface M4 : R|kotlin/Any| {
}
public abstract interface M5 : R|kotlin/Any| {
}
public abstract interface M5Sub : R|M5| {
}
public abstract interface M5SubSub : R|M5Sub| {
}
public abstract interface M6 : R|kotlin/Any| {
}
public open class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public final fun foo(a1: R|Base|, a2: R|Base|): R|M1| {
^foo R|kotlin/TODO|()
}
protected open fun foo(a1: R|Base|, a2: R|Derived|): R|M2| {
^foo R|kotlin/TODO|()
}
protected open fun foo(a1: R|Derived|, a2: R|Derived|): R|M3| {
^foo R|kotlin/TODO|()
}
public final fun baz(a1: R|Base|, a2: R|Base|): R|M4| {
^baz R|kotlin/TODO|()
}
protected open fun baz(a1: R|Derived|, a2: R|Derived|): R|M5| {
^baz R|kotlin/TODO|()
}
}
public open class B : R|A| {
public constructor(): R|B| {
super<R|A|>()
}
public final val fromB: R|kotlin/Any| = R|kotlin/Any.Any|()
public get(): R|kotlin/Any|
protected open override fun foo(a1: R|Base|, a2: R|Derived|): R|M2Sub| {
^foo R|kotlin/TODO|()
}
protected open override fun baz(a1: R|Derived|, a2: R|Derived|): R|M5Sub| {
^baz R|kotlin/TODO|()
}
public final fun f(a: R|A|, b: R|B|, c: R|C|, d: R|Derived|): R|kotlin/Unit| {
R|<local>/a|.R|/A.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M1|>(checkType@fun R|CheckTypeInv<M1>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M1|>()
}
)
R|<local>/b|.R|/A.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M3|>(checkType@fun R|CheckTypeInv<M3>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M3|>()
}
)
R|<local>/c|.R|/B.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M2Sub|>(checkType@fun R|CheckTypeInv<M2Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M2Sub|>()
}
)
R|<local>/a|.R|/A.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M4|>(checkType@fun R|CheckTypeInv<M4>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M4|>()
}
)
R|<local>/b|.R|/B.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M5Sub|>(checkType@fun R|CheckTypeInv<M5Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M5Sub|>()
}
)
R|<local>/c|.R|/C.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M6|>(checkType@fun R|CheckTypeInv<M6>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M6|>()
}
)
when (R|<local>/a|) {
($subj$ is R|C|) -> {
R|<local>/a|.R|/C.fromC|
R|<local>/a|.R|/B.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M2Sub|>(checkType@fun R|CheckTypeInv<M2Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M2Sub|>()
}
)
R|<local>/a|.R|/C.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M6|>(checkType@fun R|CheckTypeInv<M6>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M6|>()
}
)
}
($subj$ is R|B|) -> {
R|<local>/a|.R|/B.fromB|
R|<local>/a|.R|/A.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M3|>(checkType@fun R|CheckTypeInv<M3>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M3|>()
}
)
R|<local>/a|.R|/B.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M5Sub|>(checkType@fun R|CheckTypeInv<M5Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M5Sub|>()
}
)
}
}
when (R|<local>/b|) {
($subj$ is R|C|) -> {
R|<local>/b|.R|/C.fromC|
R|<local>/b|.R|/A.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M3|>(checkType@fun R|CheckTypeInv<M3>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_<None of the following candidates is applicable because of receiver type mismatch: [/_]>#|<R|M2Sub|>()
}
)
R|<local>/b|.R|/A.foo|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M3|>(checkType@fun R|CheckTypeInv<M3>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M3|>()
}
)
R|<local>/b|.R|/B.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M5Sub|>(checkType@fun R|CheckTypeInv<M5Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_<None of the following candidates is applicable because of receiver type mismatch: [/_]>#|<R|M6|>()
}
)
R|<local>/b|.R|/B.baz|(R|<local>/d|, R|<local>/d|).R|/checkType|<R|M5Sub|>(checkType@fun R|CheckTypeInv<M5Sub>|.<anonymous>(): R|kotlin/Unit| <inline=NoInline> {
this@R|special/anonymous|.R|/_|<R|M5Sub|>()
}
)
}
}
}
}
public final class C : R|B| {
public constructor(): R|C| {
super<R|B|>()
}
public final val fromC: R|kotlin/Any| = R|kotlin/Any.Any|()
public get(): R|kotlin/Any|
protected open override fun foo(a1: R|Derived|, a2: R|Derived|): R|M3Sub| {
^foo R|kotlin/TODO|()
}
protected open override fun baz(a1: R|Derived|, a2: R|Derived|): R|M5SubSub| {
^baz R|kotlin/TODO|()
}
public final fun baz(a1: R|Derived|, a2: R|Base|): R|M6| {
^baz R|kotlin/TODO|()
}
}
@@ -0,0 +1,106 @@
// SKIP_TXT
// FIR_DUMP
// CHECK_TYPE
open class Base
class Derived : Base()
interface M1
interface M2
interface M2Sub : M2
interface M3
interface M3Sub : M3
interface M4
interface M5
interface M5Sub : M5
interface M5SubSub : M5Sub
interface M6
open class A {
public fun foo(a1: Base, a2: Base): M1 = TODO()
protected open fun foo(a1: Base, a2: Derived): M2 = TODO()
protected open fun foo(a1: Derived, a2: Derived): M3 = TODO()
public fun baz(a1: Base, a2: Base): M4 = TODO()
protected open fun baz(a1: Derived, a2: Derived): M5 = TODO()
}
open class B : A() {
public val fromB: Any = Any()
override fun foo(a1: Base, a2: Derived): M2Sub = TODO()
override fun baz(a1: Derived, a2: Derived): M5Sub = TODO()
fun f(a: A, b: B, c: C, d: Derived) {
// Only visible is M1
a.foo(d, d) checkType { _<M1>() }
// We may call M3 and it's the most specific by its parameters
b.foo(d, d) checkType { _<M3>() }
// We can't call M3 (and M3Sub) as it's protected overriden in C, so it's invisible because
// we only allow to call protected members on dispatch receiver values that are subtypes of a dispatch receiver parameter
// So, the next visible and specific member is M2Sub
c.foo(d, d) checkType { _<M2Sub>() }
// Only visible is M4
a.baz(d, d) checkType { _<M4>() }
// M5Sub is more specific and visible for `b` receiver
b.baz(d, d) checkType { _<M5Sub>() }
// M5SubSub is invisible because it's protected in something that is not our super-class
// M6 and M1 are visible, but M6 is more specific
c.baz(d, d) checkType { _<M6>() }
when (a) {
is C -> {
// Make sure smart cast works
<!DEBUG_INFO_SMARTCAST!>a<!>.fromC
// The same logic as for `c.foo`
<!DEBUG_INFO_SMARTCAST!>a<!>.foo(d, d) checkType { _<M2Sub>() }
// The same logic as for `c.baz`
<!DEBUG_INFO_SMARTCAST!>a<!>.baz(d, d) checkType { _<M6>() }
}
is B -> {
// Make sure smart cast works
<!DEBUG_INFO_SMARTCAST!>a<!>.fromB
// The same logic as `b.foo`
<!DEBUG_INFO_SMARTCAST!>a<!>.foo(d, d) checkType { _<M3>() }
// The same logic as for `b.baz`
<!DEBUG_INFO_SMARTCAST!>a<!>.baz(d, d) checkType { _<M5Sub>() }
}
}
when (b) {
is C -> {
<!DEBUG_INFO_SMARTCAST!>b<!>.fromC
// In K1, it works just as `c.foo`
b.foo(d, d) checkType { _<M2Sub>() }
// In K2, M3Sub is invisible, but we have candidate M3 from original receiver and we choose it
// Unlike the case of `c.foo` when we choose M2Sub because we don't have more special M3 there in the scope of C
// (in the meaning of overload comparison by the value parameter types)
b.foo(d, d) checkType { <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>_<!><M3>() }
// In K1, it works just as `c.foo`
<!DEBUG_INFO_SMARTCAST!>b<!>.baz(d, d) checkType { _<M6>() }
// In K2, M5SubSub is invisible, but we have candidate M5Sub from original receiver and we choose it
// Unlike the case of `c.baz` when we choose M6 because we don't have more special M5Sub there in the scope of C
// (in the meaning of overload comparison by the value parameter types)
<!DEBUG_INFO_SMARTCAST!>b<!>.baz(d, d) checkType { <!UNRESOLVED_REFERENCE_WRONG_RECEIVER!>_<!><M5Sub>() }
}
}
}
}
class C : B() {
public val fromC: Any = Any()
override fun foo(a1: Derived, a2: Derived): M3Sub = TODO()
override fun baz(a1: Derived, a2: Derived): M5SubSub = TODO()
// public fallback
public fun baz(a1: Derived, a2: Base): M6 = TODO()
}
@@ -0,0 +1,55 @@
// SKIP_TXT
// FIR_DUMP
// ISSUE: KT-56310
interface Base
interface Derived : Base
interface M1 {
val success: Boolean
}
interface M1Sub : M1
open class A {
open protected fun baz(a: Derived): M1 = TODO()
open protected fun foo(a: Derived): M1 = TODO()
fun f(a: A, b: B, d: Derived) {
a.baz(d).success // OK in K1 and K2
a.foo(d).success // OK in K1 and K2
// Both K1 and K2 resolves calls to B's members with String return type because other's members in B type are invisible
b.baz(d).length
b.foo(d).length
when (a) {
is B -> {
// OK in K1 because `a.baz(d)` resolved to String returning methid (just as `b.baz(d)`)
a.baz(d).<!UNRESOLVED_REFERENCE!>length<!>
// OK in K2, because we have two visible candidates:
// - A::baz from original (after unwrapping smart cast) receiver
// - B::baz that returns String
// But the first one is more specific via its parameter types
a.baz(d).success // Unresolved reference in K1, going to be OK in K2
// Works in K2 for the same reasons as `a.baz(d)`
// In K1, works by coincidence because we bind override groups for members from original and smart cast receiver,
// and as return type from B is the same (not more specific), we choose the member from A as a group representative.
// Thus, we have successful candidates
// - A::foo
// - B::foo returning String
// But A::foo is more specific, so we choose it
a.foo(d).success
}
}
}
}
class B : A() {
override fun baz(a: Derived): M1Sub = TODO()
public fun baz(a: Base): String = TODO()
// The only difference between `baz` and `foo` is that the former has more specific covariant return type (M1Sub)
override fun foo(a: Derived): M1 = TODO()
public fun foo(a: Base): String = TODO()
}
@@ -0,0 +1,63 @@
FILE: moreSpecificProtectedSimple.fir.kt
public abstract interface Base : R|kotlin/Any| {
}
public abstract interface Derived : R|Base| {
}
public abstract interface M1 : R|kotlin/Any| {
public abstract val success: R|kotlin/Boolean|
public get(): R|kotlin/Boolean|
}
public abstract interface M1Sub : R|M1| {
}
public open class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
protected open fun baz(a: R|Derived|): R|M1| {
^baz R|kotlin/TODO|()
}
protected open fun foo(a: R|Derived|): R|M1| {
^foo R|kotlin/TODO|()
}
public final fun f(a: R|A|, b: R|B|, d: R|Derived|): R|kotlin/Unit| {
R|<local>/a|.R|/A.baz|(R|<local>/d|).R|/M1.success|
R|<local>/a|.R|/A.foo|(R|<local>/d|).R|/M1.success|
R|<local>/b|.R|/B.baz|(R|<local>/d|).R|kotlin/String.length|
R|<local>/b|.R|/B.foo|(R|<local>/d|).R|kotlin/String.length|
when (R|<local>/a|) {
($subj$ is R|B|) -> {
R|<local>/a|.R|/A.baz|(R|<local>/d|).<Unresolved name: length>#
R|<local>/a|.R|/A.baz|(R|<local>/d|).R|/M1.success|
R|<local>/a|.R|/A.foo|(R|<local>/d|).R|/M1.success|
}
}
}
}
public final class B : R|A| {
public constructor(): R|B| {
super<R|A|>()
}
protected open override fun baz(a: R|Derived|): R|M1Sub| {
^baz R|kotlin/TODO|()
}
public final fun baz(a: R|Base|): R|kotlin/String| {
^baz R|kotlin/TODO|()
}
protected open override fun foo(a: R|Derived|): R|M1| {
^foo R|kotlin/TODO|()
}
public final fun foo(a: R|Base|): R|kotlin/String| {
^foo R|kotlin/TODO|()
}
}
@@ -0,0 +1,55 @@
// SKIP_TXT
// FIR_DUMP
// ISSUE: KT-56310
interface Base
interface Derived : Base
interface M1 {
val success: Boolean
}
interface M1Sub : M1
open class A {
open protected fun baz(a: Derived): M1 = TODO()
open protected fun foo(a: Derived): M1 = TODO()
fun f(a: A, b: B, d: Derived) {
a.baz(d).success // OK in K1 and K2
a.foo(d).success // OK in K1 and K2
// Both K1 and K2 resolves calls to B's members with String return type because other's members in B type are invisible
b.baz(d).length
b.foo(d).length
when (a) {
is B -> {
// OK in K1 because `a.baz(d)` resolved to String returning methid (just as `b.baz(d)`)
<!DEBUG_INFO_SMARTCAST!>a<!>.baz(d).length
// OK in K2, because we have two visible candidates:
// - A::baz from original (after unwrapping smart cast) receiver
// - B::baz that returns String
// But the first one is more specific via its parameter types
<!DEBUG_INFO_SMARTCAST!>a<!>.baz(d).<!UNRESOLVED_REFERENCE!>success<!> // Unresolved reference in K1, going to be OK in K2
// Works in K2 for the same reasons as `a.baz(d)`
// In K1, works by coincidence because we bind override groups for members from original and smart cast receiver,
// and as return type from B is the same (not more specific), we choose the member from A as a group representative.
// Thus, we have successful candidates
// - A::foo
// - B::foo returning String
// But A::foo is more specific, so we choose it
a.foo(d).success
}
}
}
}
class B : A() {
override fun baz(a: Derived): M1Sub = TODO()
public fun baz(a: Base): String = TODO()
// The only difference between `baz` and `foo` is that the former has more specific covariant return type (M1Sub)
override fun foo(a: Derived): M1 = TODO()
public fun foo(a: Base): String = TODO()
}
@@ -0,0 +1,20 @@
// SKIP_TXT
// ISSUE: KT-55024
// MODULE: a
interface A {
fun foo()
}
internal sealed class B(val x: A) : A {
override fun foo() {}
fun bar() {}
}
// MODULE: b(a)
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
private fun test_1(x: A) {
if (x is B) {
x.foo()
x.bar()
}
}
@@ -0,0 +1,20 @@
// SKIP_TXT
// ISSUE: KT-55024
// MODULE: a
interface A {
fun foo()
}
internal sealed class B(val x: A) : A {
override fun foo() {}
fun bar() {}
}
// MODULE: b(a)
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
private fun test_1(x: A) {
if (x is B) {
x.foo()
<!DEBUG_INFO_SMARTCAST!>x<!>.bar()
}
}