Skip to content

Commit

Permalink
Added support for the kotlin.Nothing class as built-in
Browse files Browse the repository at this point in the history
Resolves #614
Resolves #932
  • Loading branch information
shanshin committed Jan 6, 2023
1 parent 581fc3f commit 17babd2
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 0 deletions.
10 changes: 10 additions & 0 deletions core/api/kotlinx-serialization-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt {
public static final fun LongArraySerializer ()Lkotlinx/serialization/KSerializer;
public static final fun MapEntrySerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun MapSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun NothingSerializer ()Lkotlinx/serialization/KSerializer;
public static final fun PairSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun SetSerializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun ShortArraySerializer ()Lkotlinx/serialization/KSerializer;
Expand Down Expand Up @@ -902,6 +903,15 @@ public abstract class kotlinx/serialization/internal/NamedValueEncoder : kotlinx
protected final fun nested (Ljava/lang/String;)Ljava/lang/String;
}

public final class kotlinx/serialization/internal/NothingSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/NothingSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Void;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Void;)V
}

public final class kotlinx/serialization/internal/NullableSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,11 @@ public fun UShort.Companion.serializer(): KSerializer<UShort> = UShortSerializer
* The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString].
*/
public fun Duration.Companion.serializer(): KSerializer<Duration> = DurationSerializer

/**
* Returns serializer for [Nothing].
* Throws an exception when trying to encode or decode.
*
* It is used as a dummy in case it is necessary to pass a type to a parameterized class. At the same time, it is expected that this generic type will not participate in serialization.
*/
public fun NothingSerializer(): KSerializer<Nothing> = NothingSerializer
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package kotlinx.serialization.internal

import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
Expand All @@ -23,3 +24,16 @@ internal object DurationSerializer : KSerializer<Duration> {
return Duration.parseIsoString(decoder.decodeString())
}
}

@PublishedApi
internal object NothingSerializer : KSerializer<Nothing> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.Nothing", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: Nothing) {
throw SerializationException("'kotlin.Nothing' cannot be serialized")
}

override fun deserialize(decoder: Decoder): Nothing {
throw SerializationException("'kotlin.Nothing' does not have instances")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ private val BUILTIN_SERIALIZERS = mapOf(
Boolean::class to Boolean.serializer(),
BooleanArray::class to BooleanArraySerializer(),
Unit::class to Unit.serializer(),
Nothing::class to NothingSerializer(),
Duration::class to Duration.serializer()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package kotlinx.serialization

import kotlinx.serialization.builtins.NothingSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
Expand Down Expand Up @@ -191,4 +192,20 @@ class BasicTypesSerializationTest {
assertEquals(Duration.parseIsoString(durationString), other)
}

@Test
fun testNothingSerialization() {
// impossible to deserialize Nothing
assertFailsWith(SerializationException::class, "'kotlin.Nothing' does not have instances") {
val inp = KeyValueInput(Parser(StringReader("42")))
@Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION")
inp.decodeSerializableValue(NothingSerializer())
}

// it is possible to serialize only `null` for `Nothing?`
val sb = StringBuilder()
val out = KeyValueOutput(sb)
out.encodeNullableSerializableValue(NothingSerializer(), null)
assertEquals("null", sb.toString())
}

}
29 changes: 29 additions & 0 deletions docs/builtin-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ including the standard collections, is built into Kotlin Serialization. This cha
* [Maps](#maps)
* [Unit and singleton objects](#unit-and-singleton-objects)
* [Duration](#duration)
* [Nothing](#nothing)

<!--- END -->

Expand Down Expand Up @@ -405,6 +406,33 @@ Duration is serialized as a string in the ISO-8601-2 format.

<!--- TEST -->


## Nothing

By default, [Nothing] is a serializable class. However, since there are no instances of this class, it is impossible to encode or decode its values - any attempt will cause an exception.

This serializer is used when syntactically is needed some type, but it is not actually used in serialization. For example, when using parameterized classes:
```kotlin
@Serializable
sealed class ParametrizedParent<out R> {
@Serializable
data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
}

fun main() {
println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
}
```
> You can get the full code [here](../guide/example/example-builtin-13.kt).
When encoding, the serializer for the `Nothing` was not used

```text
{"value":42}
```

<!--- TEST -->

---

The next chapter covers [Serializers](serializers.md).
Expand All @@ -418,6 +446,7 @@ The next chapter covers [Serializers](serializers.md).
[Set]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/
[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/
[Duration]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/
[Nothing]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html

<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
Expand Down
1 change: 1 addition & 0 deletions docs/serialization-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Once the project is set up, we can start serializing some classes.
* <a name='maps'></a>[Maps](builtin-classes.md#maps)
* <a name='unit-and-singleton-objects'></a>[Unit and singleton objects](builtin-classes.md#unit-and-singleton-objects)
* <a name='duration'></a>[Duration](builtin-classes.md#duration)
* <a name='nothing'></a>[Nothing](builtin-classes.md#nothing)
<!--- END -->

**Chapter 3.** [Serializers](serializers.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ class SerializersLookupTest : JsonTestBase() {
}
}

@Test
fun testDurationLookup() = noLegacyJs {
assertNotNull(serializerOrNull(typeOf<Duration>()))
assertSame(Duration.serializer(), serializer<Duration>())
}

private inline fun <reified T> assertSerializedWithType(
expected: String,
value: T,
Expand Down
7 changes: 7 additions & 0 deletions guide/test/BuiltinClassesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,11 @@ class BuiltinClassesTest {
"\"PT16M40S\""
)
}

@Test
fun testExampleBuiltin13() {
captureOutput("ExampleBuiltin13") { example.exampleBuiltin13.main() }.verifyOutputLines(
"{\"value\":42}"
)
}
}

0 comments on commit 17babd2

Please sign in to comment.