diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt index 9a6efc50cb2..1e61bcb8c73 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt @@ -31,3 +31,46 @@ public annotation class CCall(val id: String) { @Retention(AnnotationRetention.BINARY) annotation class Consumed } + +/** + * Collection of annotations that allow to store + * constant values. + */ +public object ConstantValue { + @Retention(AnnotationRetention.BINARY) + annotation class Byte(val value: kotlin.Byte) + @Retention(AnnotationRetention.BINARY) + annotation class Short(val value: kotlin.Short) + @Retention(AnnotationRetention.BINARY) + annotation class Int(val value: kotlin.Int) + @Retention(AnnotationRetention.BINARY) + annotation class Long(val value: kotlin.Long) + @Retention(AnnotationRetention.BINARY) + annotation class UByte(val value: kotlin.UByte) + @Retention(AnnotationRetention.BINARY) + annotation class UShort(val value: kotlin.UShort) + @Retention(AnnotationRetention.BINARY) + annotation class UInt(val value: kotlin.UInt) + @Retention(AnnotationRetention.BINARY) + annotation class ULong(val value: kotlin.ULong) + @Retention(AnnotationRetention.BINARY) + annotation class Float(val value: kotlin.Float) + @Retention(AnnotationRetention.BINARY) + annotation class Double(val value: kotlin.Double) + @Retention(AnnotationRetention.BINARY) + annotation class String(val value: kotlin.String) +} + +/** + * Denotes property that is an alias to some enum entry. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +public annotation class CEnumEntryAlias(val entryName: String) + +/** + * Stores instance size of the type T: CEnumVar. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +public annotation class CEnumVarTypeSize(val size: Int) \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt index bdec5b7e92f..a2641da4080 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt @@ -211,6 +211,13 @@ sealed class AnnotationStub(val classifier: Classifier) { class Deprecated(val message: String, val replaceWith: String) : AnnotationStub(Classifier.topLevel("kotlin", "Deprecated")) + + class CEnumEntryAlias(val entryName: String) : + AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumEntryAlias")) + + class CEnumVarTypeSize(val size: Int) : + AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumVarTypeSize")) + private companion object { val cCallClassifier = Classifier.topLevel(cinteropInternalPackage, "CCall") } @@ -407,6 +414,11 @@ sealed class PropertyAccessor : FunctionalStub { override val annotations: List = emptyList() val typeParameters: List = listOf(pointedType) } + + class GetEnumEntry( + val enumEntryStub: EnumEntryStub, + override val annotations: List = emptyList() + ) : Getter() } sealed class Setter : PropertyAccessor() { @@ -472,11 +484,9 @@ class ConstructorStub( class EnumEntryStub( val name: String, val constant: IntegralConstantStub, - val aliases: List, - val origin: StubOrigin.EnumEntry -) { - class Alias(val name: String) -} + val origin: StubOrigin.EnumEntry, + val ordinal: Int +) class TypealiasStub( val alias: Classifier, diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt index 69dd06dcbb7..3efc63fc2a4 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt @@ -93,6 +93,7 @@ class StubIrBridgeBuilder( when { element.external -> tryProcessCCallAnnotation(element) element.isOptionalObjCMethod() -> { } + element.origin is StubOrigin.Synthetic.EnumByValue -> { } owner != null && owner.isInterface -> { } else -> generateBridgeBody(element) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt index f84c56f1e8c..668f05fd564 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt @@ -4,6 +4,7 @@ */ package org.jetbrains.kotlin.native.interop.gen +import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform import org.jetbrains.kotlin.native.interop.indexer.* @@ -200,16 +201,16 @@ internal class EnumStubBuilder( override val context: StubsBuildingContext, private val enumDef: EnumDef ) : StubElementBuilder { + + private val classifier = (context.mirror(EnumType(enumDef)) as TypeMirror.ByValue).valueType.classifier + private val baseTypeMirror = context.mirror(enumDef.baseType) + private val baseType = baseTypeMirror.argType.toStubIrType() + override fun build(): List { if (!context.isStrictEnum(enumDef)) { return generateEnumAsConstants(enumDef) } - val baseTypeMirror = context.mirror(enumDef.baseType) - val baseType = baseTypeMirror.argType.toStubIrType() - - val clazz = (context.mirror(EnumType(enumDef)) as TypeMirror.ByValue).valueType.classifier val constructorParameter = FunctionParameterStub("value", baseType) - val valueProperty = PropertyStub( name = "value", type = baseType, @@ -229,25 +230,119 @@ internal class EnumStubBuilder( } val (canonicalConstants, aliasConstants) = enumDef.constants.partition { canonicalsByValue[it.value] == it } - val canonicalEntries = canonicalConstants.map { constant -> - val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value) - ?: error("Cannot create enum value ${constant.value} of type ${enumDef.baseType}") - val aliases = aliasConstants.filter { it.value == constant.value }.map { EnumEntryStub.Alias(it.name) } - EnumEntryStub(constant.name, literal, aliases, StubOrigin.EnumEntry(constant)) - } + val canonicalEntriesWithAliases = canonicalConstants + .sortedBy { it.value } // TODO: Is it stable enough? + .mapIndexed { index, constant -> + val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value) + ?: error("Cannot create enum value ${constant.value} of type ${enumDef.baseType}") + val entry = EnumEntryStub(constant.name, literal, StubOrigin.EnumEntry(constant), index) + val aliases = aliasConstants + .filter { it.value == constant.value } + .map { constructAliasProperty(it, entry) } + entry to aliases + } val origin = StubOrigin.Enum(enumDef) - val primaryConstructor = ConstructorStub(listOf(constructorParameter), emptyList(), isPrimary = true, origin = origin) - val enum = ClassStub.Enum(clazz, canonicalEntries, + val primaryConstructor = ConstructorStub( + parameters = listOf(constructorParameter), + annotations = emptyList(), + isPrimary = true, + origin = origin, + visibility = VisibilityModifier.PRIVATE + ) + + val byValueFunction = FunctionStub( + name = "byValue", + returnType = ClassifierStubType(classifier), + parameters = listOf(FunctionParameterStub("value", baseType)), + origin = StubOrigin.Synthetic.EnumByValue(enumDef), + receiver = null, + modality = MemberStubModality.FINAL, + annotations = emptyList() + ) + + val companion = ClassStub.Companion( + classifier = classifier.nested("Companion"), + properties = canonicalEntriesWithAliases.flatMap { it.second }, + methods = listOf(byValueFunction) + ) + val enumVarClass = constructEnumVarClass().takeIf { context.generationMode == GenerationMode.METADATA } + val kotlinEnumType = ClassifierStubType(Classifier.topLevel("kotlin", "Enum"), + listOf(TypeArgumentStub(ClassifierStubType(classifier)))) + val enum = ClassStub.Enum( + classifier = classifier, + superClassInit = SuperClassInit(kotlinEnumType), + entries = canonicalEntriesWithAliases.map { it.first }, + companion = companion, constructors = listOf(primaryConstructor), properties = listOf(valueProperty), origin = origin, - interfaces = listOf(context.platform.getRuntimeType("CEnum")) + interfaces = listOf(context.platform.getRuntimeType("CEnum")), + childrenClasses = listOfNotNull(enumVarClass) ) context.bridgeComponentsBuilder.enumToTypeMirror[enum] = baseTypeMirror - return listOf(enum) } + private fun constructAliasProperty(enumConstant: EnumConstant, entry: EnumEntryStub): PropertyStub { + val aliasAnnotation = AnnotationStub.CEnumEntryAlias(entry.name) + .takeIf { context.generationMode == GenerationMode.METADATA } + return PropertyStub( + enumConstant.name, + ClassifierStubType(classifier), + kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.GetEnumEntry(entry)), + origin = StubOrigin.EnumEntry(enumConstant), + annotations = listOfNotNull(aliasAnnotation) + ) + } + + private fun constructEnumVarClass(): ClassStub.Simple { + + val enumVarClassifier = classifier.nested("Var") + + val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr")) + val superClass = context.platform.getRuntimeType("CEnumVar") + require(superClass is ClassifierStubType) + val primaryConstructor = ConstructorStub( + parameters = listOf(rawPtrConstructorParam), + isPrimary = true, + annotations = emptyList(), + origin = StubOrigin.Synthetic.DefaultConstructor + ) + val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam))) + + val baseIntegerTypeSize = when (val unwrappedType = enumDef.baseType.unwrapTypedefs()) { + is IntegerType -> unwrappedType.size.toLong() + CharType -> 1L + else -> error("Incorrect base type for enum ${classifier.fqName}") + } + val typeSize = IntegralConstantStub(baseIntegerTypeSize, 4, true) + val companionSuper = (context.platform.getRuntimeType("CPrimitiveVar") as ClassifierStubType).nested("Type") + val varSizeAnnotation = AnnotationStub.CEnumVarTypeSize(baseIntegerTypeSize.toInt()) + .takeIf { context.generationMode == GenerationMode.METADATA } + val companion = ClassStub.Companion( + classifier = enumVarClassifier.nested("Companion"), + superClassInit = SuperClassInit(companionSuper, listOf(typeSize)), + annotations = listOfNotNull(varSizeAnnotation) + ) + val valueProperty = PropertyStub( + name = "value", + type = ClassifierStubType(classifier), + kind = PropertyStub.Kind.Var( + PropertyAccessor.Getter.ExternalGetter(), + PropertyAccessor.Setter.ExternalSetter() + ), + origin = StubOrigin.Synthetic.EnumVarValueField(enumDef) + ) + return ClassStub.Simple( + classifier = enumVarClassifier, + constructors = listOf(primaryConstructor), + superClassInit = superClassInit, + companion = companion, + modality = ClassStubModality.NONE, + origin = StubOrigin.VarOf(StubOrigin.Enum(enumDef)), + properties = listOf(valueProperty) + ) + } private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) { contains("min") || contains("max") || diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt index 1cb672a110b..72fdaa872fd 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt @@ -70,4 +70,20 @@ val ClassStub.explicitPrimaryConstructor: ConstructorStub? get() = functions.filterIsInstance().firstOrNull(ConstructorStub::isPrimary) fun ClassStub.nestedName(): String = - classifier.relativeFqName.substringAfterLast('.') \ No newline at end of file + classifier.relativeFqName.substringAfterLast('.') + +fun ConstantStub.determineConstantAnnotationClassifier(): Classifier = when (this) { + is StringConstantStub -> "String" + is IntegralConstantStub -> when (size) { + 1 -> if (isSigned) "Byte" else "UByte" + 2 -> if (isSigned) "Short" else "UShort" + 4 -> if (isSigned) "Int" else "UInt" + 8 -> if (isSigned) "Long" else "ULong" + else -> error("Integral constant with unexpected size of $size.") + } + is DoubleConstantStub -> when (size) { + 4 -> "Float" + 8 -> "Double" + else -> error("Real constant with unexpected size of $size.") + } +}.let { Classifier.topLevel(cinteropInternalPackage, "ConstantValue").nested(it) } \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt index 6ae65fbe4ef..6d317d42154 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt @@ -114,6 +114,9 @@ internal class ModuleMetadataEmitter( km.constructors += elements.constructors.toList() km.companionObject = element.companion?.nestedName() km.uniqId = data.uniqIds.uniqIdForClass(element) + if (element is ClassStub.Enum) { + element.entries.mapTo(km.klibEnumEntries) { mapEnumEntry(it, classVisitingContext) } + } } } // Metadata stores classes as flat list. @@ -180,6 +183,16 @@ internal class ModuleMetadataEmitter( override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: VisitingContext): List = simpleStubContainer.children.map { it.accept(this, data) } + simpleStubContainer.simpleContainers.flatMap { visitSimpleStubContainer(it, data) } + + private fun mapEnumEntry(enumEntry: EnumEntryStub, data: VisitingContext): KlibEnumEntry = + with (MappingExtensions(data.typeParametersInterner)) { + KlibEnumEntry( + name = enumEntry.name, + uniqId = data.uniqIds.uniqIdForEnumEntry(enumEntry, data.container as ClassStub.Enum), + ordinal = enumEntry.ordinal, + annotations = mutableListOf(enumEntry.constant.mapToConstantAnnotation()) + ) + } } } @@ -371,6 +384,12 @@ private class MappingExtensions( ("replaceWith" to replaceWith(replaceWith)), ("level" to deprecationLevel(DeprecationLevel.ERROR)) ) + is AnnotationStub.CEnumEntryAlias -> mapOfNotNull( + ("entryName" to entryName).asAnnotationArgument() + ) + is AnnotationStub.CEnumVarTypeSize -> mapOfNotNull( + ("size" to KmAnnotationArgument.IntValue(size)) + ) } return KmAnnotation(classifier.fqNameSerialized, args) } @@ -491,6 +510,12 @@ private class MappingExtensions( } } + fun ConstantStub.mapToConstantAnnotation(): KmAnnotation = + KmAnnotation( + determineConstantAnnotationClassifier().fqNameSerialized, + mapOf("value" to mapToAnnotationArgument()) + ) + private val TypeParameterType.id: Int get() = typeParameterDeclaration.id diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt index 59326649218..5c612fe3605 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt @@ -154,19 +154,20 @@ class StubIrTextEmitter( } val header = renderClassHeader(element) when { - element is ClassStub.Simple && element.children.isEmpty() -> out(header) - element is ClassStub.Companion && element.children.isEmpty() -> out(header) + element.children.isEmpty() -> out(header) else -> block(header) { if (element is ClassStub.Enum) { - emitEnumBody(element) - } else { - element.children - // We render a primary constructor as part of a header. - .filterNot { it is ConstructorStub && it.isPrimary } - .forEach { - emitEmptyLine() - it.accept(this, element) - } + emitEnumEntries(element) + } + element.children + // We render a primary constructor as part of a header. + .filterNot { it is ConstructorStub && it.isPrimary } + .forEach { + emitEmptyLine() + it.accept(this, element) + } + if (element is ClassStub.Enum) { + emitEnumVarClass(element) } } } @@ -202,6 +203,8 @@ class StubIrTextEmitter( when { element.external -> out("external $header") element.isOptionalObjCMethod() -> out("$header = optional()") + element.origin is StubOrigin.Synthetic.EnumByValue -> + out("$header = values().find { it.value == value }!!") owner != null && owner.isInterface -> out(header) else -> block(header) { functionBridgeBodies.getValue(element).forEach(out) @@ -257,37 +260,17 @@ class StubIrTextEmitter( // About method naming convention: // - "emit" prefix means that method will call `out` by itself. // - "render" prefix means that method returns string that should be emitted by caller. - private fun emitEnumBody(enum: ClassStub.Enum) { - + private fun emitEnumEntries(enum: ClassStub.Enum) { enum.entries.forEach { out(renderEnumEntry(it) + ",") } + out(";") + } + private fun emitEnumVarClass(enum: ClassStub.Enum) { val simpleKotlinName = enum.classifier.topLevelName.asSimpleName() val typeMirror = builderResult.bridgeGenerationComponents.enumToTypeMirror.getValue(enum) - val baseKotlinType = typeMirror.argType.render(kotlinFile) val basePointedTypeName = typeMirror.pointedType.render(kotlinFile) - - out(";") - emitEmptyLine() - enum.properties.forEach { - emitProperty(it, enum) - emitEmptyLine() - } - - block("companion object") { - enum.entries.forEach { entry -> - entry.aliases.forEach { - out("val ${it.name.asSimpleName()} = ${entry.name.asSimpleName()}") - } - - } - emitEmptyLine() - - out("fun byValue(value: $baseKotlinType) = " + - "$simpleKotlinName.values().find { it.value == value }!!") - } - emitEmptyLine() block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") { out("companion object : Type($basePointedTypeName.size.toInt())") out("var value: $simpleKotlinName") @@ -401,7 +384,10 @@ class StubIrTextEmitter( } val constructorParams = classStub.explicitPrimaryConstructor?.parameters?.let(this::renderConstructorParams) ?: "" val inheritance = mutableListOf().apply { - addIfNotNull(classStub.superClassInit?.let { renderSuperInit(it) }) + // Enum inheritance is implicit. + if (classStub !is ClassStub.Enum) { + addIfNotNull(classStub.superClassInit?.let { renderSuperInit(it) }) + } addAll(classStub.interfaces.map { renderStubType(it) }) }.let { if (it.isNotEmpty()) " : ${it.joinToString()}" else "" } @@ -510,6 +496,9 @@ class StubIrTextEmitter( "@Deprecated(${annotationStub.message.quoteAsKotlinLiteral()}, " + "ReplaceWith(${annotationStub.replaceWith.quoteAsKotlinLiteral()}), " + "DeprecationLevel.ERROR)" + is AnnotationStub.CEnumEntryAlias, + is AnnotationStub.CEnumVarTypeSize -> + error("${annotationStub.classifier.fqName} annotation is unsupported in textual mode") } private fun renderEnumEntry(enumEntryStub: EnumEntryStub): String = @@ -561,6 +550,8 @@ class StubIrTextEmitter( propertyAccessorBridgeBodies.getValue(accessor) } + is PropertyAccessor.Getter.GetEnumEntry -> accessor.enumEntryStub.name + is PropertyAccessor.Setter.SimpleSetter -> when { accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor) else -> error("Bridge body for setter was not generated")