[StubIr][Metadata] Support for enum classes
This commit is contained in:
committed by
Sergey Bogolepov
parent
b15875cc15
commit
9224ee3abf
@@ -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)
|
||||
+15
-5
@@ -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<AnnotationStub> = emptyList()
|
||||
val typeParameters: List<StubType> = listOf(pointedType)
|
||||
}
|
||||
|
||||
class GetEnumEntry(
|
||||
val enumEntryStub: EnumEntryStub,
|
||||
override val annotations: List<AnnotationStub> = emptyList()
|
||||
) : Getter()
|
||||
}
|
||||
|
||||
sealed class Setter : PropertyAccessor() {
|
||||
@@ -472,11 +484,9 @@ class ConstructorStub(
|
||||
class EnumEntryStub(
|
||||
val name: String,
|
||||
val constant: IntegralConstantStub,
|
||||
val aliases: List<Alias>,
|
||||
val origin: StubOrigin.EnumEntry
|
||||
) {
|
||||
class Alias(val name: String)
|
||||
}
|
||||
val origin: StubOrigin.EnumEntry,
|
||||
val ordinal: Int
|
||||
)
|
||||
|
||||
class TypealiasStub(
|
||||
val alias: Classifier,
|
||||
|
||||
+1
@@ -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)
|
||||
}
|
||||
|
||||
+110
-15
@@ -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<StubIrElement> {
|
||||
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") ||
|
||||
|
||||
+17
-1
@@ -70,4 +70,20 @@ val ClassStub.explicitPrimaryConstructor: ConstructorStub?
|
||||
get() = functions.filterIsInstance<ConstructorStub>().firstOrNull(ConstructorStub::isPrimary)
|
||||
|
||||
fun ClassStub.nestedName(): String =
|
||||
classifier.relativeFqName.substringAfterLast('.')
|
||||
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) }
|
||||
+25
@@ -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<Any> =
|
||||
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
|
||||
|
||||
|
||||
+27
-36
@@ -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<String>().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")
|
||||
|
||||
Reference in New Issue
Block a user