Skip to content

Commit 15eaee9

Browse files
shanshinpdvrieze
andauthored
Added support for the kotlin.Nothing class as built-in (#1991)
Resolves #614 Resolves #932 Co-authored-by: Paul de Vrieze <pdvrieze@users.noreply.github.com>
1 parent 581fc3f commit 15eaee9

File tree

9 files changed

+103
-0
lines changed

9 files changed

+103
-0
lines changed

core/api/kotlinx-serialization-core.api

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public final class kotlinx/serialization/builtins/BuiltinSerializersKt {
166166
public static final fun LongArraySerializer ()Lkotlinx/serialization/KSerializer;
167167
public static final fun MapEntrySerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
168168
public static final fun MapSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
169+
public static final fun NothingSerializer ()Lkotlinx/serialization/KSerializer;
169170
public static final fun PairSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
170171
public static final fun SetSerializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
171172
public static final fun ShortArraySerializer ()Lkotlinx/serialization/KSerializer;
@@ -902,6 +903,15 @@ public abstract class kotlinx/serialization/internal/NamedValueEncoder : kotlinx
902903
protected final fun nested (Ljava/lang/String;)Ljava/lang/String;
903904
}
904905

906+
public final class kotlinx/serialization/internal/NothingSerializer : kotlinx/serialization/KSerializer {
907+
public static final field INSTANCE Lkotlinx/serialization/internal/NothingSerializer;
908+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
909+
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Void;
910+
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
911+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
912+
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Void;)V
913+
}
914+
905915
public final class kotlinx/serialization/internal/NullableSerializer : kotlinx/serialization/KSerializer {
906916
public fun <init> (Lkotlinx/serialization/KSerializer;)V
907917
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;

core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,12 @@ public fun UShort.Companion.serializer(): KSerializer<UShort> = UShortSerializer
250250
* The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString].
251251
*/
252252
public fun Duration.Companion.serializer(): KSerializer<Duration> = DurationSerializer
253+
254+
/**
255+
* Returns serializer for [Nothing].
256+
* Throws an exception when trying to encode or decode.
257+
*
258+
* 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.
259+
*/
260+
@ExperimentalSerializationApi
261+
public fun NothingSerializer(): KSerializer<Nothing> = NothingSerializer

core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package kotlinx.serialization.internal
55

66
import kotlinx.serialization.KSerializer
7+
import kotlinx.serialization.SerializationException
78
import kotlinx.serialization.descriptors.PrimitiveKind
89
import kotlinx.serialization.descriptors.SerialDescriptor
910
import kotlinx.serialization.encoding.Decoder
@@ -23,3 +24,16 @@ internal object DurationSerializer : KSerializer<Duration> {
2324
return Duration.parseIsoString(decoder.decodeString())
2425
}
2526
}
27+
28+
@PublishedApi
29+
internal object NothingSerializer : KSerializer<Nothing> {
30+
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.Nothing", PrimitiveKind.INT)
31+
32+
override fun serialize(encoder: Encoder, value: Nothing) {
33+
throw SerializationException("'kotlin.Nothing' cannot be serialized")
34+
}
35+
36+
override fun deserialize(decoder: Decoder): Nothing {
37+
throw SerializationException("'kotlin.Nothing' does not have instances")
38+
}
39+
}

core/commonMain/src/kotlinx/serialization/internal/Primitives.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ private val BUILTIN_SERIALIZERS = mapOf(
4444
Boolean::class to Boolean.serializer(),
4545
BooleanArray::class to BooleanArraySerializer(),
4646
Unit::class to Unit.serializer(),
47+
Nothing::class to NothingSerializer(),
4748
Duration::class to Duration.serializer()
4849
)
4950

core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.serialization
66

7+
import kotlinx.serialization.builtins.NothingSerializer
78
import kotlinx.serialization.builtins.serializer
89
import kotlinx.serialization.descriptors.*
910
import kotlinx.serialization.encoding.*
@@ -191,4 +192,20 @@ class BasicTypesSerializationTest {
191192
assertEquals(Duration.parseIsoString(durationString), other)
192193
}
193194

195+
@Test
196+
fun testNothingSerialization() {
197+
// impossible to deserialize Nothing
198+
assertFailsWith(SerializationException::class, "'kotlin.Nothing' does not have instances") {
199+
val inp = KeyValueInput(Parser(StringReader("42")))
200+
@Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION")
201+
inp.decodeSerializableValue(NothingSerializer())
202+
}
203+
204+
// it is possible to serialize only `null` for `Nothing?`
205+
val sb = StringBuilder()
206+
val out = KeyValueOutput(sb)
207+
out.encodeNullableSerializableValue(NothingSerializer(), null)
208+
assertEquals("null", sb.toString())
209+
}
210+
194211
}

docs/builtin-classes.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ including the standard collections, is built into Kotlin Serialization. This cha
2424
* [Maps](#maps)
2525
* [Unit and singleton objects](#unit-and-singleton-objects)
2626
* [Duration](#duration)
27+
* [Nothing](#nothing)
2728

2829
<!--- END -->
2930

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

406407
<!--- TEST -->
407408

409+
410+
## Nothing
411+
412+
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.
413+
414+
This serializer is used when syntactically some type is needed, but it is not actually used in serialization. For example, when using parameterized polymorphic base classes:
415+
```kotlin
416+
@Serializable
417+
sealed class ParametrizedParent<out R> {
418+
@Serializable
419+
data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
420+
}
421+
422+
fun main() {
423+
println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
424+
}
425+
```
426+
> You can get the full code [here](../guide/example/example-builtin-13.kt).
427+
428+
When encoding, the serializer for the `Nothing` was not used
429+
430+
```text
431+
{"value":42}
432+
```
433+
434+
<!--- TEST -->
435+
408436
---
409437

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

422451
<!--- MODULE /kotlinx-serialization-core -->
423452
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->

docs/serialization-guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Once the project is set up, we can start serializing some classes.
5050
* <a name='maps'></a>[Maps](builtin-classes.md#maps)
5151
* <a name='unit-and-singleton-objects'></a>[Unit and singleton objects](builtin-classes.md#unit-and-singleton-objects)
5252
* <a name='duration'></a>[Duration](builtin-classes.md#duration)
53+
* <a name='nothing'></a>[Nothing](builtin-classes.md#nothing)
5354
<!--- END -->
5455

5556
**Chapter 3.** [Serializers](serializers.md)

guide/example/example-builtin-13.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit.
2+
package example.exampleBuiltin13
3+
4+
import kotlinx.serialization.*
5+
import kotlinx.serialization.json.*
6+
7+
@Serializable
8+
sealed class ParametrizedParent<out R> {
9+
@Serializable
10+
data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
11+
}
12+
13+
fun main() {
14+
println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
15+
}

guide/test/BuiltinClassesTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,11 @@ class BuiltinClassesTest {
8989
"\"PT16M40S\""
9090
)
9191
}
92+
93+
@Test
94+
fun testExampleBuiltin13() {
95+
captureOutput("ExampleBuiltin13") { example.exampleBuiltin13.main() }.verifyOutputLines(
96+
"{\"value\":42}"
97+
)
98+
}
9299
}

0 commit comments

Comments
 (0)