From 6706ee87adf63c0a121f9d759110aa3e9f869251 Mon Sep 17 00:00:00 2001 From: Victor Petukhov Date: Wed, 21 Jul 2021 13:29:44 +0300 Subject: [PATCH] Introduce `TypeParameterUpperBoundEraser` to memorize results of type parameters erasion computation ^KT-47785 Fixed --- .../FirBlackBoxCodegenTestGenerated.java | 6 + .../codegen/box/platformTypes/kt47785.kt | 127 +++++++++++++++++ .../rawTypes/intermediateRecursion.txt | 2 +- .../codegen/BlackBoxCodegenTestGenerated.java | 6 + .../IrBlackBoxCodegenTestGenerated.java | 6 + .../LightAnalysisModeTestGenerated.java | 5 + .../kotlin/load/java/lazy/context.kt | 1 + .../load/java/lazy/types/JavaTypeResolver.kt | 80 ++--------- .../kotlin/load/java/lazy/types/RawType.kt | 7 +- .../types/TypeParameterUpperBoundEraser.kt | 128 ++++++++++++++++++ .../IrJsCodegenBoxES6TestGenerated.java | 5 + .../IrJsCodegenBoxTestGenerated.java | 5 + .../semantics/JsCodegenBoxTestGenerated.java | 5 + 13 files changed, 309 insertions(+), 74 deletions(-) create mode 100644 compiler/testData/codegen/box/platformTypes/kt47785.kt create mode 100644 core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/TypeParameterUpperBoundEraser.kt diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index d31d1fed313..26040ccde73 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -28078,6 +28078,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/platformTypes/kt14989.kt"); } + @Test + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @Test @TestMetadata("rawTypeWithNestedClass.kt") public void testRawTypeWithNestedClass() throws Exception { diff --git a/compiler/testData/codegen/box/platformTypes/kt47785.kt b/compiler/testData/codegen/box/platformTypes/kt47785.kt new file mode 100644 index 00000000000..bf9e88f8563 --- /dev/null +++ b/compiler/testData/codegen/box/platformTypes/kt47785.kt @@ -0,0 +1,127 @@ +// DONT_TARGET_EXACT_BACKEND: WASM +// FULL_JDK + +// FILE: AbstractAssert.java +public abstract class AbstractAssert> { } + +// FILE: AbstractAssertWithOriginWithColumnsAndRows.java +public abstract class AbstractAssertWithOriginWithColumnsAndRows, O extends OriginWithColumnsAndRows, D extends AbstractDbData, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> {} + +// FILE: AbstractColumnAssert.java +public abstract class AbstractColumnAssert, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractSubAssert implements ColumnElement { + +} + +// FILE: AbstractColumnValueAssert.java +public abstract class AbstractColumnValueAssert, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractValueAssert { + +} + +// FILE: AbstractDbAssert.java +public abstract class AbstractDbAssert, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractAssert implements OriginWithColumnsAndRows { + public A hasNumberOfRows(int expected) { + return (A) new RequestAssert(); + } + public R row() { + return (R) new RequestRowAssert(); + } +} + +// FILE: AbstractDbData.java +public abstract class AbstractDbData> extends AbstractDbElement { + +} + +// FILE: AbstractDbElement.java +public abstract class AbstractDbElement> { + +} + +// FILE: AbstractRowAssert.java +public abstract class AbstractRowAssert, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractSubAssert implements RowElement { + public RV value(String columnName) { + return (RV) new RequestRowValueAssert(); + } +} + +// FILE: AbstractRowValueAssert.java +public abstract class AbstractRowValueAssert, A extends AbstractDbAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractValueAssert { + +} + +// FILE: AbstractSubAssert.java +public abstract class AbstractSubAssert, A extends AbstractDbAssert, S extends AbstractSubAssert, V extends AbstractValueAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractAssertWithOriginWithColumnsAndRows implements OriginWithColumnsAndRows { + +} + +// FILE: AbstractValueAssert.java +import java.time.LocalDateTime; + +public abstract class AbstractValueAssert, A extends AbstractDbAssert, S extends AbstractSubAssert, V extends AbstractValueAssert, C extends AbstractColumnAssert, CV extends AbstractColumnValueAssert, R extends AbstractRowAssert, RV extends AbstractRowValueAssert> +extends AbstractAssertWithOriginWithColumnsAndRows { + public V isEqualTo(LocalDateTime expected) { + return (V) new RequestRowValueAssert(); + } +} + +// FILE: ColumnElement.java +public interface ColumnElement { } + +// FILE: OriginWithColumnsAndRows.java +public interface OriginWithColumnsAndRows {} + +// FILE: Request.java +public class Request extends AbstractDbData {} + +// FILE: RequestAssert.java +public class RequestAssert +extends AbstractDbAssert { +} + +// FILE: RequestColumnAssert.java +public class RequestColumnAssert +extends AbstractColumnAssert { + +} + +// FILE: RequestColumnValueAssert.java +public class RequestColumnValueAssert +extends AbstractColumnValueAssert { + +} + +// FILE: RequestRowAssert.java +public class RequestRowAssert +extends AbstractRowAssert { + +} + +// FILE: RequestRowValueAssert.java +public class RequestRowValueAssert +extends AbstractRowValueAssert { + +} + +// FILE: RowElement.java +public interface RowElement {} + +// FILE: main.kt +import java.time.LocalDateTime + +fun test(x: RequestAssert) { + val timestamp = LocalDateTime.now() + x.hasNumberOfRows(1) + .row() + .value("message_time").isEqualTo(timestamp) +} + +fun box(): String { + test(RequestAssert()) + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/rawTypes/intermediateRecursion.txt b/compiler/testData/diagnostics/tests/platformTypes/rawTypes/intermediateRecursion.txt index 39903fcab0b..bfe712fc67a 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/rawTypes/intermediateRecursion.txt +++ b/compiler/testData/diagnostics/tests/platformTypes/rawTypes/intermediateRecursion.txt @@ -17,5 +17,5 @@ public open class Foo!, /*1*/ K : Boo!, /*2*/ X : Boo! public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String // Static members - public/*package*/ open fun test1(): (Foo<(raw) Boo!>!>!, (raw) Boo!>!, (raw) Boo!>!>..Foo!>!>!, out Boo!>!, out Boo!>!>?) + public/*package*/ open fun test1(): (Foo<(raw) Boo!>!>!, (raw) Boo!>!, (raw) Boo<*>!>..Foo!>!>!, out Boo!>!, out Boo<*>!>?) } diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index 947206c6b51..aa307ded957 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -27976,6 +27976,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/platformTypes/kt14989.kt"); } + @Test + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @Test @TestMetadata("rawTypeWithNestedClass.kt") public void testRawTypeWithNestedClass() throws Exception { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index c2e6c194786..f037e191141 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -28078,6 +28078,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/platformTypes/kt14989.kt"); } + @Test + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @Test @TestMetadata("rawTypeWithNestedClass.kt") public void testRawTypeWithNestedClass() throws Exception { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 91ae5faca01..16870ed5bc1 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -23755,6 +23755,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/platformTypes/kt14989.kt"); } + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @TestMetadata("rawTypeWithNestedClass.kt") public void testRawTypeWithNestedClass() throws Exception { runTest("compiler/testData/codegen/box/platformTypes/rawTypeWithNestedClass.kt"); diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/context.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/context.kt index 8477fa2ca2f..893ac7fc616 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/context.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/context.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.load.java.components.JavaPropertyInitializerEvaluato import org.jetbrains.kotlin.load.java.components.JavaResolverCache import org.jetbrains.kotlin.load.java.components.SignaturePropagator import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver +import org.jetbrains.kotlin.load.java.lazy.types.TypeParameterUpperBoundEraser import org.jetbrains.kotlin.load.java.sources.JavaSourceElementFactory import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner import org.jetbrains.kotlin.load.java.typeEnhancement.SignatureEnhancement diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt index e0f7800a815..4f50bbb8814 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt @@ -41,6 +41,7 @@ class JavaTypeResolver( private val c: LazyJavaResolverContext, private val typeParameterResolver: TypeParameterResolver ) { + val typeParameterUpperBoundEraser = TypeParameterUpperBoundEraser() fun transformJavaType(javaType: JavaType?, attr: JavaTypeAttributes): KotlinType { return when (javaType) { @@ -229,9 +230,11 @@ class JavaTypeResolver( // so we get A<*, *>. // Summary result for upper bound of T is `A, A<*, *>>..A, out A<*, *>>` val erasedUpperBound = LazyWrappedType(c.storageManager) { - parameter.getErasedUpperBound(isRaw, attr) { - constructor.declarationDescriptor!!.defaultType.replaceArgumentsWithStarProjections() - } + typeParameterUpperBoundEraser.getErasedUpperBound( + parameter, + isRaw, + attr.withDefaultType(constructor.declarationDescriptor?.defaultType) + ) } RawSubstitution.computeProjection( @@ -310,9 +313,11 @@ data class JavaTypeAttributes( val flexibility: JavaTypeFlexibility = INFLEXIBLE, val isForAnnotationParameter: Boolean = false, // we use it to prevent happening a recursion while compute type parameter's upper bounds - val visitedTypeParameters: Set? = null + val visitedTypeParameters: Set? = null, + val defaultType: SimpleType? = null ) { fun withFlexibility(flexibility: JavaTypeFlexibility) = copy(flexibility = flexibility) + fun withDefaultType(type: SimpleType?) = copy(defaultType = type) fun withNewVisitedTypeParameter(typeParameter: TypeParameterDescriptor) = copy(visitedTypeParameters = if (visitedTypeParameters != null) visitedTypeParameters + typeParameter else setOf(typeParameter)) } @@ -331,70 +336,3 @@ fun TypeUsage.toAttributes( isForAnnotationParameter = isForAnnotationParameter, visitedTypeParameters = upperBoundForTypeParameter?.let(::setOf) ) - -// Definition: -// ErasedUpperBound(T : G) = G<*> // UpperBound(T) is a type G with arguments -// ErasedUpperBound(T : A) = A // UpperBound(T) is a type A without arguments -// ErasedUpperBound(T : F) = UpperBound(F) // UB(T) is another type parameter F -internal fun TypeParameterDescriptor.getErasedUpperBound( - // Calculation of `potentiallyRecursiveTypeParameter.upperBounds` may recursively depend on `this.getErasedUpperBound` - // E.g. `class A` - // To prevent recursive calls return defaultValue() instead - isRaw: Boolean, - typeAttr: JavaTypeAttributes, - defaultValue: (() -> KotlinType) = { ErrorUtils.createErrorType("Can't compute erased upper bound of type parameter `$this`") } -): KotlinType { - val visitedTypeParameters = typeAttr.visitedTypeParameters - - if (visitedTypeParameters != null && original in visitedTypeParameters) return defaultValue() - - /* - * We should do erasure of containing type parameters with their erasure to avoid creating inconsistent types. - * E.g. for `class Foo, B>`, we'd have erasure for lower bound: Foo, Any>, - * but it's wrong type: projection(*) != projection(Any). - * So we should substitute erasure of the corresponding type parameter: `Foo, Any>` or `Foo, *>`. - */ - val erasedUpperBounds = defaultType.extractTypeParametersFromUpperBounds(visitedTypeParameters).associate { - val boundProjection = if (visitedTypeParameters == null || it !in visitedTypeParameters) { - RawSubstitution.computeProjection( - it, - // if erasure happens due to invalid arguments number, use star projections instead - if (isRaw) typeAttr else typeAttr.withFlexibility(INFLEXIBLE), - it.getErasedUpperBound(isRaw, typeAttr.withNewVisitedTypeParameter(this)) - ) - } else makeStarProjection(it, typeAttr) - - it.typeConstructor to boundProjection - } - val erasedUpperBoundsSubstitutor = TypeSubstitutor.create(TypeConstructorSubstitution.createByConstructorsMap(erasedUpperBounds)) - - val firstUpperBound = upperBounds.first() - - if (firstUpperBound.constructor.declarationDescriptor is ClassDescriptor) { - return firstUpperBound.replaceArgumentsWithStarProjectionOrMapped( - erasedUpperBoundsSubstitutor, - erasedUpperBounds, - OUT_VARIANCE, - typeAttr.visitedTypeParameters - ) - } - - val stopAt = typeAttr.visitedTypeParameters ?: setOf(this) - var current = firstUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor - - while (current !in stopAt) { - val nextUpperBound = current.upperBounds.first() - if (nextUpperBound.constructor.declarationDescriptor is ClassDescriptor) { - return nextUpperBound.replaceArgumentsWithStarProjectionOrMapped( - erasedUpperBoundsSubstitutor, - erasedUpperBounds, - OUT_VARIANCE, - typeAttr.visitedTypeParameters - ) - } - - current = nextUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor - } - - return defaultValue() -} diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/RawType.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/RawType.kt index de50a691b49..0d73bf45c36 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/RawType.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/RawType.kt @@ -104,12 +104,15 @@ class RawTypeImpl private constructor(lowerBound: SimpleType, upperBound: Simple internal object RawSubstitution : TypeSubstitution() { override fun get(key: KotlinType) = TypeProjectionImpl(eraseType(key)) + private val typeParameterUpperBoundEraser = TypeParameterUpperBoundEraser() + private val lowerTypeAttr = TypeUsage.COMMON.toAttributes().withFlexibility(JavaTypeFlexibility.FLEXIBLE_LOWER_BOUND) private val upperTypeAttr = TypeUsage.COMMON.toAttributes().withFlexibility(JavaTypeFlexibility.FLEXIBLE_UPPER_BOUND) private fun eraseType(type: KotlinType, attr: JavaTypeAttributes = JavaTypeAttributes(TypeUsage.COMMON)): KotlinType { return when (val declaration = type.constructor.declarationDescriptor) { - is TypeParameterDescriptor -> eraseType(declaration.getErasedUpperBound(isRaw = true, attr), attr) + is TypeParameterDescriptor -> + eraseType(typeParameterUpperBoundEraser.getErasedUpperBound(declaration, isRaw = true, attr), attr) is ClassDescriptor -> { val declarationForUpper = type.upperIfFlexible().constructor.declarationDescriptor @@ -170,7 +173,7 @@ internal object RawSubstitution : TypeSubstitution() { fun computeProjection( parameter: TypeParameterDescriptor, attr: JavaTypeAttributes, - erasedUpperBound: KotlinType = parameter.getErasedUpperBound(isRaw = true, attr) + erasedUpperBound: KotlinType = typeParameterUpperBoundEraser.getErasedUpperBound(parameter, isRaw = true, attr) ) = when (attr.flexibility) { // Raw(List) => (List..List<*>) // Raw(Enum) => (Enum>..Enum>) diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/TypeParameterUpperBoundEraser.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/TypeParameterUpperBoundEraser.kt new file mode 100644 index 00000000000..428f97ca235 --- /dev/null +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/types/TypeParameterUpperBoundEraser.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.load.java.lazy.types + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.types.* +import org.jetbrains.kotlin.types.typeUtil.extractTypeParametersFromUpperBounds +import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjectionOrMapped +import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections + +class TypeParameterUpperBoundEraser { + private val storage = LockBasedStorageManager("Type parameter upper bound erasion results") + private val erroneousErasedBound by lazy { + ErrorUtils.createErrorType("Can't compute erased upper bound of type parameter `$this`") + } + + private data class DataToEraseUpperBound( + val typeParameter: TypeParameterDescriptor, + val isRaw: Boolean, + val typeAttr: JavaTypeAttributes + ) { + override fun equals(other: Any?): Boolean { + if (other !is DataToEraseUpperBound) return false + return other.typeParameter == this.typeParameter + && other.isRaw == this.isRaw + && other.typeAttr.flexibility == this.typeAttr.flexibility + && other.typeAttr.howThisTypeIsUsed == this.typeAttr.howThisTypeIsUsed + && other.typeAttr.isForAnnotationParameter == this.typeAttr.isForAnnotationParameter + && other.typeAttr.defaultType == this.typeAttr.defaultType + } + + override fun hashCode(): Int { + var result = typeParameter.hashCode() + result += 31 * result + if (isRaw) 1 else 0 + result += 31 * result + typeAttr.flexibility.hashCode() + result += 31 * result + typeAttr.howThisTypeIsUsed.hashCode() + result += 31 * result + if (typeAttr.isForAnnotationParameter) 1 else 0 + result += 31 * result + typeAttr.defaultType.hashCode() + return result + } + } + + private val getErasedUpperBound = storage.createMemoizedFunction { + with(it) { getErasedUpperBoundInternal(typeParameter, isRaw, typeAttr) } + } + + internal fun getErasedUpperBound( + typeParameter: TypeParameterDescriptor, + isRaw: Boolean, + typeAttr: JavaTypeAttributes + ) = getErasedUpperBound(DataToEraseUpperBound(typeParameter, isRaw, typeAttr)) + + private fun getDefaultType(typeAttr: JavaTypeAttributes) = + typeAttr.defaultType?.replaceArgumentsWithStarProjections() ?: erroneousErasedBound + + // Definition: + // ErasedUpperBound(T : G) = G<*> // UpperBound(T) is a type G with arguments + // ErasedUpperBound(T : A) = A // UpperBound(T) is a type A without arguments + // ErasedUpperBound(T : F) = UpperBound(F) // UB(T) is another type parameter F + private fun getErasedUpperBoundInternal( + // Calculation of `potentiallyRecursiveTypeParameter.upperBounds` may recursively depend on `this.getErasedUpperBound` + // E.g. `class A` + // To prevent recursive calls return defaultValue() instead + typeParameter: TypeParameterDescriptor, + isRaw: Boolean, + typeAttr: JavaTypeAttributes + ): KotlinType { + val visitedTypeParameters = typeAttr.visitedTypeParameters + + if (visitedTypeParameters != null && typeParameter.original in visitedTypeParameters) + return getDefaultType(typeAttr) + + /* + * We should do erasure of containing type parameters with their erasure to avoid creating inconsistent types. + * E.g. for `class Foo, B>`, we'd have erasure for lower bound: Foo, Any>, + * but it's wrong type: projection(*) != projection(Any). + * So we should substitute erasure of the corresponding type parameter: `Foo, Any>` or `Foo, *>`. + */ + val erasedUpperBounds = typeParameter.defaultType.extractTypeParametersFromUpperBounds(visitedTypeParameters).associate { + val boundProjection = if (visitedTypeParameters == null || it !in visitedTypeParameters) { + RawSubstitution.computeProjection( + it, + // if erasure happens due to invalid arguments number, use star projections instead + if (isRaw) typeAttr else typeAttr.withFlexibility(JavaTypeFlexibility.INFLEXIBLE), + getErasedUpperBound(it, isRaw, typeAttr.withNewVisitedTypeParameter(typeParameter)) + ) + } else makeStarProjection(it, typeAttr) + + it.typeConstructor to boundProjection + } + val erasedUpperBoundsSubstitutor = TypeSubstitutor.create(TypeConstructorSubstitution.createByConstructorsMap(erasedUpperBounds)) + + val firstUpperBound = typeParameter.upperBounds.first() + + if (firstUpperBound.constructor.declarationDescriptor is ClassDescriptor) { + return firstUpperBound.replaceArgumentsWithStarProjectionOrMapped( + erasedUpperBoundsSubstitutor, + erasedUpperBounds, + Variance.OUT_VARIANCE, + typeAttr.visitedTypeParameters + ) + } + + val stopAt = typeAttr.visitedTypeParameters ?: setOf(this) + var current = firstUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor + + while (current !in stopAt) { + val nextUpperBound = current.upperBounds.first() + if (nextUpperBound.constructor.declarationDescriptor is ClassDescriptor) { + return nextUpperBound.replaceArgumentsWithStarProjectionOrMapped( + erasedUpperBoundsSubstitutor, + erasedUpperBounds, + Variance.OUT_VARIANCE, + typeAttr.visitedTypeParameters + ) + } + + current = nextUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor + } + + return getDefaultType(typeAttr) + } +} \ No newline at end of file diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index 0733926f180..9efab1ca315 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -18664,6 +18664,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes runTest("compiler/testData/codegen/box/platformTypes/inferenceFlexibleTToNullable.kt"); } + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @TestMetadata("compiler/testData/codegen/box/platformTypes/primitives") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 76c1bd6e39e..5e1a8152cc2 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -18070,6 +18070,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/platformTypes/inferenceFlexibleTToNullable.kt"); } + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @TestMetadata("compiler/testData/codegen/box/platformTypes/primitives") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 5aebd79dc1b..0cf3d36c51b 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -18135,6 +18135,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/platformTypes/inferenceFlexibleTToNullable.kt"); } + @TestMetadata("kt47785.kt") + public void testKt47785() throws Exception { + runTest("compiler/testData/codegen/box/platformTypes/kt47785.kt"); + } + @TestMetadata("compiler/testData/codegen/box/platformTypes/primitives") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)