1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-03-26 07:20:09 +08:00

Support serializersModule for PluginData, close . Improve PluginData.value type inference behavior: resolve exact returned type. ()

Also support `MessageChain` and others from `MessageSerializers.serializersModule`, fix .
This commit is contained in:
Him188 2022-04-24 10:37:10 +01:00 committed by GitHub
parent e6840de0e0
commit be832c7dbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 282 additions and 101 deletions
mirai-console
backend/mirai-console
tools/intellij-plugin
run/projects/test-project/src/main/kotlin/org/example/myplugin
src/diagnostics

View File

@ -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] 被修改时被调用.
*/

View File

@ -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")

View File

@ -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
}
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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") {