001 package org.jetbrains.jet.lang.types;
002
003 import com.google.common.base.Predicate;
004 import com.google.common.collect.Lists;
005 import com.google.common.collect.Maps;
006 import org.jetbrains.annotations.NotNull;
007 import org.jetbrains.jet.lang.PlatformToKotlinClassMap;
008 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
009 import org.jetbrains.jet.lang.descriptors.ClassKind;
010 import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
011 import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
012 import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
013 import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure;
014
015 import java.util.Collection;
016 import java.util.Collections;
017 import java.util.List;
018 import java.util.Map;
019
020 public class CastDiagnosticsUtil {
021
022 // As this method produces a warning, it must be _complete_ (not sound), i.e. every time it says "cast impossible",
023 // it must be really impossible
024 public static boolean isCastPossible(
025 @NotNull JetType lhsType,
026 @NotNull JetType rhsType,
027 @NotNull PlatformToKotlinClassMap platformToKotlinClassMap
028 ) {
029 if (isRelated(lhsType, rhsType, platformToKotlinClassMap)) return true;
030 // This is an oversimplification (which does not render the method incomplete):
031 // we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
032 if (isTypeParameter(lhsType) || isTypeParameter(rhsType)) return true;
033 if (isFinal(lhsType) || isFinal(rhsType)) return false;
034 if (isTrait(lhsType) || isTrait(rhsType)) return true;
035 return false;
036 }
037
038 /**
039 * Two types are related, roughly, when one is a subtype or supertype of the other.
040 * <p/>
041 * Note that some types have platform-specific counterparts, i.e. jet.String is mapped to java.lang.String,
042 * such types (and all their sub- and supertypes) are related too.
043 * <p/>
044 * Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
045 * (i.e. java.lang.String -> jet.String) and ignore mappings that go the other way.
046 */
047 private static boolean isRelated(@NotNull JetType a, @NotNull JetType b, @NotNull PlatformToKotlinClassMap platformToKotlinClassMap) {
048 List<JetType> aTypes = mapToPlatformIndependentTypes(a, platformToKotlinClassMap);
049 List<JetType> bTypes = mapToPlatformIndependentTypes(b, platformToKotlinClassMap);
050
051 for (JetType aType : aTypes) {
052 for (JetType bType : bTypes) {
053 if (JetTypeChecker.INSTANCE.isSubtypeOf(aType, bType)) return true;
054 if (JetTypeChecker.INSTANCE.isSubtypeOf(bType, aType)) return true;
055 }
056 }
057
058 return false;
059 }
060
061 private static List<JetType> mapToPlatformIndependentTypes(
062 @NotNull JetType type,
063 @NotNull PlatformToKotlinClassMap platformToKotlinClassMap
064 ) {
065 ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
066 if (!(descriptor instanceof ClassDescriptor)) return Collections.singletonList(type);
067
068 ClassDescriptor originalClass = (ClassDescriptor) descriptor;
069 Collection<ClassDescriptor> kotlinClasses = platformToKotlinClassMap.mapPlatformClass(originalClass);
070 if (kotlinClasses.isEmpty()) return Collections.singletonList(type);
071
072 List<JetType> result = Lists.newArrayListWithCapacity(2);
073 result.add(type);
074 for (ClassDescriptor classDescriptor : kotlinClasses) {
075 JetType kotlinType = TypeUtils.substituteProjectionsForParameters(classDescriptor, type.getArguments());
076 result.add(kotlinType);
077 }
078
079 return result;
080 }
081
082 private static boolean isTypeParameter(@NotNull JetType type) {
083 return type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor;
084 }
085
086 private static boolean isFinal(@NotNull JetType type) {
087 return !TypeUtils.canHaveSubtypes(JetTypeChecker.INSTANCE, type);
088 }
089
090 private static boolean isTrait(@NotNull JetType type) {
091 ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
092 return descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.TRAIT;
093 }
094
095 /**
096 * Check if cast from supertype to subtype is erased.
097 * It is an error in "is" statement and warning in "as".
098 */
099 public static boolean isCastErased(@NotNull JetType supertype, @NotNull JetType subtype, @NotNull JetTypeChecker typeChecker) {
100 // cast between T and T? is always OK
101 if (supertype.isNullable() || subtype.isNullable()) {
102 return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker);
103 }
104
105 // if it is a upcast, it's never erased
106 if (typeChecker.isSubtypeOf(supertype, subtype)) return false;
107
108 // downcasting to a type parameter is always erased
109 if (isTypeParameter(subtype)) return true;
110
111 // Check that we are actually casting to a generic type
112 // NOTE: this does not account for 'as Array<List<T>>'
113 if (allParametersReified(subtype)) return false;
114
115 // Assume we are casting an expression of type Collection<Foo> to List<Bar>
116 // First, let's make List<T>, where T is a type variable
117 JetType subtypeWithVariables = TypeUtils.makeUnsubstitutedType(
118 subtype.getConstructor(),
119 ErrorUtils.createErrorScope("Scope for intermediate type. This type shouldn't be used outside isCastErased()", true));
120
121 // Now, let's find a supertype of List<T> that is a Collection of something,
122 // in this case it will be Collection<T>
123 JetType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype);
124
125 final List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
126
127 Map<TypeConstructor, TypeProjection> substitution;
128 if (supertypeWithVariables != null) {
129 // Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
130 TypeUnifier.UnificationResult solution = TypeUnifier.unify(
131 new TypeProjection(supertype), new TypeProjection(supertypeWithVariables),
132 new Predicate<TypeConstructor>() {
133 @Override
134 public boolean apply(TypeConstructor typeConstructor) {
135 ClassifierDescriptor descriptor = typeConstructor.getDeclarationDescriptor();
136 return descriptor instanceof TypeParameterDescriptor && variables.contains(descriptor);
137 }
138 });
139 substitution = Maps.newHashMap(solution.getSubstitution());
140 }
141 else {
142 // If there's no corresponding supertype, no variables are determined
143 // This may be OK, e.g. in case 'Any as List<*>'
144 substitution = Maps.newHashMapWithExpectedSize(variables.size());
145 }
146
147 // If some of the parameters are not determined by unification, it means that these parameters are lost,
148 // let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
149 for (TypeParameterDescriptor variable : variables) {
150 TypeProjection value = substitution.get(variable.getTypeConstructor());
151 if (value == null) {
152 substitution.put(
153 variable.getTypeConstructor(),
154 SubstitutionUtils.makeStarProjection(variable)
155 );
156 }
157 }
158
159 // At this point we have values for all type parameters of List
160 // Let's make a type by substituting them: List<T> -> List<Foo>
161 JetType staticallyKnownSubtype = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
162
163 // If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
164 // In this case, we can't guarantee anything, so the cast is considered to be erased
165 if (staticallyKnownSubtype == null) return true;
166
167 // If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
168 // If not, it's wrong to use it
169 return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype);
170 }
171
172 private static boolean allParametersReified(JetType subtype) {
173 for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
174 if (!parameterDescriptor.isReified()) return false;
175 }
176 return true;
177 }
178
179 private CastDiagnosticsUtil() {}
180 }