From 2c8b3c879c1ebb50b3041969a9fe82727858ef07 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Fri, 4 May 2012 19:11:42 +0400 Subject: [PATCH] TypeSubstitutor rewritten to be more readable --- .../jet/lang/types/TypeSubstitutor.java | 240 ++++++------------ .../tests/generics/Projections.jet | 56 ++++ 2 files changed, 128 insertions(+), 168 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/generics/Projections.jet diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java b/compiler/frontend/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java index f6a4aca71e8..c95ae021720 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/types/TypeSubstitutor.java @@ -16,14 +16,16 @@ 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; 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 @@ -32,34 +34,6 @@ public class TypeSubstitutor { private static final int MAX_RECURSION_DEPTH = 100; - public static TypeSubstitutor makeConstantSubstitutor(Collection typeParameterDescriptors, JetType type) { - final Set 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 { private final @NotNull Map substitutionContext; @@ -85,7 +59,7 @@ public class TypeSubstitutor { 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) { super(message); } @@ -143,7 +117,7 @@ public class TypeSubstitutor { } try { - return unsafeSubstitute(type, howThisTypeIsUsed); + return unsafeSubstitute(new TypeProjection(howThisTypeIsUsed, type), 0).getType(); } catch (SubstitutionException e) { return ErrorUtils.createErrorType(e.getMessage()); } @@ -156,170 +130,100 @@ public class TypeSubstitutor { } try { - return unsafeSubstitute(type, howThisTypeIsUsed); + return unsafeSubstitute(new TypeProjection(howThisTypeIsUsed, type), 0).getType(); } catch (SubstitutionException e) { return null; } } @NotNull - private JetType unsafeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) throws SubstitutionException { - if (ErrorUtils.isErrorType(type)) return type; + private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException { + 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); - if (value != null) { - TypeConstructor constructor = type.getConstructor(); - assert constructor.getDeclarationDescriptor() instanceof TypeParameterDescriptor; + TypeProjection replacement = substitution.get(type.getConstructor()); - 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 etc. + List substitutedArguments = substituteTypeArguments( + type.getConstructor().getParameters(), type.getArguments(), recursionDepth); - return specializeType(type, howThisTypeIsUsed, 0); - } - - private TypeProjection getValueWithCorrectNullability(TypeSubstitution substitution, JetType type) { - TypeProjection typeProjection = substitution.get(type.getConstructor()); - if (typeProjection == null) return null; - - 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 newArguments = new ArrayList(); - List 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)); + JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable + type.getConstructor(), // The same constructor + type.isNullable(), // Same nullability + substitutedArguments, + new SubstitutingScope(type.getMemberScope(), this)); + return new TypeProjection(originalProjection.getProjectionKind(), substitutedType); } - return new JetTypeImpl( - subjectType.getAnnotations(), - subjectType.getConstructor(), - subjectType.isNullable(), - newArguments, - new SubstitutingScope(subjectType.getMemberScope(), this)); } - @NotNull - private TypeProjection substituteInProjection( - @NotNull TypeSubstitution substitutionContext, - @NotNull TypeProjection passedProjection, - @NotNull TypeParameterDescriptor correspondingTypeParameter, - @NotNull Variance contextCallSiteVariance, - int recursionDepth) throws SubstitutionException { - assertRecursionDepth(recursionDepth, correspondingTypeParameter, passedProjection, substitution); + private List substituteTypeArguments(List typeParameters, List typeArguments, int recursionDepth) + throws SubstitutionException { + List substitutedArguments = Lists.newArrayList(); + for (int i = 0; i < typeParameters.size(); i++) { + TypeParameterDescriptor typeParameter = typeParameters.get(i); + TypeProjection typeArgument = typeArguments.get(i); - JetType typeToSubstituteIn = passedProjection.getType(); - if (ErrorUtils.isErrorType(typeToSubstituteIn)) return passedProjection; + TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1); - Variance passedProjectionKind = passedProjection.getProjectionKind(); - Variance parameterVariance = correspondingTypeParameter.getVariance(); - - Variance effectiveProjectionKind = asymmetricOr(passedProjectionKind, parameterVariance); - Variance effectiveContextVariance = contextCallSiteVariance.superpose(effectiveProjectionKind); - - TypeProjection projectionValue = getValueWithCorrectNullability(substitutionContext, typeToSubstituteIn); - if (projectionValue != null) { - assert typeToSubstituteIn.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor; - - if (!allows(parameterVariance, passedProjectionKind)) { - return SubstitutionUtils.makeStarProjection(correspondingTypeParameter); + switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) { + case OUT_IN_IN_POSITION: + substitutedTypeArgument = new TypeProjection(Variance.IN_VARIANCE, typeParameter.getLowerBoundsAsType()); + break; + case IN_IN_OUT_POSITION: + substitutedTypeArgument = SubstitutionUtils.makeStarProjection(typeParameter); + break; } - return substitutionResult(correspondingTypeParameter, effectiveContextVariance, passedProjectionKind, projectionValue, recursionDepth + 1); + substitutedArguments.add(substitutedTypeArgument); } - return new TypeProjection( - passedProjectionKind, - specializeType( - typeToSubstituteIn, - effectiveContextVariance, recursionDepth + 1)); + return substitutedArguments; } - private TypeProjection substitutionResult( - TypeParameterDescriptor correspondingTypeParameter, - Variance effectiveContextVariance, - Variance passedProjectionKind, - TypeProjection value, - int recursionDepth) throws SubstitutionException { - assertRecursionDepth(recursionDepth, correspondingTypeParameter, value, substitution); + private static Variance combine(Variance typeParameterVariance, Variance projectionKind) { + if (typeParameterVariance == Variance.INVARIANT) return projectionKind; + if (projectionKind == Variance.INVARIANT) return typeParameterVariance; + return typeParameterVariance.superpose(projectionKind); + } - Variance projectionKindValue = value.getProjectionKind(); - JetType typeValue = value.getType(); - Variance effectiveProjectionKindValue = asymmetricOr(passedProjectionKind, projectionKindValue); - JetType effectiveTypeValue; - switch (effectiveContextVariance) { - case INVARIANT: - effectiveProjectionKindValue = projectionKindValue; - effectiveTypeValue = typeValue; - break; - 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()); + private enum VarianceConflictType { + NO_CONFLICT, + IN_IN_OUT_POSITION, + OUT_IN_IN_POSITION; + } + + private static VarianceConflictType conflictType(Variance position, Variance argument) { + if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) { + return VarianceConflictType.OUT_IN_IN_POSITION; } - -// if (!allows(effectiveContextVariance, projectionKindValue)) { -// 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; + if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) { + return VarianceConflictType.IN_IN_OUT_POSITION; } - 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) { - throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(value) + " for " + safeToString(parameter) + "; 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)); + throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution)); } } diff --git a/compiler/testData/diagnostics/tests/generics/Projections.jet b/compiler/testData/diagnostics/tests/generics/Projections.jet new file mode 100644 index 00000000000..f2fdba24b41 --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/Projections.jet @@ -0,0 +1,56 @@ +class In() { + fun f(t : T) : Unit {} + fun f(t : Int) : Int = 1 + fun f1(t : T) : Unit {} +} + +class Out() { + fun f() : T {throw IllegalStateException()} + fun f(a : Int) : Int = a +} + +class Inv() { + fun f(t : T) : T = t + fun inf(t : T) : Unit {} + fun outf() : T {throw IllegalStateException()} +} + +fun testInOut() { + In().f("1"); + (null as In).f("1") + (null as In).f("1") // Wrong Arg + (null as In<*>).f("1") // Wrong Arg + + In().f(1); + (null as In).f(1) + (null as In).f(1) + (null as In).f1(1) // !! + (null as In<*>).f(1); + + Out().f(1) + (null as Out).f(1) + (null as Out).f(1) + (null as Out<*>).f(1) + + Out().f() + (null as Out).f() + (null as Out).f() + (null as Out<*>).f() + + Inv().f(1) + (null as Inv).f(1) + (null as Inv).f(1) // !! + (null as Inv<*>).f(1) // !! + + Inv().inf(1) + (null as Inv).inf(1) + (null as Inv).inf(1) // !! + (null as Inv<*>).inf(1) // !! + + Inv().outf() + (null as Inv).outf() : Int // Type mismatch + (null as Inv).outf() + (null as Inv<*>).outf() + + Inv().outf(1) // Wrong Arg +} \ No newline at end of file