diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt index af9709d68..8999c61c5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -80,7 +80,7 @@ fun Setting.value(default: Int): SerializableValue = valueImpl(default) */ @LowPriorityInOverloadResolution @OptIn(ExperimentalStdlibApi::class) // stable in 1.4 -inline fun Setting.valueReified(default: T): Value = valueFromKTypeImpl(typeOf()).cast() +inline fun Setting.valueReified(default: T): SerializableValue = valueFromKTypeImpl(typeOf()).cast() @MiraiExperimentalAPI -fun Setting.valueFromKType(type: KType): Value = valueFromKTypeImpl(type).cast() \ No newline at end of file +fun Setting.valueFromKType(type: KType): SerializableValue = valueFromKTypeImpl(type).cast() \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt index 0458bfab6..7a3d7b6e4 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.console.setting import kotlinx.serialization.KSerializer +import net.mamoe.mirai.console.setting.internal.map import net.mamoe.mirai.utils.MiraiExperimentalAPI import kotlin.reflect.KProperty @@ -40,6 +41,12 @@ class SerializableValue( override val serializer: KSerializer ) : Value by delegate, SerializerAwareValue +fun Value.serializableValueWith( + serializer: KSerializer +): SerializableValue { + return SerializableValue(this, serializer.map(serializer = { this.value }, deserializer = { this.value = it })) +} + /** * @see SerializableValue */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt index 8d1187d8a..83f29ea69 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/Setting.value composite impl.kt @@ -11,16 +11,21 @@ package net.mamoe.mirai.console.setting.internal +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import net.mamoe.mirai.console.setting.SerializableValue import net.mamoe.mirai.console.setting.Setting -import net.mamoe.mirai.console.setting.Value +import net.mamoe.mirai.console.setting.serializableValueWith import net.mamoe.mirai.console.setting.valueFromKType +import net.mamoe.yamlkt.YamlDynamicSerializer +import net.mamoe.yamlkt.YamlNullableDynamicSerializer import kotlin.reflect.KClass import kotlin.reflect.KType @PublishedApi -@Suppress("UnsafeCall", "SMARTCAST_IMPOSSIBLE") -internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { +@Suppress("UnsafeCall", "SMARTCAST_IMPOSSIBLE", "UNCHECKED_CAST") +internal fun Setting.valueFromKTypeImpl(type: KType): SerializableValue<*> { val classifier = type.classifier require(classifier is KClass<*>) @@ -30,8 +35,8 @@ internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { // 复合类型 - when { - classifier == Map::class -> { + when (classifier) { + Map::class -> { val keyClass = type.arguments[0].type?.classifier require(keyClass is KClass<*>) @@ -46,10 +51,10 @@ internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { return createCompositeMapValueImpl( kToValue = { valueFromKType(type.arguments[0].type!!) }, vToValue = { valueFromKType(type.arguments[1].type!!) } - ) + ).serializableValueWith(serializerMirai(type) as KSerializer>) // erased } } - classifier == List::class -> { + List::class -> { val elementClass = type.arguments[0].type?.classifier require(elementClass is KClass<*>) @@ -59,9 +64,10 @@ internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { TODO() } else { return createCompositeListValueImpl { valueFromKType(type.arguments[0].type!!) } + .serializableValueWith(serializerMirai(type) as KSerializer>) } } - classifier == Set::class -> { + Set::class -> { val elementClass = type.arguments[0].type?.classifier require(elementClass is KClass<*>) @@ -71,6 +77,7 @@ internal fun Setting.valueFromKTypeImpl(type: KType): Value<*> { TODO() } else { return createCompositeSetValueImpl { valueFromKType(type.arguments[0].type!!) } + .serializableValueWith(serializerMirai(type) as KSerializer>) } } else -> error("Custom composite value is not supported yet (${classifier.qualifiedName})") @@ -92,4 +99,53 @@ internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { @PublishedApi @Suppress("UNCHECKED_CAST") -internal inline fun T.cast(): R = this as R \ No newline at end of file +internal inline fun T.cast(): R = this as R + +/** + * Copied from kotlinx.serialization, modifications are marked with "/* mamoe modify */" + * Copyright 2017-2020 JetBrains s.r.o. + */ +@Suppress("UNCHECKED_CAST", "NO_REFLECTION_IN_CLASS_PATH", "UNSUPPORTED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@OptIn(ImplicitReflectionSerializer::class) +internal fun serializerMirai(type: KType): KSerializer { + fun serializerByKTypeImpl(type: KType): KSerializer { + val rootClass = when (val t = type.classifier) { + is KClass<*> -> t + else -> error("Only KClass supported as classifier, got $t") + } as KClass + + val typeArguments = type.arguments + .map { requireNotNull(it.type) { "Star projections are not allowed, had $it instead" } } + return when { + typeArguments.isEmpty() -> rootClass.serializer() + else -> { + val serializers = typeArguments + .map(::serializer) + // Array is not supported, see KT-32839 + when (rootClass) { + List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0]) + HashSet::class -> SetSerializer(serializers[0]) + Set::class, MutableSet::class, LinkedHashSet::class -> SetSerializer(serializers[0]) + HashMap::class -> MapSerializer(serializers[0], serializers[1]) + Map::class, MutableMap::class, LinkedHashMap::class -> MapSerializer(serializers[0], serializers[1]) + Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1]) + Pair::class -> PairSerializer(serializers[0], serializers[1]) + Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2]) + /* mamoe modify */ Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer + else -> { + if (isReferenceArray(type, rootClass)) { + return ArraySerializer(typeArguments[0].classifier as KClass, serializers[0]).cast() + } + requireNotNull(rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray())) { + "Can't find a method to construct serializer for type ${rootClass.simpleName()}. " + + "Make sure this class is marked as @Serializable or provide serializer explicitly." + } + } + } + } + }.cast() + } + + val result = serializerByKTypeImpl(type) + return if (type.isMarkedNullable) result.nullable else result.cast() +}