Erased casts supported
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package org.jetbrains.jet.lang.types;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jet.lang.PlatformToKotlinClassMap;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
|
||||
@@ -8,10 +10,12 @@ import org.jetbrains.jet.lang.descriptors.ClassKind;
|
||||
import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
|
||||
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
|
||||
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
|
||||
import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CastDiagnosticsUtil {
|
||||
|
||||
@@ -44,11 +48,8 @@ public class CastDiagnosticsUtil {
|
||||
List<JetType> aTypes = mapToPlatformIndependentTypes(a, platformToKotlinClassMap);
|
||||
List<JetType> bTypes = mapToPlatformIndependentTypes(b, platformToKotlinClassMap);
|
||||
|
||||
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);
|
||||
|
||||
for (JetType aType : aTypes) {
|
||||
for (JetType bType : bTypes) {
|
||||
if (JetTypeChecker.INSTANCE.isSubtypeOf(aType, bType)) return true;
|
||||
if (JetTypeChecker.INSTANCE.isSubtypeOf(bType, aType)) return true;
|
||||
}
|
||||
@@ -91,5 +92,82 @@ public class CastDiagnosticsUtil {
|
||||
return descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.TRAIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cast from supertype to subtype is erased.
|
||||
* It is an error in "is" statement and warning in "as".
|
||||
*/
|
||||
public static boolean isCastErased(@NotNull JetType supertype, @NotNull JetType subtype, @NotNull JetTypeChecker typeChecker) {
|
||||
// cast between T and T? is always OK
|
||||
if (supertype.isNullable() || subtype.isNullable()) {
|
||||
return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker);
|
||||
}
|
||||
|
||||
// if it is a upcast, it's never erased
|
||||
if (typeChecker.isSubtypeOf(supertype, subtype)) return false;
|
||||
|
||||
// downcasting to a type parameter is always erased
|
||||
if (isTypeParameter(subtype)) return true;
|
||||
|
||||
// Check that we are actually casting to a generic type
|
||||
// NOTE: this does not account for 'as Array<List<T>>'
|
||||
if (allParametersReified(subtype)) return false;
|
||||
|
||||
// Assume we are casting an expression of type Collection<Foo> to List<Bar>
|
||||
// First, let's make List<T>, where T is a type variable
|
||||
JetType subtypeWithVariables = TypeUtils.makeUnsubstitutedType(
|
||||
subtype.getConstructor(),
|
||||
ErrorUtils.createErrorScope("Scope for intermediate type. This type shouldn't be used outside isCastErased()", true));
|
||||
|
||||
// Now, let's find a supertype of List<T> that is a Collection of something,
|
||||
// in this case it will be Collection<T>
|
||||
JetType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype);
|
||||
if (supertypeWithVariables == null) return true;
|
||||
|
||||
// Now, let's try to unify Collection<T> and Collection<Foo>
|
||||
// solution is a map from T to Foo
|
||||
final List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
|
||||
TypeUnifier.UnificationResult solution = TypeUnifier.unify(
|
||||
new TypeProjection(supertype), new TypeProjection(supertypeWithVariables),
|
||||
new Predicate<TypeConstructor>() {
|
||||
@Override
|
||||
public boolean apply(TypeConstructor typeConstructor) {
|
||||
ClassifierDescriptor descriptor = typeConstructor.getDeclarationDescriptor();
|
||||
return descriptor instanceof TypeParameterDescriptor && variables.contains(descriptor);
|
||||
}
|
||||
});
|
||||
|
||||
// If some of the parameters are not determined by unification, it means that these parameters are lost,
|
||||
// let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
||||
Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMap(solution.getSubstitution());
|
||||
for (TypeParameterDescriptor variable : variables) {
|
||||
TypeProjection value = substitution.get(variable.getTypeConstructor());
|
||||
if (value == null) {
|
||||
substitution.put(
|
||||
variable.getTypeConstructor(),
|
||||
SubstitutionUtils.makeStarProjection(variable)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we have values for all type parameters of List
|
||||
// Let's make a type by substituting them: List<T> -> List<Foo>
|
||||
JetType staticallyKnownSubtype = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
|
||||
|
||||
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
||||
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
||||
if (staticallyKnownSubtype == null) return true;
|
||||
|
||||
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
||||
// If not, it's wrong to use it
|
||||
return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype);
|
||||
}
|
||||
|
||||
private static boolean allParametersReified(JetType subtype) {
|
||||
for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
|
||||
if (!parameterDescriptor.isReified()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private CastDiagnosticsUtil() {}
|
||||
}
|
||||
|
||||
@@ -353,13 +353,18 @@ public class TypeUtils {
|
||||
|
||||
@NotNull
|
||||
public static JetType makeUnsubstitutedType(ClassDescriptor classDescriptor, JetScope unsubstitutedMemberScope) {
|
||||
if (ErrorUtils.isError(classDescriptor)) {
|
||||
return ErrorUtils.createErrorType("Unsubstituted type for " + classDescriptor);
|
||||
return makeUnsubstitutedType(classDescriptor.getTypeConstructor(), unsubstitutedMemberScope);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JetType makeUnsubstitutedType(TypeConstructor typeConstructor, JetScope unsubstitutedMemberScope) {
|
||||
if (ErrorUtils.isError(typeConstructor)) {
|
||||
return ErrorUtils.createErrorType("Unsubstituted type for " + typeConstructor);
|
||||
}
|
||||
List<TypeProjection> arguments = getDefaultTypeProjections(classDescriptor.getTypeConstructor().getParameters());
|
||||
List<TypeProjection> arguments = getDefaultTypeProjections(typeConstructor.getParameters());
|
||||
return new JetTypeImpl(
|
||||
Collections.<AnnotationDescriptor>emptyList(),
|
||||
classDescriptor.getTypeConstructor(),
|
||||
typeConstructor,
|
||||
false,
|
||||
arguments,
|
||||
unsubstitutedMemberScope
|
||||
|
||||
+1
-146
@@ -17,7 +17,6 @@
|
||||
package org.jetbrains.jet.lang.types.expressions;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -57,7 +56,6 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
|
||||
import org.jetbrains.jet.lang.resolve.scopes.receivers.TransientReceiver;
|
||||
import org.jetbrains.jet.lang.types.*;
|
||||
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
|
||||
import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure;
|
||||
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
|
||||
import org.jetbrains.jet.lexer.JetTokens;
|
||||
import org.jetbrains.jet.utils.ThrowingList;
|
||||
@@ -235,156 +233,13 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
||||
context.trace.report(USELESS_CAST.on(expression.getOperationReference()));
|
||||
}
|
||||
else {
|
||||
if (isCastErased(actualType, targetType, typeChecker)) {
|
||||
if (CastDiagnosticsUtil.isCastErased(actualType, targetType, typeChecker)) {
|
||||
context.trace.report(Errors.UNCHECKED_CAST.on(expression, actualType, targetType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if assignment from supertype to subtype is erased.
|
||||
* It is an error in "is" statement and warning in "as".
|
||||
*/
|
||||
public static boolean isCastErased(@NotNull JetType supertype, @NotNull JetType subtype, @NotNull JetTypeChecker typeChecker) {
|
||||
if (!(subtype.getConstructor().getDeclarationDescriptor() instanceof ClassDescriptor)) {
|
||||
// TODO: what if it is TypeParameterDescriptor?
|
||||
return false;
|
||||
}
|
||||
|
||||
// do not crash on error types
|
||||
if (ErrorUtils.isErrorType(supertype) || ErrorUtils.isErrorType(subtype)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasDowncastsInTypeArguments(supertype, subtype, typeChecker) || hasErasedTypeArguments(supertype, subtype);
|
||||
}
|
||||
|
||||
/*
|
||||
Check if type arguments are downcasted, which cannot be checked at run-time.
|
||||
|
||||
Examples:
|
||||
1. a: MutableList<out Any> is MutableList<String> - true, because 'String' is more specific
|
||||
2. a: Collection<String> is List<Any> - false, because 'Any' is less specific, and it is guaranteed by static checker
|
||||
3. a: MutableCollection<String> is MutableList<Any> - false, because these types have empty intersection (type parameter is invariant)
|
||||
*/
|
||||
private static boolean hasDowncastsInTypeArguments(
|
||||
@NotNull JetType supertype,
|
||||
@NotNull JetType subtype,
|
||||
@NotNull JetTypeChecker typeChecker
|
||||
) {
|
||||
List<TypeParameterDescriptor> superParameters = supertype.getConstructor().getParameters();
|
||||
|
||||
// This map holds arguments for type parameters of all superclasses of a type (see method comment for sample)
|
||||
Multimap<TypeConstructor, TypeProjection> subtypeSubstitutionMap = SubstitutionUtils.buildDeepSubstitutionMultimap(subtype);
|
||||
|
||||
for (int i = 0; i < superParameters.size(); i++) {
|
||||
TypeProjection superArgument = supertype.getArguments().get(i);
|
||||
TypeParameterDescriptor parameter = superParameters.get(i);
|
||||
|
||||
if (parameter.isReified()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Collection<TypeProjection> substituted = subtypeSubstitutionMap.get(parameter.getTypeConstructor());
|
||||
for (TypeProjection substitutedArgument : substituted) {
|
||||
// For sample #2 (a: Collection<String> is List<Any>):
|
||||
// parameter = E declared in Collection
|
||||
// superArgument = String
|
||||
// substitutedArgument = Any, because Collection<Any> is the supertype of List<Any>
|
||||
|
||||
// 1. Any..Nothing
|
||||
// 2. String..Nothing
|
||||
// 3. String..String
|
||||
JetType superOut = TypeCheckingProcedure.getOutType(parameter, superArgument);
|
||||
JetType superIn = TypeCheckingProcedure.getInType(parameter, superArgument);
|
||||
|
||||
// 1. String..String
|
||||
// 2. Any..Nothing
|
||||
// 3. Any..Any
|
||||
JetType subOut = TypeCheckingProcedure.getOutType(parameter, substitutedArgument);
|
||||
JetType subIn = TypeCheckingProcedure.getInType(parameter, substitutedArgument);
|
||||
|
||||
// super type range must be a subset of sub type range
|
||||
if (typeChecker.isSubtypeOf(superOut, subOut) && typeChecker.isSubtypeOf(subIn, superIn)) {
|
||||
// continue
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if type arguments are erased, that is they are not mapped to type parameters of supertype's class
|
||||
|
||||
Examples (MyMap is defined like this: trait MyMap<T>: Map<String, T>):
|
||||
1. a: Any is List<String> - true
|
||||
2. a: Collection<CharSequence> is List<String> - false
|
||||
3. a: Map<String, String> is MyMap<String> - false
|
||||
*/
|
||||
private static boolean hasErasedTypeArguments(
|
||||
@NotNull JetType supertype,
|
||||
@NotNull JetType subtype
|
||||
) {
|
||||
// Erase all type arguments, replacing them with unsubstituted versions:
|
||||
// 1. List<E>
|
||||
// 2. List<E>
|
||||
// 3. MyMap<T>
|
||||
JetType subtypeCleared = TypeUtils.makeUnsubstitutedType(
|
||||
(ClassDescriptor) subtype.getConstructor().getDeclarationDescriptor(), null);
|
||||
|
||||
// This map holds arguments for type parameters of all superclasses of a type (see method comment for sample)
|
||||
// For all "E" declared in Collection, Iterable, etc., value will be type "E", where the latter E is declared in List
|
||||
Multimap<TypeConstructor, TypeProjection> clearTypeSubstitutionMap =
|
||||
SubstitutionUtils.buildDeepSubstitutionMultimap(subtypeCleared);
|
||||
|
||||
|
||||
// This set will contain all arguments for type parameters of superclass which are mapped from type parameters of subtype's class
|
||||
// 1. empty
|
||||
// 2. [E declared in List]
|
||||
// 3. [T declared in MyMap]
|
||||
Set<JetType> clearSubstituted = new HashSet<JetType>();
|
||||
|
||||
List<TypeParameterDescriptor> superParameters = supertype.getConstructor().getParameters();
|
||||
for (TypeParameterDescriptor superParameter : superParameters) {
|
||||
Collection<TypeProjection> substituted = clearTypeSubstitutionMap.get(superParameter.getTypeConstructor());
|
||||
for (TypeProjection substitutedProjection : substituted) {
|
||||
clearSubstituted.add(substitutedProjection.getType());
|
||||
}
|
||||
}
|
||||
|
||||
// For each type parameter of subtype's class, we check that it is mapped to type parameters of supertype,
|
||||
// that is its type is present in clearSubstituted set
|
||||
List<TypeParameterDescriptor> subParameters = subtype.getConstructor().getParameters();
|
||||
for (int i = 0; i < subParameters.size(); i++) {
|
||||
TypeParameterDescriptor parameter = subParameters.get(i);
|
||||
TypeProjection argument = subtype.getArguments().get(i);
|
||||
|
||||
if (parameter.isReified()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// "is List<*>", no check for type argument, actually
|
||||
if (argument.equals(SubstitutionUtils.makeStarProjection(parameter))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if parameter is mapped to nothing then it is erased
|
||||
// 1. return from here
|
||||
// 2. contains = true, don't return
|
||||
// 3. contains = true, don't return
|
||||
if (!clearSubstituted.contains(parameter.getDefaultType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JetTypeInfo visitThisExpression(JetThisExpression expression, ExpressionTypingContext context) {
|
||||
JetType result = null;
|
||||
|
||||
+1
-1
@@ -285,7 +285,7 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
|
||||
context.trace.report(Errors.USELESS_NULLABLE_CHECK.on(nullableType));
|
||||
}
|
||||
checkTypeCompatibility(context, type, subjectType, typeReferenceAfterIs);
|
||||
if (BasicExpressionTypingVisitor.isCastErased(subjectType, type, JetTypeChecker.INSTANCE)) {
|
||||
if (CastDiagnosticsUtil.isCastErased(subjectType, type, JetTypeChecker.INSTANCE)) {
|
||||
context.trace.report(Errors.CANNOT_CHECK_FOR_ERASED.on(typeReferenceAfterIs, type));
|
||||
}
|
||||
return new DataFlowInfos(context.dataFlowInfo.establishSubtyping(subjectDataFlowValue, type), context.dataFlowInfo);
|
||||
|
||||
Reference in New Issue
Block a user