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. kotlin.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 -> kotlin.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 JetType staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype.getConstructor()).getResultingType();
116
117 // If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
118 // In this case, we can't guarantee anything, so the cast is considered to be erased
119 if (staticallyKnownSubtype == null) return true;
120
121 // If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
122 // If not, it's wrong to use it
123 return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype);
124 }
125
126 /**
127 * Remember that we are trying to cast something of type {@code supertype} to {@code subtype}.
128 *
129 * Since at runtime we can only check the class (type constructor), the rest of the subtype should be known statically, from supertype.
130 * This method reconstructs all static information that can be obtained from supertype.
131 *
132 * Example 1:
133 * supertype = Collection<String>
134 * subtype = List<...>
135 * result = List<String>, all arguments are inferred
136 *
137 * Example 2:
138 * supertype = Any
139 * subtype = List<...>
140 * result = List<*>, some arguments were not inferred, replaced with '*'
141 */
142 public static TypeReconstructionResult findStaticallyKnownSubtype(@NotNull JetType supertype, @NotNull TypeConstructor subtypeConstructor) {
143 assert !supertype.isNullable() : "This method only makes sense for non-nullable types";
144
145 // Assume we are casting an expression of type Collection<Foo> to List<Bar>
146 // First, let's make List<T>, where T is a type variable
147 ClassifierDescriptor descriptor = subtypeConstructor.getDeclarationDescriptor();
148 assert descriptor != null : "Can't create default type for " + subtypeConstructor;
149 JetType subtypeWithVariables = descriptor.getDefaultType();
150
151 // Now, let's find a supertype of List<T> that is a Collection of something,
152 // in this case it will be Collection<T>
153 JetType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype);
154
155 final List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
156
157 Map<TypeConstructor, TypeProjection> substitution;
158 if (supertypeWithVariables != null) {
159 // Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
160 TypeUnifier.UnificationResult solution = TypeUnifier.unify(
161 new TypeProjectionImpl(supertype), new TypeProjectionImpl(supertypeWithVariables),
162 new Predicate<TypeConstructor>() {
163 @Override
164 public boolean apply(TypeConstructor typeConstructor) {
165 ClassifierDescriptor descriptor = typeConstructor.getDeclarationDescriptor();
166 return descriptor instanceof TypeParameterDescriptor && variables.contains(descriptor);
167 }
168 });
169 substitution = Maps.newHashMap(solution.getSubstitution());
170 }
171 else {
172 // If there's no corresponding supertype, no variables are determined
173 // This may be OK, e.g. in case 'Any as List<*>'
174 substitution = Maps.newHashMapWithExpectedSize(variables.size());
175 }
176
177 // If some of the parameters are not determined by unification, it means that these parameters are lost,
178 // let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
179 boolean allArgumentsInferred = true;
180 for (TypeParameterDescriptor variable : variables) {
181 TypeProjection value = substitution.get(variable.getTypeConstructor());
182 if (value == null) {
183 substitution.put(
184 variable.getTypeConstructor(),
185 SubstitutionUtils.makeStarProjection(variable)
186 );
187 allArgumentsInferred = false;
188 }
189 }
190
191 // At this point we have values for all type parameters of List
192 // Let's make a type by substituting them: List<T> -> List<Foo>
193 JetType substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
194
195 return new TypeReconstructionResult(substituted, allArgumentsInferred);
196 }
197
198 private static boolean allParametersReified(JetType subtype) {
199 for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
200 if (!parameterDescriptor.isReified()) return false;
201 }
202 return true;
203 }
204
205 private CastDiagnosticsUtil() {}
206 }