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            TypeConstructor constructor = subtype.getConstructor();
036            if (constructor.equals(supertype.getConstructor())) {
037                return subtype;
038            }
039            for (JetType immediateSupertype : constructor.getSupertypes()) {
040                JetType correspondingSupertype = findCorrespondingSupertype(immediateSupertype, supertype);
041                if (correspondingSupertype != null) {
042                    return TypeSubstitutor.create(subtype).safeSubstitute(correspondingSupertype, Variance.INVARIANT);
043                }
044            }
045            return null;
046        }
047    
048        public static JetType getOutType(TypeParameterDescriptor parameter, TypeProjection argument) {
049            boolean isOutProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE;
050            return isOutProjected ? parameter.getUpperBoundsAsType() : argument.getType();
051        }
052    
053        public static JetType getInType(TypeParameterDescriptor parameter, TypeProjection argument) {
054            boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE;
055            return isOutProjected ? KotlinBuiltIns.getInstance().getNothingType() : argument.getType();
056        }
057    
058        private final TypingConstraints constraints;
059    
060        public TypeCheckingProcedure(TypingConstraints constraints) {
061            this.constraints = constraints;
062        }
063    
064        public boolean equalTypes(@NotNull JetType type1, @NotNull JetType type2) {
065            if (type1.isNullable() != type2.isNullable()) {
066                return false;
067            }
068    
069            if (type1.isNullable()) {
070                // Then type2 is nullable, too (see the previous condition
071                return constraints.assertEqualTypes(TypeUtils.makeNotNullable(type1), TypeUtils.makeNotNullable(type2), this);
072            }
073    
074            TypeConstructor constructor1 = type1.getConstructor();
075            TypeConstructor constructor2 = type2.getConstructor();
076    
077            if (!constraints.assertEqualTypeConstructors(constructor1, constructor2)) {
078                return false;
079            }
080    
081            List<TypeProjection> type1Arguments = type1.getArguments();
082            List<TypeProjection> type2Arguments = type2.getArguments();
083            if (type1Arguments.size() != type2Arguments.size()) {
084                return false;
085            }
086    
087            for (int i = 0; i < type1Arguments.size(); i++) {
088                TypeParameterDescriptor typeParameter1 = constructor1.getParameters().get(i);
089                TypeProjection typeProjection1 = type1Arguments.get(i);
090                TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i);
091                TypeProjection typeProjection2 = type2Arguments.get(i);
092                if (getEffectiveProjectionKind(typeParameter1, typeProjection1) != getEffectiveProjectionKind(typeParameter2, typeProjection2)) {
093                    return false;
094                }
095    
096                if (!constraints.assertEqualTypes(typeProjection1.getType(), typeProjection2.getType(), this)) {
097                    return false;
098                }
099            }
100            return true;
101        }
102    
103        public enum EnrichedProjectionKind {
104            IN, OUT, INV, STAR;
105    
106            @NotNull
107            public static EnrichedProjectionKind fromVariance(@NotNull Variance variance) {
108                switch (variance) {
109                    case INVARIANT:
110                        return INV;
111                    case IN_VARIANCE:
112                        return IN;
113                    case OUT_VARIANCE:
114                        return OUT;
115                }
116                throw new IllegalStateException("Unknown variance");
117            }
118        }
119    
120        // If class C<out T> then C<T> and C<out T> mean the same
121        // out * out = out
122        // out * in  = *
123        // out * inv = out
124        //
125        // in * out  = *
126        // in * in   = in
127        // in * inv  = in
128        //
129        // inv * out = out
130        // inv * in  = out
131        // inv * inv = inv
132        public static EnrichedProjectionKind getEffectiveProjectionKind(
133                @NotNull TypeParameterDescriptor typeParameter,
134                @NotNull TypeProjection typeArgument
135        ) {
136            Variance a = typeParameter.getVariance();
137            Variance b = typeArgument.getProjectionKind();
138    
139            // If they are not both invariant, let's make b not invariant for sure
140            if (b == INVARIANT) {
141                Variance t = a;
142                a = b;
143                b = t;
144            }
145    
146            // Opposites yield STAR
147            if (a == IN_VARIANCE && b == OUT_VARIANCE) {
148                return EnrichedProjectionKind.STAR;
149            }
150            if (a == OUT_VARIANCE && b == IN_VARIANCE) {
151                return EnrichedProjectionKind.STAR;
152            }
153    
154            // If they are not opposite, return b, because b is either equal to a or b is in/out and a is inv
155            return EnrichedProjectionKind.fromVariance(b);
156        }
157    
158        public boolean isSubtypeOf(@NotNull JetType subtype, @NotNull JetType supertype) {
159            if (ErrorUtils.isErrorType(subtype) || ErrorUtils.isErrorType(supertype)) {
160                return true;
161            }
162            if (!supertype.isNullable() && subtype.isNullable()) {
163                return false;
164            }
165            subtype = TypeUtils.makeNotNullable(subtype);
166            supertype = TypeUtils.makeNotNullable(supertype);
167            if (KotlinBuiltIns.getInstance().isNothingOrNullableNothing(subtype)) {
168                return true;
169            }
170            @Nullable JetType closestSupertype = findCorrespondingSupertype(subtype, supertype);
171            if (closestSupertype == null) {
172                return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with
173            }
174    
175            return checkSubtypeForTheSameConstructor(closestSupertype, supertype);
176        }
177    
178        private boolean checkSubtypeForTheSameConstructor(@NotNull JetType subtype, @NotNull JetType supertype) {
179            TypeConstructor constructor = subtype.getConstructor();
180            assert constructor.equals(supertype.getConstructor()) : constructor + " is not " + supertype.getConstructor();
181    
182            List<TypeProjection> subArguments = subtype.getArguments();
183            List<TypeProjection> superArguments = supertype.getArguments();
184            List<TypeParameterDescriptor> parameters = constructor.getParameters();
185            for (int i = 0; i < parameters.size(); i++) {
186                TypeParameterDescriptor parameter = parameters.get(i);
187    
188    
189                TypeProjection subArgument = subArguments.get(i);
190                JetType subIn = getInType(parameter, subArgument);
191                JetType subOut = getOutType(parameter, subArgument);
192    
193                TypeProjection superArgument = superArguments.get(i);
194                JetType superIn = getInType(parameter, superArgument);
195                JetType superOut = getOutType(parameter, superArgument);
196    
197                boolean argumentIsErrorType = ErrorUtils.isErrorType(subArgument.getType()) || ErrorUtils.isErrorType(superArgument.getType());
198                if (!argumentIsErrorType && parameter.getVariance() == INVARIANT
199                        && subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) {
200                    if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false;
201                }
202                else {
203                    if (!constraints.assertSubtype(subOut, superOut, this)) return false;
204                    if (!constraints.assertSubtype(superIn, subIn, this)) return false;
205                }
206            }
207            return true;
208        }
209    }