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 return CheckerPackage.findCorrespondingSupertype(subtype, supertype, typeCheckingProcedureCallbacks);
044 }
045
046 public static JetType getOutType(TypeParameterDescriptor parameter, TypeProjection argument) {
047 boolean isOutProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE;
048 return isOutProjected ? parameter.getUpperBoundsAsType() : argument.getType();
049 }
050
051 public static JetType getInType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) {
052 boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE;
053 return isOutProjected ? getBuiltIns(parameter).getNothingType() : argument.getType();
054 }
055
056 private final TypeCheckingProcedureCallbacks constraints;
057
058 public TypeCheckingProcedure(TypeCheckingProcedureCallbacks constraints) {
059 this.constraints = constraints;
060 }
061
062 public boolean equalTypes(@NotNull JetType type1, @NotNull JetType type2) {
063 if (type1 == type2) return true;
064 if (TypesPackage.isFlexible(type1)) {
065 if (TypesPackage.isFlexible(type2)) {
066 return !type1.isError() && !type2.isError() && isSubtypeOf(type1, type2) && isSubtypeOf(type2, type1);
067 }
068 return heterogeneousEquivalence(type2, type1);
069 }
070 else if (TypesPackage.isFlexible(type2)) {
071 return heterogeneousEquivalence(type1, type2);
072 }
073
074 if (type1.isMarkedNullable() != type2.isMarkedNullable()) {
075 return false;
076 }
077
078 if (type1.isMarkedNullable()) {
079 // Then type2 is nullable, too (see the previous condition
080 return constraints.assertEqualTypes(TypeUtils.makeNotNullable(type1), TypeUtils.makeNotNullable(type2), this);
081 }
082
083 TypeConstructor constructor1 = type1.getConstructor();
084 TypeConstructor constructor2 = type2.getConstructor();
085
086 if (!constraints.assertEqualTypeConstructors(constructor1, constructor2)) {
087 return false;
088 }
089
090 List<TypeProjection> type1Arguments = type1.getArguments();
091 List<TypeProjection> type2Arguments = type2.getArguments();
092 if (type1Arguments.size() != type2Arguments.size()) {
093 return false;
094 }
095
096 for (int i = 0; i < type1Arguments.size(); i++) {
097 TypeParameterDescriptor typeParameter1 = constructor1.getParameters().get(i);
098 TypeProjection typeProjection1 = type1Arguments.get(i);
099 TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i);
100 TypeProjection typeProjection2 = type2Arguments.get(i);
101 if (typeProjection1.isStarProjection() && typeProjection2.isStarProjection()) {
102 continue;
103 }
104
105 if (capture(typeProjection1, typeProjection2, typeParameter1)) {
106 continue;
107 }
108 if (getEffectiveProjectionKind(typeParameter1, typeProjection1) != getEffectiveProjectionKind(typeParameter2, typeProjection2)) {
109 return false;
110 }
111
112 if (!constraints.assertEqualTypes(typeProjection1.getType(), typeProjection2.getType(), this)) {
113 return false;
114 }
115 }
116 return true;
117 }
118
119 protected boolean heterogeneousEquivalence(JetType inflexibleType, JetType flexibleType) {
120 // This is to account for the case when we have Collection<X> vs (Mutable)Collection<X>! or K(java.util.Collection<? extends X>)
121 assert !TypesPackage.isFlexible(inflexibleType) : "Only inflexible types are allowed here: " + inflexibleType;
122 return isSubtypeOf(TypesPackage.flexibility(flexibleType).getLowerBound(), inflexibleType)
123 && isSubtypeOf(inflexibleType, TypesPackage.flexibility(flexibleType).getUpperBound());
124 }
125
126 public enum EnrichedProjectionKind {
127 IN, OUT, INV, STAR;
128
129 @NotNull
130 public static EnrichedProjectionKind fromVariance(@NotNull Variance variance) {
131 switch (variance) {
132 case INVARIANT:
133 return INV;
134 case IN_VARIANCE:
135 return IN;
136 case OUT_VARIANCE:
137 return OUT;
138 }
139 throw new IllegalStateException("Unknown variance");
140 }
141 }
142
143 // If class C<out T> then C<T> and C<out T> mean the same
144 // out * out = out
145 // out * in = *
146 // out * inv = out
147 //
148 // in * out = *
149 // in * in = in
150 // in * inv = in
151 //
152 // inv * out = out
153 // inv * in = out
154 // inv * inv = inv
155 public static EnrichedProjectionKind getEffectiveProjectionKind(
156 @NotNull TypeParameterDescriptor typeParameter,
157 @NotNull TypeProjection typeArgument
158 ) {
159 Variance a = typeParameter.getVariance();
160 Variance b = typeArgument.getProjectionKind();
161
162 // If they are not both invariant, let's make b not invariant for sure
163 if (b == INVARIANT) {
164 Variance t = a;
165 a = b;
166 b = t;
167 }
168
169 // Opposites yield STAR
170 if (a == IN_VARIANCE && b == OUT_VARIANCE) {
171 return EnrichedProjectionKind.STAR;
172 }
173 if (a == OUT_VARIANCE && b == IN_VARIANCE) {
174 return EnrichedProjectionKind.STAR;
175 }
176
177 // If they are not opposite, return b, because b is either equal to a or b is in/out and a is inv
178 return EnrichedProjectionKind.fromVariance(b);
179 }
180
181 public boolean isSubtypeOf(@NotNull JetType subtype, @NotNull JetType supertype) {
182 if (TypesPackage.sameTypeConstructors(subtype, supertype)) {
183 return !subtype.isMarkedNullable() || supertype.isMarkedNullable();
184 }
185 JetType subtypeRepresentative = TypesPackage.getSubtypeRepresentative(subtype);
186 JetType supertypeRepresentative = TypesPackage.getSupertypeRepresentative(supertype);
187 if (subtypeRepresentative != subtype || supertypeRepresentative != supertype) {
188 // recursive invocation for possible chain of representatives
189 return isSubtypeOf(subtypeRepresentative, supertypeRepresentative);
190 }
191 return isSubtypeOfForRepresentatives(subtype, supertype);
192 }
193
194 private boolean isSubtypeOfForRepresentatives(JetType subtype, JetType supertype) {
195 if (subtype.isError() || supertype.isError()) {
196 return true;
197 }
198
199 if (!supertype.isMarkedNullable() && subtype.isMarkedNullable()) {
200 return false;
201 }
202
203 if (KotlinBuiltIns.isNothingOrNullableNothing(subtype)) {
204 return true;
205 }
206
207 @Nullable JetType closestSupertype = findCorrespondingSupertype(subtype, supertype, constraints);
208 if (closestSupertype == null) {
209 return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with
210 }
211
212 if (!supertype.isMarkedNullable() && closestSupertype.isMarkedNullable()) {
213 return false;
214 }
215
216 return checkSubtypeForTheSameConstructor(closestSupertype, supertype);
217 }
218
219 private boolean checkSubtypeForTheSameConstructor(@NotNull JetType subtype, @NotNull JetType supertype) {
220 TypeConstructor constructor = subtype.getConstructor();
221 assert constraints.assertEqualTypeConstructors(constructor, supertype.getConstructor()) : constructor + " is not " + supertype.getConstructor();
222
223 List<TypeProjection> subArguments = subtype.getArguments();
224 List<TypeProjection> superArguments = supertype.getArguments();
225 if (subArguments.size() != superArguments.size()) return false;
226
227 List<TypeParameterDescriptor> parameters = constructor.getParameters();
228 for (int i = 0; i < parameters.size(); i++) {
229 TypeParameterDescriptor parameter = parameters.get(i);
230
231 TypeProjection superArgument = superArguments.get(i);
232 if (superArgument.isStarProjection()) continue;
233
234 JetType superIn = getInType(parameter, superArgument);
235 JetType superOut = getOutType(parameter, superArgument);
236
237 TypeProjection subArgument = subArguments.get(i);
238 JetType subIn = getInType(parameter, subArgument);
239 JetType subOut = getOutType(parameter, subArgument);
240
241 if (capture(subArgument, superArgument, parameter)) continue;
242
243 boolean argumentIsErrorType = subArgument.getType().isError() || superArgument.getType().isError();
244 if (!argumentIsErrorType && parameter.getVariance() == INVARIANT
245 && subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) {
246 if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false;
247 }
248 else {
249 if (!constraints.assertSubtype(subOut, superOut, this)) return false;
250 if (superArgument.getProjectionKind() != Variance.OUT_VARIANCE) {
251 if (!constraints.assertSubtype(superIn, subIn, this)) return false;
252 }
253 else {
254 assert KotlinBuiltIns.isNothing(superIn) : "In component must be Nothing for out-projection";
255 }
256 }
257 }
258 return true;
259 }
260
261 private boolean capture(
262 @NotNull TypeProjection firstProjection,
263 @NotNull TypeProjection secondProjection,
264 @NotNull TypeParameterDescriptor parameter
265 ) {
266 // Capturing makes sense only for invariant classes
267 if (parameter.getVariance() != INVARIANT) return false;
268
269 // Now, both subtype and supertype relations transform to equality constraints on type arguments:
270 // Array<T> is a subtype, supertype or equal to Array<out Int> then T captures a type that extends Int: 'Captured(out Int)'
271 // Array<T> is a subtype, supertype or equal to Array<in Int> then T captures a type that extends Int: 'Captured(in Int)'
272
273 if (firstProjection.getProjectionKind() == INVARIANT && secondProjection.getProjectionKind() != INVARIANT) {
274 return constraints.capture(firstProjection.getType(), secondProjection);
275 }
276 if (firstProjection.getProjectionKind() != INVARIANT && secondProjection.getProjectionKind() == INVARIANT) {
277 return constraints.capture(secondProjection.getType(), firstProjection);
278 }
279 return false;
280 }
281 }