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