Implementation and tests for KT-5840 and newly created KT-7204. Now a safe call provides not-null receiver state *inside* argument list. It works also for ?. chains. #KT-5840 Fixed.

On the other hand, argument states do not propagate to successor statements for a safe call. #KT-7204 Fixed. A few additional comments.
This commit is contained in:
Mikhail Glukhikh
2015-03-24 17:03:01 +03:00
parent 79739b7090
commit d92ccad35d
53 changed files with 566 additions and 63 deletions
@@ -28,14 +28,20 @@ import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext;
import org.jetbrains.kotlin.resolve.calls.context.CheckValueArgumentsMode; import org.jetbrains.kotlin.resolve.calls.context.CheckValueArgumentsMode;
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext; import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext;
import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments; import org.jetbrains.kotlin.resolve.calls.model.MutableDataFlowInfoForArguments;
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.constants.IntegerValueTypeConstructor; import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator; import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator;
import org.jetbrains.kotlin.resolve.scopes.JetScope; import org.jetbrains.kotlin.resolve.scopes.JetScope;
import org.jetbrains.kotlin.resolve.scopes.receivers.QualifierReceiver;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.JetTypeInfo; import org.jetbrains.kotlin.types.JetTypeInfo;
import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.checker.JetTypeChecker; import org.jetbrains.kotlin.types.checker.JetTypeChecker;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices; import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collections; import java.util.Collections;
@@ -251,17 +257,36 @@ public class ArgumentTypeResolver {
return defaultValue; return defaultValue;
} }
/**
* Visits function call arguments and determines data flow information changes
*/
public void analyzeArgumentsAndRecordTypes( public void analyzeArgumentsAndRecordTypes(
@NotNull CallResolutionContext<?> context @NotNull CallResolutionContext<?> context
) { ) {
MutableDataFlowInfoForArguments infoForArguments = context.dataFlowInfoForArguments; MutableDataFlowInfoForArguments infoForArguments = context.dataFlowInfoForArguments;
infoForArguments.setInitialDataFlowInfo(context.dataFlowInfo); Call call = context.call;
ReceiverValue receiver = call.getExplicitReceiver();
DataFlowInfo initialDataFlowInfo = context.dataFlowInfo;
// QualifierReceiver is a thing like Collections. which has no type or value
if (receiver.exists() && !(receiver instanceof QualifierReceiver)) {
DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context);
// Additional "receiver != null" information for KT-5840
// Should be applied if we consider a safe call
// For an unsafe call, we should not do it,
// otherwise not-null will propagate to successive statements
// Sample: x?.foo(x.bar()) // Inside foo call, x is not-nullable
if (PsiUtilPackage.isSafeCall(call)) {
initialDataFlowInfo = initialDataFlowInfo.disequate(receiverDataFlowValue, DataFlowValue.NULL);
}
}
infoForArguments.setInitialDataFlowInfo(initialDataFlowInfo);
for (ValueArgument argument : context.call.getValueArguments()) { for (ValueArgument argument : call.getValueArguments()) {
JetExpression expression = argument.getArgumentExpression(); JetExpression expression = argument.getArgumentExpression();
if (expression == null) continue; if (expression == null) continue;
CallResolutionContext<?> newContext = context.replaceDataFlowInfo(infoForArguments.getInfo(argument)); CallResolutionContext<?> newContext = context.replaceDataFlowInfo(infoForArguments.getInfo(argument));
// Here we go inside arguments and determine additional data flow information for them
JetTypeInfo typeInfoForCall = getArgumentTypeInfo(expression, newContext, SHAPE_FUNCTION_ARGUMENTS); JetTypeInfo typeInfoForCall = getArgumentTypeInfo(expression, newContext, SHAPE_FUNCTION_ARGUMENTS);
infoForArguments.updateInfo(argument, typeInfoForCall.getDataFlowInfo()); infoForArguments.updateInfo(argument, typeInfoForCall.getDataFlowInfo());
} }
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.resolve.calls.context.TemporaryTraceAndCache;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil; import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResultsUtil;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
import org.jetbrains.kotlin.resolve.calls.util.CallMaker; import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject; import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject;
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
@@ -86,7 +87,8 @@ public class CallExpressionResolver {
} }
@Nullable @Nullable
private JetType getVariableType(@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver, private JetType getVariableType(
@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver,
@Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context, @NotNull boolean[] result @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context, @NotNull boolean[] result
) { ) {
TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create( TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
@@ -124,17 +126,24 @@ public class CallExpressionResolver {
} }
@NotNull @NotNull
public JetTypeInfo getSimpleNameExpressionTypeInfo(@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver, public JetTypeInfo getSimpleNameExpressionTypeInfo(
@NotNull JetSimpleNameExpression nameExpression, @NotNull ReceiverValue receiver,
@Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context @Nullable ASTNode callOperationNode, @NotNull ExpressionTypingContext context
) { ) {
boolean[] result = new boolean[1]; boolean[] result = new boolean[1];
TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create( TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
context, "trace to resolve as variable", nameExpression); context, "trace to resolve as variable", nameExpression);
JetType type = getVariableType(nameExpression, receiver, callOperationNode, context.replaceTraceAndCache(temporaryForVariable), result); JetType type =
getVariableType(nameExpression, receiver, callOperationNode, context.replaceTraceAndCache(temporaryForVariable), result);
DataFlowInfo dataFlowInfo = context.dataFlowInfo;
// 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
if (result[0]) { if (result[0]) {
temporaryForVariable.commit(); temporaryForVariable.commit();
return JetTypeInfo.create(type, context.dataFlowInfo); return JetTypeInfo.create(type, dataFlowInfo);
} }
Call call = CallMaker.makeCall(nameExpression, receiver, callOperationNode, nameExpression, Collections.<ValueArgument>emptyList()); Call call = CallMaker.makeCall(nameExpression, receiver, callOperationNode, nameExpression, Collections.<ValueArgument>emptyList());
@@ -149,11 +158,11 @@ public class CallExpressionResolver {
boolean hasValueParameters = functionDescriptor == null || functionDescriptor.getValueParameters().size() > 0; boolean hasValueParameters = functionDescriptor == null || functionDescriptor.getValueParameters().size() > 0;
context.trace.report(FUNCTION_CALL_EXPECTED.on(nameExpression, nameExpression, hasValueParameters)); context.trace.report(FUNCTION_CALL_EXPECTED.on(nameExpression, nameExpression, hasValueParameters));
type = functionDescriptor != null ? functionDescriptor.getReturnType() : null; type = functionDescriptor != null ? functionDescriptor.getReturnType() : null;
return JetTypeInfo.create(type, context.dataFlowInfo); return JetTypeInfo.create(type, dataFlowInfo);
} }
temporaryForVariable.commit(); temporaryForVariable.commit();
return JetTypeInfo.create(null, context.dataFlowInfo); return JetTypeInfo.create(null, dataFlowInfo);
} }
@NotNull @NotNull
@@ -168,6 +177,10 @@ public class CallExpressionResolver {
return typeInfo; return typeInfo;
} }
/**
* Visits a call expression and its arguments.
* Determines the result type and data flow information after the call.
*/
@NotNull @NotNull
public JetTypeInfo getCallExpressionTypeInfoWithoutFinalTypeCheck( public JetTypeInfo getCallExpressionTypeInfoWithoutFinalTypeCheck(
@NotNull JetCallExpression callExpression, @NotNull ReceiverValue receiver, @NotNull JetCallExpression callExpression, @NotNull ReceiverValue receiver,
@@ -179,7 +192,9 @@ public class CallExpressionResolver {
TemporaryTraceAndCache temporaryForFunction = TemporaryTraceAndCache.create( TemporaryTraceAndCache temporaryForFunction = TemporaryTraceAndCache.create(
context, "trace to resolve as function call", callExpression); context, "trace to resolve as function call", callExpression);
ResolvedCall<FunctionDescriptor> resolvedCall = getResolvedCallForFunction( ResolvedCall<FunctionDescriptor> resolvedCall = getResolvedCallForFunction(
call, callExpression, context.replaceTraceAndCache(temporaryForFunction), call, callExpression,
// It's possible start of a call so we should reset safe call chain
context.replaceTraceAndCache(temporaryForFunction).replaceInsideCallChain(false),
CheckValueArgumentsMode.ENABLED, result); CheckValueArgumentsMode.ENABLED, result);
if (result[0]) { if (result[0]) {
FunctionDescriptor functionDescriptor = resolvedCall != null ? resolvedCall.getResultingDescriptor() : null; FunctionDescriptor functionDescriptor = resolvedCall != null ? resolvedCall.getResultingDescriptor() : null;
@@ -192,7 +207,8 @@ public class CallExpressionResolver {
if (functionDescriptor == null) { if (functionDescriptor == null) {
return JetTypeInfo.create(null, context.dataFlowInfo); return JetTypeInfo.create(null, context.dataFlowInfo);
} }
if (functionDescriptor instanceof ConstructorDescriptor && DescriptorUtils.isAnnotationClass(functionDescriptor.getContainingDeclaration())) { if (functionDescriptor instanceof ConstructorDescriptor &&
DescriptorUtils.isAnnotationClass(functionDescriptor.getContainingDeclaration())) {
if (!canInstantiateAnnotationClass(callExpression)) { if (!canInstantiateAnnotationClass(callExpression)) {
context.trace.report(ANNOTATION_CLASS_CONSTRUCTOR_CALL.on(callExpression)); context.trace.report(ANNOTATION_CLASS_CONSTRUCTOR_CALL.on(callExpression));
} }
@@ -256,6 +272,36 @@ public class CallExpressionResolver {
return JetTypeInfo.create(null, context.dataFlowInfo); return JetTypeInfo.create(null, context.dataFlowInfo);
} }
/**
* 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(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo, @Nullable DataFlowInfo safeCallChainInfo) {
super(type, dataFlowInfo);
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.
*
* @return qualified expression type together with data flow information
*/
@NotNull @NotNull
public JetTypeInfo getQualifiedExpressionTypeInfo( public JetTypeInfo getQualifiedExpressionTypeInfo(
@NotNull JetQualifiedExpression expression, @NotNull ExpressionTypingContext context @NotNull JetQualifiedExpression expression, @NotNull ExpressionTypingContext context
@@ -263,16 +309,23 @@ public class CallExpressionResolver {
// TODO : functions as values // TODO : functions as values
JetExpression selectorExpression = expression.getSelectorExpression(); JetExpression selectorExpression = expression.getSelectorExpression();
JetExpression receiverExpression = expression.getReceiverExpression(); JetExpression receiverExpression = expression.getReceiverExpression();
ResolutionContext contextForReceiver = context.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); boolean safeCall = (expression.getOperationSign() == JetTokens.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); JetTypeInfo receiverTypeInfo = expressionTypingServices.getTypeInfo(receiverExpression, contextForReceiver);
JetType receiverType = receiverTypeInfo.getType(); JetType receiverType = receiverTypeInfo.getType();
QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, receiverExpression); QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, receiverExpression);
if (receiverType == null) receiverType = ErrorUtils.createErrorType("Type for " + expression.getText()); if (receiverType == null) receiverType = ErrorUtils.createErrorType("Type for " + expression.getText());
context = context.replaceDataFlowInfo(receiverTypeInfo.getDataFlowInfo());
ReceiverValue receiver = qualifierReceiver == null ? new ExpressionReceiver(receiverExpression, receiverType) : qualifierReceiver; 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);
// Visit selector (y in x.y) here. Recursion is also possible.
JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo( JetTypeInfo selectorReturnTypeInfo = getSelectorReturnTypeInfo(
receiver, expression.getOperationTokenNode(), selectorExpression, context); receiver, expression.getOperationTokenNode(), selectorExpression, context);
JetType selectorReturnType = selectorReturnTypeInfo.getType(); JetType selectorReturnType = selectorReturnTypeInfo.getType();
@@ -281,14 +334,13 @@ public class CallExpressionResolver {
checkNestedClassAccess(expression, context); checkNestedClassAccess(expression, context);
//TODO move further //TODO move further
if (expression.getOperationSign() == JetTokens.SAFE_ACCESS) { if (safeCall) {
if (selectorReturnType != null && !KotlinBuiltIns.isUnit(selectorReturnType)) { if (selectorReturnType != null && !KotlinBuiltIns.isUnit(selectorReturnType)) {
if (TypeUtils.isNullableType(receiverType)) { if (TypeUtils.isNullableType(receiverType)) {
selectorReturnType = TypeUtils.makeNullable(selectorReturnType); selectorReturnType = TypeUtils.makeNullable(selectorReturnType);
} }
} }
} }
// TODO : this is suspicious: remove this code? // TODO : this is suspicious: remove this code?
if (selectorReturnType != null) { if (selectorReturnType != null) {
context.trace.record(BindingContext.EXPRESSION_TYPE, selectorExpression, selectorReturnType); context.trace.record(BindingContext.EXPRESSION_TYPE, selectorExpression, selectorReturnType);
@@ -299,7 +351,48 @@ public class CallExpressionResolver {
return BasicExpressionTypingVisitor.createCompileTimeConstantTypeInfo(value, expression, context); return BasicExpressionTypingVisitor.createCompileTimeConstantTypeInfo(value, expression, context);
} }
JetTypeInfo typeInfo = JetTypeInfo.create(selectorReturnType, selectorReturnTypeInfo.getDataFlowInfo()); 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(selectorReturnType, selectorReturnTypeInfo.getDataFlowInfo(), 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 = JetTypeInfo.create(selectorReturnType, 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(selectorReturnType, selectorReturnTypeInfo.getDataFlowInfo(),
selectorReturnTypeInfo.getDataFlowInfo());
}
else {
// Exiting call chain with safe calls -- take data flow info from the lest-most receiver
// foo(x!!)?.bar().gav()
typeInfo = JetTypeInfo.create(selectorReturnType, safeCallChainInfo);
}
}
if (context.contextDependency == INDEPENDENT) { if (context.contextDependency == INDEPENDENT) {
DataFlowUtils.checkType(typeInfo, expression, context); DataFlowUtils.checkType(typeInfo, expression, context);
} }
@@ -336,7 +429,8 @@ public class CallExpressionResolver {
if (receiverQualifier == null && expressionQualifier != null) { if (receiverQualifier == null && expressionQualifier != null) {
assert expressionQualifier.getClassifier() instanceof ClassDescriptor : assert expressionQualifier.getClassifier() instanceof ClassDescriptor :
"Only class can (package cannot) be accessed by instance reference: " + expressionQualifier; "Only class can (package cannot) be accessed by instance reference: " + expressionQualifier;
context.trace.report(NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE.on(selectorExpression, (ClassDescriptor)expressionQualifier.getClassifier())); context.trace.report(NESTED_CLASS_ACCESSED_VIA_INSTANCE_REFERENCE
.on(selectorExpression, (ClassDescriptor) expressionQualifier.getClassifier()));
} }
} }
} }
@@ -43,10 +43,12 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
@NotNull AdditionalTypeChecker additionalTypeChecker, @NotNull AdditionalTypeChecker additionalTypeChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates); dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
collectAllCandidates, insideSafeCallChain);
} }
@NotNull @NotNull
@@ -64,7 +66,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
) { ) {
return new BasicCallResolutionContext(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, return new BasicCallResolutionContext(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
new ResolutionResultsCacheImpl(), null, new ResolutionResultsCacheImpl(), null,
callChecker, additionalTypeChecker, StatementFilter.NONE, isAnnotationContext, false); callChecker, additionalTypeChecker, StatementFilter.NONE, isAnnotationContext, false, false);
} }
@NotNull @NotNull
@@ -75,7 +77,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
return new BasicCallResolutionContext( return new BasicCallResolutionContext(
context.trace, context.scope, call, context.expectedType, context.dataFlowInfo, context.contextDependency, checkArguments, context.trace, context.scope, call, context.expectedType, context.dataFlowInfo, context.contextDependency, checkArguments,
context.resolutionResultsCache, dataFlowInfoForArguments, context.callChecker, context.additionalTypeChecker, context.statementFilter, context.resolutionResultsCache, dataFlowInfoForArguments, context.callChecker, context.additionalTypeChecker, context.statementFilter,
context.isAnnotationContext, context.collectAllCandidates); context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
} }
@NotNull @NotNull
@@ -94,17 +96,20 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
return new BasicCallResolutionContext( return new BasicCallResolutionContext(
trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates); dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
collectAllCandidates, insideSafeCallChain);
} }
@NotNull @NotNull
public BasicCallResolutionContext replaceCall(@NotNull Call newCall) { public BasicCallResolutionContext replaceCall(@NotNull Call newCall) {
return new BasicCallResolutionContext( return new BasicCallResolutionContext(
trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates); dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
collectAllCandidates, insideCallChain);
} }
} }
@@ -57,10 +57,12 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
@NotNull ReceiverValue explicitExtensionReceiverForInvoke, @NotNull ReceiverValue explicitExtensionReceiverForInvoke,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates); dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
collectAllCandidates, insideSafeCallChain);
this.candidateCall = candidateCall; this.candidateCall = candidateCall;
this.tracing = tracing; this.tracing = tracing;
this.explicitExtensionReceiverForInvoke = explicitExtensionReceiverForInvoke; this.explicitExtensionReceiverForInvoke = explicitExtensionReceiverForInvoke;
@@ -76,7 +78,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
context.dataFlowInfo, context.contextDependency, context.checkArguments, context.dataFlowInfo, context.contextDependency, context.checkArguments,
context.resolutionResultsCache, context.dataFlowInfoForArguments, context.resolutionResultsCache, context.dataFlowInfoForArguments,
context.callChecker, context.additionalTypeChecker, context.statementFilter, explicitExtensionReceiverForInvoke, context.callChecker, context.additionalTypeChecker, context.statementFilter, explicitExtensionReceiverForInvoke,
context.isAnnotationContext, context.collectAllCandidates); context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
} }
public static <D extends CallableDescriptor> CallCandidateResolutionContext<D> create( public static <D extends CallableDescriptor> CallCandidateResolutionContext<D> create(
@@ -100,7 +102,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
candidateCall, tracing, context.trace, context.scope, context.call, context.expectedType, candidateCall, tracing, context.trace, context.scope, context.call, context.expectedType,
context.dataFlowInfo, context.contextDependency, context.checkArguments, context.resolutionResultsCache, context.dataFlowInfo, context.contextDependency, context.checkArguments, context.resolutionResultsCache,
context.dataFlowInfoForArguments, context.callChecker, context.additionalTypeChecker, context.statementFilter, context.dataFlowInfoForArguments, context.callChecker, context.additionalTypeChecker, context.statementFilter,
ReceiverValue.NO_RECEIVER, context.isAnnotationContext, context.collectAllCandidates); ReceiverValue.NO_RECEIVER, context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
} }
@Override @Override
@@ -112,11 +114,12 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
return new CallCandidateResolutionContext<D>( return new CallCandidateResolutionContext<D>(
candidateCall, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, candidateCall, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter,
explicitExtensionReceiverForInvoke, isAnnotationContext, collectAllCandidates); explicitExtensionReceiverForInvoke, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
} }
@@ -52,10 +52,11 @@ public abstract class CallResolutionContext<Context extends CallResolutionContex
@NotNull AdditionalTypeChecker additionalTypeChecker, @NotNull AdditionalTypeChecker additionalTypeChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, collectAllCandidates); statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
this.call = call; this.call = call;
this.checkArguments = checkArguments; this.checkArguments = checkArguments;
if (dataFlowInfoForArguments != null) { if (dataFlowInfoForArguments != null) {
@@ -27,6 +27,12 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope;
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeUtils; import org.jetbrains.kotlin.types.TypeUtils;
/**
* This class together with its descendants is intended to transfer data flow analysis information
* in top-down direction, from AST parents to children.
*
* NB: all descendants must be immutable!
*/
public abstract class ResolutionContext<Context extends ResolutionContext<Context>> { public abstract class ResolutionContext<Context extends ResolutionContext<Context>> {
@NotNull @NotNull
public final BindingTrace trace; public final BindingTrace trace;
@@ -51,6 +57,9 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
public final boolean collectAllCandidates; public final boolean collectAllCandidates;
// True if we are inside call chain like x?.foo()!!.bar()?.gav()
public final boolean insideCallChain;
protected ResolutionContext( protected ResolutionContext(
@NotNull BindingTrace trace, @NotNull BindingTrace trace,
@NotNull JetScope scope, @NotNull JetScope scope,
@@ -62,7 +71,8 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
@NotNull AdditionalTypeChecker additionalTypeChecker, @NotNull AdditionalTypeChecker additionalTypeChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideCallChain
) { ) {
this.trace = trace; this.trace = trace;
this.scope = scope; this.scope = scope;
@@ -75,6 +85,7 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
this.additionalTypeChecker = additionalTypeChecker; this.additionalTypeChecker = additionalTypeChecker;
this.isAnnotationContext = isAnnotationContext; this.isAnnotationContext = isAnnotationContext;
this.collectAllCandidates = collectAllCandidates; this.collectAllCandidates = collectAllCandidates;
this.insideCallChain = insideCallChain;
} }
protected abstract Context create( protected abstract Context create(
@@ -85,7 +96,8 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
); );
@NotNull @NotNull
@@ -98,14 +110,14 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
public Context replaceBindingTrace(@NotNull BindingTrace trace) { public Context replaceBindingTrace(@NotNull BindingTrace trace) {
if (this.trace == trace) return self(); if (this.trace == trace) return self();
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
public Context replaceDataFlowInfo(@NotNull DataFlowInfo newDataFlowInfo) { public Context replaceDataFlowInfo(@NotNull DataFlowInfo newDataFlowInfo) {
if (newDataFlowInfo == dataFlowInfo) return self(); if (newDataFlowInfo == dataFlowInfo) return self();
return create(trace, scope, newDataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, newDataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
@@ -113,28 +125,28 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
if (newExpectedType == null) return replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE); if (newExpectedType == null) return replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE);
if (expectedType == newExpectedType) return self(); if (expectedType == newExpectedType) return self();
return create(trace, scope, dataFlowInfo, newExpectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, newExpectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
public Context replaceScope(@NotNull JetScope newScope) { public Context replaceScope(@NotNull JetScope newScope) {
if (newScope == scope) return self(); if (newScope == scope) return self();
return create(trace, newScope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, newScope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
public Context replaceContextDependency(@NotNull ContextDependency newContextDependency) { public Context replaceContextDependency(@NotNull ContextDependency newContextDependency) {
if (newContextDependency == contextDependency) return self(); if (newContextDependency == contextDependency) return self();
return create(trace, scope, dataFlowInfo, expectedType, newContextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, expectedType, newContextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
public Context replaceResolutionResultsCache(@NotNull ResolutionResultsCache newResolutionResultsCache) { public Context replaceResolutionResultsCache(@NotNull ResolutionResultsCache newResolutionResultsCache) {
if (newResolutionResultsCache == resolutionResultsCache) return self(); if (newResolutionResultsCache == resolutionResultsCache) return self();
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, newResolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, expectedType, contextDependency, newResolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
@@ -145,12 +157,19 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
@NotNull @NotNull
public Context replaceCollectAllCandidates(boolean newCollectAllCandidates) { public Context replaceCollectAllCandidates(boolean newCollectAllCandidates) {
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
newCollectAllCandidates); newCollectAllCandidates, insideCallChain);
} }
@NotNull @NotNull
public Context replacestatementFilter(@NotNull StatementFilter statementFilter) { public Context replaceStatementFilter(@NotNull StatementFilter statementFilter) {
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter, return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates); collectAllCandidates, insideCallChain);
}
@NotNull
public Context replaceInsideCallChain(boolean insideSafeCallChain) {
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
collectAllCandidates, insideSafeCallChain);
} }
} }
@@ -37,10 +37,11 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
@NotNull AdditionalTypeChecker additionalTypeChecker, @NotNull AdditionalTypeChecker additionalTypeChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, collectAllCandidates); statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
public SimpleResolutionContext( public SimpleResolutionContext(
@@ -54,7 +55,7 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
@NotNull StatementFilter statementFilter @NotNull StatementFilter statementFilter
) { ) {
this(trace, scope, expectedType, dataFlowInfo, contextDependency, new ResolutionResultsCacheImpl(), this(trace, scope, expectedType, dataFlowInfo, contextDependency, new ResolutionResultsCacheImpl(),
callChecker, additionalTypeChecker, statementFilter, false, false); callChecker, additionalTypeChecker, statementFilter, false, false, false);
} }
@Override @Override
@@ -66,10 +67,11 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
return new SimpleResolutionContext( return new SimpleResolutionContext(
trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, collectAllCandidates); statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
} }
@@ -61,10 +61,11 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
@NotNull Collection<MutableResolvedCall<F>> resolvedCalls, @NotNull Collection<MutableResolvedCall<F>> resolvedCalls,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates); dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
this.lazyCandidates = lazyCandidates; this.lazyCandidates = lazyCandidates;
this.resolvedCalls = resolvedCalls; this.resolvedCalls = resolvedCalls;
this.tracing = tracing; this.tracing = tracing;
@@ -80,7 +81,7 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
context.expectedType, context.dataFlowInfo, context.contextDependency, context.checkArguments, context.expectedType, context.dataFlowInfo, context.contextDependency, context.checkArguments,
context.resolutionResultsCache, context.dataFlowInfoForArguments, context.resolutionResultsCache, context.dataFlowInfoForArguments,
context.callChecker, context.additionalTypeChecker, context.statementFilter, Lists.<MutableResolvedCall<F>>newArrayList(), context.callChecker, context.additionalTypeChecker, context.statementFilter, Lists.<MutableResolvedCall<F>>newArrayList(),
context.isAnnotationContext, context.collectAllCandidates); context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
} }
public ResolutionTask( public ResolutionTask(
@@ -119,12 +120,13 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
return new ResolutionTask<D, F>( return new ResolutionTask<D, F>(
lazyCandidates, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, lazyCandidates, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls, resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls,
isAnnotationContext, collectAllCandidates); isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
public ResolutionTask<D, F> replaceContext(@NotNull BasicCallResolutionContext newContext) { public ResolutionTask<D, F> replaceContext(@NotNull BasicCallResolutionContext newContext) {
@@ -135,7 +137,7 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
return new ResolutionTask<D, F>( return new ResolutionTask<D, F>(
lazyCandidates, tracing, trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, lazyCandidates, tracing, trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments,
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls, resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls,
isAnnotationContext, collectAllCandidates); isAnnotationContext, collectAllCandidates, insideCallChain);
} }
public interface DescriptorCheckStrategy { public interface DescriptorCheckStrategy {
@@ -20,16 +20,23 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo;
/**
* This class is intended to transfer data flow analysis information in bottom-up direction,
* from AST children to parents. It stores a type of expression under analysis,
* current information about types and nullabilities.
*
* NB: it must be immutable together with all its descendants!
*/
public class JetTypeInfo { public class JetTypeInfo {
@NotNull @NotNull
public static JetTypeInfo create(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) { public static JetTypeInfo create(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
return new JetTypeInfo(type, dataFlowInfo); return new JetTypeInfo(type, dataFlowInfo);
} }
private final JetType type; private final JetType type;
private final DataFlowInfo dataFlowInfo; private final DataFlowInfo dataFlowInfo;
private JetTypeInfo(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) { protected JetTypeInfo(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
this.type = type; this.type = type;
this.dataFlowInfo = dataFlowInfo; this.dataFlowInfo = dataFlowInfo;
} }
@@ -876,6 +876,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
JetSimpleNameExpression operationSign = expression.getOperationReference(); JetSimpleNameExpression operationSign = expression.getOperationReference();
assert operationSign.getReferencedNameElementType() == JetTokens.EXCLEXCL; assert operationSign.getReferencedNameElementType() == JetTokens.EXCLEXCL;
// TODO: something must be done for not to lose safe call chain information here
// See also CallExpressionResolver.getSimpleNameExpressionTypeInfo, .getQualifiedExpressionTypeInfo
Call call = createCallForSpecialConstruction(expression, expression.getOperationReference(), Collections.singletonList(baseExpression)); Call call = createCallForSpecialConstruction(expression, expression.getOperationReference(), Collections.singletonList(baseExpression));
components.controlStructureTypingUtils.resolveSpecialConstructionAsCall( components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
call, "ExclExcl", Collections.singletonList("baseExpr"), Collections.singletonList(true), context, null); call, "ExclExcl", Collections.singletonList("baseExpr"), Collections.singletonList(true), context, null);
@@ -66,7 +66,7 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
context.trace, context.scope, context.dataFlowInfo, context.expectedType, context.trace, context.scope, context.dataFlowInfo, context.expectedType,
context.contextDependency, context.resolutionResultsCache, context.callChecker, context.additionalTypeChecker, context.contextDependency, context.resolutionResultsCache, context.callChecker, context.additionalTypeChecker,
context.statementFilter, context.statementFilter,
context.isAnnotationContext, context.collectAllCandidates context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain
); );
} }
@@ -85,7 +85,7 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
) { ) {
return new ExpressionTypingContext( return new ExpressionTypingContext(
trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, false); statementFilter, isAnnotationContext, false, false);
} }
private CompileTimeConstantChecker compileTimeConstantChecker; private CompileTimeConstantChecker compileTimeConstantChecker;
@@ -101,10 +101,11 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
@NotNull AdditionalTypeChecker additionalTypeChecker, @NotNull AdditionalTypeChecker additionalTypeChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, collectAllCandidates); statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
@Override @Override
@@ -116,11 +117,12 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
@NotNull ContextDependency contextDependency, @NotNull ContextDependency contextDependency,
@NotNull ResolutionResultsCache resolutionResultsCache, @NotNull ResolutionResultsCache resolutionResultsCache,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean collectAllCandidates boolean collectAllCandidates,
boolean insideSafeCallChain
) { ) {
return new ExpressionTypingContext(trace, scope, dataFlowInfo, return new ExpressionTypingContext(trace, scope, dataFlowInfo,
expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker, expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
statementFilter, isAnnotationContext, collectAllCandidates); statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
} }
///////////// LAZY ACCESSORS ///////////// LAZY ACCESSORS
@@ -266,7 +266,7 @@ public class ExpressionTypingServices {
} }
else { else {
r = getBlockReturnedTypeWithWritableScope(scope, block, coercionStrategyForLastExpression, r = getBlockReturnedTypeWithWritableScope(scope, block, coercionStrategyForLastExpression,
context.replacestatementFilter(getStatementFilter())); context.replaceStatementFilter(getStatementFilter()));
} }
scope.changeLockLevel(WritableScope.LockLevel.READING); scope.changeLockLevel(WritableScope.LockLevel.READING);
@@ -303,6 +303,10 @@ public class ExpressionTypingServices {
} }
} }
/**
* Visits block statements propagating data flow information from the first to the last.
* Determines block returned type and data flow information at the end of the block.
*/
/*package*/ JetTypeInfo getBlockReturnedTypeWithWritableScope( /*package*/ JetTypeInfo getBlockReturnedTypeWithWritableScope(
@NotNull WritableScope scope, @NotNull WritableScope scope,
@NotNull List<? extends JetElement> block, @NotNull List<? extends JetElement> block,
@@ -0,0 +1,6 @@
fun calc(x: List<String>?, y: Int?): Int {
x?.get(y!! - 1)
// y!! above should not provide smart cast here
val yy: Int = <!TYPE_MISMATCH!>y<!>
return yy + (x?.size() ?: 0)
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?, /*1*/ y: kotlin.Int?): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// After KT-5840 fix !! assertion should become unnecessary here
x?.get(x<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.size() - 1)
// x?. or x!! above should not provide smart cast here
return x<!UNSAFE_CALL!>.<!>size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list, despite of a chain
x?.subList(0, 1)?.get(<!DEBUG_INFO_SMARTCAST!>x<!>.size())
// But not here!
return x!!.size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?, y: List<Int>?) {
// x and y should be non-null in arguments list, despite of a chains
x?.subList(y?.subList(1, 2)?.get(<!DEBUG_INFO_SMARTCAST!>y<!>.size()) ?: 0,
y?.get(0) ?: 1) // But safe call is NECESSARY here for y
?.get(<!DEBUG_INFO_SMARTCAST!>x<!>.size())
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?, /*1*/ y: kotlin.List<kotlin.Int>?): kotlin.Unit
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list, despite of a chain
x?.subList(0, 1)<!UNSAFE_CALL!>.<!>get(<!DEBUG_INFO_SMARTCAST!>x<!>.size())
// But not here!
return x!!.size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list, including inner call
x?.get(<!DEBUG_INFO_SMARTCAST!>x<!>.get(<!DEBUG_INFO_SMARTCAST!>x<!>.size() - 1).length())
// but not also here!
return x<!UNSAFE_CALL!>.<!>size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -0,0 +1,6 @@
fun String.foo(arg: Int) = this[arg]
fun calc(x: String?) {
// x should be non-null in arguments list
x?.foo(<!DEBUG_INFO_SMARTCAST!>x<!>.length() - 1)
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.String?): kotlin.Unit
internal fun kotlin.String.foo(/*0*/ arg: kotlin.Int): kotlin.Char
@@ -0,0 +1,7 @@
fun foo(y: Int) = y
fun calc(x: List<String>?): Int {
foo(x!!.size())
// Here we should have smart cast because of x!!, despite of KT-7204 fixed
return <!DEBUG_INFO_SMARTCAST!>x<!>.size()
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
internal fun foo(/*0*/ y: kotlin.Int): kotlin.Int
@@ -0,0 +1,4 @@
fun calc(x: List<String>?, y: Int?) {
// Smart cast should work here despite of KT-7204 fixed
x?.subList(0, y!!)?.get(<!DEBUG_INFO_SMARTCAST!>y<!>)
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?, /*1*/ y: kotlin.Int?): kotlin.Unit
@@ -0,0 +1,7 @@
fun String.foo(y: Int) = y
fun calc(x: List<String>?): Int {
"abc".foo(x!!.size())
// Here we should have smart cast because of x!!, despite of KT-7204 fixed
return <!DEBUG_INFO_SMARTCAST!>x<!>.size()
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
internal fun kotlin.String.foo(/*0*/ y: kotlin.Int): kotlin.Int
@@ -0,0 +1,5 @@
fun calc(x: List<String>?, y: Int?): Int {
x?.subList(y!! - 1, <!DEBUG_INFO_SMARTCAST!>y<!>)
// y!! above should not provide smart cast here
return <!TYPE_MISMATCH!>y<!>
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?, /*1*/ y: kotlin.Int?): kotlin.Int
@@ -0,0 +1,8 @@
fun foo(x: String): String? = x
fun calc(x: String?, y: String?): Int {
// Smart cast because of y!! in receiver
x?.subSequence(y!!.subSequence(0, 1).length(), <!DEBUG_INFO_SMARTCAST!>y<!>.length())
// No smart cast possible
return y<!UNSAFE_CALL!>.<!>length()
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.String?): kotlin.Int
internal fun foo(/*0*/ x: kotlin.String): kotlin.String?
@@ -0,0 +1,8 @@
fun foo(y: Int): Int {
return y + 1
}
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list
return foo(x?.get(<!DEBUG_INFO_SMARTCAST!>x<!>.size() - 1)!!.length())
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
internal fun foo(/*0*/ y: kotlin.Int): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?) {
// x should be non-null in arguments list, despite of a chain
x?.subList(0, <!DEBUG_INFO_SMARTCAST!>x<!>.size())?.
subList(0, <!DEBUG_INFO_SMARTCAST!>x<!>.size())?.
get(<!DEBUG_INFO_SMARTCAST!>x<!>.size())
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Unit
@@ -0,0 +1,6 @@
data class MyClass(val x: String?)
fun foo(y: MyClass): Int {
val z = y.x?.subSequence(0, <!DEBUG_INFO_SMARTCAST!>y.x<!>.length())
return z?.length() ?: -1
}
@@ -0,0 +1,13 @@
package
internal fun foo(/*0*/ y: MyClass): kotlin.Int
kotlin.data() internal final class MyClass {
public constructor MyClass(/*0*/ x: kotlin.String?)
internal final val x: kotlin.String?
internal final /*synthesized*/ fun component1(): kotlin.String?
public final /*synthesized*/ fun copy(/*0*/ x: kotlin.String? = ...): MyClass
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
}
@@ -0,0 +1,8 @@
fun foo(x: String): String? = x
fun calc(x: String?): Int {
// Smart cast because of x!! in receiver
foo(x!!)?.subSequence(0, <!DEBUG_INFO_SMARTCAST!>x<!>.length())
// Smart cast because of x!! in receiver
return <!DEBUG_INFO_SMARTCAST!>x<!>.length()
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.String?): kotlin.Int
internal fun foo(/*0*/ x: kotlin.String): kotlin.String?
@@ -0,0 +1,8 @@
fun foo(x: String): String? = x
fun calc(x: String?): Int {
// Smart cast because of x!! in receiver
foo(x!!)?.subSequence(0, <!DEBUG_INFO_SMARTCAST!>x<!>.length())?.length()
// Smart cast because of x!! in receiver
return <!DEBUG_INFO_SMARTCAST!>x<!>.length()
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.String?): kotlin.Int
internal fun foo(/*0*/ x: kotlin.String): kotlin.String?
@@ -0,0 +1,8 @@
fun foo(x: String): String? = x
fun calc(x: String?, y: Int?): Int {
// Smart cast because of x!! in receiver
foo(x!!)?.subSequence(y!!, <!DEBUG_INFO_SMARTCAST!>x<!>.length())?.length()
// No smart cast possible
return <!TYPE_MISMATCH!>y<!>
}
@@ -0,0 +1,4 @@
package
internal fun calc(/*0*/ x: kotlin.String?, /*1*/ y: kotlin.Int?): kotlin.Int
internal fun foo(/*0*/ x: kotlin.String): kotlin.String?
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list
x?.get(<!DEBUG_INFO_SMARTCAST!>x<!>.size() - 1)
// but not also here!
return x<!UNSAFE_CALL!>.<!>size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -0,0 +1,6 @@
fun calc(x: List<String>?): Int {
// x should be non-null in arguments list
x?.subList(<!DEBUG_INFO_SMARTCAST!>x<!>.size() - 1, <!DEBUG_INFO_SMARTCAST!>x<!>.size())
// but not also here!
return x<!UNSAFE_CALL!>.<!>size()
}
@@ -0,0 +1,3 @@
package
internal fun calc(/*0*/ x: kotlin.List<kotlin.String>?): kotlin.Int
@@ -11024,6 +11024,7 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({ @InnerTestClasses({
SmartCasts.Inference.class, SmartCasts.Inference.class,
SmartCasts.Safecalls.class,
SmartCasts.Varnotnull.class, SmartCasts.Varnotnull.class,
}) })
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -11185,6 +11186,135 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
} }
} }
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Safecalls extends AbstractJetDiagnosticsTest {
public void testAllFilesPresentInSafecalls() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/safecalls"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("anotherVal.kt")
public void testAnotherVal() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/anotherVal.kt");
doTest(fileName);
}
@TestMetadata("argument.kt")
public void testArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/argument.kt");
doTest(fileName);
}
@TestMetadata("chainAndUse.kt")
public void testChainAndUse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/chainAndUse.kt");
doTest(fileName);
}
@TestMetadata("chainInChain.kt")
public void testChainInChain() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/chainInChain.kt");
doTest(fileName);
}
@TestMetadata("chainMixedUnsafe.kt")
public void testChainMixedUnsafe() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/chainMixedUnsafe.kt");
doTest(fileName);
}
@TestMetadata("doubleCall.kt")
public void testDoubleCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/doubleCall.kt");
doTest(fileName);
}
@TestMetadata("extension.kt")
public void testExtension() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/extension.kt");
doTest(fileName);
}
@TestMetadata("falseArgument.kt")
public void testFalseArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/falseArgument.kt");
doTest(fileName);
}
@TestMetadata("falseChain.kt")
public void testFalseChain() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/falseChain.kt");
doTest(fileName);
}
@TestMetadata("falseExtension.kt")
public void testFalseExtension() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/falseExtension.kt");
doTest(fileName);
}
@TestMetadata("falseSecondArgument.kt")
public void testFalseSecondArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/falseSecondArgument.kt");
doTest(fileName);
}
@TestMetadata("innerReceiver.kt")
public void testInnerReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/innerReceiver.kt");
doTest(fileName);
}
@TestMetadata("insideCall.kt")
public void testInsideCall() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/insideCall.kt");
doTest(fileName);
}
@TestMetadata("longChain.kt")
public void testLongChain() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/longChain.kt");
doTest(fileName);
}
@TestMetadata("property.kt")
public void testProperty() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/property.kt");
doTest(fileName);
}
@TestMetadata("receiver.kt")
public void testReceiver() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/receiver.kt");
doTest(fileName);
}
@TestMetadata("receiverAndChain.kt")
public void testReceiverAndChain() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/receiverAndChain.kt");
doTest(fileName);
}
@TestMetadata("receiverAndChainFalse.kt")
public void testReceiverAndChainFalse() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/receiverAndChainFalse.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/simple.kt");
doTest(fileName);
}
@TestMetadata("twoArgs.kt")
public void testTwoArgs() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/twoArgs.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull") @TestMetadata("compiler/testData/diagnostics/tests/smartCasts/varnotnull")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)