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);
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> JSON_FORMAT_REDUNDANT_DEFAULT = 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 inSameModule =
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))
}
@@ -263,6 +264,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
}
private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean {
if (kind != ClassKind.ENUM_CLASS) return false
if (hasSerializableOrMetaAnnotation) return false
@@ -344,10 +346,23 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi)
} else {
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(
module: ModuleDescriptor,
type: KotlinType,
@@ -95,6 +95,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
SerializationErrors.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(
SerializationErrors.JSON_FORMAT_REDUNDANT_DEFAULT,
"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_TYPE_INCOMPATIBLE by warning3<PsiElement, ConeKotlinType, ConeKotlinType, 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_IS_REDUNDANT by warning0<PsiElement>()
val INCORRECT_TRANSIENT by warning0<PsiElement>()
val REQUIRED_KOTLIN_TOO_HIGH by error3<KtAnnotationEntry, String, String, String>()
@@ -204,9 +204,9 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
)
return false
}
if (!classSymbol.hasSerializableOrMetaAnnotation) return false
if (classSymbol.isAnonymousObjectOrInsideIt) {
reporter.reportOn(classSymbol.serializableOrMetaAnnotationSource, FirSerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED)
return false
@@ -370,10 +370,21 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
checkSerializerNullability(propertyType, serializerType, source, reporter)
} else {
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)
@Suppress("IncorrectFormatting") // KTIJ-22227
private fun checkTypeArguments(type: ConeKotlinType, source: KtSourceElement?, reporter: DiagnosticReporter) {
@@ -102,6 +102,10 @@ object KtDefaultErrorMessagesSerialization {
FirSerializationErrors.INCORRECT_TRANSIENT,
"@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(
FirSerializationErrors.REQUIRED_KOTLIN_TOO_HIGH,
"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");
}
@Test
@TestMetadata("GenericArrays.kt")
public void testGenericArrays() throws Exception {
runTest("plugins/kotlinx-serialization/testData/diagnostics/GenericArrays.kt");
}
@Test
@TestMetadata("IncorrectTransient.kt")
public void testIncorrectTransient() throws Exception {
@@ -42,6 +42,12 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
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
@TestMetadata("IncorrectTransient.kt")
public void testIncorrectTransient() throws Exception {