Prohibit Array<T> in @Serializable classes

because it's impossible to correctly create array reflectively without
knowing correct KClass.

Fixes https://github.com/Kotlin/kotlinx.serialization/issues/1243
This commit is contained in:
Leonid Startsev
2022-09-26 19:00:48 +02:00
committed by Space Team
parent 596949a501
commit 090aec6b3b
9 changed files with 65 additions and 4 deletions
@@ -32,6 +32,8 @@ public interface SerializationErrors {
DiagnosticFactory1<PsiElement, KotlinType> LOCAL_SERIALIZER_USAGE = DiagnosticFactory1.create(ERROR); DiagnosticFactory1<PsiElement, KotlinType> LOCAL_SERIALIZER_USAGE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING); DiagnosticFactory0<PsiElement> TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> JSON_FORMAT_REDUNDANT_DEFAULT = DiagnosticFactory0.create(WARNING); DiagnosticFactory0<PsiElement> JSON_FORMAT_REDUNDANT_DEFAULT = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> JSON_FORMAT_REDUNDANT = DiagnosticFactory0.create(WARNING); DiagnosticFactory0<PsiElement> JSON_FORMAT_REDUNDANT = DiagnosticFactory0.create(WARNING);
@@ -69,7 +69,8 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val entry = classDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName) val entry = classDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName)
val inSameModule = val inSameModule =
trace.bindingContext[BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, serializableDescriptor.fqNameUnsafe] != null trace.bindingContext[BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, serializableDescriptor.fqNameUnsafe] != null
val diagnostic = if (inSameModule) SerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE else SerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE val diagnostic =
if (inSameModule) SerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE else SerializationErrors.EXTERNAL_CLASS_IN_ANOTHER_MODULE
trace.report(diagnostic.on(entry ?: declaration, classDescriptor.defaultType, serializableKType)) trace.report(diagnostic.on(entry ?: declaration, classDescriptor.defaultType, serializableKType))
} }
@@ -263,6 +264,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
} }
} }
private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean { private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean {
if (kind != ClassKind.ENUM_CLASS) return false if (kind != ClassKind.ENUM_CLASS) return false
if (hasSerializableOrMetaAnnotation) return false if (hasSerializableOrMetaAnnotation) return false
@@ -344,10 +346,23 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi) generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi)
} else { } else {
generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace, propertyPsi) generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace, propertyPsi)
checkGenericArrayType(it.type, ktType, trace, propertyPsi)
} }
} }
} }
private fun checkGenericArrayType(
type: KotlinType,
ktType: KtTypeReference?,
trace: BindingTrace,
fallbackElement: PsiElement
) {
if (KotlinBuiltIns.isArray(type) && type.arguments.first().type.genericIndex != null) {
// Array<T> is unsupported, since we can't get T::class from KSerializer<T>
trace.report(SerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED.on(ktType ?: fallbackElement))
}
}
private fun AbstractSerialGenerator.checkTypeArguments( private fun AbstractSerialGenerator.checkTypeArguments(
module: ModuleDescriptor, module: ModuleDescriptor,
type: KotlinType, type: KotlinType,
@@ -95,6 +95,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
SerializationErrors.TRANSIENT_IS_REDUNDANT, SerializationErrors.TRANSIENT_IS_REDUNDANT,
"Property does not have backing field which makes it non-serializable and therefore @Transient is redundant" "Property does not have backing field which makes it non-serializable and therefore @Transient is redundant"
) )
MAP.put(
SerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED,
"Serialization of Arrays with generic type arguments is impossible because of unknown compile-time type."
)
MAP.put( MAP.put(
SerializationErrors.JSON_FORMAT_REDUNDANT_DEFAULT, SerializationErrors.JSON_FORMAT_REDUNDANT_DEFAULT,
"Redundant creation of Json default format. Creating instances for each usage can be slow." "Redundant creation of Json default format. Creating instances for each usage can be slow."
@@ -29,10 +29,10 @@ object FirSerializationErrors {
val SERIALIZER_NULLABILITY_INCOMPATIBLE by error2<PsiElement, ConeKotlinType, ConeKotlinType>() val SERIALIZER_NULLABILITY_INCOMPATIBLE by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val SERIALIZER_TYPE_INCOMPATIBLE by warning3<PsiElement, ConeKotlinType, ConeKotlinType, ConeKotlinType>() val SERIALIZER_TYPE_INCOMPATIBLE by warning3<PsiElement, ConeKotlinType, ConeKotlinType, ConeKotlinType>()
val LOCAL_SERIALIZER_USAGE by error1<PsiElement, ConeKotlinType>() val LOCAL_SERIALIZER_USAGE by error1<PsiElement, ConeKotlinType>()
val GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED by error0<PsiElement>()
val TRANSIENT_MISSING_INITIALIZER by error0<PsiElement>() val TRANSIENT_MISSING_INITIALIZER by error0<PsiElement>()
val TRANSIENT_IS_REDUNDANT by warning0<PsiElement>() val TRANSIENT_IS_REDUNDANT by warning0<PsiElement>()
val INCORRECT_TRANSIENT by warning0<PsiElement>() val INCORRECT_TRANSIENT by warning0<PsiElement>()
val REQUIRED_KOTLIN_TOO_HIGH by error3<KtAnnotationEntry, String, String, String>() val REQUIRED_KOTLIN_TOO_HIGH by error3<KtAnnotationEntry, String, String, String>()
@@ -204,9 +204,9 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
) )
return false return false
} }
if (!classSymbol.hasSerializableOrMetaAnnotation) return false if (!classSymbol.hasSerializableOrMetaAnnotation) return false
if (classSymbol.isAnonymousObjectOrInsideIt) { if (classSymbol.isAnonymousObjectOrInsideIt) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED) reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED)
return false return false
@@ -370,10 +370,21 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
checkSerializerNullability(propertyType, serializerType, source, reporter) checkSerializerNullability(propertyType, serializerType, source, reporter)
} else { } else {
checkType(propertyType, source, reporter) checkType(propertyType, source, reporter)
checkGenericArrayType(propertyType, source, reporter)
} }
} }
} }
context(CheckerContext)
private fun checkGenericArrayType(propertyType: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) {
if (propertyType.isNonPrimitiveArray && propertyType.typeArguments.first().type?.isTypeParameter == true) {
reporter.reportOn(
source,
FirSerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED,
)
}
}
context(CheckerContext) context(CheckerContext)
@Suppress("IncorrectFormatting") // KTIJ-22227 @Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkTypeArguments(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) { private fun checkTypeArguments(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) {
@@ -102,6 +102,10 @@ object KtDefaultErrorMessagesSerialization {
FirSerializationErrors.INCORRECT_TRANSIENT, FirSerializationErrors.INCORRECT_TRANSIENT,
"@kotlin.jvm.Transient does not affect @Serializable classes. Please use @kotlinx.serialization.Transient instead." "@kotlin.jvm.Transient does not affect @Serializable classes. Please use @kotlinx.serialization.Transient instead."
) )
put(
FirSerializationErrors.GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED,
"Serialization of Arrays with generic type arguments is impossible because of unknown compile-time type."
)
put( put(
FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH, FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH,
"Your current Kotlin version is {0}, while kotlinx.serialization core runtime {1} requires at least Kotlin {2}. " + "Your current Kotlin version is {0}, while kotlinx.serialization core runtime {1} requires at least Kotlin {2}. " +
@@ -0,0 +1,13 @@
// FIR_IDENTICAL
// WITH_STDLIB
// SKIP_TXT
import kotlinx.serialization.*
@Serializable
class C(val values: IntArray) // OK
@Serializable
class B(val values: Array<String>) // OK
@Serializable
class A<T>(val values: <!GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED!>Array<T><!>)
@@ -44,6 +44,12 @@ public class SerializationFirDiagnosticTestGenerated extends AbstractSerializati
runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt"); runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt");
} }
@Test
@TestMetadata("GenericArrays.kt")
public void testGenericArrays() throws Exception {
runTest("plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt");
}
@Test @Test
@TestMetadata("IncorrectTransient.kt") @TestMetadata("IncorrectTransient.kt")
public void testIncorrectTransient() throws Exception { public void testIncorrectTransient() throws Exception {
@@ -42,6 +42,12 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt"); runTest("plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt");
} }
@Test
@TestMetadata("GenericArrays.kt")
public void testGenericArrays() throws Exception {
runTest("plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt");
}
@Test @Test
@TestMetadata("IncorrectTransient.kt") @TestMetadata("IncorrectTransient.kt")
public void testIncorrectTransient() throws Exception { public void testIncorrectTransient() throws Exception {