[Commonizer] Apply interner to avoid duplicated CirSimpleType objects

This commit is contained in:
Dmitriy Dolovov
2020-02-16 21:44:43 +07:00
parent 84bc151cb3
commit c88ea2f2e6
7 changed files with 115 additions and 35 deletions
@@ -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,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) {
@@ -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()
@@ -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)
} }
@@ -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)
} }
} }
@@ -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()
@@ -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)