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 5282af54009..c1f7bb423ca 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.java @@ -22,11 +22,11 @@ import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; 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; import org.jetbrains.kotlin.resolve.DescriptorUtils; +import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext; import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode; @@ -53,6 +53,7 @@ import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryKt; import javax.inject.Inject; import java.util.Collections; +import java.util.Deque; import java.util.List; import static org.jetbrains.kotlin.diagnostics.Errors.*; @@ -307,31 +308,6 @@ public class CallExpressionResolver { return TypeInfoFactoryKt.noTypeInfo(context); } - /** - * Extended variant of JetTypeInfo stores additional information - * about data flow info from the left-more receiver, e.g. x != null for - * foo(x!!)?.bar(y!!)?.baz() - */ - private static class JetTypeInfoInsideSafeCall extends JetTypeInfo { - - private final DataFlowInfo safeCallChainInfo; - - private JetTypeInfoInsideSafeCall(@NotNull JetTypeInfo typeInfo, @Nullable DataFlowInfo safeCallChainInfo) { - super(typeInfo.getType(), typeInfo.getDataFlowInfo(), typeInfo.getJumpOutPossible(), typeInfo.getJumpFlowInfo()); - this.safeCallChainInfo = safeCallChainInfo; - } - - /** - * Returns safe call chain information which is taken from the left-most receiver of a chain - * foo(x!!)?.bar(y!!)?.gav() ==> x != null is safe call chain information - */ - @Nullable - public DataFlowInfo getSafeCallChainInfo() { - return safeCallChainInfo; - } - } - - /** * Visits a qualified expression like x.y or x?.z controlling data flow information changes. * @@ -341,97 +317,82 @@ public class CallExpressionResolver { public JetTypeInfo getQualifiedExpressionTypeInfo( @NotNull KtQualifiedExpression expression, @NotNull ExpressionTypingContext context ) { - // TODO : functions as values - KtExpression selectorExpression = expression.getSelectorExpression(); - KtExpression receiverExpression = expression.getReceiverExpression(); - boolean safeCall = (expression.getOperationSign() == KtTokens.SAFE_ACCESS); - ResolutionContext contextForReceiver = context.replaceExpectedType(NO_EXPECTED_TYPE). - replaceContextDependency(INDEPENDENT). - replaceInsideCallChain(true); // Enter call chain - // Visit receiver (x in x.y or x?.z) here. Recursion is possible. - JetTypeInfo receiverTypeInfo = expressionTypingServices.getTypeInfo(receiverExpression, contextForReceiver); + ExpressionTypingContext currentContext = context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); + Deque elementChain = CallExpressionUnrollerKt.unroll(expression); + + JetTypeInfo receiverTypeInfo = expressionTypingServices.getTypeInfo(elementChain.getFirst().getReceiver(), currentContext); KotlinType receiverType = receiverTypeInfo.getType(); - QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, receiverExpression); - - if (receiverType == null) receiverType = ErrorUtils.createErrorType("Type for " + expression.getText()); - - ReceiverValue receiver = qualifierReceiver == null ? new ExpressionReceiver(receiverExpression, receiverType) : qualifierReceiver; DataFlowInfo receiverDataFlowInfo = receiverTypeInfo.getDataFlowInfo(); - // Receiver changes should be always applied, at least for argument analysis - context = context.replaceDataFlowInfo(receiverDataFlowInfo); + JetTypeInfo resultTypeInfo = receiverTypeInfo; - // Visit selector (y in x.y) here. Recursion is also possible. - JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo( - receiver, expression.getOperationTokenNode(), selectorExpression, context); - KotlinType selectorReturnType = selectorReturnTypeInfo.getType(); + boolean unconditional = true; + DataFlowInfo unconditionalDataFlowInfo = receiverDataFlowInfo; - resolveDeferredReceiverInQualifiedExpression(qualifierReceiver, expression, context); - checkNestedClassAccess(expression, context); + for (CallExpressionElement element : elementChain) { + if (receiverType == null) { + receiverType = ErrorUtils.createErrorType("Type for " + expression.getText()); + } + QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, element.getReceiver()); - //TODO move further - if (safeCall) { - if (selectorReturnType != null) { - if (TypeUtils.isNullableType(receiverType)) { - selectorReturnType = TypeUtils.makeNullable(selectorReturnType); - selectorReturnTypeInfo = selectorReturnTypeInfo.replaceType(selectorReturnType); - } + ReceiverValue receiver = qualifierReceiver == null ? new ExpressionReceiver(element.getReceiver(), receiverType) : qualifierReceiver; + + boolean lastStage = element.getQualified() == expression; + assert lastStage == (element == elementChain.getLast()); + // 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; + currentContext = baseContext.replaceDataFlowInfo(receiverDataFlowInfo); + + KtExpression selectorExpression = element.getSelector(); + JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo(receiver, element.getNode(), selectorExpression, currentContext); + KotlinType selectorReturnType = selectorReturnTypeInfo.getType(); + + resolveDeferredReceiverInQualifiedExpression(qualifierReceiver, element.getQualified(), currentContext); + checkNestedClassAccess(element.getQualified(), currentContext); + + boolean safeCall = element.getSafe(); + if (safeCall && selectorReturnType != null && TypeUtils.isNullableType(receiverType)) { + selectorReturnType = TypeUtils.makeNullable(selectorReturnType); + selectorReturnTypeInfo = selectorReturnTypeInfo.replaceType(selectorReturnType); + } + + // TODO : this is suspicious: remove this code? + if (selectorExpression != null && selectorReturnType != null) { + currentContext.trace.recordType(selectorExpression, selectorReturnType); + } + resultTypeInfo = selectorReturnTypeInfo; + CompileTimeConstant value = constantExpressionEvaluator.evaluateExpression(element.getQualified(), currentContext.trace, currentContext.expectedType); + if (value != null && value.isPure()) { + resultTypeInfo = dataFlowAnalyzer.createCompileTimeConstantTypeInfo(value, element.getQualified(), currentContext); + if (lastStage) return resultTypeInfo; + } + if (currentContext.contextDependency == INDEPENDENT) { + dataFlowAnalyzer.checkType(resultTypeInfo.getType(), element.getQualified(), currentContext); + } + // For the next stage, if any, current stage selector is the receiver! + receiverTypeInfo = selectorReturnTypeInfo; + receiverType = selectorReturnType; + receiverDataFlowInfo = receiverTypeInfo.getDataFlowInfo(); + // if we have only dots and not ?. move unconditional data flow info further + if (safeCall) { + unconditional = false; + } + else if (unconditional) { + unconditionalDataFlowInfo = receiverDataFlowInfo; + } + //noinspection ConstantConditions + if (!lastStage && !currentContext.trace.get(BindingContext.PROCESSED, element.getQualified())) { + // Store type information (to prevent problems in call completer) + currentContext.trace.record(BindingContext.PROCESSED, element.getQualified()); + currentContext.trace.record(BindingContext.EXPRESSION_TYPE_INFO, element.getQualified(), + resultTypeInfo.replaceDataFlowInfo(unconditionalDataFlowInfo)); + // save scope before analyze and fix debugger: see CodeFragmentAnalyzer.correctContextForExpression + BindingContextUtilsKt.recordScope(currentContext.trace, currentContext.scope, element.getQualified()); + BindingContextUtilsKt.recordDataFlowInfo(currentContext.replaceDataFlowInfo(unconditionalDataFlowInfo), element.getQualified()); } } - // TODO : this is suspicious: remove this code? - if (selectorExpression != null && selectorReturnType != null) { - context.trace.recordType(selectorExpression, selectorReturnType); - } - - CompileTimeConstant value = constantExpressionEvaluator.evaluateExpression(expression, context.trace, context.expectedType); - if (value != null && value.isPure()) { - return dataFlowAnalyzer.createCompileTimeConstantTypeInfo(value, expression, context); - } - - JetTypeInfo typeInfo; - DataFlowInfo safeCallChainInfo; - if (receiverTypeInfo instanceof JetTypeInfoInsideSafeCall) { - safeCallChainInfo = ((JetTypeInfoInsideSafeCall) receiverTypeInfo).getSafeCallChainInfo(); - } - else { - safeCallChainInfo = null; - } - if (safeCall) { - if (safeCallChainInfo == null) safeCallChainInfo = receiverDataFlowInfo; - if (context.insideCallChain) { - // If we are inside safe call chain, we SHOULD take arguments into account, for example - // x?.foo(y!!)?.bar(x.field)?.gav(y.field) (like smartCasts\safecalls\longChain) - // Also, we should provide further safe call chain data flow information or - // if we are in the most left safe call then just take it from receiver - typeInfo = new JetTypeInfoInsideSafeCall(selectorReturnTypeInfo, safeCallChainInfo); - } - else { - // Here we should not take selector data flow info into account because it's only one branch, see KT-7204 - // x?.foo(y!!) // y becomes not-nullable during argument analysis - // y.bar() // ERROR: y is nullable at this point - // So we should just take safe call chain data flow information, e.g. foo(x!!)?.bar()?.gav() - // If it's null, we must take receiver normal info, it's a chain with length of one like foo(x!!)?.bar() and - // safe call chain information does not yet exist - typeInfo = selectorReturnTypeInfo.replaceDataFlowInfo(safeCallChainInfo); - } - } - else { - // It's not a safe call, so we can take selector data flow information - // Safe call chain information also should be provided because it's can be a part of safe call chain - if (context.insideCallChain || safeCallChainInfo == null) { - // Not a safe call inside call chain with safe calls OR - // call chain without safe calls at all - typeInfo = new JetTypeInfoInsideSafeCall(selectorReturnTypeInfo, selectorReturnTypeInfo.getDataFlowInfo()); - } - else { - // Exiting call chain with safe calls -- take data flow info from the lest-most receiver - // foo(x!!)?.bar().gav() - typeInfo = selectorReturnTypeInfo.replaceDataFlowInfo(safeCallChainInfo); - } - } - if (context.contextDependency == INDEPENDENT) { - dataFlowAnalyzer.checkType(typeInfo.getType(), expression, context); - } - return typeInfo; + // if we are at last stage, we should just take result type info and set unconditional data flow info + return resultTypeInfo.replaceDataFlowInfo(unconditionalDataFlowInfo); } private void resolveDeferredReceiverInQualifiedExpression( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionUnroller.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionUnroller.kt new file mode 100644 index 00000000000..4f992e04034 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionUnroller.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve.calls + +import com.intellij.lang.ASTNode +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import java.util.* + +internal fun unroll(root: KtQualifiedExpression): Deque { + var current: KtExpression = root + val result = linkedListOf() + while (current is KtQualifiedExpression) { + result.addFirst(CallExpressionElement(current)) + current = result.first.receiver + } + return result +} + +internal data class CallExpressionElement(val qualified: KtQualifiedExpression) { + + val receiver: KtExpression + get() = qualified.receiverExpression + + val selector: KtExpression? + get() = qualified.selectorExpression + + val safe: Boolean + get() = qualified.operationSign == KtTokens.SAFE_ACCESS + + val node: ASTNode + get() = qualified.operationTokenNode +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/JetTypeInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/JetTypeInfo.kt index 7ea8cfdcbd8..59a05debc7f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/JetTypeInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/JetTypeInfo.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.types.KotlinType * At the end current data flow info is x != null && y != null, but jump data flow info is x != null only. * Both break and continue are counted as possible jump outside of a loop, but return is not. */ -/*package*/ open class JetTypeInfo( +class JetTypeInfo( val type: KotlinType?, val dataFlowInfo: DataFlowInfo, val jumpOutPossible: Boolean = false, @@ -39,7 +39,7 @@ import org.jetbrains.kotlin.types.KotlinType fun clearType() = replaceType(null) - // NB: do not compare type with this.type because this comparison is complex and unstabld + // NB: do not compare type with this.type because this comparison is complex and unstable fun replaceType(type: KotlinType?) = JetTypeInfo(type, dataFlowInfo, jumpOutPossible, jumpFlowInfo) fun replaceJumpOutPossible(jumpOutPossible: Boolean) = diff --git a/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.kt b/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.kt new file mode 100644 index 00000000000..146492c79f3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.kt @@ -0,0 +1,13 @@ +class Your + +fun Your.foo() = Any() + +fun T?.let(f: (T) -> Unit) { + if (this != null) f(this) +} + +fun test(your: Your?) { + (your?.foo() as? Any)?.let {} + // strange smart cast to 'Your' at this point + your.hashCode() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.txt b/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.txt new file mode 100644 index 00000000000..0b401f00c25 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.txt @@ -0,0 +1,12 @@ +package + +public fun test(/*0*/ your: Your?): kotlin.Unit +public fun Your.foo(): kotlin.Any +public fun T?.let(/*0*/ f: (T) -> kotlin.Unit): kotlin.Unit + +public final class Your { + public constructor Your() + 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 c0f9426eec9..e5bab456612 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -14265,6 +14265,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("extensionSafeCall.kt") + public void testExtensionSafeCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/extensionSafeCall.kt"); + doTest(fileName); + } + @TestMetadata("fieldExclExcl.kt") public void testFieldExclExcl() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/fieldExclExcl.kt");