[StubIr][Metadata] Support for enum classes

This commit is contained in:
Sergey Bogolepov
2020-01-29 13:22:49 +07:00
committed by Sergey Bogolepov
parent b15875cc15
commit 9224ee3abf
7 changed files with 238 additions and 57 deletions
@@ -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)
@@ -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,
@@ -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)
}
@@ -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") ||
@@ -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) }
@@ -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
@@ -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")