From 8788d8e2d55423295064e1eec132bc766ca1932b Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Wed, 18 Nov 2015 14:41:48 +0300 Subject: [PATCH] Call chains: receiver data flow info is dropped for extensions with nullable receiver #KT-10056 Fixed --- .../resolve/calls/CallExpressionResolver.java | 10 ++++- .../smartCasts/safecalls/chainMixedUnsafe.kt | 5 +-- .../smartCasts/safecalls/nullableReceiver.kt | 15 +++++++ .../smartCasts/safecalls/nullableReceiver.txt | 12 +++++ .../safecalls/nullableReceiverInLongChain.kt | 44 +++++++++++++++++++ .../safecalls/nullableReceiverInLongChain.txt | 26 +++++++++++ .../safecalls/nullableReceiverWithExclExcl.kt | 13 ++++++ .../nullableReceiverWithExclExcl.txt | 12 +++++ .../safecalls/nullableReceiverWithFlexible.kt | 27 ++++++++++++ .../nullableReceiverWithFlexible.txt | 15 +++++++ .../checkers/DiagnosticsTestGenerated.java | 24 ++++++++++ 11 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.txt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java index 5235a4f6156..849bc9bd2fb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java @@ -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); diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/chainMixedUnsafe.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/chainMixedUnsafe.kt index 510272128ed..c85d5cada83 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/safecalls/chainMixedUnsafe.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/chainMixedUnsafe.kt @@ -1,6 +1,5 @@ fun calc(x: List?): Int { - // x should be non-null in arguments list, despite of a chain - x?.subList(0, 1).get(x.size) - // But not here! + // x is not-null only inside subList + x?.subList(0, x.size - 1).get(x.size) return x!!.size } diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.kt new file mode 100644 index 00000000000..2521c8aeac7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.kt @@ -0,0 +1,15 @@ +// See KT-10056 +class Foo(val bar: String) + +public inline fun T.let(f: (T) -> R): R = f(this) + +fun test(foo: Foo?) { + foo?.bar.let { + // Error, foo?.bar is nullable + it.length + // Error, foo is nullable + foo.bar.length + // Correct + foo?.bar?.length + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.txt new file mode 100644 index 00000000000..602fd914a30 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiver.txt @@ -0,0 +1,12 @@ +package + +public fun test(/*0*/ foo: Foo?): kotlin.Unit +public inline fun 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.kt new file mode 100644 index 00000000000..486dcbd3973 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.kt @@ -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.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.length + // Error, foo is nullable + foo.bar.gav.length + // Correct + foo?.bar?.gav?.length + } + foo?.bar?.gav.call { it }?.notNullLet { + foo.hashCode() + foo.bar.hashCode() + } +} + +fun testNotNull(foo: Foo) { + val s: String? = "" + foo.baz(s!!)?.gav.let { + it.length + // Ok because of foo. + s.length.hashCode() + } +} + +fun testNullable(foo: Foo?) { + val s: String? = "" + foo?.baz(s!!)?.gav.let { + it.length + // Ok because of foo?. + s?.length?.hashCode() + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.txt new file mode 100644 index 00000000000..b15c8e6912e --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverInLongChain.txt @@ -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 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.kt new file mode 100644 index 00000000000..c0441272b8d --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.kt @@ -0,0 +1,13 @@ +class Foo(val bar: String?) + +public inline fun T.let(f: (T) -> R): R = f(this) + +fun test(foo: Foo?) { + foo!!.bar.let { + // Correct + foo.bar?.length + // Unnecessary + foo?.bar?.length + } + foo.bar?.length +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.txt new file mode 100644 index 00000000000..1a3e5e25375 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithExclExcl.txt @@ -0,0 +1,12 @@ +package + +public fun test(/*0*/ foo: Foo?): kotlin.Unit +public inline fun 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 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.kt new file mode 100644 index 00000000000..43aee6ff6a8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.kt @@ -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.let(f: (T) -> R): R = f(this) + +fun test() { + val foo = Foo.create(null) + foo?.bar.let { + // Error, foo?.bar is nullable + it.length + // Foo is nullable but flexible, so call is considered safe here + foo.bar.length + // Correct + foo?.bar?.length + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.txt new file mode 100644 index 00000000000..884991819ca --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/nullableReceiverWithFlexible.txt @@ -0,0 +1,15 @@ +package + +public fun test(): kotlin.Unit +public inline fun 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! +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 8a0d42997f0..850c5a8639f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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");