[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].
|
* See also [AbstractStrictEqualityTypeChecker].
|
||||||
*/
|
*/
|
||||||
internal fun areTypesEqual(cache: CirClassifiersCache, a: CirType, b: CirType): Boolean = when {
|
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 CirSimpleType -> (b is CirSimpleType) && areSimpleTypesEqual(cache, a, b)
|
||||||
a is CirFlexibleType -> (b is CirFlexibleType)
|
a is CirFlexibleType -> (b is CirFlexibleType)
|
||||||
&& areSimpleTypesEqual(cache, a.lowerBound, b.lowerBound)
|
&& 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 {
|
private fun areSimpleTypesEqual(cache: CirClassifiersCache, a: CirSimpleType, b: CirSimpleType): Boolean {
|
||||||
if (a.arguments.size != b.arguments.size
|
if (a !== b
|
||||||
|| a.isMarkedNullable != b.isMarkedNullable
|
&& (a.arguments.size != b.arguments.size
|
||||||
|| a.isDefinitelyNotNullType != b.isDefinitelyNotNullType
|
|| a.isMarkedNullable != b.isMarkedNullable
|
||||||
|
|| a.isDefinitelyNotNullType != b.isDefinitelyNotNullType
|
||||||
|
|| a.fqName != b.fqName)
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.fqName != b.fqName)
|
|
||||||
return false
|
|
||||||
|
|
||||||
fun isClassOrTypeAliasUnderStandardKotlinPackages() =
|
fun isClassOrTypeAliasUnderStandardKotlinPackages() =
|
||||||
// N.B. only for descriptors that represent classes or type aliases, but not type parameters!
|
// N.B. only for descriptors that represent classes or type aliases, but not type parameters!
|
||||||
a.isClassOrTypeAlias && b.isClassOrTypeAlias
|
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.
|
// 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.
|
// 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.
|
// See org.jetbrains.kotlin.types.AbstractClassTypeConstructor.equals() for details.
|
||||||
&& a.expandedTypeConstructorId == b.expandedTypeConstructorId
|
&& (a === b || a.expandedTypeConstructorId == b.expandedTypeConstructorId)
|
||||||
|
|
||||||
fun descriptorsCanBeCommonizedThemselves() =
|
fun descriptorsCanBeCommonizedThemselves() =
|
||||||
a.kind == b.kind && when (a.kind) {
|
a.kind == b.kind && when (a.kind) {
|
||||||
|
|||||||
+20
@@ -20,6 +20,26 @@ class CirAnnotation(original: AnnotationDescriptor) {
|
|||||||
checkSupportedInCommonization(constantValue) { "${original::class.java}, $original[$name]" }
|
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) {
|
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.TypeParameterDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.commonizer.mergedtree.ir.CirSimpleTypeKind.CLASS
|
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.mergedtree.ir.CirSimpleTypeKind.TYPE_ALIAS
|
||||||
import org.jetbrains.kotlin.descriptors.commonizer.utils.declarationDescriptor
|
import org.jetbrains.kotlin.descriptors.commonizer.utils.*
|
||||||
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.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
|
|
||||||
@@ -22,8 +19,8 @@ sealed class CirType {
|
|||||||
companion object {
|
companion object {
|
||||||
fun create(type: KotlinType): CirType = type.unwrap().run {
|
fun create(type: KotlinType): CirType = type.unwrap().run {
|
||||||
when (this) {
|
when (this) {
|
||||||
is SimpleType -> CirSimpleType(this)
|
is SimpleType -> CirSimpleType.create(this)
|
||||||
is FlexibleType -> CirFlexibleType(CirSimpleType(lowerBound), CirSimpleType(upperBound))
|
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.
|
* 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 annotations: List<CirAnnotation>
|
||||||
val kind: CirSimpleTypeKind
|
val kind: CirSimpleTypeKind
|
||||||
val fqName: FqName
|
val fqName: FqName
|
||||||
@@ -63,8 +60,55 @@ class CirSimpleType(original: SimpleType) : CirType() {
|
|||||||
inline val isClassOrTypeAlias get() = (kind == CLASS || kind == TYPE_ALIAS)
|
inline val isClassOrTypeAlias get() = (kind == CLASS || kind == TYPE_ALIAS)
|
||||||
val fqNameWithTypeParameters = original.fqNameWithTypeParameters
|
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 equals(other: Any?) = fqNameWithTypeParameters == (other as? CirSimpleType)?.fqNameWithTypeParameters
|
||||||
override fun hashCode() = fqNameWithTypeParameters.hashCode()
|
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 {
|
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)
|
constructor(type: SimpleType) : this(type.fqNameInterned, type.constructor.parameters.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
class CirTypeProjection(original: TypeProjection) {
|
data class CirTypeProjection(
|
||||||
val projectionKind = original.projectionKind
|
val projectionKind: Variance,
|
||||||
val isStarProjection = original.isStarProjection
|
val isStarProjection: Boolean,
|
||||||
val type = CirType.create(original.type)
|
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()
|
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 name = original.name.intern()
|
||||||
override val typeParameters = original.declaredTypeParameters.map(::CirTypeParameterImpl)
|
override val typeParameters = original.declaredTypeParameters.map(::CirTypeParameterImpl)
|
||||||
override val visibility = original.visibility
|
override val visibility = original.visibility
|
||||||
override val underlyingType = CirSimpleType(original.underlyingType)
|
override val underlyingType = CirSimpleType.create(original.underlyingType)
|
||||||
override val expandedType = CirSimpleType(original.expandedType)
|
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")
|
if (!isEqual(expected, actual)) fail("Expected: $expected\nActual: $actual")
|
||||||
}
|
}
|
||||||
|
|
||||||
// should fail on the last variant
|
protected fun doTestFailure(
|
||||||
protected fun doTestFailure(vararg variants: T) {
|
vararg variants: T,
|
||||||
|
shouldFailOnFirstVariant: Boolean = false // by default should fail on the last variant
|
||||||
|
) {
|
||||||
check(variants.isNotEmpty())
|
check(variants.isNotEmpty())
|
||||||
|
|
||||||
|
val failureIndex = if (shouldFailOnFirstVariant) 0 else variants.size - 1
|
||||||
|
|
||||||
val commonized = createCommonizer().apply {
|
val commonized = createCommonizer().apply {
|
||||||
variants.forEachIndexed { index, variant ->
|
variants.forEachIndexed { index, variant ->
|
||||||
val result = commonizeWith(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?) =
|
private fun doTestFailure(vararg variants: Visibility?) =
|
||||||
super.doTestFailure(
|
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()
|
override fun createCommonizer() = PropertySetterCommonizer.default()
|
||||||
|
|||||||
+21
-10
@@ -100,19 +100,22 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
|||||||
@Test(expected = IllegalCommonizerStateException::class)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun classTypesInUserPackageWithDifferentNames1() = doTestFailure(
|
fun classTypesInUserPackageWithDifferentNames1() = doTestFailure(
|
||||||
mockClassType("org.sample.Foo"),
|
mockClassType("org.sample.Foo"),
|
||||||
mockClassType("org.fictitiousPackageName.Foo")
|
mockClassType("org.fictitiousPackageName.Foo"),
|
||||||
|
shouldFailOnFirstVariant = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test(expected = IllegalCommonizerStateException::class)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun classTypesInUserPackageWithDifferentNames2() = doTestFailure(
|
fun classTypesInUserPackageWithDifferentNames2() = doTestFailure(
|
||||||
mockClassType("org.sample.Foo"),
|
mockClassType("org.sample.Foo"),
|
||||||
mockClassType("org.sample.Bar")
|
mockClassType("org.sample.Bar"),
|
||||||
|
shouldFailOnFirstVariant = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test(expected = IllegalCommonizerStateException::class)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun classTypesInUserPackageWithDifferentNames3() = doTestFailure(
|
fun classTypesInUserPackageWithDifferentNames3() = doTestFailure(
|
||||||
mockClassType("org.sample.Foo"),
|
mockClassType("org.sample.Foo"),
|
||||||
mockClassType("kotlin.String")
|
mockClassType("kotlin.String"),
|
||||||
|
shouldFailOnFirstVariant = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -256,13 +259,15 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
|||||||
@Test(expected = IllegalCommonizerStateException::class)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun taTypesInUserPackageWithDifferentNames() = doTestFailure(
|
fun taTypesInUserPackageWithDifferentNames() = doTestFailure(
|
||||||
mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo") },
|
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)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun taTypesInUserPackageWithDifferentClasses() = doTestFailure(
|
fun taTypesInUserPackageWithDifferentClasses() = doTestFailure(
|
||||||
mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo") },
|
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)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
@@ -275,7 +280,8 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
|||||||
mockTAType("org.sample.FooAliasL2") {
|
mockTAType("org.sample.FooAliasL2") {
|
||||||
mockClassType("org.sample.Foo")
|
mockClassType("org.sample.Foo")
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
shouldFailOnFirstVariant = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -357,13 +363,15 @@ class DefaultTypeCommonizerTest : AbstractCommonizerTest<CirType, CirType>() {
|
|||||||
@Test(expected = IllegalCommonizerStateException::class)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun taTypesInUserPackageWithDifferentNullability3() = doTestFailure(
|
fun taTypesInUserPackageWithDifferentNullability3() = doTestFailure(
|
||||||
mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = false) },
|
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)
|
@Test(expected = IllegalCommonizerStateException::class)
|
||||||
fun taTypesInUserPackageWithDifferentNullability4() = doTestFailure(
|
fun taTypesInUserPackageWithDifferentNullability4() = doTestFailure(
|
||||||
mockTAType("org.sample.FooAlias") { mockClassType("org.sample.Foo", nullable = true) },
|
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>) {
|
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)
|
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)
|
override fun createCommonizer() = TypeCommonizer.default(cache)
|
||||||
|
|||||||
Reference in New Issue
Block a user