001 /*
002 * Copyright 2010-2016 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.descriptorUtil.DescriptorUtilsKt;
024 import org.jetbrains.kotlin.types.*;
025
026 import java.util.List;
027
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 KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType 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 KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType supertype, @NotNull TypeCheckingProcedureCallbacks typeCheckingProcedureCallbacks) {
043 return UtilsKt.findCorrespondingSupertype(subtype, supertype, typeCheckingProcedureCallbacks);
044 }
045
046 @NotNull
047 private static KotlinType getOutType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) {
048 boolean isInProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE;
049 return isInProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNullableAnyType() : argument.getType();
050 }
051
052 @NotNull
053 private static KotlinType getInType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) {
054 boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE;
055 return isOutProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNothingType() : argument.getType();
056 }
057
058 private final TypeCheckingProcedureCallbacks constraints;
059
060 public TypeCheckingProcedure(TypeCheckingProcedureCallbacks constraints) {
061 this.constraints = constraints;
062 }
063
064 public boolean equalTypes(@NotNull KotlinType type1, @NotNull KotlinType type2) {
065 if (type1 == type2) return true;
066 if (FlexibleTypesKt.isFlexible(type1)) {
067 if (FlexibleTypesKt.isFlexible(type2)) {
068 return !type1.isError() && !type2.isError() && isSubtypeOf(type1, type2) && isSubtypeOf(type2, type1);
069 }
070 return heterogeneousEquivalence(type2, type1);
071 }
072 else if (FlexibleTypesKt.isFlexible(type2)) {
073 return heterogeneousEquivalence(type1, type2);
074 }
075
076 if (type1.isMarkedNullable() != type2.isMarkedNullable()) {
077 return false;
078 }
079
080 if (type1.isMarkedNullable()) {
081 // Then type2 is nullable, too (see the previous condition
082 return constraints.assertEqualTypes(TypeUtils.makeNotNullable(type1), TypeUtils.makeNotNullable(type2), this);
083 }
084
085 TypeConstructor constructor1 = type1.getConstructor();
086 TypeConstructor constructor2 = type2.getConstructor();
087
088 if (!constraints.assertEqualTypeConstructors(constructor1, constructor2)) {
089 return false;
090 }
091
092 List<TypeProjection> type1Arguments = type1.getArguments();
093 List<TypeProjection> type2Arguments = type2.getArguments();
094 if (type1Arguments.size() != type2Arguments.size()) {
095 return false;
096 }
097
098 for (int i = 0; i < type1Arguments.size(); i++) {
099 TypeProjection typeProjection1 = type1Arguments.get(i);
100 TypeProjection typeProjection2 = type2Arguments.get(i);
101 if (typeProjection1.isStarProjection() && typeProjection2.isStarProjection()) {
102 continue;
103 }
104 TypeParameterDescriptor typeParameter1 = constructor1.getParameters().get(i);
105 TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i);
106
107 if (capture(typeProjection1, typeProjection2, typeParameter1)) {
108 continue;
109 }
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 protected boolean heterogeneousEquivalence(KotlinType inflexibleType, KotlinType 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 !FlexibleTypesKt.isFlexible(inflexibleType) : "Only inflexible types are allowed here: " + inflexibleType;
124 return isSubtypeOf(FlexibleTypesKt.asFlexibleType(flexibleType).getLowerBound(), inflexibleType)
125 && isSubtypeOf(inflexibleType, FlexibleTypesKt.asFlexibleType(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 KotlinType subtype, @NotNull KotlinType supertype) {
184 if (TypeCapabilitiesKt.sameTypeConstructors(subtype, supertype)) {
185 return !subtype.isMarkedNullable() || supertype.isMarkedNullable();
186 }
187 KotlinType subtypeRepresentative = TypeCapabilitiesKt.getSubtypeRepresentative(subtype);
188 KotlinType supertypeRepresentative = TypeCapabilitiesKt.getSupertypeRepresentative(supertype);
189 if (subtypeRepresentative != subtype || supertypeRepresentative != supertype) {
190 // recursive invocation for possible chain of representatives
191 return isSubtypeOf(subtypeRepresentative, supertypeRepresentative);
192 }
193 return isSubtypeOfForRepresentatives(subtype, supertype);
194 }
195
196 private boolean isSubtypeOfForRepresentatives(KotlinType subtype, KotlinType supertype) {
197 if (subtype.isError() || supertype.isError()) {
198 return true;
199 }
200
201 if (!supertype.isMarkedNullable() && subtype.isMarkedNullable()) {
202 return false;
203 }
204
205 if (KotlinBuiltIns.isNothingOrNullableNothing(subtype)) {
206 return true;
207 }
208
209 @Nullable KotlinType closestSupertype = findCorrespondingSupertype(subtype, supertype, constraints);
210 if (closestSupertype == null) {
211 return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with
212 }
213
214 if (!supertype.isMarkedNullable() && closestSupertype.isMarkedNullable()) {
215 return false;
216 }
217
218 return checkSubtypeForTheSameConstructor(closestSupertype, supertype);
219 }
220
221 private boolean checkSubtypeForTheSameConstructor(@NotNull KotlinType subtype, @NotNull KotlinType supertype) {
222 TypeConstructor constructor = subtype.getConstructor();
223
224 // this assert was moved to checker/utils.kt
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 TypeProjection subArgument = subArguments.get(i);
237
238 if (superArgument.isStarProjection()) continue;
239
240 if (capture(subArgument, superArgument, parameter)) continue;
241
242 boolean argumentIsErrorType = subArgument.getType().isError() || superArgument.getType().isError();
243 if (!argumentIsErrorType && parameter.getVariance() == INVARIANT &&
244 subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) {
245 if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false;
246 continue;
247 }
248
249 KotlinType superOut = getOutType(parameter, superArgument);
250 KotlinType subOut = getOutType(parameter, subArgument);
251 if (!constraints.assertSubtype(subOut, superOut, this)) return false;
252
253 KotlinType superIn = getInType(parameter, superArgument);
254 KotlinType subIn = getInType(parameter, subArgument);
255
256 if (superArgument.getProjectionKind() != Variance.OUT_VARIANCE) {
257 if (!constraints.assertSubtype(superIn, subIn, this)) return false;
258 }
259 else {
260 assert KotlinBuiltIns.isNothing(superIn) : "In component must be Nothing for out-projection";
261 }
262 }
263 return true;
264 }
265
266 private boolean capture(
267 @NotNull TypeProjection subtypeArgumentProjection,
268 @NotNull TypeProjection supertypeArgumentProjection,
269 @NotNull TypeParameterDescriptor parameter
270 ) {
271 // Capturing makes sense only for invariant classes
272 if (parameter.getVariance() != INVARIANT) return false;
273
274 // Now, both subtype and supertype relations transform to equality constraints on type arguments:
275 // Array<out Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(out Int)'
276 // Array<in Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(in Int)'
277
278 if (subtypeArgumentProjection.getProjectionKind() != INVARIANT && supertypeArgumentProjection.getProjectionKind() == INVARIANT) {
279 return constraints.capture(supertypeArgumentProjection.getType(), subtypeArgumentProjection);
280 }
281 return false;
282 }
283 }