Iterative call chain handling to get rid of JetTypeInfoInsideCallChain and other nasty things #KT-9754 Fixed

This commit is contained in:
Mikhail Glukhikh
2015-10-26 17:33:10 +03:00
parent 16b6bccd1e
commit d2c65a5336
6 changed files with 152 additions and 112 deletions
@@ -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<CallExpressionElement> 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(
@@ -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.
* 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) =
@@ -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);
}
@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");