Smart casts inside property chains like a?.b?.c.foo(a, b) are now handled correctly #KT-7290 Fixed
This commit is contained in:
+18
-4
@@ -21,7 +21,9 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
@@ -36,6 +38,8 @@ 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.smartcasts.DataFlowValue;
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory;
|
||||
import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
|
||||
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject;
|
||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant;
|
||||
@@ -66,17 +70,20 @@ public class CallExpressionResolver {
|
||||
private final ConstantExpressionEvaluator constantExpressionEvaluator;
|
||||
private final SymbolUsageValidator symbolUsageValidator;
|
||||
private final DataFlowAnalyzer dataFlowAnalyzer;
|
||||
@NotNull private final KotlinBuiltIns builtIns;
|
||||
|
||||
public CallExpressionResolver(
|
||||
@NotNull CallResolver callResolver,
|
||||
@NotNull ConstantExpressionEvaluator constantExpressionEvaluator,
|
||||
@NotNull SymbolUsageValidator symbolUsageValidator,
|
||||
@NotNull DataFlowAnalyzer dataFlowAnalyzer
|
||||
@NotNull DataFlowAnalyzer dataFlowAnalyzer,
|
||||
@NotNull KotlinBuiltIns builtIns
|
||||
) {
|
||||
this.callResolver = callResolver;
|
||||
this.constantExpressionEvaluator = constantExpressionEvaluator;
|
||||
this.symbolUsageValidator = symbolUsageValidator;
|
||||
this.dataFlowAnalyzer = dataFlowAnalyzer;
|
||||
this.builtIns = builtIns;
|
||||
}
|
||||
|
||||
private ExpressionTypingServices expressionTypingServices;
|
||||
@@ -152,9 +159,16 @@ public class CallExpressionResolver {
|
||||
context, "trace to resolve as variable", nameExpression);
|
||||
KotlinType type =
|
||||
getVariableType(nameExpression, receiver, callOperationNode, context.replaceTraceAndCache(temporaryForVariable), result);
|
||||
// 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
|
||||
// NB: we have duplicating code in ArgumentTypeResolver.
|
||||
// It would be better to do it in getSelectorTypeInfo, but it breaks call expression analysis
|
||||
// (all safe calls become unnecessary after it)
|
||||
// QualifierReceiver is a thing like Collections. which has no type or value
|
||||
if (receiver.exists() && !(receiver instanceof QualifierReceiver)) {
|
||||
DataFlowValue receiverDataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, context);
|
||||
if (callOperationNode != null && callOperationNode.getElementType() == KtTokens.SAFE_ACCESS) {
|
||||
context = context.replaceDataFlowInfo(context.dataFlowInfo.disequate(receiverDataFlowValue, DataFlowValue.nullValue(builtIns)));
|
||||
}
|
||||
}
|
||||
|
||||
if (result[0]) {
|
||||
temporaryForVariable.commit();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// See KT-7290
|
||||
class MyClass(val x: String?)
|
||||
fun foo(y: MyClass?): Int {
|
||||
// x here is smartcast but y is not
|
||||
val z = y?.x?.subSequence(0, <!DEBUG_INFO_SMARTCAST!><!DEBUG_INFO_SMARTCAST!>y<!>.x<!>.length)
|
||||
// !! is necessary here
|
||||
y!!.x
|
||||
return z?.length ?: -1
|
||||
}
|
||||
fun bar(y: MyClass?) {
|
||||
y?.x!!.length
|
||||
// !! is necessary here
|
||||
y!!.x
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package
|
||||
|
||||
public fun bar(/*0*/ y: MyClass?): kotlin.Unit
|
||||
public fun foo(/*0*/ y: MyClass?): kotlin.Int
|
||||
|
||||
public final class MyClass {
|
||||
public constructor MyClass(/*0*/ x: kotlin.String?)
|
||||
public final val x: kotlin.String?
|
||||
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
|
||||
}
|
||||
@@ -15194,6 +15194,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyChain.kt")
|
||||
public void testPropertyChain() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/propertyChain.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("receiver.kt")
|
||||
public void testReceiver() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/safecalls/receiver.kt");
|
||||
|
||||
Reference in New Issue
Block a user