Skip to content

Commit

Permalink
Make DeserializationStrategy covariant at declaration-site (Kotlin#1897)
Browse files Browse the repository at this point in the history
The type parameter T of DeserializationStrategy only appears in an out
position, namely as the return type of deserialize().
It can therefore be annotated with `out` to increase API flexibility
compared to DeserializationStrategy being invariant.
E.g. a function taking DeserializationStrategy<Number> will now also
accept an object of type DeserializationStrategy<Int>.

This also results in DeserializationStrategy being more similar to the
symmetric SerializationStrategy interface which is already contravariant
at declaration-site (interface SerializationStrategy<in T>):
SerializationStrategy is a consumer, and DeserializationStrategy is a producer.

This change is
* binary backward compatible: generics are erased at runtime
* source backward compatible: previously specified out projections
  (use-site covariance) will only report a warning about a redundant
  projection
  • Loading branch information
lukellmann authored and fred01 committed Nov 24, 2022
1 parent ef79970 commit 25a65a3
Show file tree
Hide file tree
Showing 12 changed files with 19 additions and 21 deletions.
4 changes: 1 addition & 3 deletions core/commonMain/src/kotlinx/serialization/KSerializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package kotlinx.serialization

import kotlinx.serialization.descriptors.*
import kotlinx.serialization.descriptors.elementNames
import kotlinx.serialization.encoding.*

/**
Expand Down Expand Up @@ -140,7 +139,7 @@ public interface SerializationStrategy<in T> {
*
* For a more detailed explanation of the serialization process, please refer to [KSerializer] documentation.
*/
public interface DeserializationStrategy<T> {
public interface DeserializationStrategy<out T> {
/**
* Describes the structure of the serializable representation of [T], that current
* deserializer is able to deserialize.
Expand Down Expand Up @@ -193,4 +192,3 @@ public interface DeserializationStrategy<T> {
*/
public fun deserialize(decoder: Decoder): T
}

Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public class PolymorphicSerializer<T : Any>(override val baseClass: KClass<T>) :
public fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer(
decoder: CompositeDecoder,
klassName: String?
): DeserializationStrategy<out T> =
): DeserializationStrategy<T> =
findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass)

@InternalSerializationApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public class SealedClassSerializer<T : Any>(
}.mapValues { it.value.value }
}

override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy<out T>? {
override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy<T>? {
return serialName2Serializer[klassName] ?: super.findPolymorphicSerializerOrNull(decoder, klassName)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo
public open fun findPolymorphicSerializerOrNull(
decoder: CompositeDecoder,
klassName: String?
): DeserializationStrategy<out T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName)
): DeserializationStrategy<T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName)


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
) {
private val subclasses: MutableList<Pair<KClass<out Base>, KSerializer<out Base>>> = mutableListOf()
private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null
private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null
private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<Base>?)? = null

/**
* Registers a [subclass] [serializer] in the resulting module under the [base class][Base].
Expand All @@ -46,7 +46,7 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
* Default deserializers provider affects only deserialization process.
*/
@ExperimentalSerializationApi
public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?) {
require(this.defaultDeserializerProvider == null) {
"Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}"
}
Expand Down Expand Up @@ -75,7 +75,7 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
*/
@OptIn(ExperimentalSerializationApi::class)
// TODO: deprecate in 1.4
public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<Base>?) {
defaultDeserializer(defaultSerializerProvider)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public sealed class SerializersModule {
* or default value constructed from [serializedClassName] if a default serializer provider was registered.
*/
@ExperimentalSerializationApi
public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>?
public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>?

/**
* Copies contents of this module to the given [collector].
Expand Down Expand Up @@ -129,7 +129,7 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri

override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true)
}
Expand Down Expand Up @@ -161,7 +161,7 @@ internal class SerialModuleImpl(
return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider<T>)?.invoke(value)
}

override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? {
override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>? {
// Registered
val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T>
if (registered != null) return registered
Expand Down Expand Up @@ -204,7 +204,7 @@ internal class SerialModuleImpl(
}
}

internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<out Base>?
internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<Base>?
internal typealias PolymorphicSerializerProvider<Base> = (value: Base) -> SerializationStrategy<Base>?

/** This class is needed to support re-registering the same static (argless) serializers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
@ExperimentalSerializationApi
public override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false)
}
Expand Down Expand Up @@ -168,7 +168,7 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
@JvmName("registerDefaultPolymorphicDeserializer") // Don't mangle method name for prettier stack traces
internal fun <Base : Any> registerDefaultPolymorphicDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?,
allowOverwrite: Boolean
) {
val previous = polyBase2DefaultDeserializerProvider[baseClass]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public interface SerializersModuleCollector {
@ExperimentalSerializationApi
public fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
)

/**
Expand All @@ -84,7 +84,7 @@ public interface SerializersModuleCollector {
// TODO: deprecate in 1.4
public fun <Base : Any> polymorphicDefault(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public abstract class JsonContentPolymorphicSerializer<T : Any>(private val base
/**
* Determines a particular strategy for deserialization by looking on a parsed JSON [element].
*/
protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<out T>
protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>

private fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing {
val subClassName = subClass.simpleName ?: "$subClass"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal class PolymorphismValidator(

override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
// Nothing here
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ internal open class StreamingJsonDecoder(

val discriminator = deserializer.descriptor.classDiscriminator(json)
val type = lexer.consumeLeadingMatchingValue(discriminator, configuration.isLenient)
var actualSerializer: DeserializationStrategy<out Any>? = null
var actualSerializer: DeserializationStrategy<Any>? = null
if (type != null) {
actualSerializer = deserializer.findPolymorphicSerializerOrNull(this, type)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public sealed class Properties(
val type = map["type"]?.toString()

if (deserializer is AbstractPolymorphicSerializer<*>) {
val actualSerializer: DeserializationStrategy<out Any> = deserializer.findPolymorphicSerializer(this, type)
val actualSerializer: DeserializationStrategy<Any> = deserializer.findPolymorphicSerializer(this, type)

@Suppress("UNCHECKED_CAST")
return actualSerializer.deserialize(this) as T
Expand Down

0 comments on commit 25a65a3

Please sign in to comment.