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,
|
||||
|
||||
@@ -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")
|
||||
@InnerTestClasses({
|
||||
SmartCasts.Inference.class,
|
||||
SmartCasts.Safecalls.class,
|
||||
SmartCasts.Varnotnull.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")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user