diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index 5e1bd2ec202..16280027d76 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -20,6 +20,8 @@ public interface SerializationErrors { DiagnosticFactory0 INLINE_CLASSES_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PLUGIN_IS_NOT_ENABLED = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 EXPLICIT_SERIALIZABLE_IS_REQUIRED = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR); diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index 30c43ba74be..efc859d2624 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -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. diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index ab71c7e7581..e6de411bd1c 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -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. " + diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt index 3980586c923..b5441c7f79b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt @@ -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 { .toList() } -private val Annotations.hasAnySerialAnnotation: Boolean +internal val Annotations.hasAnySerialAnnotation: Boolean get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true } internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java index 292408e4710..44786160767 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java @@ -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"); diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Basic.ir.txt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Basic.ir.txt index 9d23ae3c5b4..137de33621b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Basic.ir.txt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/codegen/Basic.ir.txt @@ -962,4 +962,4 @@ public final class User : java/lang/Object { public final java.lang.String getFirstName() public final java.lang.String getLastName() -} \ No newline at end of file +} diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt new file mode 100644 index 00000000000..a6f237f39cd --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt @@ -0,0 +1,13 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE + +// FILE: test.kt +import kotlinx.serialization.* + +enum class SimpleEnum { A, B } + +enum class MarkedNameEnum { @SerialName("a") A, B} + +enum class MarkedInfoEnum { @SerialId(10) A, B} + +@Serializable +enum class ExplicitlyMarkedEnum { @SerialId(10) A, B} \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.txt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.txt new file mode 100644 index 00000000000..53e7210286a --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.txt @@ -0,0 +1,105 @@ +package + +@kotlinx.serialization.Serializable public final enum class ExplicitlyMarkedEnum : kotlin.Enum { + @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! + 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 { + private constructor `$serializer`() + public open override /*1*/ /*synthesized*/ val descriptor: kotlinx.serialization.SerialDescriptor + public open override /*1*/ /*synthesized*/ fun childSerializers(): kotlin.Array> + 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 + 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 +} + +public final enum class MarkedInfoEnum : kotlin.Enum { + @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! + 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 +} + +public final enum class MarkedNameEnum : kotlin.Enum { + @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! + 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 +} + +public final enum class SimpleEnum : kotlin.Enum { + 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! + 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 +} diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.kt index 4785ce3ad5c..7887dd76483 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.kt @@ -5,6 +5,3 @@ import kotlinx.serialization.* @Serializable interface INonSerializable - -@Serializable -enum class ENonSerializable { A, B } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.txt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.txt index d14434efe39..28c3fd2c122 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.txt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableIgnored.txt @@ -1,26 +1,5 @@ package -@kotlinx.serialization.Serializable public final enum class ENonSerializable : kotlin.Enum { - 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! - 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 -} - @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