Provide a diagnostic when @Serializable annotation is missing on enum.
It is required when enum members contain some special annotations which should be recorded in descriptor. Due to some recursive/lazy resolve problems, compiler plugin frontend can't infer @Serializable on enum automatically, therefore, we ask a user to explicitly provide it. Also fix ir tests
This commit is contained in:
+2
@@ -20,6 +20,8 @@ public interface SerializationErrors {
|
||||
DiagnosticFactory0<PsiElement> INLINE_CLASSES_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> PLUGIN_IS_NOT_ENABLED = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
DiagnosticFactory0<PsiElement> EXPLICIT_SERIALIZABLE_IS_REQUIRED = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
DiagnosticFactory0<KtAnnotationEntry> SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
+19
-6
@@ -6,10 +6,7 @@
|
||||
package org.jetbrains.kotlinx.serialization.compiler.diagnostic
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
|
||||
import org.jetbrains.kotlin.diagnostics.reportFromPlugin
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
@@ -37,14 +34,23 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
||||
final override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
if (descriptor !is ClassDescriptor) return
|
||||
|
||||
if (!canBeSerializedInternally(descriptor, context.trace)) return
|
||||
if (!canBeSerializedInternally(descriptor, declaration, context.trace)) return
|
||||
if (declaration !is KtPureClassOrObject) return
|
||||
val props = buildSerializableProperties(descriptor, context.trace) ?: return
|
||||
checkTransients(declaration, context.trace)
|
||||
analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties)
|
||||
}
|
||||
|
||||
private fun canBeSerializedInternally(descriptor: ClassDescriptor, trace: BindingTrace): Boolean {
|
||||
private fun canBeSerializedInternally(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace): Boolean {
|
||||
if (descriptor.isSerializableEnumWithMissingSerializer()) {
|
||||
val declarationToReport = declaration.modifierList ?: declaration
|
||||
trace.reportFromPlugin(
|
||||
SerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED.on(declarationToReport),
|
||||
SerializationPluginErrorsRendering
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return false
|
||||
|
||||
if (!serializationPluginEnabledOn(descriptor)) {
|
||||
@@ -74,6 +80,13 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean {
|
||||
if (kind != ClassKind.ENUM_CLASS) return false
|
||||
if (hasSerializableAnnotationWithoutArgs) return false
|
||||
if (annotations.hasAnySerialAnnotation) return true
|
||||
return enumEntries().any { (it.annotations.hasAnySerialAnnotation) }
|
||||
}
|
||||
|
||||
open fun serializationPluginEnabledOn(descriptor: ClassDescriptor): Boolean {
|
||||
// In the CLI/Gradle compiler, this diagnostic is located in the plugin itself.
|
||||
// Therefore, if we are here, plugin is in the compile classpath and enabled.
|
||||
|
||||
+4
@@ -23,6 +23,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
|
||||
"kotlinx.serialization compiler plugin is not applied to the module, so this annotation would not be processed. " +
|
||||
"Make sure that you've setup your buildscript correctly and re-import project."
|
||||
)
|
||||
MAP.put(
|
||||
SerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED,
|
||||
"Explicit @Serializable annotation on enum class is required when @SerialName or @SerialInfo annotations are used on its members."
|
||||
)
|
||||
MAP.put(
|
||||
SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED,
|
||||
"@Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums. " +
|
||||
|
||||
+1
-6
@@ -119,11 +119,6 @@ internal val ClassDescriptor.isInternalSerializable: Boolean //todo normal check
|
||||
internal fun ClassDescriptor.isSerializableEnum(): Boolean {
|
||||
if (this.kind != ClassKind.ENUM_CLASS) return false
|
||||
if (hasSerializableAnnotationWithoutArgs) return true
|
||||
if (annotations.hasAnySerialAnnotation) return true
|
||||
// todo: check entries without 'recursive call in lazy class member scope'
|
||||
// for (enumEntry in enumEntries()) {
|
||||
// if (enumEntry.annotations.hasAnySerialAnnotation) return true
|
||||
// }
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -138,7 +133,7 @@ internal fun ClassDescriptor.enumEntries(): List<ClassDescriptor> {
|
||||
.toList()
|
||||
}
|
||||
|
||||
private val Annotations.hasAnySerialAnnotation: Boolean
|
||||
internal val Annotations.hasAnySerialAnnotation: Boolean
|
||||
get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true }
|
||||
|
||||
internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
|
||||
|
||||
+5
@@ -54,6 +54,11 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/ParamIsNotProperty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("SerializableEnums.kt")
|
||||
public void testSerializableEnums() throws Exception {
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("SerializableIgnored.kt")
|
||||
public void testSerializableIgnored() throws Exception {
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.kt");
|
||||
|
||||
+1
-1
@@ -962,4 +962,4 @@ public final class User : java/lang/Object {
|
||||
public final java.lang.String getFirstName()
|
||||
|
||||
public final java.lang.String getLastName()
|
||||
}
|
||||
}
|
||||
|
||||
plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE
|
||||
|
||||
// FILE: test.kt
|
||||
import kotlinx.serialization.*
|
||||
|
||||
enum class SimpleEnum { A, B }
|
||||
|
||||
<!PLUGIN_WARNING("Explicit @Serializable annotation on enum class is required when @SerialName or @SerialInfo annotations are used on its members.")!>enum<!> class MarkedNameEnum { @SerialName("a") A, B}
|
||||
|
||||
<!PLUGIN_WARNING("Explicit @Serializable annotation on enum class is required when @SerialName or @SerialInfo annotations are used on its members.")!>enum<!> class MarkedInfoEnum { @SerialId(10) A, B}
|
||||
|
||||
@Serializable
|
||||
enum class ExplicitlyMarkedEnum { @SerialId(10) A, B}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package
|
||||
|
||||
@kotlinx.serialization.Serializable public final enum class ExplicitlyMarkedEnum : kotlin.Enum<ExplicitlyMarkedEnum> {
|
||||
@kotlinx.serialization.SerialId(id = 10) enum entry A
|
||||
|
||||
enum entry B
|
||||
|
||||
private constructor ExplicitlyMarkedEnum()
|
||||
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: ExplicitlyMarkedEnum): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit
|
||||
public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class<ExplicitlyMarkedEnum!>!
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
@kotlin.Deprecated(level = DeprecationLevel.HIDDEN, message = "This synthesized declaration should not be used directly", replaceWith = kotlin.ReplaceWith(expression = "", imports = {})) public object `$serializer` : kotlinx.serialization.internal.GeneratedSerializer<ExplicitlyMarkedEnum> {
|
||||
private constructor `$serializer`()
|
||||
public open override /*1*/ /*synthesized*/ val descriptor: kotlinx.serialization.SerialDescriptor
|
||||
public open override /*1*/ /*synthesized*/ fun childSerializers(): kotlin.Array<kotlinx.serialization.KSerializer<*>>
|
||||
public open override /*1*/ /*synthesized*/ fun deserialize(/*0*/ decoder: kotlinx.serialization.Decoder): ExplicitlyMarkedEnum
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun patch(/*0*/ decoder: kotlinx.serialization.Decoder, /*1*/ old: ExplicitlyMarkedEnum): ExplicitlyMarkedEnum
|
||||
public open override /*1*/ /*synthesized*/ fun serialize(/*0*/ encoder: kotlinx.serialization.Encoder, /*1*/ obj: ExplicitlyMarkedEnum): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
public companion object Companion {
|
||||
private constructor Companion()
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public final /*synthesized*/ fun serializer(): kotlinx.serialization.KSerializer<ExplicitlyMarkedEnum>
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): ExplicitlyMarkedEnum
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<ExplicitlyMarkedEnum>
|
||||
}
|
||||
|
||||
public final enum class MarkedInfoEnum : kotlin.Enum<MarkedInfoEnum> {
|
||||
@kotlinx.serialization.SerialId(id = 10) enum entry A
|
||||
|
||||
enum entry B
|
||||
|
||||
private constructor MarkedInfoEnum()
|
||||
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MarkedInfoEnum): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit
|
||||
public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class<MarkedInfoEnum!>!
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MarkedInfoEnum
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<MarkedInfoEnum>
|
||||
}
|
||||
|
||||
public final enum class MarkedNameEnum : kotlin.Enum<MarkedNameEnum> {
|
||||
@kotlinx.serialization.SerialName(value = "a") enum entry A
|
||||
|
||||
enum entry B
|
||||
|
||||
private constructor MarkedNameEnum()
|
||||
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MarkedNameEnum): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit
|
||||
public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class<MarkedNameEnum!>!
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MarkedNameEnum
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<MarkedNameEnum>
|
||||
}
|
||||
|
||||
public final enum class SimpleEnum : kotlin.Enum<SimpleEnum> {
|
||||
enum entry A
|
||||
|
||||
enum entry B
|
||||
|
||||
private constructor SimpleEnum()
|
||||
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: SimpleEnum): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit
|
||||
public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class<SimpleEnum!>!
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): SimpleEnum
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<SimpleEnum>
|
||||
}
|
||||
-3
@@ -5,6 +5,3 @@ import kotlinx.serialization.*
|
||||
|
||||
<!PLUGIN_ERROR("@Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums. Provide serializer manually via e.g. companion object")!>@Serializable<!>
|
||||
interface INonSerializable
|
||||
|
||||
<!PLUGIN_ERROR("@Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums. Provide serializer manually via e.g. companion object")!>@Serializable<!>
|
||||
enum class ENonSerializable { A, B }
|
||||
|
||||
-21
@@ -1,26 +1,5 @@
|
||||
package
|
||||
|
||||
@kotlinx.serialization.Serializable public final enum class ENonSerializable : kotlin.Enum<ENonSerializable> {
|
||||
enum entry A
|
||||
|
||||
enum entry B
|
||||
|
||||
private constructor ENonSerializable()
|
||||
public final override /*1*/ /*fake_override*/ val name: kotlin.String
|
||||
public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int
|
||||
protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any
|
||||
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: ENonSerializable): kotlin.Int
|
||||
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit
|
||||
public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class<ENonSerializable!>!
|
||||
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): ENonSerializable
|
||||
public final /*synthesized*/ fun values(): kotlin.Array<ENonSerializable>
|
||||
}
|
||||
|
||||
@kotlinx.serialization.Serializable public interface INonSerializable {
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
|
||||
Reference in New Issue
Block a user