From 17593e4ef62f9cbbd7323a0418cdd266fde2a03d Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 12 Feb 2016 12:42:29 +0300 Subject: [PATCH] Call completer: safe call with nullable receiver has nullable return type #KT-11007 Fixed --- .../kotlin/resolve/calls/CallCompleter.kt | 6 ++- .../kotlin/resolve/calls/resolvedCallUtil.kt | 16 +++++++ .../cfg/deadCode/throwInLambda.instructions | 5 +-- .../smartCasts/safecalls/insideIfExpr.kt | 43 +++++++++++++++++++ .../smartCasts/safecalls/insideIfExpr.txt | 13 ++++++ .../checkers/DiagnosticsTestGenerated.java | 6 +++ 6 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt index 81a8df2d29e..9377394c708 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.Constrain import org.jetbrains.kotlin.resolve.calls.inference.filterConstraintsOut import org.jetbrains.kotlin.resolve.calls.inference.toHandle import org.jetbrains.kotlin.resolve.calls.model.* +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.makeNullableTypeIfSafeReceiver import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsImpl import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo @@ -274,7 +275,10 @@ class CallCompleter( val results = completeCallForArgument(deparenthesized, context) if (results != null && results.isSingleResult) { val resolvedCall = results.resultingCall - updatedType = if (resolvedCall.hasInferredReturnType()) resolvedCall.resultingDescriptor?.returnType else null + updatedType = if (resolvedCall.hasInferredReturnType()) { + resolvedCall.makeNullableTypeIfSafeReceiver(resolvedCall.resultingDescriptor?.returnType, context) + } + else null } // For the cases like 'foo(1)' the type of '1' depends on expected type (it can be Int, Byte, etc.), diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt index 9adee72977d..a62f5fb432c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt @@ -16,16 +16,22 @@ package org.jetbrains.kotlin.resolve.calls.resolvedCallUtil +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.psi.KtPsiUtil import org.jetbrains.kotlin.psi.KtThisExpression import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext +import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.descriptorUtil.getOwnerForEffectiveDispatchReceiverParameter import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils // it returns true if call has no dispatch receiver (e.g. resulting descriptor is top-level function or local variable) // or call receiver is effectively `this` instance (explicitly or implicitly) of resulting descriptor @@ -72,4 +78,14 @@ fun ResolvedCall<*>.getImplicitReceiverValue(): ReceiverValue? { } } +private fun ResolvedCall<*>.hasSafeNullableReceiver(context: CallResolutionContext<*>): Boolean { + if (!isSafeCall) return false + val receiverValue = getExplicitReceiverValue()?.let { DataFlowValueFactory.createDataFlowValue(it, context) } + ?: return false + return context.dataFlowInfo.getPredictableNullability(receiverValue).canBeNull() +} + +fun ResolvedCall<*>.makeNullableTypeIfSafeReceiver(type: KotlinType?, context: CallResolutionContext<*>) = + type?.let { TypeUtils.makeNullableIfNeeded(type, hasSafeNullableReceiver(context)) } + fun ResolvedCall<*>.hasBothReceivers() = dispatchReceiver != null && extensionReceiver != null \ No newline at end of file diff --git a/compiler/testData/cfg/deadCode/throwInLambda.instructions b/compiler/testData/cfg/deadCode/throwInLambda.instructions index 1b82575ddcf..c23b3e926cd 100644 --- a/compiler/testData/cfg/deadCode/throwInLambda.instructions +++ b/compiler/testData/cfg/deadCode/throwInLambda.instructions @@ -53,9 +53,8 @@ L3 [after local declaration]: r({ throw Exception() }) -> PREV:[jmp?(L3)] mark(let { throw Exception() }) call(let { throw Exception() }, let|, ) - jmp(error) NEXT:[] L2 [result of call]: - mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2)] + mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2), call(let { throw Exception() }, let|, )] jt(L6|!) NEXT:[mark("unreachable?"), merge(fn()?.let { throw Exception() } ?: "unreachable?"|!, ) -> ] mark("unreachable?") r("unreachable?") -> @@ -67,7 +66,7 @@ L6 [after elvis operator]: L1: 1 NEXT:[] error: - PREV:[jmp(error)] + PREV:[] sink: PREV:[, , d({ throw Exception() })] ===================== diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt new file mode 100644 index 00000000000..d588ecf52fe --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt @@ -0,0 +1,43 @@ +// See KT-11007: Wrong smart cast to not-null type after safe calls in if / when expression + +val String.copy: String + get() = this + +fun foo() { + val s: String? = null + val ss = if (true) { + s?.length + } else { + s?.length + } + ss.hashCode() // Smart-cast to Int, should be unsafe call + val sss = if (true) { + s?.copy + } + else { + s?.copy + } + sss.length +} + +class My { + val String.copy2: String + get() = this + + fun foo() { + val s: String? = null + val ss = if (true) { + s?.length + } else { + s?.length + } + ss.hashCode() + val sss = if (true) { + s?.copy2 + } + else { + s?.copy2 + } + sss.length + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.txt new file mode 100644 index 00000000000..a27bba4a042 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.txt @@ -0,0 +1,13 @@ +package + +public val kotlin.String.copy: kotlin.String +public fun foo(): kotlin.Unit + +public final class My { + public constructor My() + public final val kotlin.String.copy2: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun foo(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index ff9cc269959..9b566736e09 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -17252,6 +17252,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("insideIfExpr.kt") + public void testInsideIfExpr() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/insideIfExpr.kt"); + doTest(fileName); + } + @TestMetadata("longChain.kt") public void testLongChain() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/longChain.kt");