001    /*
002     * Copyright 2010-2013 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.jet.lang.types.checker;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
022    import org.jetbrains.jet.lang.types.*;
023    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
024    
025    import java.util.List;
026    
027    import static org.jetbrains.jet.lang.types.Variance.*;
028    
029    public class TypeCheckingProcedure {
030    
031        // This method returns the supertype of the first parameter that has the same constructor
032        // as the second parameter, applying the substitution of type arguments to it
033        @Nullable
034        public static JetType findCorrespondingSupertype(@NotNull JetType subtype, @NotNull JetType supertype) {
035            return findCorrespondingSupertype(subtype, supertype, new TypeCheckerTypingConstraints());
036        }
037    
038        // This method returns the supertype of the first parameter that has the same constructor
039        // as the second parameter, applying the substitution of type arguments to it
040        @Nullable
041        public static JetType findCorrespondingSupertype(@NotNull JetType subtype, @NotNull JetType supertype, @NotNull TypingConstraints typingConstraints) {
042            TypeConstructor constructor = subtype.getConstructor();
043            if (typingConstraints.assertEqualTypeConstructors(constructor, supertype.getConstructor())) {
044                return subtype;
045            }
046            for (JetType immediateSupertype : constructor.getSupertypes()) {
047                JetType correspondingSupertype = findCorrespondingSupertype(immediateSupertype, supertype, typingConstraints);
048                if (correspondingSupertype != null) {
049                    return TypeSubstitutor.create(subtype).safeSubstitute(correspondingSupertype, Variance.INVARIANT);
050                }
051            }
052            return null;
053        }
054    
055        public static JetType getOutType(TypeParameterDescriptor parameter, TypeProjection argument) {
056            boolean isOutProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE;
057            return isOutProjected ? parameter.getUpperBoundsAsType() : argument.getType();
058        }
059    
060        public static JetType getInType(TypeParameterDescriptor parameter, TypeProjection argument) {
061            boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE;
062            return isOutProjected ? KotlinBuiltIns.getInstance().getNothingType() : argument.getType();
063        }
064    
065        private final TypingConstraints constraints;
066    
067        public TypeCheckingProcedure(TypingConstraints constraints) {
068            this.constraints = constraints;
069        }
070    
071        public boolean equalTypes(@NotNull JetType type1, @NotNull JetType type2) {
072            if (TypesPackage.isFlexible(type1)) {
073                if (TypesPackage.isFlexible(type2)) {
074                    return equalTypes(TypesPackage.flexibility(type1).getLowerBound(), TypesPackage.flexibility(type2).getLowerBound())
075                            && equalTypes(TypesPackage.flexibility(type1).getUpperBound(), TypesPackage.flexibility(type2).getUpperBound());
076                }
077                return heterogeneousEquivalence(type2, type1);
078            }
079            else if (TypesPackage.isFlexible(type2)) {
080                return heterogeneousEquivalence(type1, type2);
081            }
082    
083            if (type1.isNullable() != type2.isNullable()) {
084                return false;
085            }
086    
087            if (type1.isNullable()) {
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 (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        private boolean heterogeneousEquivalence(JetType inflexibleType, JetType 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 !TypesPackage.isFlexible(inflexibleType) : "Only inflexible types are allowed here: " + inflexibleType;
124            return isSubtypeOf(TypesPackage.flexibility(flexibleType).getLowerBound(), inflexibleType)
125                   && isSubtypeOf(inflexibleType, TypesPackage.flexibility(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 JetType subtype, @NotNull JetType supertype) {
184            if (TypesPackage.isFlexible(subtype)) {
185                return isSubtypeOf(TypesPackage.flexibility(subtype).getLowerBound(), supertype);
186            }
187            if (TypesPackage.isFlexible(supertype)) {
188                return isSubtypeOf(subtype, TypesPackage.flexibility(supertype).getUpperBound());
189            }
190            if (subtype.isError() || supertype.isError()) {
191                return true;
192            }
193            if (!supertype.isNullable() && subtype.isNullable()) {
194                return false;
195            }
196            subtype = TypeUtils.makeNotNullable(subtype);
197            supertype = TypeUtils.makeNotNullable(supertype);
198            if (KotlinBuiltIns.getInstance().isNothingOrNullableNothing(subtype)) {
199                return true;
200            }
201            @Nullable JetType closestSupertype = findCorrespondingSupertype(subtype, supertype, constraints);
202            if (closestSupertype == null) {
203                return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with
204            }
205    
206            return checkSubtypeForTheSameConstructor(closestSupertype, supertype);
207        }
208    
209        private boolean checkSubtypeForTheSameConstructor(@NotNull JetType subtype, @NotNull JetType supertype) {
210            TypeConstructor constructor = subtype.getConstructor();
211            assert constraints.assertEqualTypeConstructors(constructor, supertype.getConstructor()) : constructor + " is not " + supertype.getConstructor();
212    
213            List<TypeProjection> subArguments = subtype.getArguments();
214            List<TypeProjection> superArguments = supertype.getArguments();
215            if (subArguments.size() != superArguments.size()) return false;
216    
217            List<TypeParameterDescriptor> parameters = constructor.getParameters();
218            for (int i = 0; i < parameters.size(); i++) {
219                TypeParameterDescriptor parameter = parameters.get(i);
220    
221                TypeProjection subArgument = subArguments.get(i);
222                JetType subIn = getInType(parameter, subArgument);
223                JetType subOut = getOutType(parameter, subArgument);
224    
225                TypeProjection superArgument = superArguments.get(i);
226                JetType superIn = getInType(parameter, superArgument);
227                JetType superOut = getOutType(parameter, superArgument);
228    
229                boolean argumentIsErrorType = subArgument.getType().isError() || superArgument.getType().isError();
230                if (!argumentIsErrorType && parameter.getVariance() == INVARIANT
231                        && subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) {
232                    if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false;
233                }
234                else {
235                    if (!constraints.assertSubtype(subOut, superOut, this)) return false;
236                    if (!constraints.assertSubtype(superIn, subIn, this)) return false;
237                }
238            }
239            return true;
240        }
241    }