From f7839e847abd9f937496a19c7b206e34c2859af5 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Fri, 27 Aug 2021 15:06:00 +0200 Subject: [PATCH] [Commonizer] Emit 'UnsafeNumber' for optimistically commonized typealias ^KT-48459 Verification Pending --- .../kotlin/commonizer/core/Commonizer.kt | 2 +- .../NullableSingleInvocationCommonizer.kt | 22 +++++ .../core/OptimisticNumbersTypeCommonizer.kt | 17 +++- .../commonizer/core/TypeAliasCommonizer.kt | 81 ++++++++++++++++--- .../commonizer/mergedtree/nodeBuilders.kt | 2 +- ...UnsafeNumberAnnotationCommonizationTest.kt | 59 ++++++++++++++ 6 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt create mode 100644 native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalUnsafeNumberAnnotationCommonizationTest.kt diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/Commonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/Commonizer.kt index 656cdf3ce02..b19d2930702 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/Commonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/Commonizer.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.commonizer.core -interface Commonizer { +interface Commonizer { val result: R fun commonizeWith(next: T): Boolean } diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt new file mode 100644 index 00000000000..e22a59a4e7b --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt @@ -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 { + operator fun invoke(values: List): T? +} + +fun NullableSingleInvocationCommonizer.asCommonizer(): Commonizer = object : Commonizer { + private val collectedValues = mutableListOf() + + override val result: T? + get() = this@asCommonizer.invoke(collectedValues) + + override fun commonizeWith(next: T): Boolean { + collectedValues.add(next) + return true + } +} \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt index 785da516c3b..8db592f7c25 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.commonizer.cir.CirEntityId private typealias BitWidth = Int private class SubstitutableNumbers(private val numbers: Map) { + 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 { +internal object OptimisticNumbersTypeCommonizer : AssociativeCommonizer { override fun commonize(first: CirClassType, second: CirClassType): CirClassType? { return signedIntegers.choose(first, second) ?: unsignedIntegers.choose(first, second) @@ -77,4 +78,18 @@ object OptimisticNumbersTypeCommonizer : AssociativeCommonizer { ?: 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 + } } \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt index 1589e68d54e..2c51d0e596e 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt @@ -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 { +) : NullableSingleInvocationCommonizer { + override fun invoke(values: List): 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, + values: List, +): 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() + values.forEachIndexed forEach@{ index, ta -> + val existingAnnotation = ta.annotations.firstIsInstanceOrNull() + 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) : CirAnnotation { + override val type: CirClassType = UnsafeNumberAnnotation.type + override val annotationValueArguments: Map = emptyMap() + + override val constantValueArguments: Map = 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()) + } +} \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/nodeBuilders.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/nodeBuilders.kt index c1b58115656..2fb97974b87 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/nodeBuilders.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/mergedtree/nodeBuilders.kt @@ -129,7 +129,7 @@ private fun > buildNod storageManager: StorageManager, size: Int, nodeRelationship: CirNodeRelationship?, - commonizerProducer: () -> Commonizer, + commonizerProducer: () -> Commonizer, recursionMarker: R? = null, nodeProducer: (CommonizedGroup, NullableLazyValue) -> N ): N { diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalUnsafeNumberAnnotationCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalUnsafeNumberAnnotationCommonizationTest.kt new file mode 100644 index 00000000000..791a2a7c815 --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalUnsafeNumberAnnotationCommonizationTest.kt @@ -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) + """.trimIndent(), + "UnsafeNumberAnnotation.kt" + ) +} \ No newline at end of file