'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.DataFlowValue;
|
||||||
import org.jetbrains.jet.lang.resolve.calls.autocasts.DataFlowValueFactory;
|
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.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.ResolvedCall;
|
||||||
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCallWithTrace;
|
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCallWithTrace;
|
||||||
import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
|
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.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.NO_EXPECTED_TYPE;
|
||||||
import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType;
|
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.*;
|
import static org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils.*;
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousMethodCalls")
|
@SuppressWarnings("SuspiciousMethodCalls")
|
||||||
@@ -190,7 +193,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
return DataFlowUtils.checkType(result, expression, context, dataFlowInfo);
|
return DataFlowUtils.checkType(result, expression, context, dataFlowInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkBinaryWithTypeRHS(
|
private void checkBinaryWithTypeRHS(
|
||||||
@NotNull JetBinaryExpressionWithTypeRHS expression,
|
@NotNull JetBinaryExpressionWithTypeRHS expression,
|
||||||
@NotNull ExpressionTypingContext context,
|
@NotNull ExpressionTypingContext context,
|
||||||
@NotNull JetType targetType,
|
@NotNull JetType targetType,
|
||||||
@@ -209,7 +212,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
checkForCastImpossibility(expression, actualType, targetType, context);
|
checkForCastImpossibility(expression, actualType, targetType, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkForCastImpossibility(
|
private void checkForCastImpossibility(
|
||||||
JetBinaryExpressionWithTypeRHS expression,
|
JetBinaryExpressionWithTypeRHS expression,
|
||||||
JetType actualType,
|
JetType actualType,
|
||||||
JetType targetType,
|
JetType targetType,
|
||||||
@@ -217,15 +220,15 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
) {
|
) {
|
||||||
if (actualType == null || noExpectedType(targetType)) return;
|
if (actualType == null || noExpectedType(targetType)) return;
|
||||||
|
|
||||||
|
if (!isCastPossible(actualType, targetType)) {
|
||||||
|
context.trace.report(CAST_NEVER_SUCCEEDS.on(expression.getOperationReference()));
|
||||||
|
}
|
||||||
|
|
||||||
JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
|
JetTypeChecker typeChecker = JetTypeChecker.INSTANCE;
|
||||||
if (!typeChecker.isSubtypeOf(targetType, actualType)) {
|
if (!typeChecker.isSubtypeOf(targetType, actualType)) {
|
||||||
if (typeChecker.isSubtypeOf(actualType, targetType)) {
|
if (typeChecker.isSubtypeOf(actualType, targetType)) {
|
||||||
context.trace.report(USELESS_CAST_STATIC_ASSERT_IS_FINE.on(expression.getOperationReference()));
|
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 {
|
else {
|
||||||
if (typeChecker.isSubtypeOf(actualType, targetType)) {
|
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.
|
* Check if assignment from supertype to subtype is erased.
|
||||||
* It is an error in "is" statement and warning in "as".
|
* 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")
|
@TestMetadata("compiler/testData/diagnostics/tests/cast")
|
||||||
|
@InnerTestClasses({Cast.NeverSucceeds.class})
|
||||||
public static class Cast extends AbstractDiagnosticsTestWithEagerResolve {
|
public static class Cast extends AbstractDiagnosticsTestWithEagerResolve {
|
||||||
public void testAllFilesPresentInCast() throws Exception {
|
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);
|
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");
|
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")
|
@TestMetadata("compiler/testData/diagnostics/tests/checkArguments")
|
||||||
@@ -5423,7 +5458,7 @@ public class JetDiagnosticsTestGenerated extends AbstractDiagnosticsTestWithEage
|
|||||||
suite.addTestSuite(Annotations.class);
|
suite.addTestSuite(Annotations.class);
|
||||||
suite.addTestSuite(BackingField.class);
|
suite.addTestSuite(BackingField.class);
|
||||||
suite.addTestSuite(CallableReference.class);
|
suite.addTestSuite(CallableReference.class);
|
||||||
suite.addTestSuite(Cast.class);
|
suite.addTest(Cast.innerSuite());
|
||||||
suite.addTestSuite(CheckArguments.class);
|
suite.addTestSuite(CheckArguments.class);
|
||||||
suite.addTestSuite(ControlFlowAnalysis.class);
|
suite.addTestSuite(ControlFlowAnalysis.class);
|
||||||
suite.addTestSuite(ControlStructures.class);
|
suite.addTestSuite(ControlStructures.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user