Implement basics diagnostics reporting from the serialization plugin similarly to noArg and allOpen plugins

This commit is contained in:
Leonid Startsev
2019-05-16 19:41:00 +03:00
parent d3a596e3c2
commit 6895184b84
10 changed files with 255 additions and 17 deletions
@@ -4,5 +4,6 @@
<syntheticResolveExtension implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEResolveExtension"/>
<jsSyntheticTranslateExtension implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEJsExtension"/>
<irGenerationExtension implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEIrExtension"/>
<storageComponentContainerContributor implementation="org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationIDEContainerContributor"/>
</extensions>
</idea-plugin>
@@ -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()
@@ -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? {
@@ -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<ClassDescriptor, SerializableProperties> = Slices.createSimpleSlice()
internal val SERIALIZER_FOR_PROPERTY: WritableSlice<PropertyDescriptor, ClassDescriptor> = 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<String>()
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<SerializableProperty>) {
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<KtAnnotationEntry>) {
descriptor.safeReport { e ->
reportFromPlugin(
error.on(e),
SerializationPluginErrorsRendering
)
}
}
}
@@ -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<KtAnnotationEntry> SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<KtAnnotationEntry, String> DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> SERIALIZER_NOT_FOUND = DiagnosticFactory0.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
{
Errors.Initializer.initializeFactoryNames(SerializationErrors.class);
}
};
}
@@ -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"
)
}
}
@@ -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())
}
}
}
class SerializationPluginComponentContainerContributor : StorageComponentContainerContributor {
override fun registerModuleComponents(
container: StorageComponentContainer,
platform: TargetPlatform,
moduleDescriptor: ModuleDescriptor
) {
container.useInstance(SerializationPluginDeclarationChecker())
}
}
@@ -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.
@@ -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<SerializableProperty>) {
val namesSet = mutableSetOf<String>()
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
@@ -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())
}
}