/* * Copyright 2010-2017 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; import kotlin.Unit; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.ClassifierDescriptor; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.resolve.calls.inference.CallHandle; import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem; import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl; import org.jetbrains.kotlin.types.checker.KotlinTypeChecker; import java.util.*; import static org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.SPECIAL; import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt.getBuiltIns; public class TypeIntersector { public static boolean isIntersectionEmpty(@NotNull KotlinType typeA, @NotNull KotlinType typeB) { return intersectTypes(KotlinTypeChecker.DEFAULT, new LinkedHashSet<>(Arrays.asList(typeA, typeB))) == null; } @Nullable public static KotlinType intersectTypes(@NotNull KotlinTypeChecker typeChecker, @NotNull Collection<KotlinType> types) { assert !types.isEmpty() : "Attempting to intersect empty collection of types, this case should be dealt with on the call site."; if (types.size() == 1) { return types.iterator().next(); } // Intersection of T1..Tn is an intersection of their non-null versions, // made nullable is they all were nullable KotlinType nothingOrNullableNothing = null; boolean allNullable = true; List<KotlinType> nullabilityStripped = new ArrayList<>(types.size()); for (KotlinType type : types) { if (KotlinTypeKt.isError(type)) continue; if (KotlinBuiltIns.isNothingOrNullableNothing(type)) { nothingOrNullableNothing = type; } allNullable &= type.isMarkedNullable(); nullabilityStripped.add(TypeUtils.makeNotNullable(type)); } if (nothingOrNullableNothing != null) { return TypeUtils.makeNullableAsSpecified(nothingOrNullableNothing, allNullable); } if (nullabilityStripped.isEmpty()) { // All types were errors return ErrorUtils.createErrorType("Intersection of error types: " + types); } // Now we remove types that have subtypes in the list List<KotlinType> resultingTypes = new ArrayList<>(); outer: for (KotlinType type : nullabilityStripped) { if (!TypeUtils.canHaveSubtypes(typeChecker, type)) { boolean relativeToAll = true; for (KotlinType other : nullabilityStripped) { // It makes sense to check for subtyping (other <: type), despite that // type is not supposed to be open, for there're enums boolean mayBeEqual = TypeUnifier.mayBeEqual(type, other); boolean relative = typeChecker.isSubtypeOf(type, other) || typeChecker.isSubtypeOf(other, type); if (!mayBeEqual && !relative) { return null; } else if (!relative) { // To build T & (final A), instead of returning just A as intersection relativeToAll = false; break; } } if (relativeToAll) return TypeUtils.makeNullableAsSpecified(type, allNullable); } for (KotlinType other : nullabilityStripped) { if (!type.equals(other) && typeChecker.isSubtypeOf(other, type)) { continue outer; } } // Don't add type if it is already present, to avoid trivial type intersections in result for (KotlinType other : resultingTypes) { if (typeChecker.equalTypes(other, type)) { continue outer; } } resultingTypes.add(type); } if (resultingTypes.isEmpty()) { // If we ended up here, it means that all types from `nullabilityStripped` were excluded by the code above // most likely, this is because they are all semantically interchangeable (e.g. List<Foo>! and List<Foo>), // in that case, we can safely select the best representative out of that set and return it // TODO: maybe return the most specific among the types that are subtypes to all others in the `nullabilityStripped`? // TODO: e.g. among {Int, Int?, Int!}, return `Int` (now it returns `Int!`). KotlinType bestRepresentative = FlexibleTypesKt.singleBestRepresentative(nullabilityStripped); if (bestRepresentative == null) { bestRepresentative = UtilsKt.hackForTypeIntersector(nullabilityStripped); } if (bestRepresentative == null) { throw new AssertionError("Empty intersection for types " + types); } return TypeUtils.makeNullableAsSpecified(bestRepresentative, allNullable); } if (resultingTypes.size() == 1) { return TypeUtils.makeNullableAsSpecified(resultingTypes.get(0), allNullable); } IntersectionTypeConstructor constructor = new IntersectionTypeConstructor(resultingTypes); return KotlinTypeFactory.simpleType( Annotations.Companion.getEMPTY(), constructor, Collections.emptyList(), allNullable, constructor.createScopeForKotlinType() ); } /** * Note: this method was used in overload and override bindings to approximate type parameters with several bounds, * but as it turned out at some point, that logic was inconsistent with Java rules, so it was simplified. * Most of the other usages of this method are left untouched but probably should be investigated closely if they're still valid. */ @NotNull public static KotlinType getUpperBoundsAsType(@NotNull TypeParameterDescriptor descriptor) { List<KotlinType> upperBounds = descriptor.getUpperBounds(); assert !upperBounds.isEmpty() : "Upper bound list is empty: " + descriptor; KotlinType upperBoundsAsType = intersectTypes(KotlinTypeChecker.DEFAULT, upperBounds); return upperBoundsAsType != null ? upperBoundsAsType : getBuiltIns(descriptor).getNothingType(); } private static class TypeUnifier { private static class TypeParameterUsage { private final TypeParameterDescriptor typeParameterDescriptor; private final Variance howTheTypeParameterIsUsed; public TypeParameterUsage(TypeParameterDescriptor typeParameterDescriptor, Variance howTheTypeParameterIsUsed) { this.typeParameterDescriptor = typeParameterDescriptor; this.howTheTypeParameterIsUsed = howTheTypeParameterIsUsed; } } public static boolean mayBeEqual(@NotNull KotlinType type, @NotNull KotlinType other) { return unify(type, other); } private static boolean unify(KotlinType withParameters, KotlinType expected) { // T -> how T is used Map<TypeParameterDescriptor, Variance> parameters = new HashMap<>(); Function1<TypeParameterUsage, Unit> processor = parameterUsage -> { Variance howTheTypeIsUsedBefore = parameters.get(parameterUsage.typeParameterDescriptor); if (howTheTypeIsUsedBefore == null) { howTheTypeIsUsedBefore = Variance.INVARIANT; } parameters.put(parameterUsage.typeParameterDescriptor, parameterUsage.howTheTypeParameterIsUsed.superpose(howTheTypeIsUsedBefore)); return Unit.INSTANCE; }; processAllTypeParameters(withParameters, Variance.INVARIANT, processor, parameters::containsKey); processAllTypeParameters(expected, Variance.INVARIANT, processor, parameters::containsKey); ConstraintSystem.Builder constraintSystem = new ConstraintSystemBuilderImpl(); TypeSubstitutor substitutor = constraintSystem.registerTypeVariables(CallHandle.NONE.INSTANCE, parameters.keySet(), false); constraintSystem.addSubtypeConstraint(withParameters, substitutor.substitute(expected, Variance.INVARIANT), SPECIAL.position()); return constraintSystem.build().getStatus().isSuccessful(); } private static void processAllTypeParameters( KotlinType type, Variance howThisTypeIsUsed, Function1<TypeParameterUsage, Unit> result, Function1<TypeParameterDescriptor, Boolean> containsParameter ) { ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor(); if (descriptor instanceof TypeParameterDescriptor) { if (containsParameter.invoke((TypeParameterDescriptor) descriptor)) return; result.invoke(new TypeParameterUsage((TypeParameterDescriptor) descriptor, howThisTypeIsUsed)); for (KotlinType superType : type.getConstructor().getSupertypes()) { processAllTypeParameters(superType, howThisTypeIsUsed, result, containsParameter); } } for (TypeProjection projection : type.getArguments()) { if (projection.isStarProjection()) continue; processAllTypeParameters(projection.getType(), projection.getProjectionKind(), result, containsParameter); } } } }