Call chains: receiver data flow info is dropped for extensions with nullable receiver #KT-10056 Fixed

This commit is contained in:
Mikhail Glukhikh
2015-11-18 14:41:48 +03:00
parent 97cdaba8b0
commit 8788d8e2d5
11 changed files with 198 additions and 5 deletions
@@ -413,9 +413,15 @@ public class CallExpressionResolver {
boolean lastStage = element.getQualified() == expression;
// 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;
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) {
DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue((ReceiverValue) receiver, context);
@@ -1,6 +1,5 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list, despite of a chain
x?.subList(0, 1)<!UNSAFE_CALL!>.<!>get(<!DEBUG_INFO_SMARTCAST!>x<!>.size)
// But not here!
// x is not-null only inside subList
x?.subList(0, <!DEBUG_INFO_SMARTCAST!>x<!>.size - 1)<!UNSAFE_CALL!>.<!>get(x<!UNSAFE_CALL!>.<!>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
}
}
@@ -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
}
@@ -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()
}
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
}
@@ -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);
}
@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")
public void testProperty() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/property.kt");