[Commonizer] Apply interner to avoid duplicated CirSimpleType objects
This commit is contained in:
+6
-8
@@ -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) {
|
||||
|
||||
+20
@@ -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) {
|
||||
|
||||
+57
-11
@@ -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<CirAnnotation>
|
||||
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<Interned>()
|
||||
|
||||
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()
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+7
-3
@@ -33,14 +33,18 @@ abstract class AbstractCommonizerTest<T, R> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -62,7 +62,8 @@ class DefaultPropertySetterCommonizerTest : AbstractCommonizerTest<CirSetter?, C
|
||||
|
||||
private fun doTestFailure(vararg variants: Visibility?) =
|
||||
super.doTestFailure(
|
||||
*variants.map { it?.let(CirSetter.Companion::createDefaultNoAnnotations) }.toTypedArray()
|
||||
*variants.map { it?.let(CirSetter.Companion::createDefaultNoAnnotations) }.toTypedArray(),
|
||||
shouldFailOnFirstVariant = false
|
||||
)
|
||||
|
||||
override fun createCommonizer() = PropertySetterCommonizer.default()
|
||||
|
||||
+21
-10
@@ -100,19 +100,22 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
||||
@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<CirType, CirType>() {
|
||||
@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<CirType, CirType>() {
|
||||
mockTAType("org.sample.FooAliasL2") {
|
||||
mockClassType("org.sample.Foo")
|
||||
}
|
||||
}
|
||||
},
|
||||
shouldFailOnFirstVariant = true
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -357,13 +363,15 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
||||
@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<out KotlinType>) {
|
||||
@@ -409,10 +417,13 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user