Iterative call chain handling to get rid of JetTypeInfoInsideCallChain and other nasty things #KT-9754 Fixed
This commit is contained in:
+71
-110
@@ -22,11 +22,11 @@ import com.intellij.psi.util.PsiTreeUtil;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.descriptors.*;
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
|
||||||
import org.jetbrains.kotlin.psi.*;
|
import org.jetbrains.kotlin.psi.*;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
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.callUtil.CallUtilKt;
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext;
|
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext;
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode;
|
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 javax.inject.Inject;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.diagnostics.Errors.*;
|
import static org.jetbrains.kotlin.diagnostics.Errors.*;
|
||||||
@@ -307,31 +308,6 @@ public class CallExpressionResolver {
|
|||||||
return TypeInfoFactoryKt.noTypeInfo(context);
|
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.
|
* 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(
|
public JetTypeInfo getQualifiedExpressionTypeInfo(
|
||||||
@NotNull KtQualifiedExpression expression, @NotNull ExpressionTypingContext context
|
@NotNull KtQualifiedExpression expression, @NotNull ExpressionTypingContext context
|
||||||
) {
|
) {
|
||||||
// TODO : functions as values
|
ExpressionTypingContext currentContext = context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
|
||||||
KtExpression selectorExpression = expression.getSelectorExpression();
|
Deque<CallExpressionElement> elementChain = CallExpressionUnrollerKt.unroll(expression);
|
||||||
KtExpression receiverExpression = expression.getReceiverExpression();
|
|
||||||
boolean safeCall = (expression.getOperationSign() == KtTokens.SAFE_ACCESS);
|
JetTypeInfo receiverTypeInfo = expressionTypingServices.getTypeInfo(elementChain.getFirst().getReceiver(), currentContext);
|
||||||
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);
|
|
||||||
KotlinType receiverType = receiverTypeInfo.getType();
|
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();
|
DataFlowInfo receiverDataFlowInfo = receiverTypeInfo.getDataFlowInfo();
|
||||||
// Receiver changes should be always applied, at least for argument analysis
|
JetTypeInfo resultTypeInfo = receiverTypeInfo;
|
||||||
context = context.replaceDataFlowInfo(receiverDataFlowInfo);
|
|
||||||
|
|
||||||
// Visit selector (y in x.y) here. Recursion is also possible.
|
boolean unconditional = true;
|
||||||
JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo(
|
DataFlowInfo unconditionalDataFlowInfo = receiverDataFlowInfo;
|
||||||
receiver, expression.getOperationTokenNode(), selectorExpression, context);
|
|
||||||
KotlinType selectorReturnType = selectorReturnTypeInfo.getType();
|
|
||||||
|
|
||||||
resolveDeferredReceiverInQualifiedExpression(qualifierReceiver, expression, context);
|
for (CallExpressionElement element : elementChain) {
|
||||||
checkNestedClassAccess(expression, context);
|
if (receiverType == null) {
|
||||||
|
receiverType = ErrorUtils.createErrorType("Type for " + expression.getText());
|
||||||
|
}
|
||||||
|
QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, element.getReceiver());
|
||||||
|
|
||||||
//TODO move further
|
ReceiverValue receiver = qualifierReceiver == null ? new ExpressionReceiver(element.getReceiver(), receiverType) : qualifierReceiver;
|
||||||
if (safeCall) {
|
|
||||||
if (selectorReturnType != null) {
|
boolean lastStage = element.getQualified() == expression;
|
||||||
if (TypeUtils.isNullableType(receiverType)) {
|
assert lastStage == (element == elementChain.getLast());
|
||||||
selectorReturnType = TypeUtils.makeNullable(selectorReturnType);
|
// Drop NO_EXPECTED_TYPE / INDEPENDENT at last stage
|
||||||
selectorReturnTypeInfo = selectorReturnTypeInfo.replaceType(selectorReturnType);
|
// 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 we are at last stage, we should just take result type info and set unconditional data flow info
|
||||||
if (selectorExpression != null && selectorReturnType != null) {
|
return resultTypeInfo.replaceDataFlowInfo(unconditionalDataFlowInfo);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolveDeferredReceiverInQualifiedExpression(
|
private void resolveDeferredReceiverInQualifiedExpression(
|
||||||
|
|||||||
@@ -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<CallExpressionElement> {
|
||||||
|
var current: KtExpression = root
|
||||||
|
val result = linkedListOf<CallExpressionElement>()
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -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.
|
* 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.
|
* 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 type: KotlinType?,
|
||||||
val dataFlowInfo: DataFlowInfo,
|
val dataFlowInfo: DataFlowInfo,
|
||||||
val jumpOutPossible: Boolean = false,
|
val jumpOutPossible: Boolean = false,
|
||||||
@@ -39,7 +39,7 @@ import org.jetbrains.kotlin.types.KotlinType
|
|||||||
|
|
||||||
fun clearType() = replaceType(null)
|
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 replaceType(type: KotlinType?) = JetTypeInfo(type, dataFlowInfo, jumpOutPossible, jumpFlowInfo)
|
||||||
|
|
||||||
fun replaceJumpOutPossible(jumpOutPossible: Boolean) =
|
fun replaceJumpOutPossible(jumpOutPossible: Boolean) =
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class Your
|
||||||
|
|
||||||
|
fun Your.foo() = Any()
|
||||||
|
|
||||||
|
fun <T> T?.let(f: (T) -> Unit) {
|
||||||
|
if (this != null) f(<!DEBUG_INFO_SMARTCAST!>this<!>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(your: Your?) {
|
||||||
|
(your?.foo() as? Any)?.let {}
|
||||||
|
// strange smart cast to 'Your' at this point
|
||||||
|
your<!UNSAFE_CALL!>.<!>hashCode()
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun test(/*0*/ your: Your?): kotlin.Unit
|
||||||
|
public fun Your.foo(): kotlin.Any
|
||||||
|
public fun </*0*/ T> 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
|
||||||
|
}
|
||||||
@@ -14265,6 +14265,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("fieldExclExcl.kt")
|
||||||
public void testFieldExclExcl() throws Exception {
|
public void testFieldExclExcl() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/fieldExclExcl.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/fieldExclExcl.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user