mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-26 07:20:09 +08:00
Support serializersModule
for PluginData
, close #1796. Improve PluginData.value
type inference behavior: resolve exact returned type. (#1987)
Also support `MessageChain` and others from `MessageSerializers.serializersModule`, fix #1922.
This commit is contained in:
parent
e6840de0e0
commit
be832c7dbd
mirai-console
backend/mirai-console
src
data
internal/data
test/data
tools/intellij-plugin
run/projects/test-project/src/main/kotlin/org/example/myplugin
src/diagnostics
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -78,9 +78,7 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
|
||||
public final override val updaterSerializer: KSerializer<Unit>
|
||||
get() = super.updaterSerializer
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public override val serializersModule: SerializersModule = EmptySerializersModule
|
||||
|
||||
public override val serializersModule: SerializersModule get() = EmptySerializersModule
|
||||
/**
|
||||
* 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -19,7 +19,6 @@ package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.serializersModuleOf
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
|
||||
@ -30,6 +29,9 @@ import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
@ -108,10 +110,13 @@ import kotlin.reflect.typeOf
|
||||
* ## 实现注意
|
||||
* 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数.
|
||||
*
|
||||
* 继承 [AbstractPluginData] 比继承 [PluginData] 更安全, 尽管 [AbstractPluginData] 也不稳定.
|
||||
*
|
||||
* @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
|
||||
* @see PluginDataStorage [PluginData] 存储仓库
|
||||
* @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface PluginData {
|
||||
/**
|
||||
* 这个 [PluginData] 保存时使用的名称.
|
||||
@ -130,13 +135,56 @@ public interface PluginData {
|
||||
public fun onValueChanged(value: Value<*>)
|
||||
|
||||
/**
|
||||
* 用于支持多态序列化.
|
||||
* 序列化本对象数据时使用的 [SerializersModule]. 用于支持多态序列化等.
|
||||
* 在序列化时会先使用 [PluginData.serializersModule], 再对无法找到 serializer 的类型使用 [MessageSerializers.serializersModule].
|
||||
*
|
||||
* ### 使用示例
|
||||
*
|
||||
* 假设你编写了一个类型 `ChatHistory` 用来存储一个群的消息记录:
|
||||
* ```
|
||||
* data class ChatHistory(
|
||||
* val groupId: Long,
|
||||
* val chain: List<MessageChain>,
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* 要在 [PluginData] 中支持它, 需要首先为 `ChatHistory` 编写 [KSerializer].
|
||||
*
|
||||
* 一种方式是为其添加 [kotlinx.serialization.Serializable]:
|
||||
*
|
||||
* ```
|
||||
* @Serializable
|
||||
* data class ChatHistory(
|
||||
* val groupId: Long,
|
||||
* val chain: List<MessageChain>,
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* 编译器将会自动生成一个 [KSerializer], 可通过 `ChatHistory.Companion.serializer()` 获取.
|
||||
*
|
||||
* 然后在 [PluginData] 定义中添加该 [KSerializer]:
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData("save") {
|
||||
* // 注意, serializersModule 需要早于其他属性定义初始化
|
||||
* override val serializersModule = SerializersModule {
|
||||
* contextual(ChatHistory::class, ChatHistory.serializers()) // 为 ChatHistory 指定 KSerializer
|
||||
* }
|
||||
*
|
||||
* val histories: Map<Long, ChatHistory> by value()
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 然而, 即使不覆盖 `serializersModule` 提供 [KSerializer], mirai 也会通过反射尝试获取.
|
||||
*
|
||||
* 但对于不是使用 `@Serializable` 注解方式, 或者是 `interface`, `abstract class` 等的抽象类型, 则必须覆盖 `serializersModule` 并提供其 [KSerializer].
|
||||
*
|
||||
*
|
||||
*
|
||||
* @see SerializersModule
|
||||
* @see serializersModuleOf
|
||||
*
|
||||
* @since 2.11
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val serializersModule: SerializersModule
|
||||
public val serializersModule: SerializersModule // 该属性在 2.0 增加, 但在 2.11 才正式支持并删除 @MiraiExperimentalApi
|
||||
|
||||
/**
|
||||
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
|
||||
@ -200,28 +248,67 @@ public fun PluginData.value(default: String): SerializerAwareValue<String> = val
|
||||
/**
|
||||
* 通过具体化类型创建一个 [SerializerAwareValue], 并设置初始值.
|
||||
*
|
||||
* @param T 具体化参数类型 T. 仅支持:
|
||||
* 2.11 起, 本函数会优先根据返回值推断类型. 如下示例:
|
||||
* ```
|
||||
* var singleMessage: SingleMessage by value(PlainText("str")) // value 的类型为 SerializerAwareValue<SingleMessage>
|
||||
* ```
|
||||
* 这符合正常的类型定义逻辑.
|
||||
*
|
||||
* @param T 具体化参数类型 T. 在 2.11 以前, 支持:
|
||||
* - 基础数据类型
|
||||
* - 标准库集合类型 ([List], [Map], [Set])
|
||||
* - 标准库数据类型 ([Map.Entry], [Pair], [Triple])
|
||||
* - 和使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的
|
||||
* - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的, 可以通过反射获取 [KSerializer] 的类型
|
||||
*
|
||||
* 2.11 起, 还支持:
|
||||
* - [MessageSerializers] 支持的所有类型, 如 [MessageChain].
|
||||
* - 在 [PluginData.serializersModule] 自定义支持的类型
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <reified T> PluginData.value(
|
||||
default: T,
|
||||
crossinline apply: T.() -> Unit = {},
|
||||
): SerializerAwareValue<T> =
|
||||
valueFromKType(typeOf<T>(), default).also { it.value.apply() }
|
||||
): SerializerAwareValue<@kotlin.internal.Exact T> {
|
||||
/*
|
||||
* 使用 `@Exact` 的 trick (自 2.11.0-RC)
|
||||
*
|
||||
* 使用前:
|
||||
*
|
||||
* ```
|
||||
* var singleMessage: SingleMessage by value(PlainText("str"))
|
||||
* ```
|
||||
*
|
||||
* `value` 的 reified [T] 根据其参数推断为 [PlainText], 则会使用 `PlainText.serializer()`.
|
||||
*
|
||||
* 可以通过序列化后的 YAML 文本直观地感受问题:
|
||||
* ```yaml
|
||||
* singleMessage:
|
||||
* content: str
|
||||
* ```
|
||||
* 那么将来若 `singleMessage` 的值变更为非 [PlainText] 类型, 将会无法序列化.
|
||||
*
|
||||
* 附使用 `@Exact` 时的正确结果 (使用 [SingleMessage] 的序列化器 (来自 [MessageSerializers.serializersModule])):
|
||||
*
|
||||
* ```yaml
|
||||
* singleMessage:
|
||||
* type: PlainText
|
||||
* value:
|
||||
* content: str
|
||||
* ```
|
||||
*
|
||||
* 相关测试: [net.mamoe.mirai.console.data.PluginDataTest.supports message chain]
|
||||
*/
|
||||
return valueFromKType(typeOf<T>(), default).also { it.value.apply() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过具体化类型创建一个 [SerializerAwareValue].
|
||||
* @see valueFromKType 查看更多实现信息
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T>
|
||||
PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
|
||||
PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<@kotlin.internal.Exact T> =
|
||||
valueImpl<T>(typeOf<T>(), T::class).also { it.value.apply() }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -10,12 +10,14 @@
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.console.data.*
|
||||
import kotlinx.serialization.modules.plus
|
||||
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.SilentLogger
|
||||
import net.mamoe.mirai.utils.debug
|
||||
import net.mamoe.mirai.utils.warning
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -23,7 +25,7 @@ import java.nio.file.Path
|
||||
@Suppress("RedundantVisibilityModifier") // might be public in the future
|
||||
internal open class MultiFilePluginDataStorageImpl(
|
||||
public final override val directoryPath: Path,
|
||||
private val logger: MiraiLogger = SilentLogger,
|
||||
private val logger: MiraiLogger = MiraiLogger.Factory.create(MultiFilePluginDataStorageImpl::class),
|
||||
) : PluginDataStorage, MultiFilePluginDataStorage {
|
||||
init {
|
||||
directoryPath.mkdir()
|
||||
@ -35,12 +37,15 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
// 0xFEFF is BOM, handle UTF8-BOM
|
||||
val text = getPluginDataFile(holder, instance).readText().removePrefix("\uFEFF")
|
||||
if (text.isNotBlank()) {
|
||||
logger.warning { "Deserializing $text" }
|
||||
Yaml.decodeFromString(instance.updaterSerializer, text)
|
||||
createYaml(instance).decodeFromString(instance.updaterSerializer, text)
|
||||
} else {
|
||||
this.store(holder, instance) // save an initial copy
|
||||
}
|
||||
logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
// logger.verbose { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
}
|
||||
|
||||
internal fun getPluginDataFileInternal(holder: PluginDataHolder, instance: PluginData): File {
|
||||
return getPluginDataFile(holder, instance)
|
||||
}
|
||||
|
||||
protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File {
|
||||
@ -56,39 +61,45 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
if (file.isDirectory) {
|
||||
error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.")
|
||||
}
|
||||
logger.debug { "File allocated for ${instance.saveName}: $file" }
|
||||
// logger.verbose { "File allocated for ${instance.saveName}: $file" }
|
||||
return file.toFile().also { it.createNewFile() }
|
||||
}
|
||||
|
||||
private val json = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
private val yaml = Yaml
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||
getPluginDataFile(holder, instance).writeText(
|
||||
kotlin.runCatching {
|
||||
yaml.encodeToString(instance.updaterSerializer, Unit).also {
|
||||
yaml.decodeAnyFromString(it) // test yaml
|
||||
createYaml(instance).encodeToString(instance.updaterSerializer, Unit).also {
|
||||
Yaml.decodeAnyFromString(it) // test yaml
|
||||
}
|
||||
}.recoverCatching {
|
||||
logger.warning(
|
||||
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
|
||||
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new",
|
||||
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai/issues/new/choose",
|
||||
it
|
||||
)
|
||||
json.encodeToString(instance.updaterSerializer, Unit)
|
||||
@Suppress("JSON_FORMAT_REDUNDANT")
|
||||
Json {
|
||||
serializersModule = MessageSerializers.serializersModule + instance.serializersModule
|
||||
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
encodeDefaults = true
|
||||
}.encodeToString(instance.updaterSerializer, Unit)
|
||||
}.getOrElse {
|
||||
throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
|
||||
}
|
||||
)
|
||||
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
// logger.verbose { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
}
|
||||
|
||||
private fun createYaml(instance: PluginData): Yaml {
|
||||
return Yaml {
|
||||
this.serializersModule =
|
||||
MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,29 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
import kotlinx.serialization.builtins.ArraySerializer
|
||||
import kotlinx.serialization.builtins.nullable
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.serializer
|
||||
import kotlinx.serialization.serializerOrNull
|
||||
import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.yamlkt.YamlDynamicSerializer
|
||||
import net.mamoe.yamlkt.YamlNullableDynamicSerializer
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
@ -32,41 +32,22 @@ import kotlin.reflect.KType
|
||||
* Copied from kotlinx.serialization, modifications are marked with "/* mamoe modify */"
|
||||
* Copyright 2017-2020 JetBrains s.r.o.
|
||||
*/
|
||||
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
|
||||
@Suppress(
|
||||
"UNCHECKED_CAST",
|
||||
"NO_REFLECTION_IN_CLASS_PATH",
|
||||
"UNSUPPORTED",
|
||||
"INVISIBLE_MEMBER",
|
||||
"INVISIBLE_REFERENCE",
|
||||
"IMPLICIT_CAST_TO_ANY"
|
||||
)
|
||||
internal fun serializerMirai(type: KType): KSerializer<Any?> {
|
||||
fun serializerByKTypeImpl(type: KType): KSerializer<Any> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun SerializersModule.serializerMirai(type: KType): KSerializer<Any?> {
|
||||
fun serializerByKTypeImpl(type: KType): KSerializer<*> {
|
||||
val rootClass = type.classifierAsKClass()
|
||||
|
||||
this.serializerOrNull(type)?.let { return it } // Kotlin builtin and user-defined
|
||||
MessageSerializers.serializersModule.serializerOrNull(type)?.let { return it } // Mirai Messages
|
||||
|
||||
val typeArguments = type.arguments
|
||||
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
|
||||
return when {
|
||||
typeArguments.isEmpty() -> rootClass.serializer()
|
||||
typeArguments.isEmpty() -> this.serializer(type)
|
||||
else -> {
|
||||
val serializers = typeArguments
|
||||
.map(::serializerMirai)
|
||||
// Array is not supported, see KT-32839
|
||||
val serializers = typeArguments.map(::serializerMirai)
|
||||
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])
|
||||
ConcurrentHashMap::class, ConcurrentMap::class -> MapSerializer(serializers[0], serializers[1]).map(
|
||||
serializer = { HashMap(it as Map<*, *>) },
|
||||
deserializer = { ConcurrentHashMap(it) }
|
||||
)
|
||||
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
|
||||
Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer
|
||||
else -> {
|
||||
if (rootClass.java.isArray) {
|
||||
return ArraySerializer(
|
||||
@ -81,10 +62,10 @@ internal fun serializerMirai(type: KType): KSerializer<Any?> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}.cast()
|
||||
}
|
||||
}
|
||||
|
||||
val result = serializerByKTypeImpl(type)
|
||||
val result = serializerByKTypeImpl(type) as KSerializer<Any>
|
||||
return if (type.isMarkedNullable) result.nullable else result.cast()
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@ -68,7 +68,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
|
||||
},
|
||||
kToValue = { k -> valueFromKType(type.arguments[0].type!!, k) },
|
||||
vToValue = { v -> valueFromKType(type.arguments[1].type!!, v) }
|
||||
).serializableValueWith(serializerMirai(type) as KSerializer<Map<Any?, Any?>>) // erased
|
||||
).serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Map<Any?, Any?>>) // erased
|
||||
}
|
||||
}
|
||||
MutableList::class,
|
||||
@ -84,7 +84,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
|
||||
TODO()
|
||||
} else {
|
||||
return createCompositeListValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) }
|
||||
.serializableValueWith(serializerMirai(type) as KSerializer<List<Any?>>)
|
||||
.serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<List<Any?>>)
|
||||
}
|
||||
}
|
||||
MutableSet::class,
|
||||
@ -101,11 +101,11 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
|
||||
TODO()
|
||||
} else {
|
||||
return createCompositeSetValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) }
|
||||
.serializableValueWith(serializerMirai(type) as KSerializer<Set<Any?>>)
|
||||
.serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Set<Any?>>)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val serializer = serializerMirai(type)
|
||||
val serializer = serializersModule.serializerMirai(type)
|
||||
return LazyReferenceValueImpl<Any?>().serializableValueWith(serializer)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
@ -9,14 +9,26 @@
|
||||
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl
|
||||
import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.messageChainOf
|
||||
import net.mamoe.mirai.utils.mapPrimitive
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
internal class PluginDataTest {
|
||||
internal class PluginDataTest : AbstractConsoleInstanceTest() {
|
||||
@TempDir
|
||||
lateinit var tempDir: Path
|
||||
|
||||
class MyPluginData : AutoSavePluginData("test") {
|
||||
var int by value(1)
|
||||
val map: MutableMap<String, String> by value()
|
||||
@ -128,4 +140,86 @@ internal class PluginDataTest {
|
||||
|
||||
assertSame(reference(), delegation()) // check shadowing
|
||||
}
|
||||
|
||||
|
||||
class SupportsMessageChain : AutoSavePluginData("test") {
|
||||
val chain: MessageChain by value(messageChainOf(PlainText("str")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `supports message chain`() {
|
||||
assertEquals(
|
||||
"""
|
||||
chain:
|
||||
- type: PlainText
|
||||
value:
|
||||
content: str
|
||||
""".trimIndent(), serializePluginData(SupportsMessageChain())
|
||||
)
|
||||
serializeAndRereadPluginData(SupportsMessageChain())
|
||||
}
|
||||
|
||||
class SupportsPolymorphicCorrectly : AutoSavePluginData("test") {
|
||||
val singleMessage: SingleMessage by value(PlainText("str"))
|
||||
val plainText: PlainText by value(PlainText("str"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `supports polymorphic correctly`() {
|
||||
assertEquals(
|
||||
"""
|
||||
singleMessage:
|
||||
type: PlainText
|
||||
value:
|
||||
content: str
|
||||
plainText:
|
||||
content: str
|
||||
""".trimIndent(), serializePluginData(SupportsPolymorphicCorrectly())
|
||||
)
|
||||
serializeAndRereadPluginData(SupportsPolymorphicCorrectly())
|
||||
}
|
||||
|
||||
class SupportsSerializersModule : AutoSavePluginData("test") {
|
||||
override val serializersModule: SerializersModule = SerializersModule {
|
||||
contextual(MyClass::class, myClassSerializer)
|
||||
}
|
||||
|
||||
val v: MyClass by value(MyClass("test"))
|
||||
|
||||
data class MyClass(
|
||||
val str: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val myClassSerializer = String.serializer().mapPrimitive("MyClass",
|
||||
{ MyClass(it) },
|
||||
{ it.str }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `supports serializers module`() {
|
||||
assertEquals(
|
||||
"""
|
||||
v: test
|
||||
""".trimIndent(), serializePluginData(SupportsSerializersModule())
|
||||
)
|
||||
serializeAndRereadPluginData(SupportsSerializersModule())
|
||||
}
|
||||
|
||||
|
||||
private fun serializePluginData(data: PluginData): String {
|
||||
val storage = MultiFilePluginDataStorageImpl(tempDir)
|
||||
storage.store(mockPlugin, data)
|
||||
return storage.getPluginDataFileInternal(mockPlugin, data).readText()
|
||||
}
|
||||
|
||||
private fun serializeAndRereadPluginData(data: PluginData) {
|
||||
val storage = MultiFilePluginDataStorageImpl(tempDir)
|
||||
storage.store(mockPlugin, data)
|
||||
val serialized = storage.getPluginDataFileInternal(mockPlugin, data).readText()
|
||||
storage.load(mockPlugin, data)
|
||||
assertEquals(serialized, storage.getPluginDataFileInternal(mockPlugin, data).readText())
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package org.example.myplugin
|
||||
@ -12,7 +12,10 @@ package org.example.myplugin
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.messageChainOf
|
||||
import org.example.myplugin.DataTest1.provideDelegate
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
@ -23,6 +26,9 @@ object UsingDerivedMap : AutoSavePluginConfig("data") {
|
||||
var p4 by value<HashMap<String, String>>()
|
||||
var p3 by value<ConcurrentHashMap<String, String>>()
|
||||
|
||||
var p8 by value<List<String>>()
|
||||
var p5 by value<ArrayList<String>>()
|
||||
var p6 by value<AbstractList<String>>()
|
||||
var p7 by value<AbstractList<String>>()
|
||||
|
||||
var p6 by value<MessageChain>(messageChainOf()) // no error
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
@ -31,6 +31,7 @@ import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
@ -114,6 +115,9 @@ class PluginDataValuesChecker : CallChecker, DeclarationChecker {
|
||||
val factory = when {
|
||||
jetTypeFqn == "java.util.concurrent.ConcurrentHashMap" -> MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE
|
||||
|
||||
classDescriptor.fqNameSafe.asString()
|
||||
.startsWith("net.mamoe.mirai.message.data.") -> null // Don't report for MessageChain
|
||||
|
||||
classDescriptor.isSubclassOf(builtIns.list) && jetTypeFqn != "kotlin.collections.List" -> {
|
||||
if (classDescriptor.isSubclassOf(builtIns.mutableList)) {
|
||||
if (jetTypeFqn != "kotlin.collections.MutableList" && jetTypeFqn != "java.util.List") {
|
||||
|
Loading…
Reference in New Issue
Block a user