diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizer.kt index c5471c8c361..b0b91e7a47d 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/core/TypeCommonizer.kt @@ -34,7 +34,6 @@ private class DefaultTypeCommonizer(private val cache: CirClassifiersCache) : * See also [AbstractStrictEqualityTypeChecker]. */ internal fun areTypesEqual(cache: CirClassifiersCache, a: CirType, b: CirType): Boolean = when { - a === b -> true a is CirSimpleType -> (b is CirSimpleType) && areSimpleTypesEqual(cache, a, b) a is CirFlexibleType -> (b is CirFlexibleType) && areSimpleTypesEqual(cache, a.lowerBound, b.lowerBound) @@ -43,16 +42,15 @@ internal fun areTypesEqual(cache: CirClassifiersCache, a: CirType, b: CirType): } private fun areSimpleTypesEqual(cache: CirClassifiersCache, a: CirSimpleType, b: CirSimpleType): Boolean { - if (a.arguments.size != b.arguments.size - || a.isMarkedNullable != b.isMarkedNullable - || a.isDefinitelyNotNullType != b.isDefinitelyNotNullType + if (a !== b + && (a.arguments.size != b.arguments.size + || a.isMarkedNullable != b.isMarkedNullable + || a.isDefinitelyNotNullType != b.isDefinitelyNotNullType + || a.fqName != b.fqName) ) { return false } - if (a.fqName != b.fqName) - return false - fun isClassOrTypeAliasUnderStandardKotlinPackages() = // N.B. only for descriptors that represent classes or type aliases, but not type parameters! a.isClassOrTypeAlias && b.isClassOrTypeAlias @@ -60,7 +58,7 @@ private fun areSimpleTypesEqual(cache: CirClassifiersCache, a: CirSimpleType, b: // If classes are from the standard Kotlin packages, compare them only by type constructors. // Effectively, this includes comparison of 1) FQ names of underlying descriptors and 2) number of type constructor parameters. // See org.jetbrains.kotlin.types.AbstractClassTypeConstructor.equals() for details. - && a.expandedTypeConstructorId == b.expandedTypeConstructorId + && (a === b || a.expandedTypeConstructorId == b.expandedTypeConstructorId) fun descriptorsCanBeCommonizedThemselves() = a.kind == b.kind && when (a.kind) { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt index d523b1fa4c4..2248d32287f 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirAnnotation.kt @@ -20,6 +20,26 @@ class CirAnnotation(original: AnnotationDescriptor) { checkSupportedInCommonization(constantValue) { "${original::class.java}, $original[$name]" } } } + + // See also org.jetbrains.kotlin.types.KotlinType.cachedHashCode + private var cachedHashCode = 0 + + private fun computeHashCode() = fqName.hashCode() * 31 + allValueArguments.hashCode() + + override fun hashCode(): Int { + var currentHashCode = cachedHashCode + if (currentHashCode != 0) return currentHashCode + + currentHashCode = computeHashCode() + cachedHashCode = currentHashCode + return currentHashCode + } + + override fun equals(other: Any?): Boolean = + if (other is CirAnnotation) { + fqName == other.fqName && allValueArguments == other.allValueArguments + } else + false } internal fun checkSupportedInCommonization(constantValue: ConstantValue<*>, location: () -> String) { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirType.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirType.kt index f796295e694..0d90f0654d4 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirType.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirType.kt @@ -11,10 +11,7 @@ import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir.CirSimpleTypeKind.CLASS import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir.CirSimpleTypeKind.TYPE_ALIAS -import org.jetbrains.kotlin.descriptors.commonizer.utils.declarationDescriptor -import org.jetbrains.kotlin.descriptors.commonizer.utils.fqName -import org.jetbrains.kotlin.descriptors.commonizer.utils.fqNameWithTypeParameters -import org.jetbrains.kotlin.descriptors.commonizer.utils.intern +import org.jetbrains.kotlin.descriptors.commonizer.utils.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.types.* @@ -22,8 +19,8 @@ sealed class CirType { companion object { fun create(type: KotlinType): CirType = type.unwrap().run { when (this) { - is SimpleType -> CirSimpleType(this) - is FlexibleType -> CirFlexibleType(CirSimpleType(lowerBound), CirSimpleType(upperBound)) + is SimpleType -> CirSimpleType.create(this) + is FlexibleType -> CirFlexibleType(CirSimpleType.create(lowerBound), CirSimpleType.create(upperBound)) } } } @@ -38,7 +35,7 @@ sealed class CirType { * * There is no difference between "abbreviation" and "expanded" for types representing classes and type parameters. */ -class CirSimpleType(original: SimpleType) : CirType() { +class CirSimpleType private constructor(original: SimpleType) : CirType() { val annotations: List val kind: CirSimpleTypeKind val fqName: FqName @@ -63,8 +60,55 @@ class CirSimpleType(original: SimpleType) : CirType() { inline val isClassOrTypeAlias get() = (kind == CLASS || kind == TYPE_ALIAS) val fqNameWithTypeParameters = original.fqNameWithTypeParameters + // Note: equals() and hashCode() are implemented in a way that only right-hand side declaration + // is compared for typealiases (well, actually "fqNameWithTypeParameters" for the right-hand side). + // This is sufficient for unit tests and for evaluating commonized supertypes, but is absolutely + // unsuitable for comparison of CirSimpleType for the purposes of interning, etc. override fun equals(other: Any?) = fqNameWithTypeParameters == (other as? CirSimpleType)?.fqNameWithTypeParameters override fun hashCode() = fqNameWithTypeParameters.hashCode() + + private class Interned(val type: CirSimpleType) { + // See also org.jetbrains.kotlin.types.KotlinType.cachedHashCode + private var cachedHashCode = 0 + + private fun computeHashCode(): Int { + var result = type.annotations.hashCode() + result = 31 * result + type.kind.hashCode() + result = 31 * result + type.fqName.hashCode() + result = 31 * result + type.arguments.hashCode() + result = 31 * result + type.isMarkedNullable.hashCode() + result = 31 * result + type.isDefinitelyNotNullType.hashCode() + result = 31 * result + type.fqNameWithTypeParameters.hashCode() + return result + } + + override fun hashCode(): Int { + var currentHashCode = cachedHashCode + if (currentHashCode != 0) return currentHashCode + + currentHashCode = computeHashCode() + cachedHashCode = currentHashCode + return currentHashCode + } + + override fun equals(other: Any?): Boolean = + if (other is Interned) { + type.isMarkedNullable == other.type.isMarkedNullable + && type.fqName == other.type.fqName + && type.kind == other.type.kind + && type.arguments == other.type.arguments + && type.fqNameWithTypeParameters == other.type.fqNameWithTypeParameters + && type.annotations == other.type.annotations + && type.isDefinitelyNotNullType == other.type.isDefinitelyNotNullType + } else + false + } + + companion object { + private val interner = NonThreadSafeInterner() + + fun create(original: SimpleType): CirSimpleType = interner.intern(Interned(CirSimpleType(original))).type + } } enum class CirSimpleTypeKind { @@ -89,10 +133,12 @@ data class CirTypeConstructorId(val fqName: FqName, val numberOfTypeParameters: constructor(type: SimpleType) : this(type.fqNameInterned, type.constructor.parameters.size) } -class CirTypeProjection(original: TypeProjection) { - val projectionKind = original.projectionKind - val isStarProjection = original.isStarProjection - val type = CirType.create(original.type) +data class CirTypeProjection( + val projectionKind: Variance, + val isStarProjection: Boolean, + val type: CirType +) { + constructor(original: TypeProjection) : this(original.projectionKind, original.isStarProjection, CirType.create(original.type)) } data class CirFlexibleType(val lowerBound: CirSimpleType, val upperBound: CirSimpleType) : CirType() diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirTypeAlias.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirTypeAlias.kt index 8675fffd367..aa259ef928a 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirTypeAlias.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/mergedtree/ir/CirTypeAlias.kt @@ -18,6 +18,6 @@ class CirTypeAliasImpl(original: TypeAliasDescriptor) : CirTypeAlias { override val name = original.name.intern() override val typeParameters = original.declaredTypeParameters.map(::CirTypeParameterImpl) override val visibility = original.visibility - override val underlyingType = CirSimpleType(original.underlyingType) - override val expandedType = CirSimpleType(original.expandedType) + override val underlyingType = CirSimpleType.create(original.underlyingType) + override val expandedType = CirSimpleType.create(original.expandedType) } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/AbstractCommonizerTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/AbstractCommonizerTest.kt index c34a3723fb0..9a690a82a7f 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/AbstractCommonizerTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/AbstractCommonizerTest.kt @@ -33,14 +33,18 @@ abstract class AbstractCommonizerTest { if (!isEqual(expected, actual)) fail("Expected: $expected\nActual: $actual") } - // should fail on the last variant - protected fun doTestFailure(vararg variants: T) { + protected fun doTestFailure( + vararg variants: T, + shouldFailOnFirstVariant: Boolean = false // by default should fail on the last variant + ) { check(variants.isNotEmpty()) + val failureIndex = if (shouldFailOnFirstVariant) 0 else variants.size - 1 + val commonized = createCommonizer().apply { variants.forEachIndexed { index, variant -> val result = commonizeWith(variant) - if (index == variants.size - 1) assertFalse(result) else assertTrue(result) + if (index >= failureIndex) assertFalse(result) else assertTrue(result) } } diff --git a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/DefaultPropertySetterCommonizerTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/DefaultPropertySetterCommonizerTest.kt index b7626eb3d7b..2b37631a7de 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/DefaultPropertySetterCommonizerTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/descriptors/commonizer/core/DefaultPropertySetterCommonizerTest.kt @@ -62,7 +62,8 @@ class DefaultPropertySetterCommonizerTest : AbstractCommonizerTest() { @Test(expected = IllegalCommonizerStateException::class) fun classTypesInUserPackageWithDifferentNames1() = doTestFailure( mockClassType("org.sample.Foo"), - mockClassType("org.fictitiousPackageName.Foo") + mockClassType("org.fictitiousPackageName.Foo"), + shouldFailOnFirstVariant = true ) @Test(expected = IllegalCommonizerStateException::class) fun classTypesInUserPackageWithDifferentNames2() = doTestFailure( mockClassType("org.sample.Foo"), - mockClassType("org.sample.Bar") + mockClassType("org.sample.Bar"), + shouldFailOnFirstVariant = true ) @Test(expected = IllegalCommonizerStateException::class) fun classTypesInUserPackageWithDifferentNames3() = doTestFailure( mockClassType("org.sample.Foo"), - mockClassType("kotlin.String") + mockClassType("kotlin.String"), + shouldFailOnFirstVariant = true ) @Test @@ -256,13 +259,15 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest() { @Test(expected = IllegalCommonizerStateException::class) fun taTypesInUserPackageWithDifferentNames() = doTestFailure( mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo") }, - mockTAType("org.sample.BarAlias") { mockClassType("org.sample.Foo") } + mockTAType("org.sample.BarAlias") { mockClassType("org.sample.Foo") }, + shouldFailOnFirstVariant = true ) @Test(expected = IllegalCommonizerStateException::class) fun taTypesInUserPackageWithDifferentClasses() = doTestFailure( mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo") }, - mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Bar") } + mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Bar") }, + shouldFailOnFirstVariant = true ) @Test(expected = IllegalCommonizerStateException::class) @@ -275,7 +280,8 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest() { mockTAType("org.sample.FooAliasL2") { mockClassType("org.sample.Foo") } - } + }, + shouldFailOnFirstVariant = true ) @Test @@ -357,13 +363,15 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest() { @Test(expected = IllegalCommonizerStateException::class) fun taTypesInUserPackageWithDifferentNullability3() = doTestFailure( mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = false) }, - mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = true) } + mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = true) }, + shouldFailOnFirstVariant = true ) @Test(expected = IllegalCommonizerStateException::class) fun taTypesInUserPackageWithDifferentNullability4() = doTestFailure( mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = true) }, - mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = false) } + mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = false) }, + shouldFailOnFirstVariant = true ) private fun prepareCache(variants: Array) { @@ -409,10 +417,13 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest() { ) } - fun doTestFailure(vararg variants: KotlinType) { + fun doTestFailure(vararg variants: KotlinType, shouldFailOnFirstVariant: Boolean = false) { prepareCache(variants) - doTestFailure(variants = *variants.map(CirType.Companion::create).toTypedArray()) + doTestFailure( + variants = *variants.map(CirType.Companion::create).toTypedArray(), + shouldFailOnFirstVariant = shouldFailOnFirstVariant + ) } override fun createCommonizer() = TypeCommonizer.default(cache)