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    }