Describe the bug
Using Nothing in a generic type argument fails with an internal error Serializer for element of type Nothing has not been found.
To Reproduce
This reproduces the problem when compiling:
import kotlinx.serialization.Serializable
@Serializable
sealed class SerializableEither<out L, out R> {
@Serializable
data class Left<L>(val value: L) : SerializableEither<L, Nothing>()
@Serializable
data class Right<R>(val value: R) : SerializableEither<Nothing, R>()
}
Full compiler error: https://pastebin.com/FP6zGrDf
Expected behavior
Nothing should be serializable.
Environment
I don't feel like Nothing should be serializable – since there is no legal way to have the instance of Nothing and property of type Nothing is meaningless.
However, what you want here is probably a @NotSerializable / @Serializable(NoSerializer) thing which may be useful also in cases like https://github.com/Kotlin/kotlinx.serialization/issues/607 (which slightly resembles this issue)
I am of the perspective that Nothing is serializable because it contains no information: serizialing no information should be a trivial task, right? I originally found this problem because I was trying to serialize arrow-kt's data structures. arrow makes heavy use of Nothing throughout their data structures; most of them involve Nothing in one way or another.
@raulraja If you have a different perspective on this, please feel free to contribute it to this discussion.
I think the issue here is not that Nothing is encodable or not. The issue here is that this is Nothing in type argument position. Nothing, bottom type, and subtype of all types can appear anywhere in generic arg in any data structure. For example:
sealed class Command<out A> {
data class Message(val value: String): Command<String>()
object Noop : Command<Nothing>()
}
Nothing in higher kind position still allows it's a wrapper to be instantiable if it Nothing appears as the type arg of a covariant argument like out A
Nothing itself does not need to be instantiated or serialized because the bottom type has 0 inhabitants.
Any? = All values
Unit = One singleton value
Nothing = No value
I think all Serialization needs for this to work with is to ignore Nothing when it appears in type argument position.
Revisiting a simple understood example which is object to this problem. One of the Arrow Data types.
A simplified version:
sealed class Option<out A> {
data class Some<out A>(val value: A): Option<A>()
object None : Option<Nothing>()
}
val someOne: Option<Int> = Some(1)
val noOne: Option<Int> = None
In this example where Option = Some | None that is a sealed union in order to provide a Serializer, it needs the Serializers of Some, None and A. But it does not need the serializer of Nothing to provide any of the possible values that can be created for an Option.
val kindedUpperBound: Option<Any?> = Some(1)
val kindedLowerBound: Option<Int> = Some(1)
val kindedNothing: Option<Nothing> = Some(1) //does not compile because Option<Int> is not a subtype of Option<Nothing>
In the case above since Nothing appears invariant in the receiver value it can ensure impossible values won't compile.
I believe you can support higher kinded <_ : Nothing> which is the issue at hand by simply treating Nothing in type argument position as a NOOP serializer and keep bailing for properties which specify Nothing directly. For example:
class ImpossibleToSerializeOrInstantiate(val x: Nothing = TODO())
Perhaps a NothingSerializer would be valid if it would indeed refuse to serialize or deserialize (but does exist for type argument reasons). I think the use case is valid and implementing is probably easiest when just creating this "serializer"..
So it would be basically like this:
object NotSerializable : KSerializer<Any> {
override val descriptor: SerialDescriptor = SerialClassDescImpl( "kotlin.Any")
override fun deserialize(decoder: Decoder): Any = throw SerializationException("Not serializable")
override fun serialize(encoder: Encoder, obj: Any) = throw SerializationException("Not serializable")
}
Such serializer can be applied for types in generic arguments (data class Left<L>(val value: L) : SerializableEither<L, @Serializable(NotSerializable::class) Nothing>()).
It even can be a default serializer for Nothing.
@sandwwraith The way I see it yes (except perhaps the return type for deserialize could be Nothing).
Most helpful comment
So it would be basically like this:
Such serializer can be applied for types in generic arguments (
data class Left<L>(val value: L) : SerializableEither<L, @Serializable(NotSerializable::class) Nothing>()).It even can be a default serializer for
Nothing.