Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove type parameter from Choice #868

Merged
merged 1 commit into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions common/api/common.api
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,7 @@ public abstract class dev/kord/common/entity/Choice {
}

public final class dev/kord/common/entity/Choice$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
}

Expand Down Expand Up @@ -2915,11 +2916,11 @@ public final class dev/kord/common/entity/DiscordAutoComplete {
public final fun getChoices ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Ldev/kord/common/entity/DiscordAutoComplete;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlinx/serialization/KSerializer;)V
public static final synthetic fun write$Self (Ldev/kord/common/entity/DiscordAutoComplete;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class dev/kord/common/entity/DiscordAutoComplete$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;)V
public static final field INSTANCE Ldev/kord/common/entity/DiscordAutoComplete$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/common/entity/DiscordAutoComplete;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
Expand All @@ -2930,6 +2931,7 @@ public final class dev/kord/common/entity/DiscordAutoComplete$$serializer : kotl
}

public final class dev/kord/common/entity/DiscordAutoComplete$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
}

Expand Down
100 changes: 60 additions & 40 deletions common/src/commonMain/kotlin/entity/Interactions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*

Expand Down Expand Up @@ -132,7 +133,7 @@ public data class ApplicationCommandOption(
val descriptionLocalizations: Optional<Map<Locale, String>?> = Optional.Missing(),
val default: OptionalBoolean = OptionalBoolean.Missing,
val required: OptionalBoolean = OptionalBoolean.Missing,
val choices: Optional<List<Choice<@Serializable(NotSerializable::class) Any?>>> = Optional.Missing(),
val choices: Optional<List<Choice>> = Optional.Missing(),
val autocomplete: OptionalBoolean = OptionalBoolean.Missing,
val options: Optional<List<ApplicationCommandOption>> = Optional.Missing(),
@SerialName("channel_types")
Expand All @@ -154,6 +155,7 @@ public data class ApplicationCommandOption(
* e.g: `Choice<@Serializable(NotSerializable::class) Any?>`
* The serialization is handled by [Choice] serializer instead where we don't care about the generic type.
*/
@Deprecated("This is no longer used, deprecated without a replacement.", level = DeprecationLevel.WARNING)
@KordExperimental
public object NotSerializable : KSerializer<Any?> {
override fun deserialize(decoder: Decoder): Nothing = error("This operation is not supported.")
Expand All @@ -162,80 +164,88 @@ public object NotSerializable : KSerializer<Any?> {
}


private val LocalizationSerializer =
Optional.serializer(MapSerializer(Locale.serializer(), String.serializer()).nullable)

@Serializable(Choice.Serializer::class)
public sealed class Choice<out T> {
public sealed class Choice {
public abstract val name: String
public abstract val nameLocalizations: Optional<Map<Locale, String>?>
public abstract val value: T
public abstract val value: Any

public data class IntegerChoice(
override val name: String,
override val nameLocalizations: Optional<Map<Locale, String>?>,
override val value: Long,
) : Choice<Long>()
) : Choice()

public data class NumberChoice(
override val name: String,
override val nameLocalizations: Optional<Map<Locale, String>?>,
override val value: Double
) : Choice<Double>()
) : Choice()

public data class StringChoice(
override val name: String,
override val nameLocalizations: Optional<Map<Locale, String>?>,
override val value: String
) : Choice<String>()
) : Choice()

internal object Serializer : KSerializer<Choice<*>> {
internal object Serializer : KSerializer<Choice> {
private val localizationsSerializer = MapSerializer(Locale.serializer(), String.serializer()).nullable

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Choice") {
element<String>("name")
element<JsonPrimitive>("value")
element<Map<Locale, String>?>("name_localizations", isOptional = true)
override val descriptor = buildClassSerialDescriptor("dev.kord.common.entity.Choice") {
element("name", String.serializer().descriptor)
element("value", JsonPrimitive.serializer().descriptor)
element("name_localizations", localizationsSerializer.descriptor, isOptional = true)
}

override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) {
override fun serialize(encoder: Encoder, value: Choice) = encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, index = 0, value.name)
when (value) {
is IntegerChoice -> encodeLongElement(descriptor, index = 1, value.value)
is NumberChoice -> encodeDoubleElement(descriptor, index = 1, value.value)
is StringChoice -> encodeStringElement(descriptor, index = 1, value.value)
}
if (value.nameLocalizations !is Optional.Missing) {
encodeSerializableElement(descriptor, index = 2, localizationsSerializer, value.nameLocalizations.value)
}
}

lateinit var name: String
override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) {
var name: String? = null
var nameLocalizations: Optional<Map<Locale, String>?> = Optional.Missing()
lateinit var value: JsonPrimitive
var value: JsonPrimitive? = null

while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> name = decodeStringElement(descriptor, index)
1 -> value = decodeSerializableElement(descriptor, index, JsonPrimitive.serializer())
2 -> nameLocalizations = decodeSerializableElement(descriptor, index, LocalizationSerializer)
2 -> nameLocalizations =
Optional(decodeSerializableElement(descriptor, index, localizationsSerializer))

CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("unknown index: $index")
else -> throw SerializationException("Unexpected index: $index")
}
}

when {
value.isString -> StringChoice(name, nameLocalizations, value.content)
else -> value.longOrNull?.let { IntegerChoice(name, nameLocalizations, it) }
@OptIn(ExperimentalSerializationApi::class)
if (name == null || value == null) throw MissingFieldException(
missingFields = listOfNotNull("name".takeIf { name == null }, "value".takeIf { value == null }),
serialName = descriptor.serialName,
)

if (value.isString) {
StringChoice(name, nameLocalizations, value.content)
} else {
value.longOrNull?.let { IntegerChoice(name, nameLocalizations, it) }
?: value.doubleOrNull?.let { NumberChoice(name, nameLocalizations, it) }
?: throw SerializationException("Illegal choice value: $value")
}
}
}

override fun serialize(encoder: Encoder, value: Choice<*>) = encoder.encodeStructure(descriptor) {

encodeStringElement(descriptor, 0, value.name)

when (value) {
is IntegerChoice -> encodeLongElement(descriptor, 1, value.value)
is NumberChoice -> encodeDoubleElement(descriptor, 1, value.value)
is StringChoice -> encodeStringElement(descriptor, 1, value.value)
}

if (value.nameLocalizations !is Optional.Missing) {
encodeSerializableElement(descriptor, 2, LocalizationSerializer, value.nameLocalizations)
}
}
public companion object {
@Suppress("UNUSED_PARAMETER")
@Deprecated("Choice is no longer generic", ReplaceWith("this.serializer()"), DeprecationLevel.WARNING)
public fun <T0> serializer(typeSerial0: KSerializer<T0>): KSerializer<Choice> = serializer()
}
}

Expand Down Expand Up @@ -700,9 +710,19 @@ public data class DiscordGuildApplicationCommandPermission(
)

@Serializable
public data class DiscordAutoComplete<T>(
val choices: List<Choice<T>>
)
public data class DiscordAutoComplete(
val choices: List<Choice>,
) {
public companion object {
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"DiscordAutoComplete is no longer generic",
ReplaceWith("this.serializer()"),
DeprecationLevel.WARNING,
)
public fun <T0> serializer(typeSerial0: KSerializer<T0>): KSerializer<DiscordAutoComplete> = serializer()
}
}

@Serializable
public data class DiscordModal(
Expand Down
1 change: 1 addition & 0 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,7 @@ public final class dev/kord/core/behavior/interaction/AutoCompleteInteractionBeh
}

public final class dev/kord/core/behavior/interaction/AutoCompleteInteractionBehaviorKt {
public static final fun suggest (Ldev/kord/core/behavior/interaction/AutoCompleteInteractionBehavior;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun suggestInteger (Ldev/kord/core/behavior/interaction/AutoCompleteInteractionBehavior;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun suggestNumber (Ldev/kord/core/behavior/interaction/AutoCompleteInteractionBehavior;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun suggestString (Ldev/kord/core/behavior/interaction/AutoCompleteInteractionBehavior;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public suspend inline fun AutoCompleteInteractionBehavior.suggestString(builder:
*
* The provided choices are only suggestions and the user can provide any other input as well.
*/
public suspend inline fun <reified T> AutoCompleteInteractionBehavior.suggest(choices: List<Choice<T>>) {
public suspend fun AutoCompleteInteractionBehavior.suggest(choices: List<Choice>) {
kord.rest.interaction.createAutoCompleteInteractionResponse(
id,
token,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public data class ApplicationCommandOptionChoiceData(
val value: String
) {
public companion object {
public fun from(choice: Choice<*>): ApplicationCommandOptionChoiceData {
public fun from(choice: Choice): ApplicationCommandOptionChoiceData {
return with(choice) {
ApplicationCommandOptionChoiceData(name, value.toString())
}
Expand Down
7 changes: 5 additions & 2 deletions rest/api/rest.api
Original file line number Diff line number Diff line change
Expand Up @@ -2891,11 +2891,11 @@ public final class dev/kord/rest/json/request/AutoCompleteResponseCreateRequest
public final fun getType ()Ldev/kord/common/entity/InteractionResponseType;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Ldev/kord/rest/json/request/AutoCompleteResponseCreateRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlinx/serialization/KSerializer;)V
public static final synthetic fun write$Self (Ldev/kord/rest/json/request/AutoCompleteResponseCreateRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class dev/kord/rest/json/request/AutoCompleteResponseCreateRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public synthetic fun <init> (Lkotlinx/serialization/KSerializer;)V
public static final field INSTANCE Ldev/kord/rest/json/request/AutoCompleteResponseCreateRequest$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/rest/json/request/AutoCompleteResponseCreateRequest;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
Expand All @@ -2906,6 +2906,7 @@ public final class dev/kord/rest/json/request/AutoCompleteResponseCreateRequest$
}

public final class dev/kord/rest/json/request/AutoCompleteResponseCreateRequest$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
}

Expand Down Expand Up @@ -7389,6 +7390,8 @@ public final class dev/kord/rest/service/GuildServiceKt {

public final class dev/kord/rest/service/InteractionService : dev/kord/rest/service/RestService {
public fun <init> (Ldev/kord/rest/request/RequestHandler;)V
public final fun createAutoCompleteInteractionResponse (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/common/entity/DiscordAutoComplete;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun createBuilderAutoCompleteInteractionResponse (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/rest/builder/interaction/BaseChoiceBuilder;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun createFollowupMessage (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/rest/json/request/MultipartFollowupMessageCreateRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun createFollowupMessage (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun createFollowupMessage$default (Ldev/kord/rest/service/InteractionService;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
Expand Down
10 changes: 5 additions & 5 deletions rest/src/commonMain/kotlin/builder/interaction/OptionsBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ public sealed class BaseChoiceBuilder<T>(
description: String,
type: ApplicationCommandOptionType
) : OptionsBuilder(name, description, type) {
// TODO We can change these types to Optional<MutableList<Choice<T>>> and MutableList<Choice<T>> once
// https://youtrack.jetbrains.com/issue/KT-51045 is fixed.
// The bug from that issue prevents you from setting BaseChoiceBuilder<*>.choices to `null`.
// TODO We can add another generic C : Choice and change these types to Optional<MutableList<C>> and MutableList<C>?
// once https://youtrack.jetbrains.com/issue/KT-51045 is fixed.
// The bug from that issue prevents you from setting BaseChoiceBuilder<*, *>.choices to `null`.
@Suppress("PropertyName")
internal var _choices: Optional<MutableList<Choice<*>>> = Optional.Missing()
public var choices: MutableList<Choice<*>>? by ::_choices.delegate()
internal var _choices: Optional<MutableList<Choice>> = Optional.Missing()
public var choices: MutableList<Choice>? by ::_choices.delegate()

public abstract fun choice(name: String, value: T, nameLocalizations: Optional<Map<Locale, String>?> = Optional.Missing())

Expand Down
18 changes: 15 additions & 3 deletions rest/src/commonMain/kotlin/json/request/InteractionsRequests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dev.kord.common.entity.*
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.rest.NamedFile
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand Down Expand Up @@ -70,10 +71,21 @@ public data class InteractionResponseCreateRequest(
)

@Serializable
public data class AutoCompleteResponseCreateRequest<T>(
public data class AutoCompleteResponseCreateRequest(
val type: InteractionResponseType,
val data: DiscordAutoComplete<T>
)
val data: DiscordAutoComplete,
) {
public companion object {
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"AutoCompleteResponseCreateRequest is no longer generic",
ReplaceWith("this.serializer()"),
DeprecationLevel.WARNING,
)
public fun <T0> serializer(typeSerial0: KSerializer<T0>): KSerializer<AutoCompleteResponseCreateRequest> =
serializer()
}
}

@Serializable
public data class ModalResponseCreateRequest(
Expand Down
25 changes: 17 additions & 8 deletions rest/src/commonMain/kotlin/service/InteractionService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,27 @@ public class InteractionService(requestHandler: RequestHandler) : RestService(re
body(InteractionResponseCreateRequest.serializer(), request)
}

@Suppress("UNUSED_PARAMETER")
@Deprecated(
"DiscordAutoComplete is no longer generic and the typeSerializer argument is no longer needed.",
ReplaceWith("this.createAutoCompleteInteractionResponse(interactionId, interactionToken, autoComplete)"),
DeprecationLevel.WARNING,
)
public suspend inline fun <reified T> createAutoCompleteInteractionResponse(
interactionId: Snowflake,
interactionToken: String,
autoComplete: DiscordAutoComplete<T>,
autoComplete: DiscordAutoComplete,
typeSerializer: KSerializer<T> = serializer(),
): Unit = createAutoCompleteInteractionResponse(interactionId, interactionToken, autoComplete)

public suspend fun createAutoCompleteInteractionResponse(
interactionId: Snowflake,
interactionToken: String,
autoComplete: DiscordAutoComplete,
): Unit = call(Route.InteractionResponseCreate) {
interactionIdInteractionToken(interactionId, interactionToken)
body(
AutoCompleteResponseCreateRequest.serializer(typeSerializer),
AutoCompleteResponseCreateRequest.serializer(),
AutoCompleteResponseCreateRequest(
InteractionResponseType.ApplicationCommandAutoCompleteResult,
autoComplete
Expand Down Expand Up @@ -223,18 +235,15 @@ public class InteractionService(requestHandler: RequestHandler) : RestService(re
)
}

public suspend inline fun <reified T, Builder : BaseChoiceBuilder<T>> createBuilderAutoCompleteInteractionResponse(
public suspend inline fun <Builder : BaseChoiceBuilder<*>> createBuilderAutoCompleteInteractionResponse(
interactionId: Snowflake,
interactionToken: String,
builder: Builder,
builderFunction: Builder.() -> Unit
) {
// TODO We can remove this cast when we change the type of BaseChoiceBuilder.choices to MutableList<Choice<T>>.
// This can be done once https://youtrack.jetbrains.com/issue/KT-51045 is fixed.
// Until then this cast is necessary to get the right serializer through reified generics.
@Suppress("UNCHECKED_CAST")
val choices = (builder.apply(builderFunction).choices ?: emptyList()) as List<Choice<T>>
contract { callsInPlace(builderFunction, InvocationKind.EXACTLY_ONCE) }

val choices = builder.apply(builderFunction).choices ?: emptyList()
return createAutoCompleteInteractionResponse(interactionId, interactionToken, DiscordAutoComplete(choices))
}

Expand Down