[MPP] Apply @UnsafeNumber to functions and properties

KT-51224
KT-51215
This commit is contained in:
Pavel Kirpichenkov
2022-02-14 10:41:03 +03:00
committed by teamcity
parent f8a1395a03
commit 6674c3f482
14 changed files with 216 additions and 75 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ rootProject.apply {
IdeVersionConfigurator.setCurrentIde(project)
if (!project.hasProperty("versions.kotlin-native")) {
extra["versions.kotlin-native"] = "1.7.0-dev-1132"
extra["versions.kotlin-native"] = "1.7.0-dev-1827"
}
@@ -6,8 +6,7 @@
package kotlinx.cinterop
/**
* Marker for typealias that will represent numbers of different bit width on at least two platforms,
* or function/property that have such numbers in their signature.
* Marker for declarations that depend on numeric types of different bit width on at least two platforms.
*
* @param actualPlatformTypes: Contains platform types represented as `{konanTarget}: {type fqn}`
* e.g. ["linux_x64: kotlin.Int", "linux_arm64: kotlin.Long"]
@@ -5,15 +5,20 @@
package org.jetbrains.kotlin.commonizer.core
import org.jetbrains.kotlin.commonizer.CommonizerSettings
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.commonizer.utils.singleDistinctValueOrNull
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class FunctionOrPropertyBaseCommonizer(
private val classifiers: CirKnownClassifiers,
private val settings: CommonizerSettings,
private val typeCommonizer: TypeCommonizer,
private val extensionReceiverCommonizer: ExtensionReceiverCommonizer = ExtensionReceiverCommonizer(typeCommonizer),
private val returnTypeCommonizer: ReturnTypeCommonizer = ReturnTypeCommonizer(typeCommonizer),
@@ -26,7 +31,8 @@ class FunctionOrPropertyBaseCommonizer(
val visibility: Visibility,
val extensionReceiver: CirExtensionReceiver?,
val returnType: CirType,
val typeParameters: List<CirTypeParameter>
val typeParameters: List<CirTypeParameter>,
val additionalAnnotations: List<CirAnnotation>,
)
override fun invoke(values: List<CirFunctionOrProperty>): FunctionOrProperty? {
@@ -43,6 +49,12 @@ class FunctionOrPropertyBaseCommonizer(
return null
}
val additionalUnsafeNumberAnnotation =
createUnsafeNumberAnnotationIfNecessary(
classifiers.classifierIndices.targets, settings, values,
getTypeIdFromDeclarationForCheck = ::getFunctionOrPropertyReturnTypeId,
)
return FunctionOrProperty(
name = values.first().name,
kind = values.singleDistinctValueOrNull { it.kind } ?: return null,
@@ -50,8 +62,18 @@ class FunctionOrPropertyBaseCommonizer(
visibility = VisibilityCommonizer.lowering().commonize(values) ?: return null,
extensionReceiver = (extensionReceiverCommonizer(values.map { it.extensionReceiver }) ?: return null).receiver,
returnType = returnTypeCommonizer(values) ?: return null,
typeParameters = TypeParameterListCommonizer(typeCommonizer).commonize(values.map { it.typeParameters }) ?: return null
typeParameters = TypeParameterListCommonizer(typeCommonizer).commonize(values.map { it.typeParameters }) ?: return null,
additionalAnnotations = listOfNotNull(additionalUnsafeNumberAnnotation)
)
}
}
private fun getFunctionOrPropertyReturnTypeId(functionOrProperty: CirFunctionOrProperty): CirEntityId? {
return functionOrProperty.returnType.let { returnType ->
when (returnType) {
is CirFlexibleType -> returnType.lowerBound.safeAs<CirClassOrTypeAliasType>()?.classifierId
else -> returnType.safeAs<CirClassOrTypeAliasType>()?.classifierId
}
}
}
@@ -16,7 +16,9 @@ class FunctionCommonizer(
val functionOrProperty = functionOrPropertyBaseCommonizer(values) ?: return null
val valueParametersResult = CallableValueParametersCommonizer(typeCommonizer).commonize(values) ?: return null
return CirFunction(
annotations = AnnotationsCommonizer().commonize(values.map { it.annotations }) ?: return null,
annotations = AnnotationsCommonizer().commonize(values.map { it.annotations })
?.plus(functionOrProperty.additionalAnnotations)
?: return null,
name = values.first().name,
typeParameters = functionOrProperty.typeParameters,
visibility = functionOrProperty.visibility,
@@ -30,7 +30,7 @@ class PropertyCommonizer(
val constCompileTimeInitializer = (constCommonizationState as? ConstSameValue)?.compileTimeInitializer
return CirProperty(
annotations = emptyList(),
annotations = functionOrPropertyBase.additionalAnnotations,
name = functionOrPropertyBase.name,
typeParameters = functionOrPropertyBase.typeParameters,
visibility = functionOrPropertyBase.visibility,
@@ -6,12 +6,8 @@
package org.jetbrains.kotlin.commonizer.core
import org.jetbrains.kotlin.commonizer.CommonizerSettings
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.commonizer.OptimisticNumberCommonizationEnabledKey
import org.jetbrains.kotlin.commonizer.allLeaves
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class TypeAliasCommonizer(
typeCommonizer: TypeCommonizer,
@@ -41,67 +37,11 @@ class TypeAliasCommonizer(
underlyingType = underlyingType,
expandedType = underlyingType.expandedType(),
annotations = listOfNotNull(
createUnsafeNumberAnnotationIfNecessary(classifiers.classifierIndices.targets, settings, values)
createUnsafeNumberAnnotationIfNecessary(
classifiers.classifierIndices.targets, settings, values,
getTypeIdFromDeclarationForCheck = { typeAlias -> typeAlias.expandedType.classifierId }
)
)
)
}
}
private fun createUnsafeNumberAnnotationIfNecessary(
targets: List<CommonizerTarget>,
settings: CommonizerSettings,
values: List<CirTypeAlias>,
): CirAnnotation? {
val isOptimisticCommonizationEnabled = settings.getSetting(OptimisticNumberCommonizationEnabledKey)
if (!isOptimisticCommonizationEnabled)
return null
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.toQualifiedNameString()}") }
)
)
companion object {
private val type = CirClassType.createInterned(
classId = CirEntityId.create("kotlinx/cinterop/UnsafeNumber"),
outerType = null,
arguments = emptyList(),
isMarkedNullable = false
)
val empty = UnsafeNumberAnnotation(emptyMap())
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2022 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
import org.jetbrains.kotlin.commonizer.CommonizerSettings
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.commonizer.OptimisticNumberCommonizationEnabledKey
import org.jetbrains.kotlin.commonizer.allLeaves
import org.jetbrains.kotlin.commonizer.cir.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
internal fun <T : CirHasAnnotations> createUnsafeNumberAnnotationIfNecessary(
targets: List<CommonizerTarget>,
settings: CommonizerSettings,
values: List<T>,
getTypeIdFromDeclarationForCheck: (declaration: T) -> CirEntityId?,
): CirAnnotation? {
val isOptimisticCommonizationEnabled = settings.getSetting(OptimisticNumberCommonizationEnabledKey)
if (!isOptimisticCommonizationEnabled)
return null
val typeIds = values.map { annotated -> getTypeIdFromDeclarationForCheck(annotated) }
// All typealias have to be potentially substitutable (aka have to be some kind of number type)
if (!typeIds.all { it != null && OptimisticNumbersTypeCommonizer.isOptimisticallySubstitutable(it) }) {
return null
}
return createUnsafeNumberAnnotation(targets, values, getTypeIdFromDeclarationForCheck)
}
private fun <T : CirHasAnnotations> createUnsafeNumberAnnotation(
targets: List<CommonizerTarget>,
values: List<T>,
getTypeIdFromDeclaration: (declaration: T) -> CirEntityId?,
): CirAnnotation? {
val actualPlatformTypes = mutableMapOf<String, CirEntityId>()
values.forEachIndexed forEach@{ index, annotated ->
val existingAnnotation = annotated.annotations.firstIsInstanceOrNull<UnsafeNumberAnnotation>()
if (existingAnnotation != null) {
actualPlatformTypes.putAll(existingAnnotation.actualPlatformTypes)
return@forEach
}
targets[index].allLeaves().forEach { target ->
actualPlatformTypes[target.name] = getTypeIdFromDeclaration(annotated)
?: throw IllegalStateException("Expect class or type alias type")
}
}
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.toSortedMap().map { (platform, type) ->
CirConstantValue.StringValue("$platform: ${type.toQualifiedNameString()}")
}
)
)
companion object {
private val type = CirClassType.createInterned(
classId = CirEntityId.create("kotlinx/cinterop/UnsafeNumber"),
outerType = null,
arguments = emptyList(),
isMarkedNullable = false
)
}
}
@@ -59,7 +59,9 @@ internal fun buildPropertyNode(
storageManager = storageManager,
size = size,
nodeRelationship = nodeRelationship,
commonizerProducer = { PropertyCommonizer(FunctionOrPropertyBaseCommonizer(TypeCommonizer(classifiers, settings))) },
commonizerProducer = {
PropertyCommonizer(FunctionOrPropertyBaseCommonizer(classifiers, settings, TypeCommonizer(classifiers, settings)))
},
nodeProducer = ::CirPropertyNode
)
@@ -75,7 +77,7 @@ internal fun buildFunctionNode(
nodeRelationship = nodeRelationship,
commonizerProducer = {
val typeCommonizer = TypeCommonizer(classifiers, settings)
FunctionCommonizer(typeCommonizer, FunctionOrPropertyBaseCommonizer(typeCommonizer)).asCommonizer()
FunctionCommonizer(typeCommonizer, FunctionOrPropertyBaseCommonizer(classifiers, settings, typeCommonizer)).asCommonizer()
},
nodeProducer = ::CirFunctionNode
)
@@ -28,9 +28,13 @@ expect fun function6(): Planet
expect fun function7(): C
// Optimistic Number Commonization: KT-48455, KT-48568
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Int", "jvm: kotlin.Long"])
expect val propertyWithMismatchedType1: Int
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Int", "jvm: kotlin.Short"])
expect val propertyWithMismatchedType2: Short
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Int", "jvm: kotlin.Long"])
expect fun functionWithMismatchedType1(): Int
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Int", "jvm: kotlin.Short"])
expect fun functionWithMismatchedType2(): Short
expect class Box<T>(value: T) {
@@ -0,0 +1,4 @@
package kotlinx.cinterop
@Target(AnnotationTarget.TYPEALIAS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY)
annotation class UnsafeNumber(val actualPlatformTypes: Array<String>)
@@ -22,7 +22,11 @@ expect val property20: Char
// Optimistic Number Commonization: KT-48455, KT-48568
// Mismatched const types should be commonized as expect val's
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Short", "jvm: kotlin.Byte"])
expect val property22: Byte
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Int", "jvm: kotlin.Short"])
expect val property23: Short
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Long", "jvm: kotlin.Int"])
expect val property24: Int
@kotlinx.cinterop.UnsafeNumber(["js: kotlin.Float", "jvm: kotlin.Double"])
expect val property26: Float
@@ -0,0 +1,4 @@
package kotlinx.cinterop
@Target(AnnotationTarget.TYPEALIAS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY)
annotation class UnsafeNumber(val actualPlatformTypes: Array<String>)
@@ -707,12 +707,18 @@ class HierarchicalOptimisticNumbersTypeCommonizerTest : AbstractInlineSourcesCom
val result = commonize {
outputTarget("(a, b)")
setting(OptimisticNumberCommonizationEnabledKey, true)
registerFakeStdlibIntegersDependency("(a, b)")
registerDependency("a", "b", "(a, b)") { unsignedIntegers() }
simpleSingleSourceTarget("a", "val x: UInt = null!!")
simpleSingleSourceTarget("b", "val x: ULong = null!!")
}
result.assertCommonized("(a, b)", "expect val x: kotlin.UInt")
result.assertCommonized(
"(a, b)", """
@UnsafeNumber(["a: kotlin.UInt", "b: kotlin.ULong"])
expect val x: kotlin.UInt
""".trimIndent()
)
}
fun `test property with aliased number return type`() {
@@ -790,4 +796,75 @@ class HierarchicalOptimisticNumbersTypeCommonizerTest : AbstractInlineSourcesCom
""".trimIndent()
)
}
// return types, unlike parameter types, don't participate in function signature, and therefore can be commonized optimistically
fun `test optimistic commonization in function return types`() {
val result = commonize {
outputTarget("(a, b)")
setting(OptimisticNumberCommonizationEnabledKey, true)
registerFakeStdlibIntegersDependency(("(a, b)"))
"a" withSource """
fun explicitReturnType(): Short {
return 42
}
fun implicitReturnType() = 42
""".trimIndent()
"b" withSource """
fun explicitReturnType(): Long {
return 42L
}
fun implicitReturnType() = 42L
""".trimIndent()
}
result.assertCommonized(
"(a, b)", """
import kotlinx.cinterop.*
@UnsafeNumber(["a: kotlin.Short", "b: kotlin.Long"])
expect fun explicitReturnType(): Short
@UnsafeNumber(["a: kotlin.Int", "b: kotlin.Long"])
expect fun implicitReturnType(): Int
""".trimIndent()
)
}
fun `test optimistic commonization in property return types`() {
val result = commonize {
outputTarget("(a, b)")
setting(OptimisticNumberCommonizationEnabledKey, true)
registerFakeStdlibIntegersDependency(("(a, b)"))
"a" withSource """
val explicitReturnType: Short
get() = 42
val implicitReturnType = 42
""".trimIndent()
"b" withSource """
val explicitReturnType: Long
get() = 42L
val implicitReturnType = 42L
""".trimIndent()
}
result.assertCommonized(
"(a, b)", """
import kotlinx.cinterop.*
@UnsafeNumber(["a: kotlin.Short", "b: kotlin.Long"])
expect val explicitReturnType: Short
@UnsafeNumber(["a: kotlin.Int", "b: kotlin.Long"])
expect val implicitReturnType: Int
""".trimIndent()
)
}
}
@@ -60,7 +60,7 @@ internal fun InlineSourceBuilder.ModuleBuilder.unsafeNumberAnnotationSource() {
source(
"""
package kotlinx.cinterop
@Target(AnnotationTarget.TYPEALIAS)
@Target(AnnotationTarget.TYPEALIAS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY)
annotation class UnsafeNumber(val actualPlatformTypes: Array<String>)
""".trimIndent(),