'Cast can never succeed' diagnostics improved

This commit is contained in:
Andrey Breslav
2013-09-04 13:12:07 +04:00
parent 9f4beece64
commit a28d211442
6 changed files with 250 additions and 9 deletions
@@ -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".