From e0b7e4efca946b8061b8da1cceb4e73a569b709a Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 3 Mar 2015 18:26:47 +0300 Subject: [PATCH] KT-6803 Subtyping breaks on star-projections for recursive generics Star-projections have upper bounds of the form "parameter's bound where all parameter of the same class are substituted with their star-projections" #KT-6803 Fixed --- .../diagnostics/tests/TypeInference.kt | 6 ++-- .../tests/substitutions/starProjections.kt | 24 ++++++++++++++++ .../tests/substitutions/starProjections.txt | 20 +++++++++++++ compiler/testData/type-checker-test.kt | 6 +++- .../checkers/JetDiagnosticsTestGenerated.java | 6 ++++ .../kotlin/types/JetTypeCheckerTest.java | 17 +++++++++++ .../kotlin/types/CommonSupertypes.java | 4 +++ .../jetbrains/kotlin/types/ErrorUtils.java | 2 +- .../kotlin/types/StarProjectionImpl.kt | 28 ++++++++++++++++++- .../kotlin/types/TypeProjectionBase.java | 2 +- .../kotlin/types/TypeSubstitutor.java | 3 ++ .../org/jetbrains/kotlin/types/TypeUtils.java | 6 ++-- .../org/jetbrains/kotlin/types/TypeUtils.kt | 4 +-- .../types/checker/TypeCheckingProcedure.java | 14 +++++++--- .../deserialization/TypeDeserializer.kt | 2 +- 15 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/substitutions/starProjections.kt create mode 100644 compiler/testData/diagnostics/tests/substitutions/starProjections.txt diff --git a/compiler/testData/diagnostics/tests/TypeInference.kt b/compiler/testData/diagnostics/tests/TypeInference.kt index 67a616f082a..4bdee5e7ccc 100644 --- a/compiler/testData/diagnostics/tests/TypeInference.kt +++ b/compiler/testData/diagnostics/tests/TypeInference.kt @@ -9,10 +9,10 @@ fun main(args : Array) { val a : C = C(); val x : C = C() val y : C = C() - val z : C<*> = C() + val z : C<*> = C() val ba : C = bar(); val bx : C = bar() val by : C = bar() - val bz : C<*> = bar() -} + val bz : C<*> = bar() +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/substitutions/starProjections.kt b/compiler/testData/diagnostics/tests/substitutions/starProjections.kt new file mode 100644 index 00000000000..30a15c5390a --- /dev/null +++ b/compiler/testData/diagnostics/tests/substitutions/starProjections.kt @@ -0,0 +1,24 @@ +// !CHECK_TYPE + +trait A> { + fun r(): R + fun t(): T +} + +fun testA(a: A<*, *>) { + a.r().checkType { it : _ } + a.t().checkType { it : _> } +} + +trait B, T>> { + fun r(): R + fun t(): T +} + +fun testB(b: B<*, *>) { + b.r().checkType { it : _ } + b.t().checkType { it : _, *>> } + + b.t().r().size() +} + diff --git a/compiler/testData/diagnostics/tests/substitutions/starProjections.txt b/compiler/testData/diagnostics/tests/substitutions/starProjections.txt new file mode 100644 index 00000000000..5d2c09b8704 --- /dev/null +++ b/compiler/testData/diagnostics/tests/substitutions/starProjections.txt @@ -0,0 +1,20 @@ +package + +internal fun testA(/*0*/ a: A<*, *>): kotlin.Unit +internal fun testB(/*0*/ b: B<*, *>): kotlin.Unit + +internal trait A> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal abstract fun r(): R + internal abstract fun t(): T + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +internal trait B, T>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + internal abstract fun r(): R + internal abstract fun t(): T + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/type-checker-test.kt b/compiler/testData/type-checker-test.kt index 03cbad5cb9e..516a8b4433d 100644 --- a/compiler/testData/type-checker-test.kt +++ b/compiler/testData/type-checker-test.kt @@ -44,4 +44,8 @@ trait B: Parent trait Rec class ARec : Rec -class BRec : Rec \ No newline at end of file +class BRec : Rec +trait SubRec: Rec + +trait Star> +trait SubStar> : Star \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index ce2b764c0cb..6ab32b246b7 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -10901,6 +10901,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("starProjections.kt") + public void testStarProjections() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/substitutions/starProjections.kt"); + doTest(fileName); + } + @TestMetadata("upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt") public void testUpperBoundsSubstitutionForOverloadResolutionWithAmbiguity() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/substitutions/upperBoundsSubstitutionForOverloadResolutionWithAmbiguity.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java b/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java index c8b60a2a2bd..bdf56ec4ad1 100644 --- a/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java +++ b/compiler/tests/org/jetbrains/kotlin/types/JetTypeCheckerTest.java @@ -328,6 +328,23 @@ public class JetTypeCheckerTest extends JetLiteFixture { assertSubtype("Nothing?", "Derived_T<*>?"); } + public void testStars() throws Exception { + assertSubtype("SubStar<*>", "Star<*>"); + assertSubtype("SubStar>", "Star<*>"); + assertSubtype("SubStar>", "Star>"); + assertNotSubtype("SubStar>", "Star>"); + + assertSubtype("Star>", "Star<*>"); + assertSubtype("Star<*>", "Star>"); + + assertNotSubtype("Star<*>", "Star>"); + + assertSubtype("SubRec<*>", "Rec<*>"); + assertSubtype("SubRec<*>", "Rec"); + assertSubtype("Rec<*>", "Rec"); + assertNotSubtype("Rec<*>", "Rec"); + } + public void testThis() throws Exception { assertType("Derived_T", "this", "Derived_T"); // assertType("Derived_T", "super", "Base_T"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/CommonSupertypes.java b/core/descriptors/src/org/jetbrains/kotlin/types/CommonSupertypes.java index 9f61dcc9a23..bb13a5fcc81 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/CommonSupertypes.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/CommonSupertypes.java @@ -69,6 +69,10 @@ public class CommonSupertypes { return 1 + maxDepth(KotlinPackage.map(type.getArguments(), new Function1() { @Override public JetType invoke(TypeProjection projection) { + if (projection.isStarProjection()) { + // any type is good enough for depth here + return KotlinBuiltIns.getInstance().getAnyType(); + } return projection.getType(); } })); diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java b/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java index 1bde2ed48ca..6e9a6610b24 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java @@ -377,7 +377,7 @@ public class ErrorUtils { if (type == null) return false; if (type.isError()) return true; for (TypeProjection projection : type.getArguments()) { - if (containsErrorType(projection.getType())) return true; + if (!projection.isStarProjection() && containsErrorType(projection.getType())) return true; } return false; } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt index e06f12d9b6d..d444567b289 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/StarProjectionImpl.kt @@ -16,9 +16,35 @@ package org.jetbrains.kotlin.types -import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import kotlin.properties.* +import org.jetbrains.kotlin.descriptors.* class StarProjectionImpl( + private val typeParameter: TypeParameterDescriptor +) : TypeProjectionBase() { + override fun isStarProjection() = true + + override fun getProjectionKind() = Variance.OUT_VARIANCE + + private val _type: JetType by Delegates.lazy { + val classDescriptor = typeParameter.getContainingDeclaration() as ClassDescriptor + val typeParameters = classDescriptor.getTypeConstructor().getParameters().map { it.getTypeConstructor() } + TypeSubstitutor.create( + object : TypeSubstitution() { + override fun get(key: TypeConstructor) = when (key) { + typeParameter.getTypeConstructor() -> this@StarProjectionImpl + in typeParameters -> TypeUtils.makeStarProjection(key.getDeclarationDescriptor() as TypeParameterDescriptor) + else -> null + } + + } + ).substitute(typeParameter.getUpperBounds().iterator().next(), Variance.OUT_VARIANCE)!! + } + + override fun getType() = _type +} + +class TypeBasedStarProjectionImpl( private val _type: JetType ) : TypeProjectionBase() { override fun isStarProjection() = true diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionBase.java b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionBase.java index 2af9cda6580..fd1efbda24f 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionBase.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeProjectionBase.java @@ -45,7 +45,7 @@ public abstract class TypeProjectionBase implements TypeProjection { @Override public int hashCode() { int result = getProjectionKind().hashCode(); - result = 31 * result + (getType().hashCode()); + result = 31 * result + (isStarProjection() ? 17 : getType().hashCode()); return result; } } diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitutor.java b/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitutor.java index 145fa2847ba..ff6187bd1ed 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitutor.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeSubstitutor.java @@ -213,6 +213,9 @@ public class TypeSubstitutor { if (typeVariable != null) { substitutedType = typeVariable.substitutionResult(replacement.getType()); } + else if (replacement.isStarProjection()) { + return replacement; + } else { // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable substitutedType = TypeUtils.makeNullableIfNeeded(replacement.getType(), type.isMarkedNullable()); diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.java b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.java index aee352b73d5..77cba673cef 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.java @@ -549,7 +549,7 @@ public class TypeUtils { public static boolean dependsOnTypeConstructors(@NotNull JetType type, @NotNull Collection typeParameterConstructors) { if (typeParameterConstructors.contains(type.getConstructor())) return true; for (TypeProjection typeProjection : type.getArguments()) { - if (dependsOnTypeConstructors(typeProjection.getType(), typeParameterConstructors)) { + if (!typeProjection.isStarProjection() && dependsOnTypeConstructors(typeProjection.getType(), typeParameterConstructors)) { return true; } } @@ -577,14 +577,14 @@ public class TypeUtils { return true; } for (TypeProjection projection : type.getArguments()) { - if (containsSpecialType(projection.getType(), isSpecialType)) return true; + if (!projection.isStarProjection() && containsSpecialType(projection.getType(), isSpecialType)) return true; } return false; } @NotNull public static TypeProjection makeStarProjection(@NotNull TypeParameterDescriptor parameterDescriptor) { - return new StarProjectionImpl(parameterDescriptor.getUpperBoundsAsType()); + return new StarProjectionImpl(parameterDescriptor); } @Nullable diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 7ef2423f84a..cb9679f5c40 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -33,7 +33,7 @@ import org.jetbrains.kotlin.types.TypeProjectionImpl import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.types.DelegatingType -fun JetType.getContainedTypeParameters(): Collection { +private fun JetType.getContainedTypeParameters(): Collection { val declarationDescriptor = getConstructor().getDeclarationDescriptor() if (declarationDescriptor is TypeParameterDescriptor) return listOf(declarationDescriptor) @@ -41,7 +41,7 @@ fun JetType.getContainedTypeParameters(): Collection { if (flexibility != null) { return flexibility.lowerBound.getContainedTypeParameters() + flexibility.upperBound.getContainedTypeParameters() } - return getArguments().map { it.getType() }.flatMap { it.getContainedTypeParameters() } + return getArguments().filter { !it.isStarProjection() }.map { it.getType() }.flatMap { it.getContainedTypeParameters() } } fun DeclarationDescriptor.getCapturedTypeParameters(): Collection { diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckingProcedure.java b/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckingProcedure.java index af5d701676d..a3754174e43 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckingProcedure.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/checker/TypeCheckingProcedure.java @@ -106,6 +106,10 @@ public class TypeCheckingProcedure { TypeProjection typeProjection1 = type1Arguments.get(i); TypeParameterDescriptor typeParameter2 = constructor2.getParameters().get(i); TypeProjection typeProjection2 = type2Arguments.get(i); + if (typeProjection1.isStarProjection() && typeProjection2.isStarProjection()) { + continue; + } + if (capture(typeProjection1, typeProjection2, typeParameter1)) { continue; } @@ -227,14 +231,16 @@ public class TypeCheckingProcedure { for (int i = 0; i < parameters.size(); i++) { TypeParameterDescriptor parameter = parameters.get(i); + TypeProjection superArgument = superArguments.get(i); + if (superArgument.isStarProjection()) continue; + + JetType superIn = getInType(parameter, superArgument); + JetType superOut = getOutType(parameter, superArgument); + TypeProjection subArgument = subArguments.get(i); JetType subIn = getInType(parameter, subArgument); JetType subOut = getOutType(parameter, subArgument); - TypeProjection superArgument = superArguments.get(i); - JetType superIn = getInType(parameter, superArgument); - JetType superOut = getOutType(parameter, superArgument); - if (capture(subArgument, superArgument, parameter)) continue; boolean argumentIsErrorType = subArgument.getType().isError() || superArgument.getType().isError(); diff --git a/core/serialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt b/core/serialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt index fabdb79ebf2..dc03a47c116 100644 --- a/core/serialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt +++ b/core/serialization/src/org/jetbrains/kotlin/serialization/deserialization/TypeDeserializer.kt @@ -103,7 +103,7 @@ public class TypeDeserializer( protos.map { proto -> val type = type(proto.getType()) if (proto.getProjection() == ProtoBuf.Type.Argument.Projection.STAR) - StarProjectionImpl(type) + TypeBasedStarProjectionImpl(type) else TypeProjectionImpl(variance(proto.getProjection()), type) }.toReadOnlyList()