[Commonizer] Emit 'UnsafeNumber' for optimistically commonized typealias

^KT-48459 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-08-27 15:06:00 +02:00
committed by Space
parent bff62aba5f
commit f7839e847a
6 changed files with 169 additions and 14 deletions
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.commonizer.core
interface Commonizer<T, R> {
interface Commonizer<T, out R> {
val result: R
fun commonizeWith(next: T): Boolean
}
@@ -0,0 +1,22 @@
/*
* 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 NullableSingleInvocationCommonizer<T : Any> {
operator fun invoke(values: List<T>): T?
}
fun <T : Any> NullableSingleInvocationCommonizer<T>.asCommonizer(): Commonizer<T, T?> = object : Commonizer<T, T?> {
private val collectedValues = mutableListOf<T>()
override val result: T?
get() = this@asCommonizer.invoke(collectedValues)
override fun commonizeWith(next: T): Boolean {
collectedValues.add(next)
return true
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.commonizer.cir.CirEntityId
private typealias BitWidth = Int
private class SubstitutableNumbers(private val numbers: Map<CirEntityId, BitWidth>) {
operator fun contains(id: CirEntityId) = id in numbers
fun choose(first: CirClassType, second: CirClassType): CirClassType? {
val firstBitWidth = numbers[first.classifierId] ?: return null
val secondBitWidth = numbers[second.classifierId] ?: return null
@@ -68,7 +69,7 @@ private val floatingPointVars = SubstitutableNumbers(
)
)
object OptimisticNumbersTypeCommonizer : AssociativeCommonizer<CirClassType> {
internal object OptimisticNumbersTypeCommonizer : AssociativeCommonizer<CirClassType> {
override fun commonize(first: CirClassType, second: CirClassType): CirClassType? {
return signedIntegers.choose(first, second)
?: unsignedIntegers.choose(first, second)
@@ -77,4 +78,18 @@ object OptimisticNumbersTypeCommonizer : AssociativeCommonizer<CirClassType> {
?: unsignedVarIntegers.choose(first, second)
?: floatingPointVars.choose(first, second)
}
fun isOptimisticallySubstitutable(classId: CirEntityId): Boolean {
val firstPackageSegment = classId.packageName.segments.firstOrNull()
if (firstPackageSegment != "kotlinx" && firstPackageSegment != "kotlin") {
return false
}
return classId in signedIntegers
|| classId in unsignedIntegers
|| classId in floatingPoints
|| classId in signedVarIntegers
|| classId in unsignedVarIntegers
|| classId in floatingPointVars
}
}
@@ -5,32 +5,91 @@
package org.jetbrains.kotlin.commonizer.core
import org.jetbrains.kotlin.commonizer.cir.CirClassOrTypeAliasType
import org.jetbrains.kotlin.commonizer.cir.CirTypeAlias
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.commonizer.allLeaves
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class TypeAliasCommonizer(
private val classifiers: CirKnownClassifiers
) : AssociativeCommonizer<CirTypeAlias> {
) : NullableSingleInvocationCommonizer<CirTypeAlias> {
override fun invoke(values: List<CirTypeAlias>): CirTypeAlias? {
if (values.isEmpty()) return null
override fun commonize(first: CirTypeAlias, second: CirTypeAlias): CirTypeAlias? {
val name = if (first.name == second.name) first.name else return null
val name = values.map { it.name }.distinct().singleOrNull() ?: return null
val typeParameters = TypeParameterListCommonizer(classifiers)
.commonize(listOf(first.typeParameters, second.typeParameters)) ?: return null
val typeParameters = TypeParameterListCommonizer(classifiers).commonize(values.map { it.typeParameters }) ?: return null
val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withAllowOptimisticNumberTypeCommonization())
.commonize(first.underlyingType, second.underlyingType) as? CirClassOrTypeAliasType ?: return null
.asCommonizer().commonize(values.map { it.underlyingType }) as? CirClassOrTypeAliasType ?: return null
val visibility = VisibilityCommonizer.lowering().commonize(listOf(first, second)) ?: return null
val visibility = VisibilityCommonizer.lowering().commonize(values) ?: return null
return CirTypeAlias.create(
annotations = emptyList(),
name = name,
typeParameters = typeParameters,
visibility = visibility,
underlyingType = underlyingType,
expandedType = underlyingType.expandedType()
expandedType = underlyingType.expandedType(),
annotations = listOfNotNull(
createUnsafeNumberAnnotationIfNecessary(classifiers.classifierIndices.targets, values)
)
)
}
}
private fun createUnsafeNumberAnnotationIfNecessary(
targets: List<CommonizerTarget>,
values: List<CirTypeAlias>,
): CirAnnotation? {
val expandedTypes = values.map { it.expandedType.classifierId }
// All typealias have to be potentially substitutable (aka have to be some kind of number type)
if (!expandedTypes.all { OptimisticNumbersTypeCommonizer.isOptimisticallySubstitutable(it) }) {
return null
}
val actualPlatformTypes = mutableMapOf<String, CirEntityId>()
values.forEachIndexed forEach@{ index, ta ->
val existingAnnotation = ta.annotations.firstIsInstanceOrNull<UnsafeNumberAnnotation>()
if (existingAnnotation != null) {
actualPlatformTypes.putAll(existingAnnotation.actualPlatformTypes)
return@forEach
}
targets[index].allLeaves().forEach { target ->
actualPlatformTypes[target.name] = ta.expandedType.classifierId
}
}
if (actualPlatformTypes.values.distinct().size > 1) {
return UnsafeNumberAnnotation(actualPlatformTypes)
}
return null
}
private class UnsafeNumberAnnotation(val actualPlatformTypes: Map<String, CirEntityId>) : CirAnnotation {
override val type: CirClassType = UnsafeNumberAnnotation.type
override val annotationValueArguments: Map<CirName, CirAnnotation> = emptyMap()
override val constantValueArguments: Map<CirName, CirConstantValue> = mapOf(
CirName.create("actualPlatformTypes") to CirConstantValue.ArrayValue(
actualPlatformTypes.map { (platform, type) -> CirConstantValue.StringValue("$platform: $type") }
)
)
companion object {
private val type = CirClassType.createInterned(
classId = CirEntityId.create("kotlinx/cinterop/UnsafeNumber"),
outerType = null,
visibility = Visibilities.Public,
arguments = emptyList(),
isMarkedNullable = false
)
val empty = UnsafeNumberAnnotation(emptyMap())
}
}
@@ -129,7 +129,7 @@ private fun <T : CirDeclaration, R : CirDeclaration, N : CirNode<T, R>> buildNod
storageManager: StorageManager,
size: Int,
nodeRelationship: CirNodeRelationship?,
commonizerProducer: () -> Commonizer<T, R>,
commonizerProducer: () -> Commonizer<T, R?>,
recursionMarker: R? = null,
nodeProducer: (CommonizedGroup<T>, NullableLazyValue<R>) -> N
): N {
@@ -0,0 +1,59 @@
/*
* 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.hierarchical
import org.jetbrains.kotlin.commonizer.AbstractInlineSourcesCommonizationTest
import org.jetbrains.kotlin.commonizer.assertCommonized
import org.jetbrains.kotlin.commonizer.utils.InlineSourceBuilder
class HierarchicalUnsafeNumberAnnotationCommonizationTest : AbstractInlineSourcesCommonizationTest() {
fun `test multiple targets`() {
val result = commonize {
outputTarget("(a, b)", "(c, d)", "(a, b, c, d)")
registerDependency("a", "b", "c", "d", "(a, b)", "(c, d)", "(a, b, c, d)") { unsafeNumberAnnotationSource() }
simpleSingleSourceTarget("a", "typealias X = Short")
simpleSingleSourceTarget("b", "typealias X = Int")
simpleSingleSourceTarget("c", "typealias X = Int")
simpleSingleSourceTarget("d", "typealias X = Long")
}
result.assertCommonized(
"(a, b)", """
@kotlinx.cinterop.UnsafeNumber(["a: kotlin/Short", "b: kotlin/Int"])
typealias X = Int
""".trimIndent()
)
result.assertCommonized(
"(c, d)", """
import kotlinx.cinterop.*
@UnsafeNumber(["c: kotlin/Int", "d: kotlin/Long"])
typealias X = Long
""".trimIndent()
)
result.assertCommonized(
"(a, b, c, d)", """
import kotlinx.cinterop.*
@UnsafeNumber(["a: kotlin/Short", "b: kotlin/Int", "c: kotlin/Int", "d: kotlin/Long"])
typealias X = Long
""".trimIndent()
)
}
}
private fun InlineSourceBuilder.ModuleBuilder.unsafeNumberAnnotationSource() {
source(
"""
package kotlinx.cinterop
@Target(AnnotationTarget.TYPEALIAS)
@Retention(AnnotationRetention.BINARY)
annotation class UnsafeNumber(val actualPlatformTypes: Array<String>)
""".trimIndent(),
"UnsafeNumberAnnotation.kt"
)
}