FIR checker: differentiate UNSAFE_CALL from INAPPLICABLE_CANDIDATE

To do so, inside the root cause of inapplicable candidate errors,
we will record expected/actual type of receiver, if any.
That will help identifying inapplicable calls on nullable receiver.
This commit is contained in:
Jinseong Jeon
2021-01-26 16:03:27 -08:00
committed by Mikhail Glukhikh
parent 4b823eca21
commit e72ddbcbfe
275 changed files with 1426 additions and 1388 deletions
@@ -9,5 +9,5 @@ fun checkJump(x: Int?, y: Int?) {
y.hashCode()
}
// Smart cast here is erroneous: y is nullable
y.<!INAPPLICABLE_CANDIDATE!>hashCode<!>()
y.<!UNSAFE_CALL!>hashCode<!>()
}
@@ -4,8 +4,8 @@ public fun foo(x: String?, y: String?): Int {
// z is not null in both branches
z.length
// y is nullable if x != null
y.<!INAPPLICABLE_CANDIDATE!>length<!>
y.<!UNSAFE_CALL!>length<!>
}
// y is null because of the break
return y.<!INAPPLICABLE_CANDIDATE!>length<!>
return y.<!UNSAFE_CALL!>length<!>
}
@@ -13,5 +13,5 @@ public fun foo(x: String?): Int {
checkSubtype<Int>(y)
}
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -10,7 +10,7 @@ public fun foo(x: String?, z: String?, w: String?): Int {
// w is not null because of w!!
w.length
// z is nullable despite of z!!
z.<!INAPPLICABLE_CANDIDATE!>length<!>
z.<!UNSAFE_CALL!>length<!>
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -7,7 +7,7 @@ public fun foo(x: String?, z: String?): Int {
gav(if (x == null) break else x, z!!)
} while (bar())
// z is nullable despite of z!!
z.<!INAPPLICABLE_CANDIDATE!>length<!>
z.<!UNSAFE_CALL!>length<!>
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -10,5 +10,5 @@ public fun foo(x: String?): Int {
} while (bar())
y.hashCode()
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -9,5 +9,5 @@ public fun foo(x: String?, z: String?): Int {
// z is not null because of z!!
z.length
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -12,5 +12,5 @@ public fun foo(x: String?, z: String?, w: String?): Int {
// z is not null because of z!!
z.length
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -9,5 +9,5 @@ public fun foo(p: String?): Int {
p!!.length
} while (!x())
// Here we have do while loop but p is still nullable due to break before
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -5,5 +5,5 @@ fun foo(s: String?): Int {
if (bar()) break
} while (s!!.length > 0)
// This call is unsafe due to break
return s.<!INAPPLICABLE_CANDIDATE!>length<!>
return s.<!UNSAFE_CALL!>length<!>
}
@@ -6,5 +6,5 @@ public fun foo(p: String?): Int {
if (p != null) break
} while (!x())
// p can be null despite of the break
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -5,5 +5,5 @@ fun foo(s: String?): Int {
if (bar()) break
} while (s==null)
// This call is unsafe due to break
return s.<!INAPPLICABLE_CANDIDATE!>length<!>
return s.<!UNSAFE_CALL!>length<!>
}
@@ -6,5 +6,5 @@ public fun foo(x: String?): Int {
x.length
} while (true)
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -2,8 +2,8 @@ public fun foo(x: String?, y: String?): Int {
while (true) {
x ?: if (y == null) break
// y is nullable if x != null
y.<!INAPPLICABLE_CANDIDATE!>length<!>
y.<!UNSAFE_CALL!>length<!>
}
// y is null because of the break
return y.<!INAPPLICABLE_CANDIDATE!>length<!>
return y.<!UNSAFE_CALL!>length<!>
}
@@ -2,8 +2,8 @@ public fun foo(x: String?, y: String?): Int {
while (true) {
(if (x != null) break else y) ?: y!!
// y is not null in both branches but it's hard to determine
y.<!INAPPLICABLE_CANDIDATE!>length<!>
y.<!UNSAFE_CALL!>length<!>
}
// y can be null because of the break
return y.<!INAPPLICABLE_CANDIDATE!>length<!>
return y.<!UNSAFE_CALL!>length<!>
}
@@ -10,5 +10,5 @@ public fun foo(x: String?): Int {
} while (bar())
y.hashCode()
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -6,5 +6,5 @@ public fun foo(x: String?): Int {
// In future we can infer this initialization
<!UNINITIALIZED_VARIABLE!>y<!>.hashCode()
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -6,5 +6,5 @@ public fun foo(x: String?): Int {
y.length
}
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -7,5 +7,5 @@ public fun foo(x: String?, y: String?): Int {
y.length
}
// y is null because of the break
return y.<!INAPPLICABLE_CANDIDATE!>length<!>
return y.<!UNSAFE_CALL!>length<!>
}
@@ -13,7 +13,7 @@ public fun foo(p: String?, r: String?, q: String?): Int {
if (!x(p)) break
}
// Long break allows r == null
r.<!INAPPLICABLE_CANDIDATE!>length<!>
r.<!UNSAFE_CALL!>length<!>
// Smart cast is possible
q.length
return p.length
@@ -18,5 +18,5 @@ public fun foo(x: String?): Int {
// y is always Int even here
checkSubtype<Int>(y)
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -8,5 +8,5 @@ public fun foo(x: String?): Int {
}
}
// x is null because of the break
return x.<!INAPPLICABLE_CANDIDATE!>length<!>
return x.<!UNSAFE_CALL!>length<!>
}
@@ -5,5 +5,5 @@ fun foo(s: String?): Int {
if (bar()) break
}
// Call is unsafe due to break
return s.<!INAPPLICABLE_CANDIDATE!>length<!>
return s.<!UNSAFE_CALL!>length<!>
}
@@ -6,5 +6,5 @@ public fun foo(p: String?): Int {
if (x()) break
}
// p is nullable because it's possible loop body is not executed at all
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -7,5 +7,5 @@ public fun foo(p: String?): Int {
if (x()) break
}
// Smart cast should not work in this case, see KT-6284
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -8,5 +8,5 @@ public fun foo(p: String?): Int {
p.length
}
// p can be null because break is earlier than return
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -7,5 +7,5 @@ public fun foo(p: String?): Int {
p!!.length
}
// Here we have while (true) loop but p is nullable due to break before
return p.<!INAPPLICABLE_CANDIDATE!>length<!>
return p.<!UNSAFE_CALL!>length<!>
}
@@ -8,7 +8,7 @@ fun foo() {
}
// TODO: this testdata fixates undesired behavior (it should be an unsafe call)
x.<!INAPPLICABLE_CANDIDATE!>length<!> // 'x' is unsoundly smartcasted here
x.<!UNSAFE_CALL!>length<!> // 'x' is unsoundly smartcasted here
}
fun bar() {
@@ -19,5 +19,5 @@ fun bar() {
}
// TODO: this testdata fixates undesired behavior (it should be an unsafe call)
x.<!INAPPLICABLE_CANDIDATE!>size<!> // 'x' is unsoundly smartcasted here
x.<!UNSAFE_CALL!>size<!> // 'x' is unsoundly smartcasted here
}
@@ -7,7 +7,7 @@ fun foo() {
break
}
x.<!INAPPLICABLE_CANDIDATE!>length<!> // 'x' is unsoundly smartcasted here
x.<!UNSAFE_CALL!>length<!> // 'x' is unsoundly smartcasted here
}
fun bar() {
@@ -17,5 +17,5 @@ fun bar() {
break
}
x.<!INAPPLICABLE_CANDIDATE!>size<!> // 'x' is unsoundly smartcasted here
x.<!UNSAFE_CALL!>size<!> // 'x' is unsoundly smartcasted here
}