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    }