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:
@@ -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.ResolutionContext;
|
||||
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.evaluate.ConstantExpressionEvaluator;
|
||||
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.JetTypeInfo;
|
||||
import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import org.jetbrains.kotlin.types.checker.JetTypeChecker;
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
@@ -251,17 +257,36 @@ public class ArgumentTypeResolver {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits function call arguments and determines data flow information changes
|
||||
*/
|
||||
public void analyzeArgumentsAndRecordTypes(
|
||||
@NotNull CallResolutionContext<?> context
|
||||
) {
|
||||
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();
|
||||
if (expression == null) continue;
|
||||
|
||||
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);
|
||||
infoForArguments.updateInfo(argument, typeInfoForCall.getDataFlowInfo());
|
||||
}
|
||||
|
||||
+109
-15
@@ -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.results.OverloadResolutionResults;
|
||||
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.FakeCallableDescriptorForObject;
|
||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
|
||||
@@ -86,7 +87,8 @@ public class CallExpressionResolver {
|
||||
}
|
||||
|
||||
@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
|
||||
) {
|
||||
TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
|
||||
@@ -124,17 +126,24 @@ public class CallExpressionResolver {
|
||||
}
|
||||
|
||||
@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
|
||||
) {
|
||||
boolean[] result = new boolean[1];
|
||||
|
||||
TemporaryTraceAndCache temporaryForVariable = TemporaryTraceAndCache.create(
|
||||
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]) {
|
||||
temporaryForVariable.commit();
|
||||
return JetTypeInfo.create(type, context.dataFlowInfo);
|
||||
return JetTypeInfo.create(type, dataFlowInfo);
|
||||
}
|
||||
|
||||
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;
|
||||
context.trace.report(FUNCTION_CALL_EXPECTED.on(nameExpression, nameExpression, hasValueParameters));
|
||||
type = functionDescriptor != null ? functionDescriptor.getReturnType() : null;
|
||||
return JetTypeInfo.create(type, context.dataFlowInfo);
|
||||
return JetTypeInfo.create(type, dataFlowInfo);
|
||||
}
|
||||
|
||||
temporaryForVariable.commit();
|
||||
return JetTypeInfo.create(null, context.dataFlowInfo);
|
||||
return JetTypeInfo.create(null, dataFlowInfo);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -168,6 +177,10 @@ public class CallExpressionResolver {
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a call expression and its arguments.
|
||||
* Determines the result type and data flow information after the call.
|
||||
*/
|
||||
@NotNull
|
||||
public JetTypeInfo getCallExpressionTypeInfoWithoutFinalTypeCheck(
|
||||
@NotNull JetCallExpression callExpression, @NotNull ReceiverValue receiver,
|
||||
@@ -179,7 +192,9 @@ public class CallExpressionResolver {
|
||||
TemporaryTraceAndCache temporaryForFunction = TemporaryTraceAndCache.create(
|
||||
context, "trace to resolve as function call", callExpression);
|
||||
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);
|
||||
if (result[0]) {
|
||||
FunctionDescriptor functionDescriptor = resolvedCall != null ? resolvedCall.getResultingDescriptor() : null;
|
||||
@@ -192,7 +207,8 @@ public class CallExpressionResolver {
|
||||
if (functionDescriptor == null) {
|
||||
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)) {
|
||||
context.trace.report(ANNOTATION_CLASS_CONSTRUCTOR_CALL.on(callExpression));
|
||||
}
|
||||
@@ -256,6 +272,36 @@ public class CallExpressionResolver {
|
||||
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
|
||||
public JetTypeInfo getQualifiedExpressionTypeInfo(
|
||||
@NotNull JetQualifiedExpression expression, @NotNull ExpressionTypingContext context
|
||||
@@ -263,16 +309,23 @@ public class CallExpressionResolver {
|
||||
// TODO : functions as values
|
||||
JetExpression selectorExpression = expression.getSelectorExpression();
|
||||
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);
|
||||
JetType receiverType = receiverTypeInfo.getType();
|
||||
QualifierReceiver qualifierReceiver = (QualifierReceiver) context.trace.get(BindingContext.QUALIFIER, receiverExpression);
|
||||
|
||||
if (receiverType == null) receiverType = ErrorUtils.createErrorType("Type for " + expression.getText());
|
||||
|
||||
context = context.replaceDataFlowInfo(receiverTypeInfo.getDataFlowInfo());
|
||||
|
||||
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(
|
||||
receiver, expression.getOperationTokenNode(), selectorExpression, context);
|
||||
JetType selectorReturnType = selectorReturnTypeInfo.getType();
|
||||
@@ -281,14 +334,13 @@ public class CallExpressionResolver {
|
||||
checkNestedClassAccess(expression, context);
|
||||
|
||||
//TODO move further
|
||||
if (expression.getOperationSign() == JetTokens.SAFE_ACCESS) {
|
||||
if (safeCall) {
|
||||
if (selectorReturnType != null && !KotlinBuiltIns.isUnit(selectorReturnType)) {
|
||||
if (TypeUtils.isNullableType(receiverType)) {
|
||||
selectorReturnType = TypeUtils.makeNullable(selectorReturnType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : this is suspicious: remove this code?
|
||||
if (selectorReturnType != null) {
|
||||
context.trace.record(BindingContext.EXPRESSION_TYPE, selectorExpression, selectorReturnType);
|
||||
@@ -299,7 +351,48 @@ public class CallExpressionResolver {
|
||||
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) {
|
||||
DataFlowUtils.checkType(typeInfo, expression, context);
|
||||
}
|
||||
@@ -336,7 +429,8 @@ public class CallExpressionResolver {
|
||||
if (receiverQualifier == null && expressionQualifier != null) {
|
||||
assert expressionQualifier.getClassifier() instanceof ClassDescriptor :
|
||||
"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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-7
@@ -43,10 +43,12 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
|
||||
@NotNull AdditionalTypeChecker additionalTypeChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
|
||||
collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -64,7 +66,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
|
||||
) {
|
||||
return new BasicCallResolutionContext(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
|
||||
new ResolutionResultsCacheImpl(), null,
|
||||
callChecker, additionalTypeChecker, StatementFilter.NONE, isAnnotationContext, false);
|
||||
callChecker, additionalTypeChecker, StatementFilter.NONE, isAnnotationContext, false, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -75,7 +77,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
|
||||
return new BasicCallResolutionContext(
|
||||
context.trace, context.scope, call, context.expectedType, context.dataFlowInfo, context.contextDependency, checkArguments,
|
||||
context.resolutionResultsCache, dataFlowInfoForArguments, context.callChecker, context.additionalTypeChecker, context.statementFilter,
|
||||
context.isAnnotationContext, context.collectAllCandidates);
|
||||
context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -94,17 +96,20 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
return new BasicCallResolutionContext(
|
||||
trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
|
||||
collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BasicCallResolutionContext replaceCall(@NotNull Call newCall) {
|
||||
return new BasicCallResolutionContext(
|
||||
trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, isAnnotationContext,
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -57,10 +57,12 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
|
||||
@NotNull StatementFilter statementFilter,
|
||||
@NotNull ReceiverValue explicitExtensionReceiverForInvoke,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
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.tracing = tracing;
|
||||
this.explicitExtensionReceiverForInvoke = explicitExtensionReceiverForInvoke;
|
||||
@@ -76,7 +78,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
|
||||
context.dataFlowInfo, context.contextDependency, context.checkArguments,
|
||||
context.resolutionResultsCache, context.dataFlowInfoForArguments,
|
||||
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(
|
||||
@@ -100,7 +102,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
|
||||
candidateCall, tracing, context.trace, context.scope, context.call, context.expectedType,
|
||||
context.dataFlowInfo, context.contextDependency, context.checkArguments, context.resolutionResultsCache,
|
||||
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
|
||||
@@ -112,11 +114,12 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
return new CallCandidateResolutionContext<D>(
|
||||
candidateCall, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
|
||||
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter,
|
||||
explicitExtensionReceiverForInvoke, isAnnotationContext, collectAllCandidates);
|
||||
explicitExtensionReceiverForInvoke, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -52,10 +52,11 @@ public abstract class CallResolutionContext<Context extends CallResolutionContex
|
||||
@NotNull AdditionalTypeChecker additionalTypeChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
|
||||
statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
this.call = call;
|
||||
this.checkArguments = checkArguments;
|
||||
if (dataFlowInfoForArguments != null) {
|
||||
|
||||
+30
-11
@@ -27,6 +27,12 @@ import org.jetbrains.kotlin.resolve.scopes.JetScope;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
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>> {
|
||||
@NotNull
|
||||
public final BindingTrace trace;
|
||||
@@ -51,6 +57,9 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
|
||||
public final boolean collectAllCandidates;
|
||||
|
||||
// True if we are inside call chain like x?.foo()!!.bar()?.gav()
|
||||
public final boolean insideCallChain;
|
||||
|
||||
protected ResolutionContext(
|
||||
@NotNull BindingTrace trace,
|
||||
@NotNull JetScope scope,
|
||||
@@ -62,7 +71,8 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
@NotNull AdditionalTypeChecker additionalTypeChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideCallChain
|
||||
) {
|
||||
this.trace = trace;
|
||||
this.scope = scope;
|
||||
@@ -75,6 +85,7 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
this.additionalTypeChecker = additionalTypeChecker;
|
||||
this.isAnnotationContext = isAnnotationContext;
|
||||
this.collectAllCandidates = collectAllCandidates;
|
||||
this.insideCallChain = insideCallChain;
|
||||
}
|
||||
|
||||
protected abstract Context create(
|
||||
@@ -85,7 +96,8 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
);
|
||||
|
||||
@NotNull
|
||||
@@ -98,14 +110,14 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
public Context replaceBindingTrace(@NotNull BindingTrace trace) {
|
||||
if (this.trace == trace) return self();
|
||||
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Context replaceDataFlowInfo(@NotNull DataFlowInfo newDataFlowInfo) {
|
||||
if (newDataFlowInfo == dataFlowInfo) return self();
|
||||
return create(trace, scope, newDataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -113,28 +125,28 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
if (newExpectedType == null) return replaceExpectedType(TypeUtils.NO_EXPECTED_TYPE);
|
||||
if (expectedType == newExpectedType) return self();
|
||||
return create(trace, scope, dataFlowInfo, newExpectedType, contextDependency, resolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Context replaceScope(@NotNull JetScope newScope) {
|
||||
if (newScope == scope) return self();
|
||||
return create(trace, newScope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Context replaceContextDependency(@NotNull ContextDependency newContextDependency) {
|
||||
if (newContextDependency == contextDependency) return self();
|
||||
return create(trace, scope, dataFlowInfo, expectedType, newContextDependency, resolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Context replaceResolutionResultsCache(@NotNull ResolutionResultsCache newResolutionResultsCache) {
|
||||
if (newResolutionResultsCache == resolutionResultsCache) return self();
|
||||
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, newResolutionResultsCache, statementFilter,
|
||||
collectAllCandidates);
|
||||
collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -145,12 +157,19 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
@NotNull
|
||||
public Context replaceCollectAllCandidates(boolean newCollectAllCandidates) {
|
||||
return create(trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, statementFilter,
|
||||
newCollectAllCandidates);
|
||||
newCollectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Context replacestatementFilter(@NotNull StatementFilter statementFilter) {
|
||||
public Context replaceStatementFilter(@NotNull StatementFilter 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -37,10 +37,11 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
|
||||
@NotNull AdditionalTypeChecker additionalTypeChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
|
||||
statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
public SimpleResolutionContext(
|
||||
@@ -54,7 +55,7 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
|
||||
@NotNull StatementFilter statementFilter
|
||||
) {
|
||||
this(trace, scope, expectedType, dataFlowInfo, contextDependency, new ResolutionResultsCacheImpl(),
|
||||
callChecker, additionalTypeChecker, statementFilter, false, false);
|
||||
callChecker, additionalTypeChecker, statementFilter, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,10 +67,11 @@ public class SimpleResolutionContext extends ResolutionContext<SimpleResolutionC
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
return new SimpleResolutionContext(
|
||||
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 Collection<MutableResolvedCall<F>> resolvedCalls,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
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.resolvedCalls = resolvedCalls;
|
||||
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.resolutionResultsCache, context.dataFlowInfoForArguments,
|
||||
context.callChecker, context.additionalTypeChecker, context.statementFilter, Lists.<MutableResolvedCall<F>>newArrayList(),
|
||||
context.isAnnotationContext, context.collectAllCandidates);
|
||||
context.isAnnotationContext, context.collectAllCandidates, context.insideCallChain);
|
||||
}
|
||||
|
||||
public ResolutionTask(
|
||||
@@ -119,12 +120,13 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
return new ResolutionTask<D, F>(
|
||||
lazyCandidates, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
|
||||
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls,
|
||||
isAnnotationContext, collectAllCandidates);
|
||||
isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
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>(
|
||||
lazyCandidates, tracing, trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments,
|
||||
resolutionResultsCache, dataFlowInfoForArguments, callChecker, additionalTypeChecker, statementFilter, resolvedCalls,
|
||||
isAnnotationContext, collectAllCandidates);
|
||||
isAnnotationContext, collectAllCandidates, insideCallChain);
|
||||
}
|
||||
|
||||
public interface DescriptorCheckStrategy {
|
||||
|
||||
@@ -20,16 +20,23 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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 {
|
||||
@NotNull
|
||||
public static JetTypeInfo create(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
|
||||
return new JetTypeInfo(type, dataFlowInfo);
|
||||
}
|
||||
|
||||
|
||||
private final JetType type;
|
||||
private final DataFlowInfo dataFlowInfo;
|
||||
|
||||
private JetTypeInfo(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
|
||||
protected JetTypeInfo(@Nullable JetType type, @NotNull DataFlowInfo dataFlowInfo) {
|
||||
this.type = type;
|
||||
this.dataFlowInfo = dataFlowInfo;
|
||||
}
|
||||
|
||||
+2
@@ -876,6 +876,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
JetSimpleNameExpression operationSign = expression.getOperationReference();
|
||||
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));
|
||||
components.controlStructureTypingUtils.resolveSpecialConstructionAsCall(
|
||||
call, "ExclExcl", Collections.singletonList("baseExpr"), Collections.singletonList(true), context, null);
|
||||
|
||||
+8
-6
@@ -66,7 +66,7 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
|
||||
context.trace, context.scope, context.dataFlowInfo, context.expectedType,
|
||||
context.contextDependency, context.resolutionResultsCache, context.callChecker, context.additionalTypeChecker,
|
||||
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(
|
||||
trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
|
||||
statementFilter, isAnnotationContext, false);
|
||||
statementFilter, isAnnotationContext, false, false);
|
||||
}
|
||||
|
||||
private CompileTimeConstantChecker compileTimeConstantChecker;
|
||||
@@ -101,10 +101,11 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
|
||||
@NotNull AdditionalTypeChecker additionalTypeChecker,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean isAnnotationContext,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
|
||||
statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,11 +117,12 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
|
||||
@NotNull ContextDependency contextDependency,
|
||||
@NotNull ResolutionResultsCache resolutionResultsCache,
|
||||
@NotNull StatementFilter statementFilter,
|
||||
boolean collectAllCandidates
|
||||
boolean collectAllCandidates,
|
||||
boolean insideSafeCallChain
|
||||
) {
|
||||
return new ExpressionTypingContext(trace, scope, dataFlowInfo,
|
||||
expectedType, contextDependency, resolutionResultsCache, callChecker, additionalTypeChecker,
|
||||
statementFilter, isAnnotationContext, collectAllCandidates);
|
||||
statementFilter, isAnnotationContext, collectAllCandidates, insideSafeCallChain);
|
||||
}
|
||||
|
||||
///////////// LAZY ACCESSORS
|
||||
|
||||
+5
-1
@@ -266,7 +266,7 @@ public class ExpressionTypingServices {
|
||||
}
|
||||
else {
|
||||
r = getBlockReturnedTypeWithWritableScope(scope, block, coercionStrategyForLastExpression,
|
||||
context.replacestatementFilter(getStatementFilter()));
|
||||
context.replaceStatementFilter(getStatementFilter()));
|
||||
}
|
||||
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(
|
||||
@NotNull WritableScope scope,
|
||||
@NotNull List<? extends JetElement> block,
|
||||
|
||||
Reference in New Issue
Block a user