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
@@ -26,7 +26,7 @@ fun test_3(a: A?, b: Boolean) {
if (b && a!!.foo()) {
a.foo() // OK
}
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!> // Bad
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!> // Bad
}
fun test_4(a: A?, b: Boolean) {
@@ -38,9 +38,9 @@ fun test_4(a: A?, b: Boolean) {
fun test_5(a: A?, b: Boolean) {
if (b || a!!.foo()) {
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun <X : A?> test_6(x: X) {
@@ -8,13 +8,13 @@ fun test_1(b: Boolean?) {
if ((b == true) == true) {
b.not() // OK
} else {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
}
}
fun test_2(b: Boolean?) {
if ((b == true) != true) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
} else {
b.not() // OK
}
@@ -22,7 +22,7 @@ fun test_2(b: Boolean?) {
fun test_3(b: Boolean?) {
if ((b == true) == false) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
} else {
b.not() // OK
}
@@ -32,13 +32,13 @@ fun test_4(b: Boolean?) {
if ((b == true) != false) {
b.not() // OK
} else {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
}
}
fun test_5(b: Boolean?) {
if ((b != true) == true) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
} else {
b.not() // OK
}
@@ -48,7 +48,7 @@ fun test_6(b: Boolean?) {
if ((b != true) != true) {
b.not() // OK
} else {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
}
}
@@ -56,13 +56,13 @@ fun test_7(b: Boolean?) {
if ((b != true) == false) {
b.not() // OK
} else {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
}
}
fun test_8(b: Boolean?) {
if ((b != true) != false) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>not<!>()<!> // Bad
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>not<!>()<!> // Bad
} else {
b.not() // OK
}
@@ -34,24 +34,24 @@ fun test_4(a: A?) {
fun test_5(a: A?) {
a == null || throw Exception()
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun teat_6(a: A?) {
a != null && throw Exception()
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun test_7(a: A?) {
if (a == null || throw Exception()) {
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun test_8(a: A?) {
if (a != null && throw Exception()) {
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
@@ -104,18 +104,18 @@ fun test_7() {
if (x != null) {
x.length // OK
y.<!INAPPLICABLE_CANDIDATE!>length<!> // Bad
y.<!UNSAFE_CALL!>length<!> // Bad
z.length // OK
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>length<!> // Bad
x.<!UNSAFE_CALL!>length<!> // Bad
y.length // OK
z.<!INAPPLICABLE_CANDIDATE!>length<!> // Bad
z.<!UNSAFE_CALL!>length<!> // Bad
}
if (z != null) {
x.length // OK
y.<!INAPPLICABLE_CANDIDATE!>length<!> // Bad
y.<!UNSAFE_CALL!>length<!> // Bad
z.length // OK
}
}
@@ -16,12 +16,12 @@ fun test_1(x: A, y: A?) {
fun test_2(x: A?, y: A?) {
if (x == y) {
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
y.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
y.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
if (x === y) {
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
y.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
y.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
}
@@ -39,18 +39,18 @@ fun test_1(x: A?) {
if (x != null) {
x.foo()
} else {
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun test_2(x: A?) {
if (x == null) {
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
} else {
x.foo()
}
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
fun test_3(x: A?) {
@@ -67,8 +67,8 @@ fun test_5(q: Q?) {
// `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData.
if (q?.data?.s?.inc() != null) {
q.data // good
q.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q.data.<!INAPPLICABLE_CANDIDATE!>s<!>.inc() // should be bad
q.data.<!UNSAFE_CALL!>s<!> // should be bad
q.data.<!UNSAFE_CALL!>s<!>.inc() // should be bad
}
}
@@ -76,15 +76,15 @@ fun test_6(q: Q?) {
// `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData.
q?.data?.s?.inc() ?: return
q.data // good
q.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q.data.<!INAPPLICABLE_CANDIDATE!>s<!>.inc() // should be bad
q.data.<!UNSAFE_CALL!>s<!> // should be bad
q.data.<!UNSAFE_CALL!>s<!>.inc() // should be bad
}
fun test_7(q: Q?) {
if (q?.fdata()?.fs()?.inc() != null) {
q.fdata() // good
q.fdata().<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>fs<!>()<!> // bad
q.fdata().<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>fs<!>()<!>.inc() // bad
q.fdata().<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>fs<!>()<!> // bad
q.fdata().<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>fs<!>()<!>.inc() // bad
}
}
@@ -98,44 +98,44 @@ fun test_9(a: Int, b: Int?) {
if (a == b) {
b.inc()
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (a === b) {
b.inc()
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (b == a) {
b.inc()
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (b === a) {
b.inc()
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
fun test_10(a: Int?, b: Int?) {
if (a == b) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (a === b) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (b == a) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
if (b === a) {
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
b.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>inc<!>()<!>
b.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>inc<!>()<!>
}
fun test_11(q: QImpl?, q2: QImpl) {
@@ -148,8 +148,8 @@ fun test_11(q: QImpl?, q2: QImpl) {
// Smartcasting of `q.data` should have no effect on `q2.data`.
// Issue: Smartcasting of QImpl.data affects all instances
q2.data
q2.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q2.data.<!INAPPLICABLE_CANDIDATE!>s<!>.inc() // should be bad
q2.data.<!UNSAFE_CALL!>s<!> // should be bad
q2.data.<!UNSAFE_CALL!>s<!>.inc() // should be bad
if (q2.data != null) {
q2.data.s
@@ -162,8 +162,8 @@ fun test_12(q: QImplWithCustomGetter?) {
// `q.data` is a property that has an open getter, so we can NOT smartcast it to non-nullable MyData.
if (q?.data?.s?.inc() != null) {
q.data // good
q.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q.data.<!INAPPLICABLE_CANDIDATE!>s<!>.inc() // should be bad
q.data.<!UNSAFE_CALL!>s<!> // should be bad
q.data.<!UNSAFE_CALL!>s<!>.inc() // should be bad
}
}
@@ -171,7 +171,7 @@ fun test_13(q: QImplMutable?) {
// `q.data` is a property that is mutable, so we can NOT smartcast it to non-nullable MyData.
if (q?.data?.s?.inc() != null) {
q.data // good
q.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q.data.<!INAPPLICABLE_CANDIDATE!>s<!>.inc() // should be bad
q.data.<!UNSAFE_CALL!>s<!> // should be bad
q.data.<!UNSAFE_CALL!>s<!>.inc() // should be bad
}
}
@@ -22,6 +22,6 @@ fun test_3(a: Any?, b: Boolean) {
fun test_4(a: Any?, b: Boolean) {
if (a is String || b) {
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!> // Should be Bad
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!> // Should be Bad
}
}
@@ -5,7 +5,7 @@ fun test_1(s: String?) {
if (s?.check() == true) {
s.length // Should be OK
} else {
s.<!INAPPLICABLE_CANDIDATE!>length<!> // Should be bad
s.<!UNSAFE_CALL!>length<!> // Should be bad
}
}
@@ -13,13 +13,13 @@ fun test_2(s: String?) {
if (s?.check() == false) {
s.length // Should be OK
} else {
s.<!INAPPLICABLE_CANDIDATE!>length<!> // Should be bad
s.<!UNSAFE_CALL!>length<!> // Should be bad
}
}
fun test_3(s: String?) {
if (s?.check() != true) {
s.<!INAPPLICABLE_CANDIDATE!>length<!> // Should be bad
s.<!UNSAFE_CALL!>length<!> // Should be bad
} else {
s.length // Should be OK
}
@@ -27,7 +27,7 @@ fun test_3(s: String?) {
fun test_4(s: String?) {
if (s?.check() != false) {
s.<!INAPPLICABLE_CANDIDATE!>length<!> // Should be bad
s.<!UNSAFE_CALL!>length<!> // Should be bad
} else {
s.length // Should be OK
}
@@ -5,7 +5,7 @@ fun String.let(block: () -> Unit) {}
fun test(x: String?) {
x?.foo(x.length == 1)
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
interface A {
@@ -27,12 +27,12 @@ fun test_3(x: Any) {
fun test_4(x: A?) {
x?.id()?.bool()
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>id<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>id<!>()<!>
}
fun Any?.boo(b: Boolean) {}
fun test_5(x: A?) {
x?.let { return }?.boo(x.bool())
x.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>id<!>()<!>
x.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>id<!>()<!>
}
@@ -11,5 +11,5 @@ fun test(b: Boolean) {
} else {
a = null
}
a.<!INAPPLICABLE_CANDIDATE{LT}!><!INAPPLICABLE_CANDIDATE{PSI}!>foo<!>()<!>
a.<!UNSAFE_CALL{LT}!><!UNSAFE_CALL{PSI}!>foo<!>()<!>
}
@@ -18,5 +18,5 @@ fun test_3() {
x = ""
x.length
x = null
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}