/* * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.types.checker; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.types.*; import java.util.List; import static org.jetbrains.kotlin.types.Variance.*; public class TypeCheckingProcedure { // This method returns the supertype of the first parameter that has the same constructor // as the second parameter, applying the substitution of type arguments to it @Nullable public static KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType supertype) { return findCorrespondingSupertype(subtype, supertype, new TypeCheckerProcedureCallbacksImpl()); } // This method returns the supertype of the first parameter that has the same constructor // as the second parameter, applying the substitution of type arguments to it @Nullable public static KotlinType findCorrespondingSupertype(@NotNull KotlinType subtype, @NotNull KotlinType supertype, @NotNull TypeCheckingProcedureCallbacks typeCheckingProcedureCallbacks) { return UtilsKt.findCorrespondingSupertype(subtype, supertype, typeCheckingProcedureCallbacks); } @NotNull private static KotlinType getOutType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) { boolean isInProjected = argument.getProjectionKind() == IN_VARIANCE || parameter.getVariance() == IN_VARIANCE; return isInProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNullableAnyType() : argument.getType(); } @NotNull private static KotlinType getInType(@NotNull TypeParameterDescriptor parameter, @NotNull TypeProjection argument) { boolean isOutProjected = argument.getProjectionKind() == OUT_VARIANCE || parameter.getVariance() == OUT_VARIANCE; return isOutProjected ? DescriptorUtilsKt.getBuiltIns(parameter).getNothingType() : argument.getType(); } private final TypeCheckingProcedureCallbacks constraints; public TypeCheckingProcedure(TypeCheckingProcedureCallbacks constraints) { this.constraints = constraints; } public boolean equalTypes(@NotNull KotlinType type1, @NotNull KotlinType type2) { if (type1 == type2) return true; if (FlexibleTypesKt.isFlexible(type1)) { if (FlexibleTypesKt.isFlexible(type2)) { return !KotlinTypeKt.isError(type1) && !KotlinTypeKt.isError(type2) && isSubtypeOf(type1, type2) && isSubtypeOf(type2, type1); } return heterogeneousEquivalence(type2, type1); } else if (FlexibleTypesKt.isFlexible(type2)) { return heterogeneousEquivalence(type1, type2); } if (type1.isMarkedNullable() != type2.isMarkedNullable()) { return false; } if (type1.isMarkedNullable()) { // Then type2 is nullable, too (see the previous condition return constraints.assertEqualTypes(TypeUtils.makeNotNullable(type1), TypeUtils.makeNotNullable(type2), this); } TypeConstructor constructor1 = type1.getConstructor(); TypeConstructor constructor2 = type2.getConstructor(); if (!constraints.assertEqualTypeConstructors(constructor1, constructor2)) { return false; } List<TypeProjection> type1Arguments = type1.getArguments(); List<TypeProjection> type2Arguments = type2.getArguments(); if (type1Arguments.size() != type2Arguments.size()) { return false; } for (int i = 0; i < type1Arguments.size(); i++) { TypeProjection typeProjection1 = type1Arguments.get(i); TypeProjection typeProjection2 = type2Arguments.get(i); if (typeProjection1.isStarProjection() && typeProjection2.isStarProjection()) { continue; } TypeParameterDescriptor typeParameter1 = constructor1.getParameters().get(i); TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i); if (capture(typeProjection1, typeProjection2, typeParameter1)) { continue; } if (getEffectiveProjectionKind(typeParameter1, typeProjection1) != getEffectiveProjectionKind(typeParameter2, typeProjection2)) { return false; } if (!constraints.assertEqualTypes(typeProjection1.getType(), typeProjection2.getType(), this)) { return false; } } return true; } protected boolean heterogeneousEquivalence(KotlinType inflexibleType, KotlinType flexibleType) { // This is to account for the case when we have Collection<X> vs (Mutable)Collection<X>! or K(java.util.Collection<? extends X>) assert !FlexibleTypesKt.isFlexible(inflexibleType) : "Only inflexible types are allowed here: " + inflexibleType; return isSubtypeOf(FlexibleTypesKt.asFlexibleType(flexibleType).getLowerBound(), inflexibleType) && isSubtypeOf(inflexibleType, FlexibleTypesKt.asFlexibleType(flexibleType).getUpperBound()); } public enum EnrichedProjectionKind { IN, OUT, INV, STAR; @NotNull public static EnrichedProjectionKind fromVariance(@NotNull Variance variance) { switch (variance) { case INVARIANT: return INV; case IN_VARIANCE: return IN; case OUT_VARIANCE: return OUT; } throw new IllegalStateException("Unknown variance"); } } // If class C<out T> then C<T> and C<out T> mean the same // out * out = out // out * in = * // out * inv = out // // in * out = * // in * in = in // in * inv = in // // inv * out = out // inv * in = out // inv * inv = inv public static EnrichedProjectionKind getEffectiveProjectionKind( @NotNull TypeParameterDescriptor typeParameter, @NotNull TypeProjection typeArgument ) { Variance a = typeParameter.getVariance(); Variance b = typeArgument.getProjectionKind(); // If they are not both invariant, let's make b not invariant for sure if (b == INVARIANT) { Variance t = a; a = b; b = t; } // Opposites yield STAR if (a == IN_VARIANCE && b == OUT_VARIANCE) { return EnrichedProjectionKind.STAR; } if (a == OUT_VARIANCE && b == IN_VARIANCE) { return EnrichedProjectionKind.STAR; } // If they are not opposite, return b, because b is either equal to a or b is in/out and a is inv return EnrichedProjectionKind.fromVariance(b); } public boolean isSubtypeOf(@NotNull KotlinType subtype, @NotNull KotlinType supertype) { if (TypeCapabilitiesKt.sameTypeConstructors(subtype, supertype)) { return !subtype.isMarkedNullable() || supertype.isMarkedNullable(); } KotlinType subtypeRepresentative = TypeCapabilitiesKt.getSubtypeRepresentative(subtype); KotlinType supertypeRepresentative = TypeCapabilitiesKt.getSupertypeRepresentative(supertype); if (subtypeRepresentative != subtype || supertypeRepresentative != supertype) { // recursive invocation for possible chain of representatives return isSubtypeOf(subtypeRepresentative, supertypeRepresentative); } return isSubtypeOfForRepresentatives(subtype, supertype); } private boolean isSubtypeOfForRepresentatives(KotlinType subtype, KotlinType supertype) { if (KotlinTypeKt.isError(subtype) || KotlinTypeKt.isError(supertype)) { return true; } if (!supertype.isMarkedNullable() && subtype.isMarkedNullable()) { return false; } if (KotlinBuiltIns.isNothingOrNullableNothing(subtype)) { return true; } @Nullable KotlinType closestSupertype = findCorrespondingSupertype(subtype, supertype, constraints); if (closestSupertype == null) { return constraints.noCorrespondingSupertype(subtype, supertype); // if this returns true, there still isn't any supertype to continue with } if (!supertype.isMarkedNullable() && closestSupertype.isMarkedNullable()) { return false; } return checkSubtypeForTheSameConstructor(closestSupertype, supertype); } private boolean checkSubtypeForTheSameConstructor(@NotNull KotlinType subtype, @NotNull KotlinType supertype) { TypeConstructor constructor = subtype.getConstructor(); // this assert was moved to checker/utils.kt //assert constraints.assertEqualTypeConstructors(constructor, supertype.getConstructor()) : constructor + " is not " + supertype.getConstructor(); List<TypeProjection> subArguments = subtype.getArguments(); List<TypeProjection> superArguments = supertype.getArguments(); if (subArguments.size() != superArguments.size()) return false; List<TypeParameterDescriptor> parameters = constructor.getParameters(); for (int i = 0; i < parameters.size(); i++) { TypeParameterDescriptor parameter = parameters.get(i); TypeProjection superArgument = superArguments.get(i); TypeProjection subArgument = subArguments.get(i); if (superArgument.isStarProjection()) continue; if (capture(subArgument, superArgument, parameter)) continue; boolean argumentIsErrorType = KotlinTypeKt.isError(subArgument.getType()) || KotlinTypeKt.isError(superArgument.getType()); if (!argumentIsErrorType && parameter.getVariance() == INVARIANT && subArgument.getProjectionKind() == INVARIANT && superArgument.getProjectionKind() == INVARIANT) { if (!constraints.assertEqualTypes(subArgument.getType(), superArgument.getType(), this)) return false; continue; } KotlinType superOut = getOutType(parameter, superArgument); KotlinType subOut = getOutType(parameter, subArgument); if (!constraints.assertSubtype(subOut, superOut, this)) return false; KotlinType superIn = getInType(parameter, superArgument); KotlinType subIn = getInType(parameter, subArgument); if (superArgument.getProjectionKind() != Variance.OUT_VARIANCE) { if (!constraints.assertSubtype(superIn, subIn, this)) return false; } else { assert KotlinBuiltIns.isNothing(superIn) : "In component must be Nothing for out-projection"; } } return true; } private boolean capture( @NotNull TypeProjection subtypeArgumentProjection, @NotNull TypeProjection supertypeArgumentProjection, @NotNull TypeParameterDescriptor parameter ) { // Capturing makes sense only for invariant classes if (parameter.getVariance() != INVARIANT) return false; // Now, both subtype and supertype relations transform to equality constraints on type arguments: // Array<out Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(out Int)' // Array<in Int> is a subtype or equal to Array<T> then T captures a type that extends Int: 'Captured(in Int)' if (subtypeArgumentProjection.getProjectionKind() != INVARIANT && supertypeArgumentProjection.getProjectionKind() == INVARIANT) { return constraints.capture(supertypeArgumentProjection.getType(), subtypeArgumentProjection); } return false; } }