/*
* 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);
}
}
}
}