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 }