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