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