001    /*
002     * Copyright 2010-2016 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.checker;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
022    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
023    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
024    import org.jetbrains.kotlin.types.*;
025    
026    import java.util.List;
027    
028    import static org.jetbrains.kotlin.types.Variance.*;
029    
030    public class TypeCheckingProcedure {
031    
032        // This method returns the supertype of the first parameter that has the same constructor
033        // as the second parameter, applying the substitution of type arguments to it
034        @Nullable
035        public static KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType supertype) {
036            return findCorrespondingSupertype(subtype, supertype, new TypeCheckerProcedureCallbacksImpl());
037        }
038    
039        // This method returns the supertype of the first parameter that has the same constructor
040        // as the second parameter, applying the substitution of type arguments to it
041        @Nullable
042        public static KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType supertype, @NotNull TypeCheckingProcedureCallbacks typeCheckingProcedureCallbacks) {
043            return UtilsKt.findCorrespondingSupertype(subtype, supertype, typeCheckingProcedureCallbacks);
044        }
045    
046        @NotNull
047        private static KotlinType getOutType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) {
048            boolean isInProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE;
049            return isInProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNullableAnyType() : argument.getType();
050        }
051    
052        @NotNull
053        private static KotlinType getInType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) {
054            boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE;
055            return isOutProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNothingType() : argument.getType();
056        }
057    
058        private final TypeCheckingProcedureCallbacks constraints;
059    
060        public TypeCheckingProcedure(TypeCheckingProcedureCallbacks constraints) {
061            this.constraints = constraints;
062        }
063    
064        public boolean equalTypes(@NotNull KotlinType type1, @NotNull KotlinType type2) {
065            if (type1 == type2) return true;
066            if (FlexibleTypesKt.isFlexible(type1)) {
067                if (FlexibleTypesKt.isFlexible(type2)) {
068                    return !type1.isError() && !type2.isError() && isSubtypeOf(type1, type2) && isSubtypeOf(type2, type1);
069                }
070                return heterogeneousEquivalence(type2, type1);
071            }
072            else if (FlexibleTypesKt.isFlexible(type2)) {
073                return heterogeneousEquivalence(type1, type2);
074            }
075    
076            if (type1.isMarkedNullable() != type2.isMarkedNullable()) {
077                return false;
078            }
079    
080            if (type1.isMarkedNullable()) {
081                // Then type2 is nullable, too (see the previous condition
082                return constraints.assertEqualTypes(TypeUtils.makeNotNullable(type1), TypeUtils.makeNotNullable(type2), this);
083            }
084    
085            TypeConstructor constructor1 = type1.getConstructor();
086            TypeConstructor constructor2 = type2.getConstructor();
087    
088            if (!constraints.assertEqualTypeConstructors(constructor1, constructor2)) {
089                return false;
090            }
091    
092            List<TypeProjection> type1Arguments = type1.getArguments();
093            List<TypeProjection> type2Arguments = type2.getArguments();
094            if (type1Arguments.size() != type2Arguments.size()) {
095                return false;
096            }
097    
098            for (int i = 0; i < type1Arguments.size(); i++) {
099                TypeProjection typeProjection1 = type1Arguments.get(i);
100                TypeProjection typeProjection2 = type2Arguments.get(i);
101                if (typeProjection1.isStarProjection() && typeProjection2.isStarProjection()) {
102                    continue;
103                }
104                TypeParameterDescriptor typeParameter1 = constructor1.getParameters().get(i);
105                TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i);
106    
107                if (capture(typeProjection1, typeProjection2, typeParameter1)) {
108                    continue;
109                }
110                if (getEffectiveProjectionKind(typeParameter1, typeProjection1) != getEffectiveProjectionKind(typeParameter2, typeProjection2)) {
111                    return false;
112                }
113    
114                if (!constraints.assertEqualTypes(typeProjection1.getType(), typeProjection2.getType(), this)) {
115                    return false;
116                }
117            }
118            return true;
119        }
120    
121        protected boolean heterogeneousEquivalence(KotlinType inflexibleType, KotlinType flexibleType) {
122            // This is to account for the case when we have Collection<X> vs (Mutable)Collection<X>! or K(java.util.Collection<? extends X>)
123            assert !FlexibleTypesKt.isFlexible(inflexibleType) : "Only inflexible types are allowed here: " + inflexibleType;
124            return isSubtypeOf(FlexibleTypesKt.asFlexibleType(flexibleType).getLowerBound(), inflexibleType)
125                   && isSubtypeOf(inflexibleType, FlexibleTypesKt.asFlexibleType(flexibleType).getUpperBound());
126        }
127    
128        public enum EnrichedProjectionKind {
129            IN, OUT, INV, STAR;
130    
131            @NotNull
132            public static EnrichedProjectionKind fromVariance(@NotNull Variance variance) {
133                switch (variance) {
134                    case INVARIANT:
135                        return INV;
136                    case IN_VARIANCE:
137                        return IN;
138                    case OUT_VARIANCE:
139                        return OUT;
140                }
141                throw new IllegalStateException("Unknown variance");
142            }
143        }
144    
145        // If class C<out T> then C<T> and C<out T> mean the same
146        // out * out = out
147        // out * in  = *
148        // out * inv = out
149        //
150        // in * out  = *
151        // in * in   = in
152        // in * inv  = in
153        //
154        // inv * out = out
155        // inv * in  = out
156        // inv * inv = inv
157        public static EnrichedProjectionKind getEffectiveProjectionKind(
158                @NotNull TypeParameterDescriptor typeParameter,
159                @NotNull TypeProjection typeArgument
160        ) {
161            Variance a = typeParameter.getVariance();
162            Variance b = typeArgument.getProjectionKind();
163    
164            // If they are not both invariant, let's make b not invariant for sure
165            if (b == INVARIANT) {
166                Variance t = a;
167                a = b;
168                b = t;
169            }
170    
171            // Opposites yield STAR
172            if (a == IN_VARIANCE && b == OUT_VARIANCE) {
173                return EnrichedProjectionKind.STAR;
174            }
175            if (a == OUT_VARIANCE && b == IN_VARIANCE) {
176                return EnrichedProjectionKind.STAR;
177            }
178    
179            // If they are not opposite, return b, because b is either equal to a or b is in/out and a is inv
180            return EnrichedProjectionKind.fromVariance(b);
181        }
182    
183        public boolean isSubtypeOf(@NotNull KotlinType subtype, @NotNull KotlinType supertype) {
184            if (TypeCapabilitiesKt.sameTypeConstructors(subtype, supertype)) {
185                return !subtype.isMarkedNullable() || supertype.isMarkedNullable();
186            }
187            KotlinType subtypeRepresentative = TypeCapabilitiesKt.getSubtypeRepresentative(subtype);
188            KotlinType supertypeRepresentative = TypeCapabilitiesKt.getSupertypeRepresentative(supertype);
189            if (subtypeRepresentative != subtype || supertypeRepresentative != supertype) {
190                // recursive invocation for possible chain of representatives
191                return isSubtypeOf(subtypeRepresentative, supertypeRepresentative);
192            }
193            return isSubtypeOfForRepresentatives(subtype, supertype);
194        }
195    
196        private boolean isSubtypeOfForRepresentatives(KotlinType subtype, KotlinType supertype) {
197            if (subtype.isError() || supertype.isError()) {
198                return true;
199            }
200    
201            if (!supertype.isMarkedNullable() && subtype.isMarkedNullable()) {
202                return false;
203            }
204    
205            if (KotlinBuiltIns.isNothingOrNullableNothing(subtype)) {
206                return true;
207            }
208    
209            @Nullable KotlinType closestSupertype = findCorrespondingSupertype(subtype, supertype, constraints);
210            if (closestSupertype == null) {
211                return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with
212            }
213    
214            if (!supertype.isMarkedNullable() && closestSupertype.isMarkedNullable()) {
215                return false;
216            }
217    
218            return checkSubtypeForTheSameConstructor(closestSupertype, supertype);
219        }
220    
221        private boolean checkSubtypeForTheSameConstructor(@NotNull KotlinType subtype, @NotNull KotlinType supertype) {
222            TypeConstructor constructor = subtype.getConstructor();
223    
224            // this assert was moved to checker/utils.kt
225            //assert constraints.assertEqualTypeConstructors(constructor, supertype.getConstructor()) : constructor + " is not " + supertype.getConstructor();
226    
227            List<TypeProjection> subArguments = subtype.getArguments();
228            List<TypeProjection> superArguments = supertype.getArguments();
229            if (subArguments.size() != superArguments.size()) return false;
230    
231            List<TypeParameterDescriptor> parameters = constructor.getParameters();
232            for (int i = 0; i < parameters.size(); i++) {
233                TypeParameterDescriptor parameter = parameters.get(i);
234    
235                TypeProjection superArgument = superArguments.get(i);
236                TypeProjection subArgument = subArguments.get(i);
237    
238                if (superArgument.isStarProjection()) continue;
239    
240                if (capture(subArgument, superArgument, parameter)) continue;
241    
242                boolean argumentIsErrorType = subArgument.getType().isError() || superArgument.getType().isError();
243                if (!argumentIsErrorType && parameter.getVariance() == INVARIANT &&
244                    subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) {
245                    if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false;
246                    continue;
247                }
248    
249                KotlinType superOut = getOutType(parameter, superArgument);
250                KotlinType subOut = getOutType(parameter, subArgument);
251                if (!constraints.assertSubtype(subOut, superOut, this)) return false;
252    
253                KotlinType superIn = getInType(parameter, superArgument);
254                KotlinType subIn = getInType(parameter, subArgument);
255    
256                if (superArgument.getProjectionKind() != Variance.OUT_VARIANCE) {
257                    if (!constraints.assertSubtype(superIn, subIn, this)) return false;
258                }
259                else {
260                    assert KotlinBuiltIns.isNothing(superIn) : "In component must be Nothing for out-projection";
261                }
262            }
263            return true;
264        }
265    
266        private boolean capture(
267                @NotNull TypeProjection subtypeArgumentProjection,
268                @NotNull TypeProjection supertypeArgumentProjection,
269                @NotNull TypeParameterDescriptor parameter
270        ) {
271            // Capturing makes sense only for invariant classes
272            if (parameter.getVariance() != INVARIANT) return false;
273    
274            // Now, both subtype and supertype relations transform to equality constraints on type arguments:
275            // Array<out Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(out Int)'
276            // Array<in Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(in Int)'
277    
278            if (subtypeArgumentProjection.getProjectionKind() != INVARIANT && supertypeArgumentProjection.getProjectionKind() == INVARIANT) {
279                return constraints.capture(supertypeArgumentProjection.getType(), subtypeArgumentProjection);
280            }
281            return false;
282        }
283    }