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.base.Predicates;
021    import com.google.common.collect.Lists;
022    import com.google.common.collect.Maps;
023    import kotlin.CollectionsKt;
024    import kotlin.jvm.functions.Function1;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap;
027    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
028    import org.jetbrains.kotlin.descriptors.ClassKind;
029    import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
030    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
031    import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
032    import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure;
033    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
034    
035    import java.util.*;
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            List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
170            Set<TypeConstructor> variableConstructors = CollectionsKt.toSet(
171                    CollectionsKt.map(variables, new Function1<TypeParameterDescriptor, TypeConstructor>() {
172                        @Override
173                        public TypeConstructor invoke(TypeParameterDescriptor descriptor) {
174                            return descriptor.getTypeConstructor();
175                        }
176                    }));
177    
178            Map<TypeConstructor, TypeProjection> substitution;
179            if (supertypeWithVariables != null) {
180                // Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
181                TypeUnifier.UnificationResult solution = TypeUnifier.unify(
182                        new TypeProjectionImpl(supertype), new TypeProjectionImpl(supertypeWithVariables),
183                        Predicates.in(variableConstructors));
184                substitution = Maps.newHashMap(solution.getSubstitution());
185            }
186            else {
187                // If there's no corresponding supertype, no variables are determined
188                // This may be OK, e.g. in case 'Any as List<*>'
189                substitution = Maps.newHashMapWithExpectedSize(variables.size());
190            }
191    
192            // If some of the parameters are not determined by unification, it means that these parameters are lost,
193            // let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
194            boolean allArgumentsInferred = true;
195            for (TypeParameterDescriptor variable : variables) {
196                TypeProjection value = substitution.get(variable.getTypeConstructor());
197                if (value == null) {
198                    substitution.put(
199                            variable.getTypeConstructor(),
200                            TypeUtils.makeStarProjection(variable)
201                    );
202                    allArgumentsInferred = false;
203                }
204            }
205    
206            // At this point we have values for all type parameters of List
207            // Let's make a type by substituting them: List<T> -> List<Foo>
208            KotlinType substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
209    
210            return new TypeReconstructionResult(substituted, allArgumentsInferred);
211        }
212    
213        private static boolean allParametersReified(KotlinType subtype) {
214            for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
215                if (!parameterDescriptor.isReified()) return false;
216            }
217            return true;
218        }
219    
220        private CastDiagnosticsUtil() {}
221    }