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:
Leonid Startsev
2019-10-14 15:55:36 +03:00
parent 3c9a02e271
commit e89aabbba1
10 changed files with 150 additions and 37 deletions
@@ -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);
@@ -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.
@@ -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. " +
@@ -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
@@ -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");
@@ -962,4 +962,4 @@ public final class User : java/lang/Object {
public final java.lang.String getFirstName()
public final java.lang.String getLastName()
}
}
@@ -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}
@@ -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>
}
@@ -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 }
@@ -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