Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize EmptySerializersModule #1921

Merged
merged 3 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ public final class kotlinx/serialization/modules/SerializersModuleBuilder : kotl
}

public final class kotlinx/serialization/modules/SerializersModuleBuildersKt {
public static final fun EmptySerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public static final fun SerializersModule (Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/modules/SerializersModule;
public static final fun polymorphic (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun polymorphic$default (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
Expand Down
9 changes: 6 additions & 3 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ public inline fun <reified T> SerializersModule.serializer(): KSerializer<T> {
* @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
*/
@OptIn(ExperimentalSerializationApi::class)
public fun serializer(type: KType): KSerializer<Any?> = EmptySerializersModule.serializer(type)
public fun serializer(type: KType): KSerializer<Any?> = EmptySerializersModule().serializer(type)

/**
* Creates a serializer for the given [type].
* [type] argument can be obtained with experimental [typeOf] method.
* Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable).
*/
@OptIn(ExperimentalSerializationApi::class)
public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule.serializerOrNull(type)
public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule().serializerOrNull(type)

/**
* Attempts to create a serializer for the given [type] and fallbacks to [contextual][SerializersModule.getContextual]
Expand Down Expand Up @@ -122,7 +122,10 @@ private fun SerializersModule.builtinSerializer(
}

@OptIn(ExperimentalSerializationApi::class)
internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
internal fun <T : Any> SerializersModule.reflectiveOrContextual(
kClass: KClass<T>,
typeArgumentsSerializers: List<KSerializer<Any?>>
): KSerializer<T>? {
return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.serialization.modules.*
*/
@OptIn(ExperimentalSerializationApi::class)
internal object NoOpEncoder : AbstractEncoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

public override fun encodeValue(value: Any): Unit = Unit

Expand Down
4 changes: 2 additions & 2 deletions core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
protected abstract fun SerialDescriptor.getTag(index: Int): Tag

override val serializersModule: SerializersModule
get() = EmptySerializersModule
get() = EmptySerializersModule()

// ---- API ----
protected open fun encodeTaggedValue(tag: Tag, value: Any): Unit =
Expand Down Expand Up @@ -177,7 +177,7 @@ public abstract class NamedValueEncoder : TaggedEncoder<String>() {
@InternalSerializationApi
public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
override val serializersModule: SerializersModule
get() = EmptySerializersModule
get() = EmptySerializersModule()

protected abstract fun SerialDescriptor.getTag(index: Int): Tag

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package kotlinx.serialization.modules

import kotlinx.serialization.*
import kotlinx.serialization.internal.*
import kotlin.js.*
import kotlin.jvm.*
import kotlin.native.concurrent.*
import kotlin.reflect.*
Expand All @@ -18,6 +19,9 @@ import kotlin.reflect.*
* To enable runtime serializers resolution, one of the special annotations must be used on target types
* ([Polymorphic] or [Contextual]), and a serial module with serializers should be used during construction of [SerialFormat].
*
* Serializers module can be built with `SerializersModule {}` builder function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we need to do one or two things:

  • Optimize SerializersModule {} so it would return EmptySerializersModule when appropriate
  • Make a diagnostic that suggest replacing SerializersModule {} with EmptySerializersModule() as we have with Json

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly do not see reasons to do so, this optimization doesn't carry any real value as well as the diagnostic.
Json is a different beast as it has a non-trivial initialization cost and also prevents any format-level caching.

* Empty module can be obtained with `EmptySerializersModule()` factory function.
*
* @see Contextual
* @see Polymorphic
*/
Expand All @@ -28,7 +32,7 @@ public sealed class SerializersModule {
"Deprecated in favor of overload with default parameter",
ReplaceWith("getContextual(kclass)"),
DeprecationLevel.HIDDEN
) // Was stable since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner
) // Was experimental since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner
public fun <T : Any> getContextual(kclass: KClass<T>): KSerializer<T>? =
getContextual(kclass, emptyList())

Expand Down Expand Up @@ -70,7 +74,10 @@ public sealed class SerializersModule {
* A [SerializersModule] which is empty and always returns `null`.
*/
@SharedImmutable
@ExperimentalSerializationApi
@Deprecated("Deprecated in the favour of 'EmptySerializersModule()'",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("EmptySerializersModule()"))
@JsName("EmptySerializersModuleLegacyJs") // Compatibility with JS
public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() -
return builder.build()
}

/**
* A [SerializersModule] which is empty and returns `null` from each method.
*/
public fun EmptySerializersModule(): SerializersModule = @Suppress("DEPRECATION") EmptySerializersModule

/**
* A builder class for [SerializersModule] DSL. To create an instance of builder, use [SerializersModule] factory function.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class BasicTypesSerializationTest {

// KeyValue Input/Output
class KeyValueOutput(private val sb: StringBuilder) : AbstractEncoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
sb.append('{')
Expand Down Expand Up @@ -56,7 +56,7 @@ class BasicTypesSerializationTest {
}

class KeyValueInput(private val inp: Parser) : AbstractDecoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
inp.expectAfterWhiteSpace('{')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ class CustomPropertyAccessorsTest {
*/



private class CommonStringDecoder(private val elementCount: Int) : AbstractDecoder() {
override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()
private var elementIndex = 0

override fun decodeString(): String {
Expand Down
26 changes: 19 additions & 7 deletions core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import kotlin.reflect.*
* @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
*/
@ExperimentalSerializationApi
public fun serializer(type: Type): KSerializer<Any> = EmptySerializersModule.serializer(type)
public fun serializer(type: Type): KSerializer<Any> = EmptySerializersModule().serializer(type)

/**
* Reflectively constructs a serializer for the given reflective Java [type].
Expand All @@ -40,7 +40,7 @@ public fun serializer(type: Type): KSerializer<Any> = EmptySerializersModule.ser
* Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable).
*/
@ExperimentalSerializationApi
public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersModule.serializerOrNull(type)
public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersModule().serializerOrNull(type)

/**
* Retrieves serializer for the given reflective Java [type] using
Expand All @@ -56,7 +56,8 @@ public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersMod
*/
@ExperimentalSerializationApi
public fun SerializersModule.serializer(type: Type): KSerializer<Any> =
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass().serializerNotRegistered()
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass()
.serializerNotRegistered()

/**
* Retrieves serializer for the given reflective Java [type] using
Expand All @@ -75,7 +76,10 @@ public fun SerializersModule.serializerOrNull(type: Type): KSerializer<Any>? =
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false)

@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissingTypeArgSerializer: Boolean = true): KSerializer<Any>? =
private fun SerializersModule.serializerByJavaTypeImpl(
type: Type,
failOnMissingTypeArgSerializer: Boolean = true
): KSerializer<Any>? =
when (type) {
is GenericArrayType -> {
genericArraySerializer(type, failOnMissingTypeArgSerializer)
Expand All @@ -85,7 +89,9 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing
val rootClass = (type.rawType as Class<*>)
val args = (type.actualTypeArguments)
val argsSerializers =
if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map { serializerOrNull(it) ?: return null }
if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map {
serializerOrNull(it) ?: return null
}
when {
Set::class.java.isAssignableFrom(rootClass) -> SetSerializer(argsSerializers[0]) as KSerializer<Any>
List::class.java.isAssignableFrom(rootClass) || Collection::class.java.isAssignableFrom(rootClass) -> ListSerializer(
Expand Down Expand Up @@ -122,7 +128,10 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing
}

@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeArgSerializer: Boolean): KSerializer<Any>? {
private fun SerializersModule.typeSerializer(
type: Class<*>,
failOnMissingTypeArgSerializer: Boolean
): KSerializer<Any>? {
return if (type.isArray && !type.componentType.isPrimitive) {
val eType: Class<*> = type.componentType
val s = if (failOnMissingTypeArgSerializer) serializer(eType) else (serializerOrNull(eType) ?: return null)
Expand All @@ -134,7 +143,10 @@ private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeAr
}

@OptIn(ExperimentalSerializationApi::class)
private fun <T : Any> SerializersModule.reflectiveOrContextual(jClass: Class<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
private fun <T : Any> SerializersModule.reflectiveOrContextual(
jClass: Class<T>,
typeArgumentsSerializers: List<KSerializer<Any?>>
): KSerializer<T>? {
jClass.constructSerializerForGivenTypeArgs(*typeArgumentsSerializers.toTypedArray())?.let { return it }
val kClass = jClass.kotlin
return kClass.builtinSerializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class SerializationMethodInvocationOrderTest {
class Out : AbstractEncoder() {
var step = 0

override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
when (step) {
Expand Down Expand Up @@ -80,29 +80,39 @@ class SerializationMethodInvocationOrderTest {

override fun <T : Any?> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
when (step) {
0, 3 -> { step++; serializer.serialize(this, value); return }
0, 3 -> {
step++; serializer.serialize(this, value); return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to make it multi-line, also split statements instead of using ;

If it's autoformatting, better to revert it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Rename signature" for some reason apply formatting all over the place

}
}
fail("@$step: encodeSerializableValue($value)")
}

override fun encodeString(value: String) {
when (step) {
6 -> if (value == "s1") { step++; return }
6 -> if (value == "s1") {
step++; return
}
}
fail("@$step: encodeString($value)")
}

override fun encodeInt(value: Int) {
when (step) {
8 -> if (value == 42) { step++; return }
8 -> if (value == 42) {
step++; return
}
}
fail("@$step: decodeInt($value)")
}

override fun endStructure(descriptor: SerialDescriptor) {
when(step) {
9 -> { checkDataDesc(descriptor); step++; return }
10 -> { checkContainerDesc(descriptor); step++; return }
when (step) {
9 -> {
checkDataDesc(descriptor); step++; return
}
10 -> {
checkContainerDesc(descriptor); step++; return
}
}
fail("@$step: endStructure($descriptor)")
}
Expand All @@ -115,7 +125,7 @@ class SerializationMethodInvocationOrderTest {
class Inp : AbstractDecoder() {
var step = 0

override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
when (step) {
Expand Down Expand Up @@ -152,29 +162,39 @@ class SerializationMethodInvocationOrderTest {

override fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
when (step) {
0, 3 -> { step++; return deserializer.deserialize(this) }
0, 3 -> {
step++; return deserializer.deserialize(this)
}
}
fail("@$step: decodeSerializableValue()")
}

override fun decodeString(): String {
when (step) {
6 -> { step++; return "s1" }
6 -> {
step++; return "s1"
}
}
fail("@$step: decodeString()")
}

override fun decodeInt(): Int {
when (step) {
8 -> { step++; return 42 }
8 -> {
step++; return 42
}
}
fail("@$step: decodeInt()")
}

override fun endStructure(descriptor: SerialDescriptor) {
when(step) {
10 -> { checkDataDesc(descriptor); step++; return }
12 -> { checkContainerDesc(descriptor); step++; return }
when (step) {
10 -> {
checkDataDesc(descriptor); step++; return
}
12 -> {
checkContainerDesc(descriptor); step++; return
}
}
fail("@$step: endStructure($descriptor)")
}
Expand Down
16 changes: 11 additions & 5 deletions core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class SerializeFlatTest() {
class Out(private val name: String) : AbstractEncoder() {
var step = 0

override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(
descriptor: SerialDescriptor
Expand Down Expand Up @@ -232,7 +232,9 @@ class SerializeFlatTest() {

override fun encodeInt(value: Int) {
when (step) {
4 -> if (value == 42) { step++; return }
4 -> if (value == 42) {
step++; return
}
}
fail("@$step: decodeInt($value)")
}
Expand All @@ -250,7 +252,7 @@ class SerializeFlatTest() {
class Inp(private val name: String) : AbstractDecoder() {
var step = 0

override val serializersModule: SerializersModule = EmptySerializersModule
override val serializersModule: SerializersModule = EmptySerializersModule()

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
checkDesc(name, descriptor)
Expand All @@ -276,14 +278,18 @@ class SerializeFlatTest() {

override fun decodeString(): String {
when (step) {
2 -> { step++; return "s1" }
2 -> {
step++; return "s1"
}
}
fail("@$step: decodeString()")
}

override fun decodeInt(): Int {
when (step) {
4 -> { step++; return 42 }
4 -> {
step++; return 42
}
}
fail("@$step: decodeInt()")
}
Expand Down
Loading