Skip to content

Commit

Permalink
Stabilize SerialDescriptor delegate builder
Browse files Browse the repository at this point in the history
and allow creating primitive descriptors via it as well.

This constructor function is a way to create a descriptor for your
custom serializer if it simply delegates to an existing one.
Since it fits its purpose and is unlikely to change in the future, we can promote it to stable API
alongside other descriptor builders.

Fixes #2547
  • Loading branch information
sandwwraith committed Oct 9, 2024
1 parent df27589 commit ada6ab1
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ import kotlin.reflect.*
* }
* ```
*/
@Suppress("FunctionName")
@OptIn(ExperimentalSerializationApi::class)
public fun buildClassSerialDescriptor(
serialName: String,
vararg typeParameters: SerialDescriptor,
Expand All @@ -69,7 +67,7 @@ public fun buildClassSerialDescriptor(
}

/**
* Factory to create a trivial primitive descriptors.
* Factory to create trivial primitive descriptors. [serialName] must be non-blank and unique.
* Primitive descriptors should be used when the serialized form of the data has a primitive form, for example:
* ```
* object LongAsStringSerializer : KSerializer<Long> {
Expand All @@ -86,16 +84,16 @@ public fun buildClassSerialDescriptor(
* }
* ```
*/
@Suppress("FunctionName")
public fun PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
return PrimitiveDescriptorSafe(serialName, kind)
}

/**
* Factory to create a new descriptor that is identical to [original] except that the name is equal to [serialName].
* Should be used when you want to serialize a type as another non-primitive type.
* Don't use this if you want to serialize a type as a primitive value, use [PrimitiveSerialDescriptor] instead.
*
* Usually used when you want to serialize a type as another type, delegating implementation of `serialize` and `deserialize`.
*
* Example:
* ```
* @Serializable(CustomSerializer::class)
Expand All @@ -115,27 +113,24 @@ public fun PrimitiveSerialDescriptor(serialName: String, kind: PrimitiveKind): S
* }
* ```
*/
@ExperimentalSerializationApi
public fun SerialDescriptor(serialName: String, original: SerialDescriptor): SerialDescriptor {
require(serialName.isNotBlank()) { "Blank serial names are prohibited" }
require(original.kind !is PrimitiveKind) { "For primitive descriptors please use 'PrimitiveSerialDescriptor' instead" }
require(serialName != original.serialName) { "The name of the wrapped descriptor ($serialName) cannot be the same as the name of the original descriptor (${original.serialName})" }

if (original.kind is PrimitiveKind) checkNameIsNotAPrimitive(serialName)

return WrappedSerialDescriptor(serialName, original)
}

@OptIn(ExperimentalSerializationApi::class)
internal class WrappedSerialDescriptor(override val serialName: String, original: SerialDescriptor) : SerialDescriptor by original

/**
* An unsafe alternative to [buildClassSerialDescriptor] that supports an arbitrary [SerialKind].
* This function is left public only for migration of pre-release users and is not intended to be used
* as generally-safe and stable mechanism. Beware that it can produce inconsistent or non spec-compliant instances.
* as a generally safe and stable mechanism. Beware that it can produce inconsistent or non-spec-compliant instances.
*
* If you end up using this builder, please file an issue with your use-case in kotlinx.serialization issue tracker.
* If you end up using this builder, please file an issue with your use-case to the kotlinx.serialization issue tracker.
*/
@InternalSerializationApi
@OptIn(ExperimentalSerializationApi::class)
public fun buildSerialDescriptor(
serialName: String,
kind: SerialKind,
Expand All @@ -152,14 +147,32 @@ public fun buildSerialDescriptor(

/**
* Retrieves descriptor of type [T] using reified [serializer] function.
*
* Example:
* ```
* serialDescriptor<List<String>>() // Returns kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))
* ```
*/
public inline fun <reified T> serialDescriptor(): SerialDescriptor = serializer<T>().descriptor

/**
* Retrieves descriptor of type associated with the given [KType][type]
* Retrieves descriptor of a type associated with the given [KType][type].
*
* Example:
* ```
* val type = typeOf<List<String>>()
*
* serialDescriptor(type) // Returns kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))
* ```
*/
public fun serialDescriptor(type: KType): SerialDescriptor = serializer(type).descriptor

/* The rest of the functions intentionally left experimental for later stabilization
It is unclear whether they should be left as-is,
or moved to ClassSerialDescriptorBuilder (because this is the main place for them to be used),
or simply deprecated in favor of ListSerializer(Element.serializer()).descriptor
*/

/**
* Creates a descriptor for the type `List<T>` where `T` is the type associated with [elementDescriptor].
*/
Expand Down Expand Up @@ -227,9 +240,10 @@ public val SerialDescriptor.nullable: SerialDescriptor
* Returns non-nullable serial descriptor for the type if this descriptor has been auto-generated (plugin
* generated descriptors) or created with `.nullable` extension on a descriptor or serializer.
*
* Otherwise, returns this.
* Otherwise, returns `this`.
*
* It may return nullable descriptor if this descriptor has been created manually as nullable by directly implementing SerialDescriptor interface.
* It may return a nullable descriptor
* if `this` descriptor has been created manually as nullable by directly implementing SerialDescriptor interface.
*
* @see SerialDescriptor.nullable
* @see KSerializer.nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ internal class PrimitiveSerialDescriptor(
return false
}
override fun hashCode() = serialName.hashCode() + 31 * kind.hashCode()
private fun error(): Nothing = throw IllegalStateException("Primitive descriptor does not have elements")
private fun error(): Nothing = throw IllegalStateException("Primitive descriptor $serialName does not have elements")
}

internal fun PrimitiveDescriptorSafe(serialName: String, kind: PrimitiveKind): SerialDescriptor {
checkName(serialName)
checkNameIsNotAPrimitive(serialName)
return PrimitiveSerialDescriptor(serialName, kind)
}

private fun checkName(serialName: String) {
internal fun checkNameIsNotAPrimitive(serialName: String) {
val values = BUILTIN_SERIALIZERS.values
for (primitive in values) {
val primitiveName = primitive.descriptor.serialName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,21 @@ class WrappedSerialDescriptorTest {
fun testWrappedComplexClass() {
checkWrapped(ComplexType.serializer().descriptor, "WrappedComplexType")
}
}

@Test
fun testWrappedPrimitive() {
checkWrapped(Int.serializer().descriptor, "MyInt")
}

@Test
fun testWrappedPrimitiveContract() {
assertFails { SerialDescriptor(" ", ComplexType.serializer().descriptor) }
assertFails {
SerialDescriptor(
SimpleType.serializer().descriptor.serialName,
SimpleType.serializer().descriptor
)
}
assertFails { SerialDescriptor("kotlin.Int", Int.serializer().descriptor) }
}
}

0 comments on commit ada6ab1

Please sign in to comment.