TypeSubstitutor rewritten to be more readable

This commit is contained in:
Andrey Breslav
2012-05-04 19:11:42 +04:00
parent 7390aeb022
commit 2c8b3c879c
2 changed files with 128 additions and 168 deletions
@@ -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
}