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    }