'Cast can never succeed' diagnostics improved
This commit is contained in:
+80
-8
@@ -35,7 +35,9 @@ import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowInfo;
|
||||
import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValue;
|
||||
import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValueFactory;
|
||||
import org.jetbrains.jet.lang.resolve.calls.autocasts.Nullability;
|
||||
import org.jetbrains.jet.lang.resolve.calls.context.*;
|
||||
import org.jetbrains.jet.lang.resolve.calls.context.CheckValueArgumentsMode;
|
||||
import org.jetbrains.jet.lang.resolve.calls.context.ExpressionPosition;
|
||||
import org.jetbrains.jet.lang.resolve.calls.context.TemporaryTraceAndCache;
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCallWithTrace;
|
||||
import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
|
||||
@@ -71,7 +73,8 @@ import static org.jetbrains.jet.lang.resolve.calls.context.ContextDependency.IND
|
||||
import static org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue.NO_RECEIVER;
|
||||
import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
|
||||
import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType;
|
||||
import static org.jetbrains.jet.lang.types.expressions.ControlStructureTypingUtils.*;
|
||||
import static org.jetbrains.jet.lang.types.expressions.ControlStructureTypingUtils.createCallForSpecialConstruction;
|
||||
import static org.jetbrains.jet.lang.types.expressions.ControlStructureTypingUtils.resolveSpecialConstructionAsCall;
|
||||
import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.*;
|
||||
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
@@ -190,7 +193,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
return DataFlowUtils.checkType(result, expression, context, dataFlowInfo);
|
||||
}
|
||||
|
||||
private static void checkBinaryWithTypeRHS(
|
||||
private void checkBinaryWithTypeRHS(
|
||||
@NotNull JetBinaryExpressionWithTypeRHS expression,
|
||||
@NotNull ExpressionTypingContext context,
|
||||
@NotNull JetType targetType,
|
||||
@@ -209,7 +212,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
checkForCastImpossibility(expression, actualType, targetType, context);
|
||||
}
|
||||
|
||||
private static void checkForCastImpossibility(
|
||||
private void checkForCastImpossibility(
|
||||
JetBinaryExpressionWithTypeRHS expression,
|
||||
JetType actualType,
|
||||
JetType targetType,
|
||||
@@ -217,15 +220,15 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
) {
|
||||
if (actualType == null || noExpectedType(targetType)) return;
|
||||
|
||||
if (!isCastPossible(actualType, targetType)) {
|
||||
context.trace.report(CAST_NEVER_SUCCEEDS.on(expression.getOperationReference()));
|
||||
}
|
||||
|
||||
JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
|
||||
if (!typeChecker.isSubtypeOf(targetType, actualType)) {
|
||||
if (typeChecker.isSubtypeOf(actualType, targetType)) {
|
||||
context.trace.report(USELESS_CAST_STATIC_ASSERT_IS_FINE.on(expression.getOperationReference()));
|
||||
}
|
||||
else {
|
||||
// See JET-58 Make 'as never succeeds' a warning, or even never check for Java (external) types
|
||||
context.trace.report(CAST_NEVER_SUCCEEDS.on(expression.getOperationReference()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (typeChecker.isSubtypeOf(actualType, targetType)) {
|
||||
@@ -239,6 +242,75 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
// As this method produces a warning, it must be _complete_ (not sound), i.e. every time it says "cast impossible",
|
||||
// it must be really impossible
|
||||
public boolean isCastPossible(@NotNull JetType lhsType, @NotNull JetType rhsType) {
|
||||
if (isRelated(lhsType, rhsType)) return true;
|
||||
// This is an oversimplification (which does not render the method incomplete):
|
||||
// we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
|
||||
if (isTypeParameter(lhsType) || isTypeParameter(rhsType)) return true;
|
||||
if (isFinal(lhsType) || isFinal(rhsType)) return false;
|
||||
if (isTrait(lhsType) || isTrait(rhsType)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Two types are related, roughly, when one is a subtype or supertype of the other.
|
||||
*
|
||||
* Note that some types have platform-specific counterparts, i.e. jet.String is mapped to java.lang.String,
|
||||
* such types (and all their sub- and supertypes) are related too.
|
||||
*
|
||||
* Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
|
||||
* (i.e. java.lang.String -> jet.String) and ignore mappings that go the other way.
|
||||
*/
|
||||
private boolean isRelated(@NotNull JetType a, @NotNull JetType b) {
|
||||
List<JetType> aTypes = mapToPlatformIndependentTypes(a);
|
||||
List<JetType> bTypes = mapToPlatformIndependentTypes(b);
|
||||
|
||||
for (int i = 0; i < aTypes.size(); i++) {
|
||||
JetType aType = aTypes.get(i);
|
||||
for (int j = 0; j < bTypes.size(); j++) {
|
||||
JetType bType = bTypes.get(j);
|
||||
|
||||
if (JetTypeChecker.INSTANCE.isSubtypeOf(aType, bType)) return true;
|
||||
if (JetTypeChecker.INSTANCE.isSubtypeOf(bType, aType)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<JetType> mapToPlatformIndependentTypes(@NotNull JetType type) {
|
||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
||||
if (!(descriptor instanceof ClassDescriptor)) return Collections.singletonList(type);
|
||||
|
||||
ClassDescriptor originalClass = (ClassDescriptor) descriptor;
|
||||
Collection<ClassDescriptor> kotlinClasses = platformToKotlinClassMap.mapPlatformClass(originalClass);
|
||||
if (kotlinClasses.isEmpty()) return Collections.singletonList(type);
|
||||
|
||||
List<JetType> result = Lists.newArrayListWithCapacity(2);
|
||||
result.add(type);
|
||||
for (ClassDescriptor classDescriptor : kotlinClasses) {
|
||||
JetType kotlinType = TypeUtils.substituteProjectionsForParameters(classDescriptor, type.getArguments());
|
||||
result.add(kotlinType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isTypeParameter(@NotNull JetType type) {
|
||||
return type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor;
|
||||
}
|
||||
|
||||
private static boolean isFinal(@NotNull JetType type) {
|
||||
return !TypeUtils.canHaveSubtypes(JetTypeChecker.INSTANCE, type);
|
||||
}
|
||||
|
||||
private static boolean isTrait(@NotNull JetType type) {
|
||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
||||
return descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.TRAIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if assignment from supertype to subtype is erased.
|
||||
* It is an error in "is" statement and warning in "as".
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// !DIAGNOSTICS: -WARNING +CAST_NEVER_SUCCEEDS
|
||||
import java.lang.String as JString
|
||||
import java.lang.CharSequence as JCS
|
||||
|
||||
fun test(
|
||||
s: String,
|
||||
js: JString,
|
||||
cs: CharSequence,
|
||||
jcs: JCS
|
||||
) {
|
||||
s as JString
|
||||
s as JCS
|
||||
s as CharSequence
|
||||
s as String
|
||||
|
||||
js as JString
|
||||
js as JCS
|
||||
js as CharSequence
|
||||
js as String
|
||||
|
||||
cs as JString
|
||||
cs as JCS
|
||||
cs as CharSequence
|
||||
cs as String
|
||||
|
||||
jcs as JString
|
||||
jcs as JCS
|
||||
jcs as CharSequence
|
||||
jcs as String
|
||||
|
||||
jcs <!CAST_NEVER_SUCCEEDS!>as<!> Int
|
||||
s <!CAST_NEVER_SUCCEEDS!>as<!> java.lang.Integer
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// !DIAGNOSTICS: -WARNING +CAST_NEVER_SUCCEEDS -ABSTRACT_MEMBER_NOT_IMPLEMENTED
|
||||
import java.lang.CharSequence as JCS
|
||||
|
||||
class JSub: JCS
|
||||
class Sub: CharSequence
|
||||
|
||||
fun test(
|
||||
s: Sub,
|
||||
js: JSub,
|
||||
cs: CharSequence,
|
||||
jcs: JCS
|
||||
) {
|
||||
// js as CharSequence // - this case is not supported due to limitation in PlatformToKotlinClassMap
|
||||
js as JCS
|
||||
|
||||
s as CharSequence
|
||||
s as JCS
|
||||
|
||||
js <!CAST_NEVER_SUCCEEDS!>as<!> Sub
|
||||
s <!CAST_NEVER_SUCCEEDS!>as<!> JSub
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// !DIAGNOSTICS: -WARNING +CAST_NEVER_SUCCEEDS
|
||||
trait T1
|
||||
trait T2
|
||||
trait T3
|
||||
open class OC1: T1
|
||||
open class OC2: OC1(), T2
|
||||
class FC1: OC2(), T3
|
||||
trait T4: OC1
|
||||
trait T5: T2
|
||||
|
||||
fun test<TP1: OC1, TP2: T2, TP3: OC2>(
|
||||
t2: T2,
|
||||
t4: T4,
|
||||
fc1: FC1,
|
||||
oc1: OC1,
|
||||
oc2: OC2,
|
||||
tp1: TP1,
|
||||
tp2: TP2
|
||||
) {
|
||||
fc1 as FC1
|
||||
fc1 as OC1
|
||||
fc1 as T1
|
||||
fc1 as TP1
|
||||
|
||||
oc1 as FC1
|
||||
oc1 as OC2
|
||||
oc2 as OC1
|
||||
oc1 as T2
|
||||
oc1 as T1
|
||||
oc1 as TP1
|
||||
oc1 as TP2
|
||||
|
||||
t2 as FC1
|
||||
t2 as OC2
|
||||
t4 as OC1
|
||||
t2 as T2
|
||||
t2 as T5
|
||||
t2 as TP2
|
||||
|
||||
tp1 as FC1
|
||||
tp1 as OC1
|
||||
tp1 as OC2
|
||||
tp2 as T2
|
||||
tp2 as T5
|
||||
tp1 as TP3
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// !DIAGNOSTICS: -WARNING +CAST_NEVER_SUCCEEDS
|
||||
trait Trait1
|
||||
trait Trait2
|
||||
open class OClass1
|
||||
open class OClass2
|
||||
class FClass1
|
||||
class FClass2
|
||||
|
||||
fun test<TP1: OClass1, TP2: OClass2>(
|
||||
t1: Trait1,
|
||||
oc1: OClass1,
|
||||
fc1: FClass1,
|
||||
tp1: TP1
|
||||
) {
|
||||
t1 as Trait2
|
||||
t1 as OClass2
|
||||
t1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
|
||||
t1 as TP2
|
||||
|
||||
oc1 as Trait2
|
||||
oc1 <!CAST_NEVER_SUCCEEDS!>as<!> OClass2
|
||||
oc1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
|
||||
oc1 as TP2
|
||||
|
||||
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> Trait2
|
||||
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> OClass2
|
||||
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
|
||||
fc1 as TP2
|
||||
|
||||
tp1 as Trait2
|
||||
tp1 as OClass2
|
||||
tp1 as FClass2
|
||||
tp1 as TP2
|
||||
}
|
||||
@@ -954,6 +954,7 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/cast")
|
||||
@InnerTestClasses({Cast.NeverSucceeds.class})
|
||||
public static class Cast extends AbstractDiagnosticsTestWithEagerResolve {
|
||||
public void testAllFilesPresentInCast() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/diagnostics/tests/cast"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
@@ -1064,6 +1065,40 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage
|
||||
doTest("compiler/testData/diagnostics/tests/cast/WhenWithExpression.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/cast/neverSucceeds")
|
||||
public static class NeverSucceeds extends AbstractDiagnosticsTestWithEagerResolve {
|
||||
public void testAllFilesPresentInNeverSucceeds() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("compiler/testData/diagnostics/tests/cast/neverSucceeds"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("MappedDirect.kt")
|
||||
public void testMappedDirect() throws Exception {
|
||||
doTest("compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedDirect.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("MappedSubtypes.kt")
|
||||
public void testMappedSubtypes() throws Exception {
|
||||
doTest("compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("NoGenericsRelated.kt")
|
||||
public void testNoGenericsRelated() throws Exception {
|
||||
doTest("compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("NoGenericsUnrelated.kt")
|
||||
public void testNoGenericsUnrelated() throws Exception {
|
||||
doTest("compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Test innerSuite() {
|
||||
TestSuite suite = new TestSuite("Cast");
|
||||
suite.addTestSuite(Cast.class);
|
||||
suite.addTestSuite(NeverSucceeds.class);
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/tests/checkArguments")
|
||||
@@ -5423,7 +5458,7 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage
|
||||
suite.addTestSuite(Annotations.class);
|
||||
suite.addTestSuite(BackingField.class);
|
||||
suite.addTestSuite(CallableReference.class);
|
||||
suite.addTestSuite(Cast.class);
|
||||
suite.addTest(Cast.innerSuite());
|
||||
suite.addTestSuite(CheckArguments.class);
|
||||
suite.addTestSuite(ControlFlowAnalysis.class);
|
||||
suite.addTestSuite(ControlStructures.class);
|
||||
|
||||
Reference in New Issue
Block a user