[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].
*/
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,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) {
@@ -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()
@@ -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)
}
@@ -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)
}
}
@@ -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()
@@ -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)