Call chains: receiver data flow info is dropped for extensions with nullable receiver #KT-10056 Fixed
This commit is contained in:
+8
-2
@@ -413,9 +413,15 @@ public class CallExpressionResolver {
|
|||||||
|
|
||||||
boolean lastStage = element.getQualified() == expression;
|
boolean lastStage = element.getQualified() == expression;
|
||||||
// Drop NO_EXPECTED_TYPE / INDEPENDENT at last stage
|
// Drop NO_EXPECTED_TYPE / INDEPENDENT at last stage
|
||||||
// But receiver data flow info changes should be always applied, while we are inside call chain
|
|
||||||
ExpressionTypingContext baseContext = lastStage ? context : currentContext;
|
ExpressionTypingContext baseContext = lastStage ? context : currentContext;
|
||||||
contextForSelector = baseContext.replaceDataFlowInfo(receiverDataFlowInfo);
|
if (TypeUtils.isNullableType(receiverType) && !element.getSafe()) {
|
||||||
|
// Call with nullable receiver: take unconditional data flow info
|
||||||
|
contextForSelector = baseContext.replaceDataFlowInfo(unconditionalDataFlowInfo);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Take data flow info from the current receiver
|
||||||
|
contextForSelector = baseContext.replaceDataFlowInfo(receiverDataFlowInfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (receiver.exists() && receiver instanceof ReceiverValue) {
|
if (receiver.exists() && receiver instanceof ReceiverValue) {
|
||||||
DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue((ReceiverValue) receiver, context);
|
DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue((ReceiverValue) receiver, context);
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
fun calc(x: List<String>?): Int {
|
fun calc(x: List<String>?): Int {
|
||||||
// x should be non-null in arguments list, despite of a chain
|
// x is not-null only inside subList
|
||||||
x?.subList(0, 1)<!UNSAFE_CALL!>.<!>get(<!DEBUG_INFO_SMARTCAST!>x<!>.size)
|
x?.subList(0, <!DEBUG_INFO_SMARTCAST!>x<!>.size - 1)<!UNSAFE_CALL!>.<!>get(x<!UNSAFE_CALL!>.<!>size)
|
||||||
// But not here!
|
|
||||||
return x!!.size
|
return x!!.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// See KT-10056
|
||||||
|
class Foo(val bar: String)
|
||||||
|
|
||||||
|
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||||
|
|
||||||
|
fun test(foo: Foo?) {
|
||||||
|
foo?.bar.let {
|
||||||
|
// Error, foo?.bar is nullable
|
||||||
|
it<!UNSAFE_CALL!>.<!>length
|
||||||
|
// Error, foo is nullable
|
||||||
|
foo<!UNSAFE_CALL!>.<!>bar.length
|
||||||
|
// Correct
|
||||||
|
foo?.bar?.length
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun test(/*0*/ foo: Foo?): kotlin.Unit
|
||||||
|
public inline fun </*0*/ T, /*1*/ R> T.let(/*0*/ f: (T) -> R): R
|
||||||
|
|
||||||
|
public final class Foo {
|
||||||
|
public constructor Foo(/*0*/ bar: kotlin.String)
|
||||||
|
public final val bar: kotlin.String
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
class Bar(val gav: String)
|
||||||
|
|
||||||
|
class Foo(val bar: Bar, val nbar: Bar?) {
|
||||||
|
fun baz(s: String) = if (s != "") Bar(s) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||||
|
|
||||||
|
fun String?.call(f: (String?) -> String?) = f(this)
|
||||||
|
|
||||||
|
fun String.notNullLet(f: (String) -> Unit) = f(this)
|
||||||
|
|
||||||
|
fun test(foo: Foo?) {
|
||||||
|
foo?.bar?.gav.let {
|
||||||
|
// Error, foo?.bar?.gav is nullable
|
||||||
|
it<!UNSAFE_CALL!>.<!>length
|
||||||
|
// Error, foo is nullable
|
||||||
|
foo<!UNSAFE_CALL!>.<!>bar.gav.length
|
||||||
|
// Correct
|
||||||
|
foo?.bar?.gav?.length
|
||||||
|
}
|
||||||
|
foo?.bar?.gav.call { it }?.notNullLet {
|
||||||
|
foo<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
foo<!UNSAFE_CALL!>.<!>bar.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNotNull(foo: Foo) {
|
||||||
|
val s: String? = ""
|
||||||
|
foo.baz(s!!)?.gav.let {
|
||||||
|
it<!UNSAFE_CALL!>.<!>length
|
||||||
|
// Ok because of foo.
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>s<!>.length.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNullable(foo: Foo?) {
|
||||||
|
val s: String? = ""
|
||||||
|
foo?.baz(s!!)?.gav.let {
|
||||||
|
it<!UNSAFE_CALL!>.<!>length
|
||||||
|
// Ok because of foo?.
|
||||||
|
s?.length?.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun test(/*0*/ foo: Foo?): kotlin.Unit
|
||||||
|
public fun testNotNull(/*0*/ foo: Foo): kotlin.Unit
|
||||||
|
public fun testNullable(/*0*/ foo: Foo?): kotlin.Unit
|
||||||
|
public fun kotlin.String?.call(/*0*/ f: (kotlin.String?) -> kotlin.String?): kotlin.String?
|
||||||
|
public inline fun </*0*/ T, /*1*/ R> T.let(/*0*/ f: (T) -> R): R
|
||||||
|
public fun kotlin.String.notNullLet(/*0*/ f: (kotlin.String) -> kotlin.Unit): kotlin.Unit
|
||||||
|
|
||||||
|
public final class Bar {
|
||||||
|
public constructor Bar(/*0*/ gav: kotlin.String)
|
||||||
|
public final val gav: kotlin.String
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class Foo {
|
||||||
|
public constructor Foo(/*0*/ bar: Bar, /*1*/ nbar: Bar?)
|
||||||
|
public final val bar: Bar
|
||||||
|
public final val nbar: Bar?
|
||||||
|
public final fun baz(/*0*/ s: kotlin.String): Bar?
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
class Foo(val bar: String?)
|
||||||
|
|
||||||
|
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||||
|
|
||||||
|
fun test(foo: Foo?) {
|
||||||
|
foo!!.bar.let {
|
||||||
|
// Correct
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>foo<!>.bar?.length
|
||||||
|
// Unnecessary
|
||||||
|
foo<!UNNECESSARY_SAFE_CALL!>?.<!>bar?.length
|
||||||
|
}
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>foo<!>.bar?.length
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun test(/*0*/ foo: Foo?): kotlin.Unit
|
||||||
|
public inline fun </*0*/ T, /*1*/ R> T.let(/*0*/ f: (T) -> R): R
|
||||||
|
|
||||||
|
public final class Foo {
|
||||||
|
public constructor Foo(/*0*/ bar: kotlin.String?)
|
||||||
|
public final val bar: kotlin.String?
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// FILE: Foo.java
|
||||||
|
public class Foo {
|
||||||
|
public String bar;
|
||||||
|
|
||||||
|
private Foo(String bar) {
|
||||||
|
this.bar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Foo create(String bar) {
|
||||||
|
return new Foo(bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: Test.kt
|
||||||
|
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val foo = Foo.create(null)
|
||||||
|
foo?.bar.let {
|
||||||
|
// Error, foo?.bar is nullable
|
||||||
|
it<!UNSAFE_CALL!>.<!>length
|
||||||
|
// Foo is nullable but flexible, so call is considered safe here
|
||||||
|
foo.bar.length
|
||||||
|
// Correct
|
||||||
|
foo?.bar?.length
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun test(): kotlin.Unit
|
||||||
|
public inline fun </*0*/ T, /*1*/ R> T.let(/*0*/ f: (T) -> R): R
|
||||||
|
|
||||||
|
public open class Foo {
|
||||||
|
private constructor Foo(/*0*/ bar: kotlin.String!)
|
||||||
|
public final var bar: kotlin.String!
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
|
||||||
|
// Static members
|
||||||
|
public open fun create(/*0*/ bar: kotlin.String!): Foo!
|
||||||
|
}
|
||||||
@@ -15608,6 +15608,30 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableReceiver.kt")
|
||||||
|
public void testNullableReceiver() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableReceiverInLongChain.kt")
|
||||||
|
public void testNullableReceiverInLongChain() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableReceiverWithExclExcl.kt")
|
||||||
|
public void testNullableReceiverWithExclExcl() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nullableReceiverWithFlexible.kt")
|
||||||
|
public void testNullableReceiverWithFlexible() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("property.kt")
|
@TestMetadata("property.kt")
|
||||||
public void testProperty() throws Exception {
|
public void testProperty() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/property.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/property.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user