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    }