do not record smart cast to not nullable type separately
This commit is contained in:
@@ -864,7 +864,7 @@ public class CandidateResolver {
|
|||||||
|
|
||||||
JetType erasedReceiverType = CallResolverUtil.getErasedReceiverType(receiverParameterDescriptor, candidateDescriptor);
|
JetType erasedReceiverType = CallResolverUtil.getErasedReceiverType(receiverParameterDescriptor, candidateDescriptor);
|
||||||
|
|
||||||
boolean isSubtypeByAutoCast = AutoCastUtils.isSubTypeByAutoCast(receiverArgument, erasedReceiverType, context);
|
boolean isSubtypeByAutoCast = AutoCastUtils.isSubTypeByAutoCastIgnoringNullability(receiverArgument, erasedReceiverType, context);
|
||||||
if (!isSubtypeByAutoCast) {
|
if (!isSubtypeByAutoCast) {
|
||||||
return RECEIVER_TYPE_ERROR;
|
return RECEIVER_TYPE_ERROR;
|
||||||
}
|
}
|
||||||
@@ -883,30 +883,26 @@ public class CandidateResolver {
|
|||||||
) {
|
) {
|
||||||
if (receiverParameter == null || !receiverArgument.exists()) return SUCCESS;
|
if (receiverParameter == null || !receiverArgument.exists()) return SUCCESS;
|
||||||
D candidateDescriptor = candidateCall.getCandidateDescriptor();
|
D candidateDescriptor = candidateCall.getCandidateDescriptor();
|
||||||
|
if (TypeUtils.dependsOnTypeParameters(receiverParameter.getType(), candidateDescriptor.getTypeParameters())) return SUCCESS;
|
||||||
|
|
||||||
boolean smartCastRecorded = false;
|
boolean safeAccess = isExplicitReceiver && !implicitInvokeCheck && candidateCall.isSafeCall();
|
||||||
if (!TypeUtils.dependsOnTypeParameters(receiverParameter.getType(), candidateDescriptor.getTypeParameters())) {
|
boolean isSubtypeByAutoCast = AutoCastUtils.isSubTypeByAutoCastIgnoringNullability(
|
||||||
boolean isSubtypeByAutoCast = AutoCastUtils.isSubTypeByAutoCast(receiverArgument, receiverParameter.getType(), context);
|
receiverArgument, receiverParameter.getType(), context);
|
||||||
if (!isSubtypeByAutoCast) {
|
if (!isSubtypeByAutoCast) {
|
||||||
context.tracing.wrongReceiverType(trace, receiverParameter, receiverArgument);
|
context.tracing.wrongReceiverType(trace, receiverParameter, receiverArgument);
|
||||||
return OTHER_ERROR;
|
return OTHER_ERROR;
|
||||||
}
|
|
||||||
smartCastRecorded = AutoCastUtils.recordAutoCastIfNecessary(receiverArgument, receiverParameter.getType(), context);
|
|
||||||
}
|
}
|
||||||
|
AutoCastUtils.recordAutoCastIfNecessary(receiverArgument, receiverParameter.getType(), context, safeAccess);
|
||||||
|
|
||||||
JetType receiverArgumentType = receiverArgument.getType();
|
JetType receiverArgumentType = receiverArgument.getType();
|
||||||
|
|
||||||
BindingContext bindingContext = trace.getBindingContext();
|
BindingContext bindingContext = trace.getBindingContext();
|
||||||
boolean safeAccess = isExplicitReceiver && !implicitInvokeCheck && candidateCall.isSafeCall();
|
if (!safeAccess && !receiverParameter.getType().isNullable() && receiverArgumentType.isNullable()) {
|
||||||
if (!safeAccess && !receiverParameter.getType().isNullable() && receiverArgument.getType().isNullable()) {
|
|
||||||
if (!AutoCastUtils.isNotNull(receiverArgument, bindingContext, context.dataFlowInfo)) {
|
if (!AutoCastUtils.isNotNull(receiverArgument, bindingContext, context.dataFlowInfo)) {
|
||||||
|
|
||||||
context.tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck);
|
context.tracing.unsafeCall(trace, receiverArgumentType, implicitInvokeCheck);
|
||||||
return UNSAFE_CALL_ERROR;
|
return UNSAFE_CALL_ERROR;
|
||||||
}
|
}
|
||||||
if (!smartCastRecorded && isExplicitReceiver) {
|
|
||||||
AutoCastUtils.recordAutoCastToNotNullableType(receiverArgument, context.trace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DataFlowValue receiverValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, bindingContext);
|
DataFlowValue receiverValue = DataFlowValueFactory.createDataFlowValue(receiverArgument, bindingContext);
|
||||||
if (safeAccess && !context.dataFlowInfo.getNullability(receiverValue).canBeNull()) {
|
if (safeAccess && !context.dataFlowInfo.getNullability(receiverValue).canBeNull()) {
|
||||||
|
|||||||
+10
-21
@@ -94,13 +94,13 @@ public class AutoCastUtils {
|
|||||||
return Lists.newArrayList(dataFlowInfo.getPossibleTypes(dataFlowValue));
|
return Lists.newArrayList(dataFlowInfo.getPossibleTypes(dataFlowValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSubTypeByAutoCast(
|
public static boolean isSubTypeByAutoCastIgnoringNullability(
|
||||||
@NotNull ReceiverValue receiverArgument,
|
@NotNull ReceiverValue receiverArgument,
|
||||||
@NotNull JetType receiverParameterType,
|
@NotNull JetType receiverParameterType,
|
||||||
@NotNull ResolutionContext context
|
@NotNull ResolutionContext context
|
||||||
) {
|
) {
|
||||||
List<JetType> autoCastTypes = getAutoCastVariants(receiverArgument, context);
|
List<JetType> autoCastTypes = getAutoCastVariants(receiverArgument, context);
|
||||||
return getAutoCastSubType(receiverParameterType, autoCastTypes) != null;
|
return getAutoCastSubType(TypeUtils.makeNullable(receiverParameterType), autoCastTypes) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -110,8 +110,7 @@ public class AutoCastUtils {
|
|||||||
) {
|
) {
|
||||||
Set<JetType> subTypes = Sets.newHashSet();
|
Set<JetType> subTypes = Sets.newHashSet();
|
||||||
for (JetType autoCastType : autoCastTypes) {
|
for (JetType autoCastType : autoCastTypes) {
|
||||||
JetType effectiveAutoCastType = TypeUtils.makeNotNullable(autoCastType);
|
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(autoCastType, receiverParameterType)) {
|
||||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(effectiveAutoCastType, receiverParameterType)) {
|
|
||||||
subTypes.add(autoCastType);
|
subTypes.add(autoCastType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,19 +124,21 @@ public class AutoCastUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean recordAutoCastIfNecessary(
|
public static boolean recordAutoCastIfNecessary(
|
||||||
ReceiverValue receiver,
|
@NotNull ReceiverValue receiver,
|
||||||
@NotNull JetType receiverType,
|
@NotNull JetType receiverParameterType,
|
||||||
@NotNull ResolutionContext context
|
@NotNull ResolutionContext context,
|
||||||
|
boolean safeAccess
|
||||||
) {
|
) {
|
||||||
if (!(receiver instanceof ExpressionReceiver)) return false;
|
if (!(receiver instanceof ExpressionReceiver)) return false;
|
||||||
|
|
||||||
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(receiver.getType(), receiverType)) {
|
receiverParameterType = safeAccess ? TypeUtils.makeNullable(receiverParameterType) : receiverParameterType;
|
||||||
|
if (ArgumentTypeResolver.isSubtypeOfForArgumentType(receiver.getType(), receiverParameterType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<JetType> autoCastTypesExcludingReceiver = getAutoCastVariantsExcludingReceiver(
|
List<JetType> autoCastTypesExcludingReceiver = getAutoCastVariantsExcludingReceiver(
|
||||||
context.trace.getBindingContext(), context.dataFlowInfo, receiver);
|
context.trace.getBindingContext(), context.dataFlowInfo, receiver);
|
||||||
JetType autoCastSubType = getAutoCastSubType(receiverType, autoCastTypesExcludingReceiver);
|
JetType autoCastSubType = getAutoCastSubType(receiverParameterType, autoCastTypesExcludingReceiver);
|
||||||
if (autoCastSubType == null) return false;
|
if (autoCastSubType == null) return false;
|
||||||
|
|
||||||
JetExpression expression = ((ExpressionReceiver) receiver).getExpression();
|
JetExpression expression = ((ExpressionReceiver) receiver).getExpression();
|
||||||
@@ -147,18 +148,6 @@ public class AutoCastUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void recordAutoCastToNotNullableType(@NotNull ReceiverValue receiver, @NotNull BindingTrace trace) {
|
|
||||||
if (!(receiver instanceof ExpressionReceiver)) return;
|
|
||||||
|
|
||||||
JetType receiverType = receiver.getType();
|
|
||||||
if (!receiverType.isNullable()) return;
|
|
||||||
JetType notNullableType = TypeUtils.makeNotNullable(receiverType);
|
|
||||||
|
|
||||||
DataFlowValue dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiver, trace.getBindingContext());
|
|
||||||
JetExpression expression = ((ExpressionReceiver) receiver).getExpression();
|
|
||||||
recordCastOrError(expression, notNullableType, trace, dataFlowValue.isStableIdentifier(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void recordCastOrError(
|
public static void recordCastOrError(
|
||||||
@NotNull JetExpression expression,
|
@NotNull JetExpression expression,
|
||||||
@NotNull JetType type,
|
@NotNull JetType type,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
trait A {
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
trait B : A {
|
||||||
|
fun bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
trait C
|
||||||
|
|
||||||
|
trait D : A
|
||||||
|
|
||||||
|
fun test(a: A?) {
|
||||||
|
if (a != null && a is B?) {
|
||||||
|
<info descr="Automatically cast to B">a</info>.bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a is B && a is C) {
|
||||||
|
<info descr="Automatically cast to B">a</info>.foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a is B? && a is C?) {
|
||||||
|
<info descr="Automatically cast to B?">a</info><info>?.</info>bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
a<info>?.</info>foo()
|
||||||
|
if (a is B? && a is C?) {
|
||||||
|
a<info>?.</info>foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a is B && a is D) {
|
||||||
|
//when it's resolved, the message should be 'Automatically cast to A'
|
||||||
|
a.<error>foo</error>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -442,6 +442,11 @@ public class JetPsiCheckerTestGenerated extends AbstractJetPsiCheckerTest {
|
|||||||
doTestWithInfos("idea/testData/checker/infos/PropertiesWithBackingFields.kt");
|
doTestWithInfos("idea/testData/checker/infos/PropertiesWithBackingFields.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("SmartCastsWithSafeAccess.kt")
|
||||||
|
public void testSmartCastsWithSafeAccess() throws Exception {
|
||||||
|
doTestWithInfos("idea/testData/checker/infos/SmartCastsWithSafeAccess.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("Typos.kt")
|
@TestMetadata("Typos.kt")
|
||||||
public void testTypos() throws Exception {
|
public void testTypos() throws Exception {
|
||||||
doTestWithInfos("idea/testData/checker/infos/Typos.kt");
|
doTestWithInfos("idea/testData/checker/infos/Typos.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user