[KxSerialization] Cover all combinations of language and serialization features with tests for enums
Resolves https://github.com/Kotlin/kotlinx.serialization/issues/2324 Merge-request: KT-MR-13775 Merged-by: Sergei Shanshin <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
e39af4583e
commit
9a8ebd5c21
@@ -0,0 +1,254 @@
|
|||||||
|
// WITH_STDLIB
|
||||||
|
|
||||||
|
//
|
||||||
|
// NOTE: THIS FILE IS AUTO-GENERATED by the TestMatrixIntegration.kt, DO NOT EDIT!
|
||||||
|
//
|
||||||
|
|
||||||
|
@file:UseSerializers(EnumWithUseSerializer::class, NestedEnumWithUseSerializer::class, )
|
||||||
|
@file:UseContextualSerialization(EnumWithUseContextual::class, Container.NestedEnumWithUseContextual::class, )
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
import kotlinx.serialization.modules.*
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
enum class EnumWithDef {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class Enum {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(EnumWithCustomSerializer::class)
|
||||||
|
enum class EnumWithCustom {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(EnumWithCustomClSerializer::class)
|
||||||
|
enum class EnumWithCustomCl {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EnumWithContextual {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EnumWithUseContextual {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class EnumWithUse {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Extra("Enum1")
|
||||||
|
enum class Enum1 {
|
||||||
|
@Extra("A") A,
|
||||||
|
@Extra("B") B,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Container {
|
||||||
|
enum class NestedEnumWithDef {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class NestedEnum {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(NestedEnumWithCustomSerializer::class)
|
||||||
|
enum class NestedEnumWithCustom {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(NestedEnumWithCustomClSerializer::class)
|
||||||
|
enum class NestedEnumWithCustomCl {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NestedEnumWithContextual {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NestedEnumWithUseContextual {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NestedEnumWithUse {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Outer {
|
||||||
|
}
|
||||||
|
|
||||||
|
object EnumWithCustomSerializer: ToDoSerializer<EnumWithCustom>("custom|EnumWithCustom")
|
||||||
|
object NestedEnumWithCustomSerializer: ToDoSerializer<Container.NestedEnumWithCustom>("custom|Container.NestedEnumWithCustom")
|
||||||
|
class EnumWithCustomClSerializer: ToDoSerializer<EnumWithCustomCl>("custom|EnumWithCustomCl")
|
||||||
|
class NestedEnumWithCustomClSerializer: ToDoSerializer<Container.NestedEnumWithCustomCl>("custom|Container.NestedEnumWithCustomCl")
|
||||||
|
|
||||||
|
object EnumWithContextualSerializer: ToDoSerializer<EnumWithContextual>("contextual|EnumWithContextual")
|
||||||
|
object NestedEnumWithContextualSerializer: ToDoSerializer<Container.NestedEnumWithContextual>("contextual|Container.NestedEnumWithContextual")
|
||||||
|
object EnumWithUseContextualSerializer: ToDoSerializer<EnumWithUseContextual>("contextual|EnumWithUseContextual")
|
||||||
|
object NestedEnumWithUseContextualSerializer: ToDoSerializer<Container.NestedEnumWithUseContextual>("contextual|Container.NestedEnumWithUseContextual")
|
||||||
|
|
||||||
|
class EnumWithUseSerializer: ToDoSerializer<EnumWithUse>("useSerializer|EnumWithUse")
|
||||||
|
class NestedEnumWithUseSerializer: ToDoSerializer<Container.NestedEnumWithUse>("useSerializer|Container.NestedEnumWithUse")
|
||||||
|
|
||||||
|
@kotlinx.serialization.SerialInfo
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||||
|
annotation class Extra(val value: String)
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val module = SerializersModule {
|
||||||
|
contextual(EnumWithContextualSerializer)
|
||||||
|
contextual(NestedEnumWithContextualSerializer)
|
||||||
|
contextual(EnumWithUseContextualSerializer)
|
||||||
|
contextual(NestedEnumWithUseContextualSerializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer<EnumWithDef>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithDef>().checkElements("A", "B")
|
||||||
|
serializer<Enum>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnum>().checkElements("A", "B")
|
||||||
|
serializer<EnumWithCustom>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithCustom>().checkElements("A", "B")
|
||||||
|
serializer<EnumWithCustomCl>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithCustomCl>().checkElements("A", "B")
|
||||||
|
serializer<EnumWithContextual>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithContextual>().checkElements("A", "B")
|
||||||
|
serializer<EnumWithUseContextual>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithUseContextual>().checkElements("A", "B")
|
||||||
|
serializer<EnumWithUse>().checkElements("A", "B")
|
||||||
|
serializer<Container.NestedEnumWithUse>().checkElements("A", "B")
|
||||||
|
serializer<Enum1>().checkElements("A", "B")
|
||||||
|
|
||||||
|
// Call serializer factory function in companion
|
||||||
|
Enum.serializer().checkSerialName("Enum")?.let { return it }
|
||||||
|
Container.NestedEnum.serializer().checkSerialName("Container.NestedEnum")?.let { return it }
|
||||||
|
EnumWithCustom.serializer().checkSerialName("custom|EnumWithCustom")?.let { return it }
|
||||||
|
Container.NestedEnumWithCustom.serializer().checkSerialName("custom|Container.NestedEnumWithCustom")?.let { return it }
|
||||||
|
EnumWithCustomCl.serializer().checkSerialName("custom|EnumWithCustomCl")?.let { return it }
|
||||||
|
Container.NestedEnumWithCustomCl.serializer().checkSerialName("custom|Container.NestedEnumWithCustomCl")?.let { return it }
|
||||||
|
Enum1.serializer().checkSerialName("Enum1")?.let { return it }
|
||||||
|
|
||||||
|
// Serializer lookup by generic parameter
|
||||||
|
serializer<EnumWithDef>().checkSerialName("EnumWithDef")?.let { return it }
|
||||||
|
serializer<Container.NestedEnumWithDef>().checkSerialName("Container.NestedEnumWithDef")?.let { return it }
|
||||||
|
serializer<Enum>().checkSerialName("Enum")?.let { return it }
|
||||||
|
serializer<Container.NestedEnum>().checkSerialName("Container.NestedEnum")?.let { return it }
|
||||||
|
serializer<EnumWithCustom>().checkSerialName("custom|EnumWithCustom")?.let { return it }
|
||||||
|
serializer<Container.NestedEnumWithCustom>().checkSerialName("custom|Container.NestedEnumWithCustom")?.let { return it }
|
||||||
|
serializer<EnumWithCustomCl>().checkSerialName("custom|EnumWithCustomCl")?.let { return it }
|
||||||
|
serializer<Container.NestedEnumWithCustomCl>().checkSerialName("custom|Container.NestedEnumWithCustomCl")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer<EnumWithUseContextual>().checkSerialName("EnumWithUseContextual")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer<Container.NestedEnumWithUseContextual>().checkSerialName("Container.NestedEnumWithUseContextual")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer<EnumWithUse>().checkSerialName("EnumWithUse")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer<Container.NestedEnumWithUse>().checkSerialName("Container.NestedEnumWithUse")?.let { return it }
|
||||||
|
serializer<Enum1>().checkSerialName("Enum1")?.let { return it }
|
||||||
|
|
||||||
|
// Serializer lookup by typeOf function
|
||||||
|
serializer(typeOf<EnumWithDef>()).checkSerialName("EnumWithDef")?.let { return it }
|
||||||
|
serializer(typeOf<Container.NestedEnumWithDef>()).checkSerialName("Container.NestedEnumWithDef")?.let { return it }
|
||||||
|
serializer(typeOf<Enum>()).checkSerialName("Enum")?.let { return it }
|
||||||
|
serializer(typeOf<Container.NestedEnum>()).checkSerialName("Container.NestedEnum")?.let { return it }
|
||||||
|
serializer(typeOf<EnumWithCustom>()).checkSerialName("custom|EnumWithCustom")?.let { return it }
|
||||||
|
serializer(typeOf<Container.NestedEnumWithCustom>()).checkSerialName("custom|Container.NestedEnumWithCustom")?.let { return it }
|
||||||
|
serializer(typeOf<EnumWithCustomCl>()).checkSerialName("custom|EnumWithCustomCl")?.let { return it }
|
||||||
|
serializer(typeOf<Container.NestedEnumWithCustomCl>()).checkSerialName("custom|Container.NestedEnumWithCustomCl")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer(typeOf<EnumWithUseContextual>()).checkSerialName("EnumWithUseContextual")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer(typeOf<Container.NestedEnumWithUseContextual>()).checkSerialName("Container.NestedEnumWithUseContextual")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer(typeOf<EnumWithUse>()).checkSerialName("EnumWithUse")?.let { return it }
|
||||||
|
// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
serializer(typeOf<Container.NestedEnumWithUse>()).checkSerialName("Container.NestedEnumWithUse")?.let { return it }
|
||||||
|
serializer(typeOf<Enum1>()).checkSerialName("Enum1")?.let { return it }
|
||||||
|
|
||||||
|
// Serializer lookup by generic parameter in custom module
|
||||||
|
module.serializer<EnumWithDef>().checkSerialName("EnumWithDef")?.let { return it }
|
||||||
|
module.serializer<Container.NestedEnumWithDef>().checkSerialName("Container.NestedEnumWithDef")?.let { return it }
|
||||||
|
module.serializer<Enum>().checkSerialName("Enum")?.let { return it }
|
||||||
|
module.serializer<Container.NestedEnum>().checkSerialName("Container.NestedEnum")?.let { return it }
|
||||||
|
module.serializer<EnumWithCustom>().checkSerialName("custom|EnumWithCustom")?.let { return it }
|
||||||
|
module.serializer<Container.NestedEnumWithCustom>().checkSerialName("custom|Container.NestedEnumWithCustom")?.let { return it }
|
||||||
|
module.serializer<EnumWithCustomCl>().checkSerialName("custom|EnumWithCustomCl")?.let { return it }
|
||||||
|
module.serializer<Container.NestedEnumWithCustomCl>().checkSerialName("custom|Container.NestedEnumWithCustomCl")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer<EnumWithUseContextual>().checkSerialName("EnumWithUseContextual")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer<Container.NestedEnumWithUseContextual>().checkSerialName("Container.NestedEnumWithUseContextual")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer<EnumWithUse>().checkSerialName("EnumWithUse")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer<Container.NestedEnumWithUse>().checkSerialName("Container.NestedEnumWithUse")?.let { return it }
|
||||||
|
module.serializer<Enum1>().checkSerialName("Enum1")?.let { return it }
|
||||||
|
|
||||||
|
// Serializer lookup by typeOf function in custom module
|
||||||
|
module.serializer(typeOf<EnumWithDef>()).checkSerialName("EnumWithDef")?.let { return it }
|
||||||
|
module.serializer(typeOf<Container.NestedEnumWithDef>()).checkSerialName("Container.NestedEnumWithDef")?.let { return it }
|
||||||
|
module.serializer(typeOf<Enum>()).checkSerialName("Enum")?.let { return it }
|
||||||
|
module.serializer(typeOf<Container.NestedEnum>()).checkSerialName("Container.NestedEnum")?.let { return it }
|
||||||
|
module.serializer(typeOf<EnumWithCustom>()).checkSerialName("custom|EnumWithCustom")?.let { return it }
|
||||||
|
module.serializer(typeOf<Container.NestedEnumWithCustom>()).checkSerialName("custom|Container.NestedEnumWithCustom")?.let { return it }
|
||||||
|
module.serializer(typeOf<EnumWithCustomCl>()).checkSerialName("custom|EnumWithCustomCl")?.let { return it }
|
||||||
|
module.serializer(typeOf<Container.NestedEnumWithCustomCl>()).checkSerialName("custom|Container.NestedEnumWithCustomCl")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer(typeOf<EnumWithUseContextual>()).checkSerialName("EnumWithUseContextual")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer(typeOf<Container.NestedEnumWithUseContextual>()).checkSerialName("Container.NestedEnumWithUseContextual")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer(typeOf<EnumWithUse>()).checkSerialName("EnumWithUse")?.let { return it }
|
||||||
|
// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers
|
||||||
|
module.serializer(typeOf<Container.NestedEnumWithUse>()).checkSerialName("Container.NestedEnumWithUse")?.let { return it }
|
||||||
|
module.serializer(typeOf<Enum1>()).checkSerialName("Enum1")?.let { return it }
|
||||||
|
|
||||||
|
// Annotation on type should have value same as a class name
|
||||||
|
serializer<Enum1>().checkAnnotation("Enum1")
|
||||||
|
|
||||||
|
// Annotation on enum entries should have value same as a entry names
|
||||||
|
serializer<Enum1>().checkElementAnnotations("A", "B")
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KSerializer<*>.checkSerialName(name: String): String? = if (descriptor.serialName != name) "Wrong serial name: Expected '$name' actual '${descriptor.serialName}'" else null
|
||||||
|
fun KSerializer<*>.checkAnnotation(value: String): String? = descriptor.annotations.filterIsInstance<Extra>().single().value.let { if (it != value) it else null }
|
||||||
|
|
||||||
|
fun KSerializer<*>.checkElementAnnotations(vararg values: String): String? = (0 ..< descriptor.elementsCount).map { descriptor.getElementAnnotations(it).filterIsInstance<Extra>().single().value}.let { if (it != values.toList()) it.toString() else null }
|
||||||
|
|
||||||
|
fun KSerializer<*>.checkElements(vararg values: String): String? = (0 ..< descriptor.elementsCount).map { descriptor.getElementName(it) }.let { if (it != values.toList()) it.toString() else null }
|
||||||
|
|
||||||
|
|
||||||
|
abstract class ToDoSerializer<T: Any>(descriptorName: String): KSerializer<T> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(descriptorName, PrimitiveKind.STRING)
|
||||||
|
override fun deserialize(decoder: Decoder): T = TODO()
|
||||||
|
override fun serialize(encoder: Encoder, value: T) = TODO()
|
||||||
|
}
|
||||||
|
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix;
|
||||||
|
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
|
import org.jetbrains.kotlin.test.util.KtTestUtil;
|
||||||
|
import org.jetbrains.kotlin.test.TargetBackend;
|
||||||
|
import org.jetbrains.kotlin.test.TestMetadata;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** This class is generated by {@link org.jetbrains.kotlinx.serialization.TestGeneratorKt}. DO NOT MODIFY MANUALLY */
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
@TestMetadata("plugins/kotlinx-serialization/testData/matrix")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
public class FirTestMatrixGenerated extends AbstractFirTestMatrix {
|
||||||
|
@Test
|
||||||
|
public void testAllFilesPresentInMatrix() throws Exception {
|
||||||
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/matrix"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("enums.kt")
|
||||||
|
public void testEnums() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/matrix/enums.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix;
|
||||||
|
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
|
import org.jetbrains.kotlin.test.util.KtTestUtil;
|
||||||
|
import org.jetbrains.kotlin.test.TargetBackend;
|
||||||
|
import org.jetbrains.kotlin.test.TestMetadata;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** This class is generated by {@link org.jetbrains.kotlinx.serialization.TestGeneratorKt}. DO NOT MODIFY MANUALLY */
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
@TestMetadata("plugins/kotlinx-serialization/testData/matrix")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
public class TestMatrixGenerated extends AbstractTestMatrix {
|
||||||
|
@Test
|
||||||
|
public void testAllFilesPresentInMatrix() throws Exception {
|
||||||
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/matrix"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("enums.kt")
|
||||||
|
public void testEnums() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/matrix/enums.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -7,6 +7,8 @@ package org.jetbrains.kotlinx.serialization
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5
|
import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5
|
||||||
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
|
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.cases.enumsTestMatrix
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.testMatrix
|
||||||
import org.jetbrains.kotlinx.serialization.runners.*
|
import org.jetbrains.kotlinx.serialization.runners.*
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
@@ -77,6 +79,10 @@ fun main(args: Array<String>) {
|
|||||||
testClass<AbstractSerializationFirJsBoxTest> {
|
testClass<AbstractSerializationFirJsBoxTest> {
|
||||||
model("boxIr")
|
model("boxIr")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testMatrix {
|
||||||
|
add("enums") { enumsTestMatrix() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix
|
||||||
|
|
||||||
|
|
||||||
|
interface CombinationContext {
|
||||||
|
fun defineEnums(
|
||||||
|
serializers: Set<SerializerKind>,
|
||||||
|
locations: Set<TypeLocation>,
|
||||||
|
optionsConfig: EnumOptionsBuilder.() -> Unit = {},
|
||||||
|
): Set<EnumVariant>
|
||||||
|
|
||||||
|
fun function(name: String, block: FunctionContext.() -> Unit)
|
||||||
|
|
||||||
|
fun box(block: FunctionContext.() -> Unit)
|
||||||
|
|
||||||
|
fun generate(appendable: Appendable, generator: String)
|
||||||
|
|
||||||
|
val TypeVariant.named: NamedTypeVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface FunctionContext {
|
||||||
|
fun line(code: String = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EnumOptionsBuilder {
|
||||||
|
fun serialInfo(vararg serialInfo: SerialInfo)
|
||||||
|
fun descriptorAccessing(vararg descriptorAccessing: DescriptorAccessing)
|
||||||
|
|
||||||
|
fun entries(vararg entries: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.SerializerKind.BY_DEFAULT
|
||||||
|
|
||||||
|
internal fun hasFactoryFun(type: TypeVariant): Boolean {
|
||||||
|
return type.features.serializer in setOf(
|
||||||
|
SerializerKind.GENERATED,
|
||||||
|
SerializerKind.CUSTOM_OBJECT,
|
||||||
|
SerializerKind.CUSTOM_CLASS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun canBeUsedInLookup(type: TypeVariant): Boolean {
|
||||||
|
return type.features.serializer in setOf(
|
||||||
|
SerializerKind.GENERATED,
|
||||||
|
SerializerKind.CUSTOM_OBJECT,
|
||||||
|
SerializerKind.CUSTOM_CLASS,
|
||||||
|
SerializerKind.CLASS_USE_SERIALIZER,
|
||||||
|
SerializerKind.USE_CONTEXTUAL
|
||||||
|
) || (type is EnumVariant && type.features.serializer == BY_DEFAULT)
|
||||||
|
}
|
||||||
|
internal fun canBeUsedInModuleLookup(type: TypeVariant): Boolean {
|
||||||
|
return type.features.serializer in setOf(
|
||||||
|
SerializerKind.GENERATED,
|
||||||
|
SerializerKind.CUSTOM_OBJECT,
|
||||||
|
SerializerKind.CUSTOM_CLASS,
|
||||||
|
SerializerKind.CLASS_USE_SERIALIZER,
|
||||||
|
SerializerKind.USE_CONTEXTUAL
|
||||||
|
) || (type is EnumVariant && type.features.serializer == BY_DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun shouldAddContextualSerializerToModule(type: TypeVariant): Boolean {
|
||||||
|
return type.features.serializer == SerializerKind.CONTEXTUAL || type.features.serializer == SerializerKind.USE_CONTEXTUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun hasAnnotationOnType(type: TypeVariant): Boolean {
|
||||||
|
return type is EnumVariant && SerialInfo.ON_TYPE in type.options.serialInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun hasAnnotationOnElement(type: TypeVariant): Boolean {
|
||||||
|
return type is EnumVariant && SerialInfo.ON_ELEMENTS in type.options.serialInfo
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.generators.TestGroup
|
||||||
|
import org.jetbrains.kotlin.incremental.deleteDirectoryContents
|
||||||
|
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||||
|
import org.jetbrains.kotlin.test.runners.codegen.AbstractFirLightTreeBlackBoxCodegenTest
|
||||||
|
import org.jetbrains.kotlin.test.runners.codegen.AbstractIrBlackBoxCodegenTest
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.impl.CombinationContextImpl
|
||||||
|
import org.jetbrains.kotlinx.serialization.configureForKotlinxSerialization
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
internal fun TestGroup.testMatrix(casesBlock: TestCaseContext.() -> Unit) {
|
||||||
|
val relativeRootPath = "matrix"
|
||||||
|
val dir = File("$testDataRoot/$relativeRootPath")
|
||||||
|
dir.mkdirs()
|
||||||
|
dir.deleteDirectoryContents()
|
||||||
|
|
||||||
|
val caseContext = TestCaseContext()
|
||||||
|
caseContext.casesBlock()
|
||||||
|
|
||||||
|
caseContext.testCases.forEach { (caseName, block) ->
|
||||||
|
val combinationContext = CombinationContextImpl()
|
||||||
|
combinationContext.block()
|
||||||
|
combinationContext.generateInto(dir.resolve("$caseName.kt"))
|
||||||
|
}
|
||||||
|
|
||||||
|
testClass<AbstractTestMatrix> {
|
||||||
|
model(relativeRootPath)
|
||||||
|
}
|
||||||
|
testClass<AbstractFirTestMatrix> {
|
||||||
|
model(relativeRootPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TestCaseContext {
|
||||||
|
val testCases: MutableMap<String, CombinationContext.() -> Unit> = mutableMapOf()
|
||||||
|
|
||||||
|
fun add(name: String, block: CombinationContext.() -> Unit) {
|
||||||
|
testCases[name] = block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal open class AbstractTestMatrix : AbstractIrBlackBoxCodegenTest() {
|
||||||
|
override fun configure(builder: TestConfigurationBuilder) {
|
||||||
|
super.configure(builder)
|
||||||
|
builder.configureForKotlinxSerialization()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal open class AbstractFirTestMatrix : AbstractFirLightTreeBlackBoxCodegenTest() {
|
||||||
|
override fun configure(builder: TestConfigurationBuilder) {
|
||||||
|
super.configure(builder)
|
||||||
|
builder.configureForKotlinxSerialization()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CombinationContext.generateInto(file: File) {
|
||||||
|
file.writer().use {
|
||||||
|
generate(it, "TestMatrixIntegration.kt")
|
||||||
|
}
|
||||||
|
}
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.impl.CLASS_FOR_NESTED
|
||||||
|
|
||||||
|
|
||||||
|
internal fun Set<TypeVariant>.filterEnums(predicate: (EnumVariant) -> Boolean): Set<EnumVariant> {
|
||||||
|
val filtered = filterIsInstance<EnumVariant>().filter { variant -> predicate(variant) }
|
||||||
|
return filtered.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A combination of mutually exclusive Kotlin language and serialization features.
|
||||||
|
*/
|
||||||
|
sealed class TypeFeatures {
|
||||||
|
abstract val serializer: SerializerKind
|
||||||
|
abstract val location: TypeLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the type used to generate type definitions and their uses.
|
||||||
|
*
|
||||||
|
* Parent type.
|
||||||
|
*/
|
||||||
|
sealed class TypeVariant {
|
||||||
|
/**
|
||||||
|
* A combination of mutually exclusive Kotlin language and serialization features.
|
||||||
|
*/
|
||||||
|
abstract val features: TypeFeatures
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the enum types used to generate type definitions and their uses.
|
||||||
|
*/
|
||||||
|
data class EnumVariant(
|
||||||
|
override val features: EnumFeatures,
|
||||||
|
val options: EnumOptions,
|
||||||
|
) : TypeVariant()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A combination of mutually exclusive Kotlin language and serialization features for enum types.
|
||||||
|
*/
|
||||||
|
data class EnumFeatures(
|
||||||
|
override val serializer: SerializerKind,
|
||||||
|
override val location: TypeLocation,
|
||||||
|
) : TypeFeatures()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional features for the type.
|
||||||
|
*/
|
||||||
|
data class EnumOptions(
|
||||||
|
val serialInfo: Set<SerialInfo>,
|
||||||
|
val descriptorAccessing: Set<DescriptorAccessing>,
|
||||||
|
val entries: Set<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
enum class SerializerKind {
|
||||||
|
/**
|
||||||
|
* Serializable by default (Without @Serializable annotation: enum, interface, sealed interface)
|
||||||
|
*/
|
||||||
|
BY_DEFAULT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializable
|
||||||
|
*/
|
||||||
|
GENERATED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializable(CustomObjectSerializer::class)
|
||||||
|
*/
|
||||||
|
CUSTOM_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializable(CustomSerializer::class)
|
||||||
|
*/
|
||||||
|
CUSTOM_CLASS,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contextual by SerialModule.
|
||||||
|
*/
|
||||||
|
CONTEXTUAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes marked by file-level annotation @file:UseContextualSerialization
|
||||||
|
*/
|
||||||
|
USE_CONTEXTUAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializable by @UseSerializers
|
||||||
|
*/
|
||||||
|
CLASS_USE_SERIALIZER
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location of the type definition.
|
||||||
|
*/
|
||||||
|
enum class TypeLocation {
|
||||||
|
FILE_ROOT,
|
||||||
|
LOCAL,
|
||||||
|
NESTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SerialInfo {
|
||||||
|
ON_TYPE,
|
||||||
|
ON_ELEMENTS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kotlin and serialization feature that are not mutually exclusive and there may be several features of the same kind.
|
||||||
|
*/
|
||||||
|
interface TypeOptionalFeature
|
||||||
|
|
||||||
|
enum class DescriptorAccessing : TypeOptionalFeature {
|
||||||
|
FROM_INIT,
|
||||||
|
FROM_COMPANION_INIT,
|
||||||
|
FROM_COMPANION_PROPERTY_INIT
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pair in the description of the type and the name that was assigned to it during generation.
|
||||||
|
*/
|
||||||
|
class NamedTypeVariant(val name: String, val variant: TypeVariant) {
|
||||||
|
internal val classUsage: String
|
||||||
|
get() {
|
||||||
|
return when (variant.features.location) {
|
||||||
|
TypeLocation.FILE_ROOT -> name
|
||||||
|
TypeLocation.LOCAL -> name
|
||||||
|
TypeLocation.NESTED -> "$CLASS_FOR_NESTED.$name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix.cases
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.SerializerKind.*
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.TypeLocation.FILE_ROOT
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.TypeLocation.NESTED
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.*
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.impl.*
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.impl.elements
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.impl.serialName
|
||||||
|
|
||||||
|
fun CombinationContext.enumsTestMatrix() {
|
||||||
|
val enums = defineEnums(
|
||||||
|
SerializerKind.entries.toSet(),
|
||||||
|
setOf(FILE_ROOT, NESTED)
|
||||||
|
) {
|
||||||
|
entries("A", "B")
|
||||||
|
descriptorAccessing(*DescriptorAccessing.entries.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
val enumsWithAnnotations = defineEnums(
|
||||||
|
setOf(GENERATED),
|
||||||
|
setOf(FILE_ROOT)
|
||||||
|
) {
|
||||||
|
entries("A", "B")
|
||||||
|
serialInfo(*SerialInfo.entries.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
val allTypes = enums + enumsWithAnnotations
|
||||||
|
|
||||||
|
val withCompanion = allTypes.filter(::hasFactoryFun)
|
||||||
|
val lookupTypes = allTypes.filter(::canBeUsedInLookup)
|
||||||
|
|
||||||
|
val moduleLookupTypes = allTypes.filter(::canBeUsedInModuleLookup)
|
||||||
|
|
||||||
|
val annotatedTypes = allTypes.filter(::hasAnnotationOnType)
|
||||||
|
val annotatedElementsTypes = allTypes.filter(::hasAnnotationOnElement)
|
||||||
|
|
||||||
|
box() {
|
||||||
|
line("val module = SerializersModule {")
|
||||||
|
allTypes.filter(::shouldAddContextualSerializerToModule).forEach { type ->
|
||||||
|
line(" contextual(${type.named.serializerName})")
|
||||||
|
}
|
||||||
|
line("}")
|
||||||
|
line()
|
||||||
|
|
||||||
|
allTypes.forEach { type ->
|
||||||
|
line("serializer<${type.named.classUsage}>().checkElements(${
|
||||||
|
type.elements.joinToString(", ") { "\"$it\"" }
|
||||||
|
})")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Call serializer factory function in companion")
|
||||||
|
withCompanion.forEach { type ->
|
||||||
|
line("${type.named.classUsage}.serializer().checkSerialName(\"${type.named.serialName}\")?.let { return it }")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Serializer lookup by generic parameter")
|
||||||
|
lookupTypes.forEach { type ->
|
||||||
|
val serialName = if (type.features.serializer == CLASS_USE_SERIALIZER || type.features.serializer == USE_CONTEXTUAL) {
|
||||||
|
line("// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers")
|
||||||
|
type.named.classUsage
|
||||||
|
} else {
|
||||||
|
type.named.serialName
|
||||||
|
}
|
||||||
|
line("serializer<${type.named.classUsage}>().checkSerialName(\"$serialName\")?.let { return it }")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Serializer lookup by typeOf function")
|
||||||
|
lookupTypes.forEach { type ->
|
||||||
|
val serialName = if (type.features.serializer == CLASS_USE_SERIALIZER || type.features.serializer == USE_CONTEXTUAL) {
|
||||||
|
line("// generated serializer used in lookup for the empty module in case of specifying of @file:UseContextualSerialization or @UseSerializers")
|
||||||
|
type.named.classUsage
|
||||||
|
} else {
|
||||||
|
type.named.serialName
|
||||||
|
}
|
||||||
|
line("serializer(typeOf<${type.named.classUsage}>()).checkSerialName(\"$serialName\")?.let { return it }")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Serializer lookup by generic parameter in custom module")
|
||||||
|
moduleLookupTypes.forEach { type ->
|
||||||
|
val serialName = if (type.features.serializer == CLASS_USE_SERIALIZER || type.features.serializer == USE_CONTEXTUAL) {
|
||||||
|
line("// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers")
|
||||||
|
type.named.classUsage
|
||||||
|
} else {
|
||||||
|
type.named.serialName
|
||||||
|
}
|
||||||
|
line("module.serializer<${type.named.classUsage}>().checkSerialName(\"$serialName\")?.let { return it }")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Serializer lookup by typeOf function in custom module")
|
||||||
|
moduleLookupTypes.forEach { type ->
|
||||||
|
val serialName = if (type.features.serializer == CLASS_USE_SERIALIZER || type.features.serializer == USE_CONTEXTUAL) {
|
||||||
|
line("// !!! for some reason, the generated serializer is still lookup for the custom module in case of specifying of @file:UseContextualSerialization or @UseSerializers")
|
||||||
|
type.named.classUsage
|
||||||
|
} else {
|
||||||
|
type.named.serialName
|
||||||
|
}
|
||||||
|
line("module.serializer(typeOf<${type.named.classUsage}>()).checkSerialName(\"$serialName\")?.let { return it }")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Annotation on type should have value same as a class name")
|
||||||
|
annotatedTypes.forEach { type ->
|
||||||
|
line("serializer<${type.named.classUsage}>().checkAnnotation(\"${type.named.classUsage}\")")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
|
||||||
|
line("// Annotation on enum entries should have value same as a entry names")
|
||||||
|
annotatedElementsTypes.forEach { type ->
|
||||||
|
line("serializer<${type.named.classUsage}>().checkElementAnnotations(${
|
||||||
|
type.elements.joinToString(", ") { "\"$it\"" }
|
||||||
|
})")
|
||||||
|
}
|
||||||
|
line()
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix.impl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.*
|
||||||
|
|
||||||
|
internal class CombinationContextImpl : CombinationContext {
|
||||||
|
private val typesByFeatures = mutableMapOf<TypeFeatures, MutableList<TypeVariant>>()
|
||||||
|
private val types: MutableMap<TypeVariant, String> = mutableMapOf()
|
||||||
|
private val functions: MutableMap<String, FunctionContext.() -> Unit> = mutableMapOf()
|
||||||
|
|
||||||
|
override fun defineEnums(
|
||||||
|
serializers: Set<SerializerKind>,
|
||||||
|
locations: Set<TypeLocation>,
|
||||||
|
optionsConfig: EnumOptionsBuilder.() -> Unit,
|
||||||
|
): Set<EnumVariant> {
|
||||||
|
val enumTypes: MutableSet<EnumVariant> = mutableSetOf()
|
||||||
|
val options = EnumOptionsBuilderImpl().also { it.optionsConfig() }.build()
|
||||||
|
for (serializer in serializers) {
|
||||||
|
for (location in locations) {
|
||||||
|
val features = EnumFeatures(serializer, location)
|
||||||
|
enumTypes += EnumVariant(features, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTypes(enumTypes)
|
||||||
|
return enumTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun function(name: String, block: FunctionContext.() -> Unit) {
|
||||||
|
functions[name] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun box(block: FunctionContext.() -> Unit) {
|
||||||
|
functions["box(): String"] = {
|
||||||
|
block()
|
||||||
|
line("return \"OK\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generate(appendable: Appendable, generator: String) {
|
||||||
|
val namedTypes = types.map { (type, name) -> NamedTypeVariant(name, type) }
|
||||||
|
appendable.writeHeader(namedTypes, generator)
|
||||||
|
appendable.writeTypes(namedTypes)
|
||||||
|
|
||||||
|
functions.forEach { (signature, builder) ->
|
||||||
|
appendable.writeFunction(signature, builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendable.writeUtils()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val TypeVariant.named: NamedTypeVariant
|
||||||
|
get() = NamedTypeVariant(types[this] ?: throw Exception("Type variant wasn't defined properly $this"), this)
|
||||||
|
|
||||||
|
private fun addTypes(types: Iterable<TypeVariant>) {
|
||||||
|
for (type in types) {
|
||||||
|
val typesForFeatures = typesByFeatures.getOrPut(type.features) { mutableListOf() }
|
||||||
|
val className = type.className + if (typesForFeatures.size > 0) typesForFeatures.size.toString() else ""
|
||||||
|
typesForFeatures += type
|
||||||
|
this.types[type] = className
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EnumOptionsBuilderImpl : EnumOptionsBuilder {
|
||||||
|
private val serialInfoP: MutableSet<SerialInfo> = mutableSetOf()
|
||||||
|
private val descriptorAccessingP: MutableSet<DescriptorAccessing> = mutableSetOf()
|
||||||
|
private val entriesP: MutableSet<String> = mutableSetOf()
|
||||||
|
|
||||||
|
|
||||||
|
override fun serialInfo(vararg serialInfo: SerialInfo) {
|
||||||
|
serialInfo.forEach { serialInfoP.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun descriptorAccessing(vararg descriptorAccessing: DescriptorAccessing) {
|
||||||
|
descriptorAccessing.forEach { descriptorAccessingP.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun entries(vararg entries: String) {
|
||||||
|
entries.forEach { entriesP.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): EnumOptions {
|
||||||
|
return EnumOptions(serialInfoP, descriptorAccessingP, entriesP)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix.impl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.FunctionContext
|
||||||
|
|
||||||
|
internal fun Appendable.writeFunction(signature: String, builder: FunctionContext.() -> Unit) {
|
||||||
|
appendLine("fun $signature {")
|
||||||
|
|
||||||
|
val context = object : FunctionContext {
|
||||||
|
override fun line(code: String) {
|
||||||
|
append(" ")
|
||||||
|
appendLine(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.builder()
|
||||||
|
|
||||||
|
appendLine("}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
+234
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix.impl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.EnumVariant
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.NamedTypeVariant
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.SerialInfo
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.SerializerKind.*
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.TypeLocation.*
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
<file level declarations>
|
||||||
|
|
||||||
|
class Container {
|
||||||
|
<nested declarations>
|
||||||
|
}
|
||||||
|
|
||||||
|
class Outer {
|
||||||
|
<inner declarations>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <function name> () {
|
||||||
|
<local classes>
|
||||||
|
<function declaration>
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
internal fun Appendable.writeTypes(types: List<NamedTypeVariant>) {
|
||||||
|
// file-level
|
||||||
|
types.filter { type -> type.variant.features.location == FILE_ROOT }.forEach { type ->
|
||||||
|
writeTypeDef(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested
|
||||||
|
append("class ")
|
||||||
|
append(CLASS_FOR_NESTED)
|
||||||
|
append(" {\n")
|
||||||
|
types.filter { named -> named.variant.features.location == NESTED }.forEach { type ->
|
||||||
|
writeTypeDef(type, " ")
|
||||||
|
}
|
||||||
|
append("}\n\n")
|
||||||
|
|
||||||
|
// write inner
|
||||||
|
append("class ")
|
||||||
|
append(CLASS_FOR_INNER)
|
||||||
|
append(" {\n")
|
||||||
|
types.filter { named -> named.variant.features.location == LOCAL }.forEach { type ->
|
||||||
|
writeTypeDef(type, " ")
|
||||||
|
}
|
||||||
|
append("}\n\n")
|
||||||
|
|
||||||
|
// write custom serializers
|
||||||
|
types.forEach { type ->
|
||||||
|
writeCustomSerializer(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine()
|
||||||
|
|
||||||
|
// write contextual serializers
|
||||||
|
types.forEach { type ->
|
||||||
|
writeContextualSerializer(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine()
|
||||||
|
|
||||||
|
// write use serializers
|
||||||
|
types.forEach { type ->
|
||||||
|
writeUseSerializer(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLine()
|
||||||
|
|
||||||
|
writeSerialInfo()
|
||||||
|
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Appendable.writeHeader(types: List<NamedTypeVariant>, generator: String) {
|
||||||
|
appendLine("// WITH_STDLIB")
|
||||||
|
appendLine()
|
||||||
|
appendLine("//")
|
||||||
|
appendLine("// NOTE: THIS FILE IS AUTO-GENERATED by the $generator, DO NOT EDIT!")
|
||||||
|
appendLine("//")
|
||||||
|
appendLine()
|
||||||
|
writeUseSerializers(types)
|
||||||
|
writeUseContextualSerializers(types)
|
||||||
|
appendLine()
|
||||||
|
appendLine("import kotlinx.serialization.*")
|
||||||
|
appendLine("import kotlinx.serialization.descriptors.*")
|
||||||
|
appendLine("import kotlinx.serialization.json.*")
|
||||||
|
appendLine("import kotlinx.serialization.encoding.*")
|
||||||
|
appendLine("import kotlinx.serialization.modules.*")
|
||||||
|
appendLine("import kotlin.reflect.typeOf")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Appendable.writeUtils() {
|
||||||
|
append("fun KSerializer<*>.checkSerialName(name: String): String? = if (descriptor.serialName != name) \"Wrong serial name: Expected '\$name' actual '\${descriptor.serialName}'\" else null\n")
|
||||||
|
append("fun KSerializer<*>.checkAnnotation(value: String): String? = descriptor.annotations.filterIsInstance<Extra>().single().value.let { if (it != value) it else null }\n\n")
|
||||||
|
append("fun KSerializer<*>.checkElementAnnotations(vararg values: String): String? = (0 ..< descriptor.elementsCount).map { descriptor.getElementAnnotations(it).filterIsInstance<Extra>().single().value}.let { if (it != values.toList()) it.toString() else null }\n\n")
|
||||||
|
append("fun KSerializer<*>.checkElements(vararg values: String): String? = (0 ..< descriptor.elementsCount).map { descriptor.getElementName(it) }.let { if (it != values.toList()) it.toString() else null }\n\n")
|
||||||
|
|
||||||
|
// write abstract serializer
|
||||||
|
appendLine(TODO_SERIALIZER)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun Appendable.writeTypeDef(named: NamedTypeVariant, indent: String = "") {
|
||||||
|
when (named.variant) {
|
||||||
|
is EnumVariant -> writeEnumDef(named, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeEnumDef(named: NamedTypeVariant, indent: String) {
|
||||||
|
val enum = named.variant as EnumVariant
|
||||||
|
|
||||||
|
if (enum.features.location == LOCAL) {
|
||||||
|
throw IllegalArgumentException("Local enums are not allowed, can't generate test code.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val classUsage = named.classUsage
|
||||||
|
|
||||||
|
if (enum.features.serializer == GENERATED) {
|
||||||
|
append(indent)
|
||||||
|
appendLine("@Serializable")
|
||||||
|
}
|
||||||
|
if (enum.features.serializer == CUSTOM_OBJECT || enum.features.serializer == CUSTOM_CLASS) {
|
||||||
|
append(indent)
|
||||||
|
appendLine("@Serializable(${named.serializerName}::class)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SerialInfo.ON_TYPE in enum.options.serialInfo) {
|
||||||
|
append(indent)
|
||||||
|
appendLine("@$SERIAL_ANNOTATION(\"$classUsage\")")
|
||||||
|
}
|
||||||
|
|
||||||
|
append(indent)
|
||||||
|
appendLine("enum class ${named.name} {")
|
||||||
|
enum.options.entries.forEach { entry ->
|
||||||
|
append(indent)
|
||||||
|
append(" ")
|
||||||
|
|
||||||
|
if (SerialInfo.ON_ELEMENTS in enum.options.serialInfo) {
|
||||||
|
append("@$SERIAL_ANNOTATION(\"$entry\") ")
|
||||||
|
}
|
||||||
|
append(entry)
|
||||||
|
appendLine(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
append(indent)
|
||||||
|
appendLine("}")
|
||||||
|
appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Appendable.writeUseSerializers(types: List<NamedTypeVariant>) {
|
||||||
|
val serializers = types.mapNotNull { type -> type.useSerializer }
|
||||||
|
if (serializers.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
append("@file:UseSerializers(")
|
||||||
|
serializers.forEach { name ->
|
||||||
|
append(name)
|
||||||
|
append("::class, ")
|
||||||
|
}
|
||||||
|
appendLine(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeUseContextualSerializers(types: List<NamedTypeVariant>) {
|
||||||
|
val types = types.filter { type -> type.useContextualSerializer != null }
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
append("@file:UseContextualSerialization(")
|
||||||
|
types.forEach { type ->
|
||||||
|
append(type.classUsage)
|
||||||
|
append("::class, ")
|
||||||
|
}
|
||||||
|
appendLine(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeSerialInfo() {
|
||||||
|
appendLine("@kotlinx.serialization.SerialInfo")
|
||||||
|
appendLine("@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)")
|
||||||
|
appendLine("annotation class $SERIAL_ANNOTATION(val value: String)")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeCustomSerializer(type: NamedTypeVariant) {
|
||||||
|
when (type.variant.features.serializer) {
|
||||||
|
CUSTOM_CLASS -> {
|
||||||
|
appendLine("class ${type.serializerName}: ToDoSerializer<${type.classUsage}>(\"${type.serialName}\")")
|
||||||
|
}
|
||||||
|
CUSTOM_OBJECT -> {
|
||||||
|
appendLine("object ${type.serializerName}: ToDoSerializer<${type.classUsage}>(\"${type.serialName}\")")
|
||||||
|
}
|
||||||
|
else -> Unit // no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeContextualSerializer(type: NamedTypeVariant) {
|
||||||
|
when (type.variant.features.serializer) {
|
||||||
|
CONTEXTUAL, USE_CONTEXTUAL -> {
|
||||||
|
val usage = type.classUsage
|
||||||
|
appendLine("object ${type.serializerName}: ToDoSerializer<$usage>(\"${type.serialName}\")")
|
||||||
|
}
|
||||||
|
else -> Unit // no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Appendable.writeUseSerializer(type: NamedTypeVariant) {
|
||||||
|
when (type.variant.features.serializer) {
|
||||||
|
CLASS_USE_SERIALIZER -> {
|
||||||
|
val usage = type.classUsage
|
||||||
|
appendLine("class ${type.useSerializer}: ToDoSerializer<$usage>(\"${type.serialName}\")")
|
||||||
|
}
|
||||||
|
else -> Unit // no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TODO_SERIALIZER_NAME = "ToDoSerializer"
|
||||||
|
|
||||||
|
private const val TODO_SERIALIZER = """
|
||||||
|
abstract class $TODO_SERIALIZER_NAME<T: Any>(descriptorName: String): KSerializer<T> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(descriptorName, PrimitiveKind.STRING)
|
||||||
|
override fun deserialize(decoder: Decoder): T = TODO()
|
||||||
|
override fun serialize(encoder: Encoder, value: T) = TODO()
|
||||||
|
}
|
||||||
|
"""
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 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.matrix.impl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.*
|
||||||
|
import org.jetbrains.kotlinx.serialization.matrix.SerializerKind.*
|
||||||
|
|
||||||
|
internal const val CLASS_FOR_NESTED = "Container"
|
||||||
|
|
||||||
|
internal const val CLASS_FOR_INNER = "Outer"
|
||||||
|
|
||||||
|
internal const val SERIAL_ANNOTATION = "Extra"
|
||||||
|
|
||||||
|
|
||||||
|
internal val TypeVariant.className: String
|
||||||
|
get() = when (this) {
|
||||||
|
is EnumVariant -> features.location.namePart + "Enum" + features.serializer.namePart
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val TypeVariant.elements: List<String>
|
||||||
|
get() = when (this) {
|
||||||
|
is EnumVariant -> options.entries.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val NamedTypeVariant.serialName: String
|
||||||
|
get() = when (variant.features.serializer) {
|
||||||
|
CUSTOM_CLASS, CUSTOM_OBJECT -> "custom|$classUsage"
|
||||||
|
CONTEXTUAL, USE_CONTEXTUAL -> "contextual|$classUsage"
|
||||||
|
CLASS_USE_SERIALIZER -> "useSerializer|$classUsage"
|
||||||
|
else -> classUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal val NamedTypeVariant.useSerializer: String?
|
||||||
|
get() {
|
||||||
|
if (variant.features.serializer != CLASS_USE_SERIALIZER) return null
|
||||||
|
return serializerName
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val NamedTypeVariant.useContextualSerializer: String?
|
||||||
|
get() {
|
||||||
|
if (variant.features.serializer != USE_CONTEXTUAL) return null
|
||||||
|
return serializerName
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val NamedTypeVariant.serializerName: String
|
||||||
|
get() = when (variant.features.serializer) {
|
||||||
|
CUSTOM_CLASS, CUSTOM_OBJECT, CLASS_USE_SERIALIZER, CONTEXTUAL, USE_CONTEXTUAL -> name + "Serializer"
|
||||||
|
BY_DEFAULT -> throw IllegalStateException("No named serializer for type serializable by default '$name'")
|
||||||
|
GENERATED -> throw IllegalStateException("No named serializer for type '$name' with automatically generated serializer")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TypeLocation.namePart: String
|
||||||
|
get() = when (this) {
|
||||||
|
TypeLocation.FILE_ROOT -> ""
|
||||||
|
TypeLocation.LOCAL -> "Local"
|
||||||
|
TypeLocation.NESTED -> "Nested"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val SerializerKind.namePart: String
|
||||||
|
get() = when (this) {
|
||||||
|
BY_DEFAULT -> "WithDef"
|
||||||
|
GENERATED -> ""
|
||||||
|
CUSTOM_OBJECT -> "WithCustom"
|
||||||
|
CUSTOM_CLASS -> "WithCustomCl"
|
||||||
|
CONTEXTUAL -> "WithContextual"
|
||||||
|
USE_CONTEXTUAL -> "WithUseContextual"
|
||||||
|
CLASS_USE_SERIALIZER -> "WithUse"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user