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
017package org.jetbrains.jet.lang.types.checker;
018
019import org.jetbrains.annotations.NotNull;
020import org.jetbrains.annotations.Nullable;
021import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
022import org.jetbrains.jet.lang.types.*;
023import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
024
025import java.util.List;
026
027import static org.jetbrains.jet.lang.types.Variance.*;
028
029public 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}