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 2204efa7a57..332a45d8230 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java @@ -21,7 +21,9 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingTrace; @@ -36,6 +38,8 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue; +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory; import org.jetbrains.kotlin.resolve.calls.util.CallMaker; import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; @@ -66,17 +70,20 @@ public class CallExpressionResolver { private final ConstantExpressionEvaluator constantExpressionEvaluator; private final SymbolUsageValidator symbolUsageValidator; private final DataFlowAnalyzer dataFlowAnalyzer; + @NotNull private final KotlinBuiltIns builtIns; public CallExpressionResolver( @NotNull CallResolver callResolver, @NotNull ConstantExpressionEvaluator constantExpressionEvaluator, @NotNull SymbolUsageValidator symbolUsageValidator, - @NotNull DataFlowAnalyzer dataFlowAnalyzer + @NotNull DataFlowAnalyzer dataFlowAnalyzer, + @NotNull KotlinBuiltIns builtIns ) { this.callResolver = callResolver; this.constantExpressionEvaluator = constantExpressionEvaluator; this.symbolUsageValidator = symbolUsageValidator; this.dataFlowAnalyzer = dataFlowAnalyzer; + this.builtIns = builtIns; } private ExpressionTypingServices expressionTypingServices; @@ -152,9 +159,16 @@ public class CallExpressionResolver { context, "trace to resolve as variable", nameExpression); KotlinType type = getVariableType(nameExpression, receiver, callOperationNode, context.replaceTraceAndCache(temporaryForVariable), result); - // TODO: for a safe call, it's necessary to set receiver != null here, as inside ArgumentTypeResolver.analyzeArgumentsAndRecordTypes - // Unfortunately it provokes problems with x?.y!!.foo() with the following x!!.bar(): - // x != null proceeds to successive statements + // NB: we have duplicating code in ArgumentTypeResolver. + // It would be better to do it in getSelectorTypeInfo, but it breaks call expression analysis + // (all safe calls become unnecessary after it) + // QualifierReceiver is a thing like Collections. which has no type or value + if (receiver.exists() && !(receiver instanceof QualifierReceiver)) { + DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context); + if (callOperationNode != null && callOperationNode.getElementType() == KtTokens.SAFE_ACCESS) { + context = context.replaceDataFlowInfo(context.dataFlowInfo.disequate(receiverDataFlowValue, DataFlowValue.nullValue(builtIns))); + } + } if (result[0]) { temporaryForVariable.commit(); diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.kt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.kt new file mode 100644 index 00000000000..43bf6b3472f --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.kt @@ -0,0 +1,14 @@ +// See KT-7290 +class MyClass(val x: String?) +fun foo(y: MyClass?): Int { + // x here is smartcast but y is not + val z = y?.x?.subSequence(0, y.x.length) + // !! is necessary here + y!!.x + return z?.length ?: -1 +} +fun bar(y: MyClass?) { + y?.x!!.length + // !! is necessary here + y!!.x +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.txt b/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.txt new file mode 100644 index 00000000000..1f104ce61cd --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.txt @@ -0,0 +1,12 @@ +package + +public fun bar(/*0*/ y: MyClass?): kotlin.Unit +public fun foo(/*0*/ y: MyClass?): kotlin.Int + +public final class MyClass { + public constructor MyClass(/*0*/ x: kotlin.String?) + public final val x: 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/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 5348ec126bb..3373ae214f1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -15194,6 +15194,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("propertyChain.kt") + public void testPropertyChain() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.kt"); + doTest(fileName); + } + @TestMetadata("receiver.kt") public void testReceiver() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/receiver.kt");