001 /*
002 * Copyright 2010-2014 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.jet.lang.types;
018
019 import com.google.common.base.Predicate;
020 import com.google.common.collect.Lists;
021 import com.google.common.collect.Maps;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.jet.lang.PlatformToKotlinClassMap;
024 import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
025 import org.jetbrains.jet.lang.descriptors.ClassKind;
026 import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
027 import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
028 import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
029 import org.jetbrains.jet.lang.types.checker.TypeCheckingProcedure;
030
031 import java.util.Collection;
032 import java.util.Collections;
033 import java.util.List;
034 import java.util.Map;
035
036 public class CastDiagnosticsUtil {
037
038 // As this method produces a warning, it must be _complete_ (not sound), i.e. every time it says "cast impossible",
039 // it must be really impossible
040 public static boolean isCastPossible(
041 @NotNull JetType lhsType,
042 @NotNull JetType rhsType,
043 @NotNull PlatformToKotlinClassMap platformToKotlinClassMap
044 ) {
045 if (isRelated(lhsType, rhsType, platformToKotlinClassMap)) return true;
046 // This is an oversimplification (which does not render the method incomplete):
047 // we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
048 if (isTypeParameter(lhsType) || isTypeParameter(rhsType)) return true;
049 if (isFinal(lhsType) || isFinal(rhsType)) return false;
050 if (isTrait(lhsType) || isTrait(rhsType)) return true;
051 return false;
052 }
053
054 /**
055 * Two types are related, roughly, when one is a subtype or supertype of the other.
056 * <p/>
057 * Note that some types have platform-specific counterparts, i.e. kotlin.String is mapped to java.lang.String,
058 * such types (and all their sub- and supertypes) are related too.
059 * <p/>
060 * Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
061 * (i.e. java.lang.String -> kotlin.String) and ignore mappings that go the other way.
062 */
063 private static boolean isRelated(@NotNull JetType a, @NotNull JetType b, @NotNull PlatformToKotlinClassMap platformToKotlinClassMap) {
064 List<JetType> aTypes = mapToPlatformIndependentTypes(a, platformToKotlinClassMap);
065 List<JetType> bTypes = mapToPlatformIndependentTypes(b, platformToKotlinClassMap);
066
067 for (JetType aType : aTypes) {
068 for (JetType bType : bTypes) {
069 if (JetTypeChecker.DEFAULT.isSubtypeOf(aType, bType)) return true;
070 if (JetTypeChecker.DEFAULT.isSubtypeOf(bType, aType)) return true;
071 }
072 }
073
074 return false;
075 }
076
077 private static List<JetType> mapToPlatformIndependentTypes(
078 @NotNull JetType type,
079 @NotNull PlatformToKotlinClassMap platformToKotlinClassMap
080 ) {
081 ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
082 if (!(descriptor instanceof ClassDescriptor)) return Collections.singletonList(type);
083
084 ClassDescriptor originalClass = (ClassDescriptor) descriptor;
085 Collection<ClassDescriptor> kotlinClasses = platformToKotlinClassMap.mapPlatformClass(originalClass);
086 if (kotlinClasses.isEmpty()) return Collections.singletonList(type);
087
088 List<JetType> result = Lists.newArrayListWithCapacity(2);
089 result.add(type);
090 for (ClassDescriptor classDescriptor : kotlinClasses) {
091 JetType kotlinType = TypeUtils.substituteProjectionsForParameters(classDescriptor, type.getArguments());
092 result.add(kotlinType);
093 }
094
095 return result;
096 }
097
098 private static boolean isTypeParameter(@NotNull JetType type) {
099 return type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor;
100 }
101
102 private static boolean isFinal(@NotNull JetType type) {
103 return !TypeUtils.canHaveSubtypes(JetTypeChecker.DEFAULT, type);
104 }
105
106 private static boolean isTrait(@NotNull JetType type) {
107 ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
108 return descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.TRAIT;
109 }
110
111 /**
112 * Check if cast from supertype to subtype is erased.
113 * It is an error in "is" statement and warning in "as".
114 */
115 public static boolean isCastErased(@NotNull JetType supertype, @NotNull JetType subtype, @NotNull JetTypeChecker typeChecker) {
116 // cast between T and T? is always OK
117 if (supertype.isNullable() || subtype.isNullable()) {
118 return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker);
119 }
120
121 // if it is a upcast, it's never erased
122 if (typeChecker.isSubtypeOf(supertype, subtype)) return false;
123
124 // downcasting to a type parameter is always erased
125 if (isTypeParameter(subtype)) return true;
126
127 // Check that we are actually casting to a generic type
128 // NOTE: this does not account for 'as Array<List<T>>'
129 if (allParametersReified(subtype)) return false;
130
131 JetType staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype.getConstructor()).getResultingType();
132
133 // If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
134 // In this case, we can't guarantee anything, so the cast is considered to be erased
135 if (staticallyKnownSubtype == null) return true;
136
137 // If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
138 // If not, it's wrong to use it
139 return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype);
140 }
141
142 /**
143 * Remember that we are trying to cast something of type {@code supertype} to {@code subtype}.
144 *
145 * Since at runtime we can only check the class (type constructor), the rest of the subtype should be known statically, from supertype.
146 * This method reconstructs all static information that can be obtained from supertype.
147 *
148 * Example 1:
149 * supertype = Collection<String>
150 * subtype = List<...>
151 * result = List<String>, all arguments are inferred
152 *
153 * Example 2:
154 * supertype = Any
155 * subtype = List<...>
156 * result = List<*>, some arguments were not inferred, replaced with '*'
157 */
158 public static TypeReconstructionResult findStaticallyKnownSubtype(@NotNull JetType supertype, @NotNull TypeConstructor subtypeConstructor) {
159 assert !supertype.isNullable() : "This method only makes sense for non-nullable types";
160
161 // Assume we are casting an expression of type Collection<Foo> to List<Bar>
162 // First, let's make List<T>, where T is a type variable
163 ClassifierDescriptor descriptor = subtypeConstructor.getDeclarationDescriptor();
164 assert descriptor != null : "Can't create default type for " + subtypeConstructor;
165 JetType subtypeWithVariables = descriptor.getDefaultType();
166
167 // Now, let's find a supertype of List<T> that is a Collection of something,
168 // in this case it will be Collection<T>
169 JetType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype);
170
171 final List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
172
173 Map<TypeConstructor, TypeProjection> substitution;
174 if (supertypeWithVariables != null) {
175 // Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
176 TypeUnifier.UnificationResult solution = TypeUnifier.unify(
177 new TypeProjectionImpl(supertype), new TypeProjectionImpl(supertypeWithVariables),
178 new Predicate<TypeConstructor>() {
179 @Override
180 public boolean apply(TypeConstructor typeConstructor) {
181 ClassifierDescriptor descriptor = typeConstructor.getDeclarationDescriptor();
182 return descriptor instanceof TypeParameterDescriptor && variables.contains(descriptor);
183 }
184 });
185 substitution = Maps.newHashMap(solution.getSubstitution());
186 }
187 else {
188 // If there's no corresponding supertype, no variables are determined
189 // This may be OK, e.g. in case 'Any as List<*>'
190 substitution = Maps.newHashMapWithExpectedSize(variables.size());
191 }
192
193 // If some of the parameters are not determined by unification, it means that these parameters are lost,
194 // let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
195 boolean allArgumentsInferred = true;
196 for (TypeParameterDescriptor variable : variables) {
197 TypeProjection value = substitution.get(variable.getTypeConstructor());
198 if (value == null) {
199 substitution.put(
200 variable.getTypeConstructor(),
201 TypeUtils.makeStarProjection(variable)
202 );
203 allArgumentsInferred = false;
204 }
205 }
206
207 // At this point we have values for all type parameters of List
208 // Let's make a type by substituting them: List<T> -> List<Foo>
209 JetType substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
210
211 return new TypeReconstructionResult(substituted, allArgumentsInferred);
212 }
213
214 private static boolean allParametersReified(JetType subtype) {
215 for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
216 if (!parameterDescriptor.isReified()) return false;
217 }
218 return true;
219 }
220
221 private CastDiagnosticsUtil() {}
222 }