TypeSubstitutor rewritten to be more readable
This commit is contained in:
@@ -16,14 +16,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.jet.lang.types;
|
package org.jetbrains.jet.lang.types;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Lists;
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
|
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
|
||||||
import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope;
|
import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope;
|
||||||
|
import org.jetbrains.jet.lang.types.lang.JetStandardClasses;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author abreslav
|
* @author abreslav
|
||||||
@@ -32,34 +34,6 @@ public class TypeSubstitutor {
|
|||||||
|
|
||||||
private static final int MAX_RECURSION_DEPTH = 100;
|
private static final int MAX_RECURSION_DEPTH = 100;
|
||||||
|
|
||||||
public static TypeSubstitutor makeConstantSubstitutor(Collection<TypeParameterDescriptor> typeParameterDescriptors, JetType type) {
|
|
||||||
final Set<TypeConstructor> constructors = Sets.newHashSet();
|
|
||||||
for (TypeParameterDescriptor typeParameterDescriptor : typeParameterDescriptors) {
|
|
||||||
constructors.add(typeParameterDescriptor.getTypeConstructor());
|
|
||||||
}
|
|
||||||
final TypeProjection projection = new TypeProjection(type);
|
|
||||||
|
|
||||||
return TypeSubstitutor.create(new TypeSubstitution() {
|
|
||||||
@Override
|
|
||||||
public TypeProjection get(TypeConstructor key) {
|
|
||||||
if (constructors.contains(key)) {
|
|
||||||
return projection;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "TypeConstructor.makeConstantSubstitutor(" + constructors + " -> " + projection + ")";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MapToTypeSubstitutionAdapter implements TypeSubstitution {
|
public static class MapToTypeSubstitutionAdapter implements TypeSubstitution {
|
||||||
private final @NotNull Map<TypeConstructor, TypeProjection> substitutionContext;
|
private final @NotNull Map<TypeConstructor, TypeProjection> substitutionContext;
|
||||||
|
|
||||||
@@ -85,7 +59,7 @@ public class TypeSubstitutor {
|
|||||||
|
|
||||||
public static final TypeSubstitutor EMPTY = create(TypeSubstitution.EMPTY);
|
public static final TypeSubstitutor EMPTY = create(TypeSubstitution.EMPTY);
|
||||||
|
|
||||||
public static final class SubstitutionException extends Exception {
|
private static final class SubstitutionException extends Exception {
|
||||||
public SubstitutionException(String message) {
|
public SubstitutionException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
@@ -143,7 +117,7 @@ public class TypeSubstitutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return unsafeSubstitute(type, howThisTypeIsUsed);
|
return unsafeSubstitute(new TypeProjection(howThisTypeIsUsed, type), 0).getType();
|
||||||
} catch (SubstitutionException e) {
|
} catch (SubstitutionException e) {
|
||||||
return ErrorUtils.createErrorType(e.getMessage());
|
return ErrorUtils.createErrorType(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -156,170 +130,100 @@ public class TypeSubstitutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return unsafeSubstitute(type, howThisTypeIsUsed);
|
return unsafeSubstitute(new TypeProjection(howThisTypeIsUsed, type), 0).getType();
|
||||||
} catch (SubstitutionException e) {
|
} catch (SubstitutionException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private JetType unsafeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) throws SubstitutionException {
|
private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
|
||||||
if (ErrorUtils.isErrorType(type)) return type;
|
assertRecursionDepth(recursionDepth, originalProjection, substitution);
|
||||||
|
// The type is within the substitution range, i.e. T or T?
|
||||||
|
JetType type = originalProjection.getType();
|
||||||
|
if (JetStandardClasses.isNothing(type) || ErrorUtils.isErrorType(type)) return originalProjection;
|
||||||
|
|
||||||
TypeProjection value = getValueWithCorrectNullability(substitution, type);
|
TypeProjection replacement = substitution.get(type.getConstructor());
|
||||||
if (value != null) {
|
|
||||||
TypeConstructor constructor = type.getConstructor();
|
|
||||||
assert constructor.getDeclarationDescriptor() instanceof TypeParameterDescriptor;
|
|
||||||
|
|
||||||
TypeParameterDescriptor typeParameterDescriptor = (TypeParameterDescriptor) constructor.getDeclarationDescriptor();
|
if (replacement != null) {
|
||||||
|
// It must be a type parameter: only they can be directly substituted for
|
||||||
|
TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
|
||||||
|
|
||||||
TypeProjection result = substitutionResult(typeParameterDescriptor, howThisTypeIsUsed, Variance.INVARIANT, value, 0);
|
switch (conflictType(originalProjection.getProjectionKind(), replacement.getProjectionKind())) {
|
||||||
|
case OUT_IN_IN_POSITION:
|
||||||
|
throw new SubstitutionException("Out-projection in in-position");
|
||||||
|
case IN_IN_OUT_POSITION:
|
||||||
|
replacement = SubstitutionUtils.makeStarProjection(typeParameter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
boolean resultingIsNullable = type.isNullable() || replacement.getType().isNullable();
|
||||||
|
JetType substitutedType = TypeUtils.makeNullableAsSpecified(replacement.getType(), resultingIsNullable);
|
||||||
|
Variance resultingProjectionKind = combine(originalProjection.getProjectionKind(), replacement.getProjectionKind());
|
||||||
|
|
||||||
return TypeUtils.makeNullableIfNeeded(result.getType(), type.isNullable());
|
return new TypeProjection(resultingProjectionKind, substitutedType);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// The type is not within the substitution range, i.e. Foo, Bar<T> etc.
|
||||||
|
List<TypeProjection> substitutedArguments = substituteTypeArguments(
|
||||||
|
type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
|
||||||
|
|
||||||
return specializeType(type, howThisTypeIsUsed, 0);
|
JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable
|
||||||
}
|
type.getConstructor(), // The same constructor
|
||||||
|
type.isNullable(), // Same nullability
|
||||||
private TypeProjection getValueWithCorrectNullability(TypeSubstitution substitution, JetType type) {
|
substitutedArguments,
|
||||||
TypeProjection typeProjection = substitution.get(type.getConstructor());
|
new SubstitutingScope(type.getMemberScope(), this));
|
||||||
if (typeProjection == null) return null;
|
return new TypeProjection(originalProjection.getProjectionKind(), substitutedType);
|
||||||
|
|
||||||
return type.isNullable() ? makeNullableProjection(typeProjection) : typeProjection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static TypeProjection makeNullableProjection(@NotNull TypeProjection value) {
|
|
||||||
return new TypeProjection(value.getProjectionKind(), TypeUtils.makeNullable(value.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private JetType specializeType(JetType subjectType, Variance callSiteVariance, int recursionDepth) throws SubstitutionException {
|
|
||||||
assertRecursionDepth(recursionDepth, subjectType, substitution);
|
|
||||||
if (ErrorUtils.isErrorType(subjectType)) return subjectType;
|
|
||||||
|
|
||||||
List<TypeProjection> newArguments = new ArrayList<TypeProjection>();
|
|
||||||
List<TypeProjection> arguments = subjectType.getArguments();
|
|
||||||
for (int i = 0, argumentsSize = arguments.size(); i < argumentsSize; i++) {
|
|
||||||
TypeProjection argument = arguments.get(i);
|
|
||||||
TypeParameterDescriptor parameterDescriptor = subjectType.getConstructor().getParameters().get(i);
|
|
||||||
newArguments.add(substituteInProjection(
|
|
||||||
substitution,
|
|
||||||
argument,
|
|
||||||
parameterDescriptor,
|
|
||||||
callSiteVariance, recursionDepth + 1));
|
|
||||||
}
|
}
|
||||||
return new JetTypeImpl(
|
|
||||||
subjectType.getAnnotations(),
|
|
||||||
subjectType.getConstructor(),
|
|
||||||
subjectType.isNullable(),
|
|
||||||
newArguments,
|
|
||||||
new SubstitutingScope(subjectType.getMemberScope(), this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private List<TypeProjection> substituteTypeArguments(List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth)
|
||||||
private TypeProjection substituteInProjection(
|
throws SubstitutionException {
|
||||||
@NotNull TypeSubstitution substitutionContext,
|
List<TypeProjection> substitutedArguments = Lists.newArrayList();
|
||||||
@NotNull TypeProjection passedProjection,
|
for (int i = 0; i < typeParameters.size(); i++) {
|
||||||
@NotNull TypeParameterDescriptor correspondingTypeParameter,
|
TypeParameterDescriptor typeParameter = typeParameters.get(i);
|
||||||
@NotNull Variance contextCallSiteVariance,
|
TypeProjection typeArgument = typeArguments.get(i);
|
||||||
int recursionDepth) throws SubstitutionException {
|
|
||||||
assertRecursionDepth(recursionDepth, correspondingTypeParameter, passedProjection, substitution);
|
|
||||||
|
|
||||||
JetType typeToSubstituteIn = passedProjection.getType();
|
TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
|
||||||
if (ErrorUtils.isErrorType(typeToSubstituteIn)) return passedProjection;
|
|
||||||
|
|
||||||
Variance passedProjectionKind = passedProjection.getProjectionKind();
|
switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
|
||||||
Variance parameterVariance = correspondingTypeParameter.getVariance();
|
case OUT_IN_IN_POSITION:
|
||||||
|
substitutedTypeArgument = new TypeProjection(Variance.IN_VARIANCE, typeParameter.getLowerBoundsAsType());
|
||||||
Variance effectiveProjectionKind = asymmetricOr(passedProjectionKind, parameterVariance);
|
break;
|
||||||
Variance effectiveContextVariance = contextCallSiteVariance.superpose(effectiveProjectionKind);
|
case IN_IN_OUT_POSITION:
|
||||||
|
substitutedTypeArgument = SubstitutionUtils.makeStarProjection(typeParameter);
|
||||||
TypeProjection projectionValue = getValueWithCorrectNullability(substitutionContext, typeToSubstituteIn);
|
break;
|
||||||
if (projectionValue != null) {
|
|
||||||
assert typeToSubstituteIn.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor;
|
|
||||||
|
|
||||||
if (!allows(parameterVariance, passedProjectionKind)) {
|
|
||||||
return SubstitutionUtils.makeStarProjection(correspondingTypeParameter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return substitutionResult(correspondingTypeParameter, effectiveContextVariance, passedProjectionKind, projectionValue, recursionDepth + 1);
|
substitutedArguments.add(substitutedTypeArgument);
|
||||||
}
|
}
|
||||||
return new TypeProjection(
|
return substitutedArguments;
|
||||||
passedProjectionKind,
|
|
||||||
specializeType(
|
|
||||||
typeToSubstituteIn,
|
|
||||||
effectiveContextVariance, recursionDepth + 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeProjection substitutionResult(
|
private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
|
||||||
TypeParameterDescriptor correspondingTypeParameter,
|
if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
|
||||||
Variance effectiveContextVariance,
|
if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
|
||||||
Variance passedProjectionKind,
|
return typeParameterVariance.superpose(projectionKind);
|
||||||
TypeProjection value,
|
}
|
||||||
int recursionDepth) throws SubstitutionException {
|
|
||||||
assertRecursionDepth(recursionDepth, correspondingTypeParameter, value, substitution);
|
|
||||||
|
|
||||||
Variance projectionKindValue = value.getProjectionKind();
|
private enum VarianceConflictType {
|
||||||
JetType typeValue = value.getType();
|
NO_CONFLICT,
|
||||||
Variance effectiveProjectionKindValue = asymmetricOr(passedProjectionKind, projectionKindValue);
|
IN_IN_OUT_POSITION,
|
||||||
JetType effectiveTypeValue;
|
OUT_IN_IN_POSITION;
|
||||||
switch (effectiveContextVariance) {
|
}
|
||||||
case INVARIANT:
|
|
||||||
effectiveProjectionKindValue = projectionKindValue;
|
private static VarianceConflictType conflictType(Variance position, Variance argument) {
|
||||||
effectiveTypeValue = typeValue;
|
if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
|
||||||
break;
|
return VarianceConflictType.OUT_IN_IN_POSITION;
|
||||||
case IN_VARIANCE:
|
|
||||||
if (projectionKindValue == Variance.OUT_VARIANCE) {
|
|
||||||
throw new SubstitutionException(""); // TODO
|
|
||||||
// effectiveProjectionKindValue = Variance.INVARIANT;
|
|
||||||
// effectiveTypeValue = JetStandardClasses.getNothingType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
effectiveTypeValue = typeValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OUT_VARIANCE:
|
|
||||||
if (projectionKindValue == Variance.IN_VARIANCE) {
|
|
||||||
effectiveProjectionKindValue = Variance.INVARIANT;
|
|
||||||
effectiveTypeValue = correspondingTypeParameter.getUpperBoundsAsType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
effectiveTypeValue = typeValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(effectiveContextVariance.toString());
|
|
||||||
}
|
}
|
||||||
|
if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
|
||||||
// if (!allows(effectiveContextVariance, projectionKindValue)) {
|
return VarianceConflictType.IN_IN_OUT_POSITION;
|
||||||
// throw new SubstitutionException(""); // TODO : error message
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
return new TypeProjection(effectiveProjectionKindValue, specializeType(effectiveTypeValue, effectiveContextVariance, recursionDepth + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Variance asymmetricOr(Variance a, Variance b) {
|
|
||||||
return a == Variance.INVARIANT ? b : a;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean allows(Variance declarationSiteVariance, Variance callSiteVariance) {
|
|
||||||
switch (declarationSiteVariance) {
|
|
||||||
case INVARIANT: return true;
|
|
||||||
case IN_VARIANCE: return callSiteVariance != Variance.OUT_VARIANCE;
|
|
||||||
case OUT_VARIANCE: return callSiteVariance != Variance.IN_VARIANCE;
|
|
||||||
}
|
}
|
||||||
throw new IllegalStateException(declarationSiteVariance.toString());
|
return VarianceConflictType.NO_CONFLICT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertRecursionDepth(int recursionDepth, TypeParameterDescriptor parameter, TypeProjection value, TypeSubstitution substitution) {
|
private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
|
||||||
if (recursionDepth > MAX_RECURSION_DEPTH) {
|
if (recursionDepth > MAX_RECURSION_DEPTH) {
|
||||||
throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(value) + " for " + safeToString(parameter) + "; substitution: " + safeToString(substitution));
|
throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void assertRecursionDepth(int recursionDepth, JetType type, TypeSubstitution substitution) {
|
|
||||||
if (recursionDepth > MAX_RECURSION_DEPTH) {
|
|
||||||
throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(type) + "; substitution: " + safeToString(substitution));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
class In<in T>() {
|
||||||
|
fun f(<!UNUSED_PARAMETER!>t<!> : T) : Unit {}
|
||||||
|
fun f(<!UNUSED_PARAMETER!>t<!> : Int) : Int = 1
|
||||||
|
fun f1(<!UNUSED_PARAMETER!>t<!> : T) : Unit {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Out<out T>() {
|
||||||
|
fun f() : T {throw IllegalStateException()}
|
||||||
|
fun f(a : Int) : Int = a
|
||||||
|
}
|
||||||
|
|
||||||
|
class Inv<T>() {
|
||||||
|
fun f(t : T) : T = t
|
||||||
|
fun inf(<!UNUSED_PARAMETER!>t<!> : T) : Unit {}
|
||||||
|
fun outf() : T {throw IllegalStateException()}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testInOut() {
|
||||||
|
In<String>().f("1");
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<in String>).f("1")
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<out String>).f(<!TYPE_MISMATCH!>"1"<!>) // Wrong Arg
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<*>).f(<!TYPE_MISMATCH!>"1"<!>) // Wrong Arg
|
||||||
|
|
||||||
|
In<String>().f(1);
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<in String>).f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<out String>).f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<out String>).<!UNRESOLVED_REFERENCE!>f1<!>(1) // !!
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> In<*>).f(1);
|
||||||
|
|
||||||
|
Out<Int>().f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<out Int>).f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<in Int>).f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<*>).f(1)
|
||||||
|
|
||||||
|
Out<Int>().f()
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<out Int>).f()
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<in Int>).f()
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Out<*>).f()
|
||||||
|
|
||||||
|
Inv<Int>().f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<in Int>).f(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<out Int>).<!UNRESOLVED_REFERENCE!>f<!>(1) // !!
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<*>).<!UNRESOLVED_REFERENCE!>f<!>(1) // !!
|
||||||
|
|
||||||
|
Inv<Int>().inf(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<in Int>).inf(1)
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<out Int>).<!UNRESOLVED_REFERENCE!>inf<!>(1) // !!
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<*>).<!UNRESOLVED_REFERENCE!>inf<!>(1) // !!
|
||||||
|
|
||||||
|
Inv<Int>().outf()
|
||||||
|
<!TYPE_MISMATCH!>(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<in Int>).outf()<!> : Int // Type mismatch
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<out Int>).outf()
|
||||||
|
(null <!CAST_NEVER_SUCCEEDS!>as<!> Inv<*>).outf()
|
||||||
|
|
||||||
|
Inv<Int>().outf(<!TOO_MANY_ARGUMENTS!>1<!>) // Wrong Arg
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user