diff --git a/idea/resources/META-INF/kotlinx-serialization.xml b/idea/resources/META-INF/kotlinx-serialization.xml index cda0cb08d86..4ad16af7a6a 100644 --- a/idea/resources/META-INF/kotlinx-serialization.xml +++ b/idea/resources/META-INF/kotlinx-serialization.xml @@ -4,5 +4,6 @@ + diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt index 13839ad2086..cfde3f05396 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt @@ -21,13 +21,15 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.secondaryConstructors +import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SERIALIZABLE_PROPERTIES import org.jetbrains.kotlinx.serialization.compiler.resolve.* abstract class SerializableCodegen( protected val serializableDescriptor: ClassDescriptor, bindingContext: BindingContext ) : AbstractSerialGenerator(bindingContext, serializableDescriptor) { - protected val properties = SerializableProperties(serializableDescriptor, bindingContext) + protected val properties = bindingContext.get(SERIALIZABLE_PROPERTIES, serializableDescriptor) + ?: SerializableProperties(serializableDescriptor, bindingContext) fun generate() { generateSyntheticInternalConstructor() diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt index 15fe72bc74e..e2f76693487 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt @@ -116,12 +116,12 @@ fun AbstractSerialGenerator.findTypeSerializerOrContext( annotations.serializableWith(module)?.let { return it.toClassDescriptor } additionalSerializersInScopeOfCurrentFile[kType]?.let { return it } if (kType in contextualKClassListInCurrentFile) return module.getClassFromSerializationPackage(SpecialBuiltins.contextSerializer) - return analyzeSpecialSerializers(module, annotations) ?: findTypeSerializer(module, kType) ?: throw CompilationException( + return analyzeSpecialSerializers(module, annotations) ?: findTypeSerializer(module, kType) /*?: throw CompilationException( "Serializer for element of type $kType has not been found.\n" + "To use context serializer as fallback, explicitly annotate element with @ContextualSerialization", null, sourceElement - ) + )*/ } fun findTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt new file mode 100644 index 00000000000..58a1de1a264 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.compiler.diagnostic + +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.diagnostics.DiagnosticFactory0 +import org.jetbrains.kotlin.diagnostics.reportFromPlugin +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.util.slicedMap.Slices +import org.jetbrains.kotlin.util.slicedMap.WritableSlice +import org.jetbrains.kotlinx.serialization.compiler.backend.common.AbstractSerialGenerator +import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContext +import org.jetbrains.kotlinx.serialization.compiler.resolve.* + +internal val SERIALIZABLE_PROPERTIES: WritableSlice = Slices.createSimpleSlice() +internal val SERIALIZER_FOR_PROPERTY: WritableSlice = Slices.createSimpleSlice() + +class SerializationPluginDeclarationChecker : DeclarationChecker { + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + if (descriptor !is ClassDescriptor) return + + checkCanBeSerializedInternally(descriptor, context.trace) + val props = buildSerializableProperties(descriptor, context.trace) ?: return + analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties) + } + + private fun checkCanBeSerializedInternally(descriptor: ClassDescriptor, trace: BindingTrace) { + if (!descriptor.hasSerializableAnnotationWithoutArgs) return + + if (!descriptor.isInternalSerializable && !descriptor.hasCompanionObjectAsSerializer) { + trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED) + } + } + + // todo: also check on a side of external serializer + private fun buildSerializableProperties(descriptor: ClassDescriptor, trace: BindingTrace): SerializableProperties? { + if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return null + if (!descriptor.isInternalSerializable) return null + if (descriptor.hasCompanionObjectAsSerializer) return null // customized by user + + val props = SerializableProperties(descriptor, trace.bindingContext) + if (!props.isExternallySerializable) trace.reportOnSerializableAnnotation( + descriptor, + SerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY + ) + + // check that all names are unique + val namesSet = mutableSetOf() + props.serializableProperties.forEach { + if (!namesSet.add(it.name)) { + descriptor.safeReport { a -> + trace.reportFromPlugin( + SerializationErrors.DUPLICATE_SERIAL_NAME.on(a, it.name), + SerializationPluginErrorsRendering + ) + } + } + } + + trace.record(SERIALIZABLE_PROPERTIES, descriptor, props) + return props + } + + private fun analyzePropertiesSerializers(trace: BindingTrace, serializableClass: ClassDescriptor, props: List) { + val generatorContextForAnalysis = object : AbstractSerialGenerator(trace.bindingContext, serializableClass) {} + props.forEach { + val serializer = it.serializableWith?.toClassDescriptor + val ktType = (it.descriptor.findPsi() as? KtCallableDeclaration)?.typeReference ?: return@forEach + if (serializer != null) { + val element = ktType.typeElement ?: return + generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace) + trace.record(SERIALIZER_FOR_PROPERTY, it.descriptor, serializer) + } else { + generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace) + } + } + } + + private fun AbstractSerialGenerator.checkTypeArguments( + module: ModuleDescriptor, + type: KotlinType, + element: KtTypeElement, + trace: BindingTrace + ) { + type.arguments.forEachIndexed { i, it -> checkType(module, it.type, element.typeArgumentsAsTypes[i], trace) } + } + + private fun AbstractSerialGenerator.checkType( + module: ModuleDescriptor, + type: KotlinType, + ktType: KtTypeReference, + trace: BindingTrace + ) { + if (type.genericIndex != null) return + val element = ktType.typeElement ?: return + val serializerForType = findTypeSerializerOrContext(module, type) + if (serializerForType != null) { + checkTypeArguments(module, type, element, trace) + } else { + trace.reportFromPlugin( + SerializationErrors.SERIALIZER_NOT_FOUND.on(element), + SerializationPluginErrorsRendering + ) + } + } + + private inline fun ClassDescriptor.safeReport(report: (KtAnnotationEntry) -> Unit) { + findSerializableAnnotationDeclaration()?.let(report) + } + + private fun BindingTrace.reportOnSerializableAnnotation(descriptor: ClassDescriptor, error: DiagnosticFactory0) { + descriptor.safeReport { e -> + reportFromPlugin( + error.on(e), + SerializationPluginErrorsRendering + ) + } + } + +} 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 new file mode 100644 index 00000000000..64d616ed8c9 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.compiler.diagnostic; + +import com.intellij.psi.PsiElement; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; +import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.psi.KtAnnotationEntry; + +import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; + +public interface SerializationErrors { + DiagnosticFactory0 SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 SERIALIZER_NOT_FOUND = DiagnosticFactory0.create(ERROR); + + @SuppressWarnings("UnusedDeclaration") + Object _initializer = new Object() { + { + Errors.Initializer.initializeFactoryNames(SerializationErrors.class); + } + }; + +} \ No newline at end of file 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 new file mode 100644 index 00000000000..08ef1bfc58b --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.compiler.diagnostic + +import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.rendering.Renderers + +object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { + private val MAP = DiagnosticFactoryToRendererMap("SerializationPlugin") + override fun getMap() = MAP + + init { + MAP.put( + SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED, + "@Serializable annotation would be ignored because it is impossible to serialize automatically interfaces or enums. " + + "Provide serializer manually via e.g. companion object" + ) + MAP.put( + SerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY, + "This class is not serializable automatically because it has primary constructor parameters which are not properties." + ) + MAP.put( + SerializationErrors.DUPLICATE_SERIAL_NAME, + "Serializable class has duplicate serial name of property ''{0}'', either in it or its parents.", + Renderers.STRING + ) + MAP.put( + SerializationErrors.SERIALIZER_NOT_FOUND, + "Serializer has not been found for type of this property. " + + "To use context serializer as fallback, explicitly annotate element with @ContextualSerialization" + ) + } +} \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt index 9b3a73cab85..3a6bb822744 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt @@ -21,14 +21,33 @@ import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension +import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension +import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationPluginDeclarationChecker class SerializationComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { - ExpressionCodegenExtension.registerExtension(project, SerializationCodegenExtension()) SyntheticResolveExtension.registerExtension(project, SerializationResolveExtension()) + + ExpressionCodegenExtension.registerExtension(project, SerializationCodegenExtension()) JsSyntheticTranslateExtension.registerExtension(project, SerializationJsExtension()) IrGenerationExtension.registerExtension(project, SerializationLoweringExtension()) + + StorageComponentContainerContributor.registerExtension(project, SerializationPluginComponentContainerContributor()) } -} \ No newline at end of file +} + +class SerializationPluginComponentContainerContributor : StorageComponentContainerContributor { + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: TargetPlatform, + moduleDescriptor: ModuleDescriptor + ) { + container.useInstance(SerializationPluginDeclarationChecker()) + } +} 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 2e782af7bed..9ec47fbe279 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 @@ -18,9 +18,11 @@ package org.jetbrains.kotlinx.serialization.compiler.resolve import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor @@ -113,12 +115,15 @@ internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean if (!annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) 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 lazyDesc = annotations.findAnnotation(SerializationAnnotations.serializableAnnotationFqName) - as? LazyAnnotationDescriptor ?: return (serializableWith == null) - val psi = lazyDesc.annotationEntry + val psi = findSerializableAnnotationDeclaration() ?: return (serializableWith == null) return psi.valueArguments.isEmpty() } +internal fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? { + val lazyDesc = annotations.findAnnotation(SerializationAnnotations.serializableAnnotationFqName) as? LazyAnnotationDescriptor + return lazyDesc?.annotationEntry +} + // For abstract classes marked with @Serializable, // methods are generated anyway although they shouldn't have // generated $serializer and use Polymorphic one. diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt index 6e06e428ed0..c77ffe4f71f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperties.kt @@ -51,15 +51,6 @@ class SerializableProperties(private val serializableClass: ClassDescriptor, val else SerializableProperties(supers, bindingContext).serializableProperties + first + second } - .also(::validateUniqueSerialNames) - - private fun validateUniqueSerialNames(props: List) { - val namesSet = mutableSetOf() - props.forEach { - if (!namesSet.add(it.name)) throw IllegalStateException("$serializableClass has duplicate serial name of property ${it.name}, either in it or its parents.") - } - } - private fun isPropSerializable(it: PropertyDescriptor) = if (serializableClass.isInternalSerializable) !it.annotations.serialTransient diff --git a/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationIDEContainerContributor.kt b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationIDEContainerContributor.kt new file mode 100644 index 00000000000..47b9a25821b --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-ide/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationIDEContainerContributor.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.serialization.compiler.extensions + +import org.jetbrains.kotlin.container.StorageComponentContainer +import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationPluginDeclarationChecker + +class SerializationIDEContainerContributor : StorageComponentContainerContributor { + override fun registerModuleComponents( + container: StorageComponentContainer, + platform: TargetPlatform, + moduleDescriptor: ModuleDescriptor + ) { + container.useInstance(SerializationPluginDeclarationChecker()) + } +} \ No newline at end of file