Implement basics diagnostics reporting from the serialization plugin similarly to noArg and allOpen plugins
This commit is contained in:
@@ -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>
|
||||
|
||||
+3
-1
@@ -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()
|
||||
|
||||
+2
-2
@@ -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? {
|
||||
|
||||
+131
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+37
@@ -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
-2
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -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.
|
||||
|
||||
-9
@@ -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
|
||||
|
||||
+23
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user