[Commonizer] Implement TypeNullabilityCommonizer and ReturnTypeCommonizer

^KT-48567 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-09-04 14:51:57 +02:00
committed by Space
parent b13f3599cf
commit 9e34382db5
10 changed files with 102 additions and 56 deletions
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.commonizer.core package org.jetbrains.kotlin.commonizer.core
import org.jetbrains.kotlin.commonizer.cir.* import org.jetbrains.kotlin.commonizer.cir.CirFunctionOrProperty
import org.jetbrains.kotlin.commonizer.cir.CirName
import org.jetbrains.kotlin.commonizer.cir.CirType
import org.jetbrains.kotlin.commonizer.core.TypeCommonizer.Options.Companion.default
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.commonizer.utils.safeCastValues
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED
@@ -45,19 +47,7 @@ private class ReturnTypeCommonizer(
override fun invoke(values: List<CirFunctionOrProperty>): CirType? { override fun invoke(values: List<CirFunctionOrProperty>): CirType? {
if (values.isEmpty()) return null if (values.isEmpty()) return null
val isTopLevel = values.all { it.containingClass == null } val isTopLevel = values.all { it.containingClass == null }
val returnTypes = if (isTopLevel) makeNullableIfNecessary(values.map { it.returnType }) else values.map { it.returnType } return TypeCommonizer(classifiers, default.withCovariantNullabilityCommonizationEnabled(isTopLevel))
return TypeCommonizer(classifiers).asCommonizer().commonize(returnTypes) .asCommonizer().commonize(values.map { it.returnType })
}
private fun makeNullableIfNecessary(types: List<CirType>): List<CirType> {
val simpleTypes = types.safeCastValues<CirType, CirSimpleType>() ?: return types
if (
simpleTypes.all { type -> type.isMarkedNullable } ||
simpleTypes.none { type -> type.isMarkedNullable }
) return types
return simpleTypes.map { type -> type.makeNullable() }
} }
} }
@@ -17,8 +17,8 @@ internal class ClassOrTypeAliasTypeCommonizer(
override fun commonize(first: CirClassOrTypeAliasType, second: CirClassOrTypeAliasType): CirClassOrTypeAliasType? { override fun commonize(first: CirClassOrTypeAliasType, second: CirClassOrTypeAliasType): CirClassOrTypeAliasType? {
if (first is CirClassType && second is CirClassType) { if (first is CirClassType && second is CirClassType) {
return ClassTypeCommonizer(classifiers).commonize(listOf(first, second)) return ClassTypeCommonizer(classifiers, options).commonize(listOf(first, second))
?: if (options.allowOptimisticNumberTypeCommonization) OptimisticNumbersTypeCommonizer.commonize(first, second) else null ?: if (options.enableOptimisticNumberTypeCommonization) OptimisticNumbersTypeCommonizer.commonize(first, second) else null
} }
if (first is CirTypeAliasType && second is CirTypeAliasType) { if (first is CirTypeAliasType && second is CirTypeAliasType) {
@@ -26,9 +26,10 @@ internal class ClassOrTypeAliasTypeCommonizer(
In case regular type-alias-type commonization fails, we try to expand all type-aliases and In case regular type-alias-type commonization fails, we try to expand all type-aliases and
try our luck with commonizing those class types try our luck with commonizing those class types
*/ */
return TypeAliasTypeCommonizer(classifiers).commonize(listOf(first, second)) return TypeAliasTypeCommonizer(classifiers, options).commonize(listOf(first, second))
?: ClassOrTypeAliasTypeCommonizer(classifiers, options.withAllowOptimisticNumberTypeCommonization()) ?: ClassOrTypeAliasTypeCommonizer(
.commonize(first.expandedType(), second.expandedType()) classifiers, options.withOptimisticNumberTypeCommonizationEnabled()
).commonize(first.expandedType(), second.expandedType())
} }
val classType = when { val classType = when {
@@ -47,7 +48,7 @@ internal class ClassOrTypeAliasTypeCommonizer(
TypeAliasCommonizer will be able to figure out if the typealias will be represented as expect class in common. TypeAliasCommonizer will be able to figure out if the typealias will be represented as expect class in common.
If so, re-use this class type, otherwise: try to expand the typeAlias If so, re-use this class type, otherwise: try to expand the typeAlias
*/ */
val typeAliasClassType = TypeAliasTypeCommonizer(classifiers).commonize(listOf(typeAliasType))?.expandedType() val typeAliasClassType = TypeAliasTypeCommonizer(classifiers, options).commonize(listOf(typeAliasType))?.expandedType()
?: typeAliasType.expandedType() ?: typeAliasType.expandedType()
return commonize(classType, typeAliasClassType) return commonize(classType, typeAliasClassType)
@@ -11,13 +11,16 @@ import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.commonizer.utils.isUnderKotlinNativeSyntheticPackages import org.jetbrains.kotlin.commonizer.utils.isUnderKotlinNativeSyntheticPackages
import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.descriptors.Visibility
internal class ClassTypeCommonizer(private val classifiers: CirKnownClassifiers) : internal class ClassTypeCommonizer(
private val classifiers: CirKnownClassifiers,
options: TypeCommonizer.Options = TypeCommonizer.Options.default
) :
AbstractStandardCommonizer<CirClassType, CirClassType>() { AbstractStandardCommonizer<CirClassType, CirClassType>() {
private lateinit var classId: CirEntityId private lateinit var classId: CirEntityId
private val outerType = OuterClassTypeCommonizer(classifiers) private val outerType = OuterClassTypeCommonizer(classifiers)
private lateinit var anyVisibility: Visibility private lateinit var anyVisibility: Visibility
private val arguments = TypeArgumentListCommonizer(classifiers) private val arguments = TypeArgumentListCommonizer(classifiers)
private var isMarkedNullable = false private val isMarkedNullable = TypeNullabilityCommonizer(options).asCommonizer()
override fun commonizationResult() = CirClassType.createInterned( override fun commonizationResult() = CirClassType.createInterned(
classId = classId, classId = classId,
@@ -28,17 +31,16 @@ internal class ClassTypeCommonizer(private val classifiers: CirKnownClassifiers)
// to reach better interning rate. // to reach better interning rate.
visibility = anyVisibility, visibility = anyVisibility,
arguments = arguments.result, arguments = arguments.result,
isMarkedNullable = isMarkedNullable isMarkedNullable = isMarkedNullable.result
) )
override fun initialize(first: CirClassType) { override fun initialize(first: CirClassType) {
classId = first.classifierId classId = first.classifierId
anyVisibility = first.visibility anyVisibility = first.visibility
isMarkedNullable = first.isMarkedNullable
} }
override fun doCommonizeWith(next: CirClassType) = override fun doCommonizeWith(next: CirClassType) =
isMarkedNullable == next.isMarkedNullable isMarkedNullable.commonizeWith(next.isMarkedNullable)
&& classId == next.classifierId && classId == next.classifierId
&& outerType.commonizeWith(next.outerType) && outerType.commonizeWith(next.outerType)
&& isClassifierAvailableInCommon(classifiers, classId) && isClassifierAvailableInCommon(classifiers, classId)
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.commonizer.core
interface NullableContextualSingleInvocationCommonizer<T : Any, R : Any> {
operator fun invoke(values: List<T>): R?
}
fun <Context : Any, R : Any> NullableContextualSingleInvocationCommonizer<Context, R>.asCommonizer(): Commonizer<Context, R?> =
object : Commonizer<Context, R?> {
private val collectedValues = mutableListOf<Context>()
override val result: R?
get() = this@asCommonizer.invoke(collectedValues)
override fun commonizeWith(next: Context): Boolean {
collectedValues.add(next)
return true
}
}
@@ -22,18 +22,3 @@ fun <T : Any> NullableSingleInvocationCommonizer<T>.asCommonizer(): Commonizer<T
} }
interface NullableContextualSingleInvocationCommonizer<T : Any, R : Any> {
operator fun invoke(values: List<T>): R?
}
fun <T : Any, R: Any> NullableContextualSingleInvocationCommonizer<T, R>.asCommonizer(): Commonizer<T, R?> = object : Commonizer<T, R?> {
private val collectedValues = mutableListOf<T>()
override val result: R?
get() = this@asCommonizer.invoke(collectedValues)
override fun commonizeWith(next: T): Boolean {
collectedValues.add(next)
return true
}
}
@@ -22,7 +22,7 @@ class TypeAliasCommonizer(
val typeParameters = TypeParameterListCommonizer(classifiers).commonize(values.map { it.typeParameters }) ?: return null val typeParameters = TypeParameterListCommonizer(classifiers).commonize(values.map { it.typeParameters }) ?: return null
val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withAllowOptimisticNumberTypeCommonization()) val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withOptimisticNumberTypeCommonizationEnabled())
.asCommonizer().commonize(values.map { it.underlyingType }) as? CirClassOrTypeAliasType ?: return null .asCommonizer().commonize(values.map { it.underlyingType }) as? CirClassOrTypeAliasType ?: return null
val visibility = VisibilityCommonizer.lowering().commonize(values) ?: return null val visibility = VisibilityCommonizer.lowering().commonize(values) ?: return null
@@ -10,13 +10,16 @@ import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.
import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.SUCCESS_FROM_DEPENDENCY_LIBRARY import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.SUCCESS_FROM_DEPENDENCY_LIBRARY
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
internal class TypeAliasTypeCommonizer(private val classifiers: CirKnownClassifiers) : internal class TypeAliasTypeCommonizer(
private val classifiers: CirKnownClassifiers,
options: TypeCommonizer.Options
) :
AbstractStandardCommonizer<CirTypeAliasType, CirClassOrTypeAliasType>() { AbstractStandardCommonizer<CirTypeAliasType, CirClassOrTypeAliasType>() {
private lateinit var typeAliasId: CirEntityId private lateinit var typeAliasId: CirEntityId
private val arguments = TypeArgumentListCommonizer(classifiers) private val arguments = TypeArgumentListCommonizer(classifiers)
private val underlyingTypeArguments = TypeArgumentListCommonizer(classifiers) private val underlyingTypeArguments = TypeArgumentListCommonizer(classifiers)
private var isMarkedNullable = false private val isMarkedNullable = TypeNullabilityCommonizer(options).asCommonizer()
private var commonizedTypeBuilder: CommonizedTypeAliasTypeBuilder? = null // null means not selected yet private var commonizedTypeBuilder: CommonizedTypeAliasTypeBuilder? = null // null means not selected yet
override fun commonizationResult() = override fun commonizationResult() =
@@ -24,17 +27,16 @@ internal class TypeAliasTypeCommonizer(private val classifiers: CirKnownClassifi
typeAliasId = typeAliasId, typeAliasId = typeAliasId,
arguments = arguments.result, arguments = arguments.result,
underlyingTypeArguments = underlyingTypeArguments.result, underlyingTypeArguments = underlyingTypeArguments.result,
isMarkedNullable = isMarkedNullable isMarkedNullable = isMarkedNullable.result
) )
override fun initialize(first: CirTypeAliasType) { override fun initialize(first: CirTypeAliasType) {
typeAliasId = first.classifierId typeAliasId = first.classifierId
isMarkedNullable = first.expandedType().isMarkedNullable
} }
override fun doCommonizeWith(next: CirTypeAliasType): Boolean { override fun doCommonizeWith(next: CirTypeAliasType): Boolean {
if (isMarkedNullable != next.expandedType().isMarkedNullable || typeAliasId != next.classifierId) if (typeAliasId != next.classifierId) return false
return false if (!isMarkedNullable.commonizeWith(next.expandedType().isMarkedNullable)) return false
if (commonizedTypeBuilder == null) { if (commonizedTypeBuilder == null) {
val answer = commonizeTypeAlias(typeAliasId, classifiers) val answer = commonizeTypeAlias(typeAliasId, classifiers)
@@ -30,12 +30,18 @@ class TypeCommonizer(
} }
data class Options( data class Options(
val allowOptimisticNumberTypeCommonization: Boolean = false val enableOptimisticNumberTypeCommonization: Boolean = false,
val enableCovariantNullabilityCommonization: Boolean = false
) { ) {
fun withAllowOptimisticNumberTypeCommonization(): Options { fun withOptimisticNumberTypeCommonizationEnabled(enabled: Boolean = true): Options {
return if (allowOptimisticNumberTypeCommonization) this return if (enableOptimisticNumberTypeCommonization == enabled) this
else copy(allowOptimisticNumberTypeCommonization = true) else copy(enableOptimisticNumberTypeCommonization = enabled)
}
fun withCovariantNullabilityCommonizationEnabled(enabled: Boolean = true): Options {
return if (enableCovariantNullabilityCommonization == enabled) this
else copy(enableCovariantNullabilityCommonization = enabled)
} }
companion object { companion object {
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.commonizer.core
private typealias IsMarkedNullable = Boolean
internal interface TypeNullabilityCommonizer : AssociativeCommonizer<IsMarkedNullable>
internal fun TypeNullabilityCommonizer(options: TypeCommonizer.Options): TypeNullabilityCommonizer {
return if (options.enableCovariantNullabilityCommonization) CovariantTypeNullabilityCommonizer
else EqualTypeNullabilityCommonizer
}
private object CovariantTypeNullabilityCommonizer : TypeNullabilityCommonizer {
override fun commonize(first: IsMarkedNullable, second: IsMarkedNullable): IsMarkedNullable {
return first || second
}
}
private object EqualTypeNullabilityCommonizer : TypeNullabilityCommonizer {
override fun commonize(first: IsMarkedNullable, second: IsMarkedNullable): IsMarkedNullable? {
if (first != second) return null
return first
}
}
@@ -92,7 +92,7 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio
result.assertCommonized( result.assertCommonized(
"(a, b)", """ "(a, b)", """
expect class X expect class X
expect fun x(): X expect fun x(): X?
""".trimIndent() """.trimIndent()
) )
} }
@@ -124,7 +124,9 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio
typealias Z = Any typealias Z = Any
expect class Y expect class Y
expect class X expect class X
fun x(): X
// Still cary nullability mark here to be extra safe
expect fun x(): X?
""".trimIndent() """.trimIndent()
) )
} }
@@ -156,7 +158,8 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio
typealias Z = Any typealias Z = Any
expect class Y expect class Y
expect class X expect class X
expect val x: X // Still cary nullability mark here to be more safe!
expect val x: X?
""".trimIndent() """.trimIndent()
) )
} }
@@ -176,6 +179,12 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio
result.assertCommonized("(a, b, c, d)", "expect val x: Any?") result.assertCommonized("(a, b, c, d)", "expect val x: Any?")
} }
/*
We expect *no* covariant nullability commonization on any member function/property because this might mess
with overrides of super classes/interfaces
*/
fun `test member - property - hierarchically`() { fun `test member - property - hierarchically`() {
val result = commonize { val result = commonize {
outputTarget("(a, b)", "(c, d)", "(a, b, c, d)") outputTarget("(a, b)", "(c, d)", "(a, b, c, d)")