Skip to content

Commit

Permalink
Use EnumSerializer for explicitly serializable enum instead of auto-g…
Browse files Browse the repository at this point in the history
  • Loading branch information
shanshin committed Oct 12, 2021
1 parent 4339671 commit 1d293c3
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ abstract class SerializableCompanionCodegen(
"probably clash with user-defined function has occurred"
)

if (serializableDescriptor.isSerializableObject || serializableDescriptor.isAbstractOrSealedSerializableClass()) {
if (serializableDescriptor.isSerializableObject || serializableDescriptor.isAbstractOrSealedSerializableClass()
// caching enum serializer if using serializer factory
|| (serializableDescriptor.isSerializableEnum() && !serializableDescriptor.useLegacyEnums())
) {
generateLazySerializerGetter(serializerGetterDescriptor)
} else {
generateSerializerGetter(serializerGetterDescriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,16 @@ fun findStandardKotlinTypeSerializer(module: ModuleDescriptor, kType: KotlinType

fun findEnumTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? {
val classDescriptor = kType.toClassDescriptor ?: return null
return if (classDescriptor.kind == ClassKind.ENUM_CLASS && !classDescriptor.isInternallySerializableEnum())

val useRuntimeEnumSerializer = if (classDescriptor.useLegacyEnums()) {
// Legacy: only for enum classes without `Serializer` annotation
!classDescriptor.hasSerializableAnnotation
} else {
//for enum classes without `Serializer` annotation and with empty `Serializer` annotation (custom serializer not defined)
!classDescriptor.hasSerializableAnnotationWithArgs
}

return if (classDescriptor.kind == ClassKind.ENUM_CLASS && useRuntimeEnumSerializer)
module.findClassAcrossModuleDependencies(enumSerializerId)
else null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ interface IrBuilderExtension {
private val throwMissedFieldExceptionArrayFunc
get() = compilerContext.referenceFunctions(SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_FQ).singleOrNull()

private val enumSerializerFactoryFunc
get() = compilerContext.referenceFunctions(SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_FQ).singleOrNull()

private val markedEnumSerializerFactoryFunc
get() = compilerContext.referenceFunctions(SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_FQ).singleOrNull()

private inline fun <reified T : IrDeclaration> IrClass.searchForDeclaration(descriptor: DeclarationDescriptor): T? {
return declarations.singleOrNull { it.descriptor == descriptor } as? T
}
Expand Down Expand Up @@ -1031,13 +1037,58 @@ interface IrBuilderExtension {
}
enumSerializerId -> {
serializerClass = module.getClassFromInternalSerializationPackage(SpecialBuiltins.enumSerializer)
args = kType.toClassDescriptor!!.let { enumDesc ->
listOf(
irString(enumDesc.serialName()),
irCall(findEnumValuesMethod(enumDesc))
val enumDescriptor = kType.toClassDescriptor!!
typeArgs = listOf(thisIrType)

// instantiate serializer only inside enum Companion
if (enclosingGenerator !is SerializableCompanionIrGenerator) {
// otherwise call Companion.serializer()
callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it }
}

val enumSerializerFactoryFunc = enumSerializerFactoryFunc
val markedEnumSerializerFactoryFunc = markedEnumSerializerFactoryFunc
if (enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null) {
// runtime contains enum serializer factory functions
val factoryFunc: IrSimpleFunctionSymbol
if (enumDescriptor.isEnumWithSerialInfoAnnotation()) {
// need to store SerialInfo annotation in descriptor
val enumEntries = enumDescriptor.enumEntries()
val entryNames = enumEntries.map { it.annotations.serialNameValue?.let { n -> irString(n) } ?: irNull() }
val entryAnnotations = enumEntries.map {
val annotationConstructors = it.annotations.mapNotNull { a ->
compilerContext.typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(a)
}
val annExpr = copyAnnotationsFrom(annotationConstructors)
createArrayOfExpression(compilerContext.builtIns.annotationType.toIrType(), annExpr)
}
val annotationArrayType =
compilerContext.irBuiltIns.arrayClass.typeWith(compilerContext.builtIns.annotationType.toIrType())

args = listOf(
irString(enumDescriptor.serialName()),
irCall(findEnumValuesMethod(enumDescriptor)),
createArrayOfExpression(compilerContext.irBuiltIns.stringType.makeNullable(), entryNames),
createArrayOfExpression(annotationArrayType, entryAnnotations)
)
factoryFunc = markedEnumSerializerFactoryFunc
} else {
args = listOf(
irString(enumDescriptor.serialName()),
irCall(findEnumValuesMethod(enumDescriptor)),
)
factoryFunc = enumSerializerFactoryFunc
}

val factoryReturnType = factoryFunc.owner.returnType.substitute(factoryFunc.owner.typeParameters, typeArgs)
return irInvoke(null, factoryFunc, typeArgs, args, factoryReturnType)
} else {
// support legacy serializer instantiation by constructor for old runtimes <= 1.3.0
args = listOf(
irString(enumDescriptor.serialName()),
irCall(findEnumValuesMethod(enumDescriptor)),
)
}
typeArgs = listOf(thisIrType)
}
else -> {
args = kType.arguments.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,8 @@ open class SerializerIrGenerator(
) {
val serializableDesc = getSerializableClassDescriptorBySerializer(irClass.symbol.descriptor) ?: return
val generator = when {
serializableDesc.isInternallySerializableEnum() -> SerializerForEnumsGenerator(irClass, context, bindingContext, serialInfoJvmGenerator)
// support generated enum serializers for legacy runtimes
serializableDesc.isInternallySerializableEnum() && serializableDesc.useLegacyEnums() -> SerializerForEnumsGenerator(irClass, context, bindingContext, serialInfoJvmGenerator)
serializableDesc.isInlineClass() -> SerializerForInlineClassGenerator(irClass, context, bindingContext, serialInfoJvmGenerator)
else -> SerializerIrGenerator(irClass, context, bindingContext, metadataPlugin, serialInfoJvmGenerator)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}

// check that we can instantiate supertype
if (!descriptor.isSerializableEnum()) { // enums are inherited from java.lang.Enum and can't be inherited from other classes
if (descriptor.kind != ClassKind.ENUM_CLASS) { // enums are inherited from java.lang.Enum and can't be inherited from other classes
val superClass = descriptor.getSuperClassOrAny()
if (!superClass.isInternalSerializable && superClass.constructors.singleOrNull { it.valueParameters.size == 0 } == null) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR)
Expand Down Expand Up @@ -420,4 +420,4 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}

internal val ClassDescriptor.serializableAnnotationIsUseless: Boolean
get() = hasSerializableAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum()
get() = hasSerializableAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && kind != ClassKind.ENUM_CLASS
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
)
MAP.put(
SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED,
"@Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums. " +
"@Serializable annotation is ignored because it is impossible to serialize automatically interfaces. " +
"Provide serializer manually via e.g. companion object"
)
MAP.put(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.inheritableSerialInfoFqName
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName

Expand Down Expand Up @@ -120,7 +122,18 @@ internal fun ClassDescriptor.isSerializableEnum(): Boolean = kind == ClassKind.E
internal fun ClassDescriptor.isInternallySerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableAnnotationWithoutArgs

internal val ClassDescriptor.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN)) || isInternallySerializableEnum()
get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN))
|| (isInternallySerializableEnum() && useLegacyEnums())


internal fun ClassDescriptor.useLegacyEnums(): Boolean {
// TODO determine compilation is IR or not. This common function called from different places including extension resolvers where it is hard to determine the type of compilation
val legacyBackend = false

val functions = module.getPackage(SerializationPackages.internalPackageFqName).memberScope.getFunctionNames()
return legacyBackend ||
!functions.contains(ENUM_SERIALIZER_FACTORY_FUNC_NAME) || !functions.contains(MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)
}

internal fun ClassDescriptor.enumEntries(): List<ClassDescriptor> {
check(this.kind == ClassKind.ENUM_CLASS)
Expand All @@ -130,6 +143,13 @@ internal fun ClassDescriptor.enumEntries(): List<ClassDescriptor> {
.toList()
}

// check enum or its elements has any SerialInfo annotation
internal fun ClassDescriptor.isEnumWithSerialInfoAnnotation(): Boolean {
if (kind != ClassKind.ENUM_CLASS) return false
if (annotations.hasAnySerialAnnotation) return true
return enumEntries().any { (it.annotations.hasAnySerialAnnotation) }
}

internal val Annotations.hasAnySerialAnnotation: Boolean
get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true }

Expand All @@ -145,6 +165,15 @@ internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
return psi.valueArguments.isEmpty()
}

internal val ClassDescriptor.hasSerializableAnnotationWithArgs: Boolean
get() {
if (!hasSerializableAnnotation) return false
// If provided descriptor is lazy, carefully look at psi in order not to trigger full resolve which may be recursive.
// Otherwise, this descriptor is deserialized from another module and it is OK to check value right away.
val psi = findSerializableAnnotationDeclaration() ?: return (serializableWith != null)
return psi.valueArguments.isNotEmpty()
}

internal fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(SerializationAnnotations.serializableAnnotationFqName) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry
Expand Down Expand Up @@ -211,6 +240,7 @@ internal fun ClassDescriptor.needSerializerFactory(): Boolean {
if (!(this.platform?.isNative() == true || this.platform.isJs())) return false
val serializableClass = getSerializableClassDescriptorByCompanion(this) ?: return false
if (serializableClass.isSerializableObject) return true
if (serializableClass.kind == ClassKind.ENUM_CLASS && !serializableClass.useLegacyEnums() && !serializableClass.hasSerializableAnnotationWithArgs) return true
if (serializableClass.isAbstractOrSealedSerializableClass()) return true
if (serializableClass.declaredTypeParameters.isEmpty()) return false
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ object SerialEntityNames {
val SERIALIZER_PROVIDER_NAME = Name.identifier("serializer")
val SINGLE_MASK_FIELD_MISSING_FUNC_NAME = Name.identifier("throwMissingFieldException")
val ARRAY_MASK_FIELD_MISSING_FUNC_NAME = Name.identifier("throwArrayMissingFieldException")
val ENUM_SERIALIZER_FACTORY_FUNC_NAME = Name.identifier("createSimpleEnumSerializer")
val MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME = Name.identifier("createMarkedEnumSerializer")
val SINGLE_MASK_FIELD_MISSING_FUNC_FQ = SerializationPackages.internalPackageFqName.child(SINGLE_MASK_FIELD_MISSING_FUNC_NAME)
val ARRAY_MASK_FIELD_MISSING_FUNC_FQ = SerializationPackages.internalPackageFqName.child(ARRAY_MASK_FIELD_MISSING_FUNC_NAME)
val CACHED_SERIALIZER_PROPERTY_NAME = Name.identifier(CACHED_SERIALIZER_PROPERTY)
val CACHED_DESCRIPTOR_FIELD_NAME = Name.identifier(CACHED_DESCRIPTOR_FIELD)

val ENUM_SERIALIZER_FACTORY_FUNC_FQ = SerializationPackages.internalPackageFqName.child(ENUM_SERIALIZER_FACTORY_FUNC_NAME)
val MARKED_ENUM_SERIALIZER_FACTORY_FUNC_FQ = SerializationPackages.internalPackageFqName.child(MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)

// parameters
val dummyParamName = Name.identifier("serializationConstructorMarker")
internal const val typeArgPrefix = "typeSerial"
Expand Down

0 comments on commit 1d293c3

Please sign in to comment.