mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-24 14:30:09 +08:00
支持 vararg 和原生类型数组命令参数 (#1760)
* fix: vararg * marge * add: other array test * api dump
This commit is contained in:
parent
eeb10cc89a
commit
6bb2bf23aa
@ -1182,8 +1182,7 @@ public abstract interface class net/mamoe/mirai/console/command/parse/CommandVal
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/console/command/parse/CommandValueArgumentKt {
|
||||
public static final fun mapToType (Lnet/mamoe/mirai/console/command/parse/CommandValueArgument;Lkotlin/reflect/KType;)Ljava/lang/Object;
|
||||
public static final fun mapToTypeOrNull (Lnet/mamoe/mirai/console/command/parse/CommandValueArgument;Lkotlin/reflect/KType;)Ljava/lang/Object;
|
||||
public static final fun mapToTypeOrNull (Lnet/mamoe/mirai/console/command/parse/CommandValueArgument;Lkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
|
||||
public static final fun mapValue (Lnet/mamoe/mirai/console/command/parse/CommandValueArgument;Lnet/mamoe/mirai/console/command/descriptor/TypeVariant;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
|
@ -175,6 +175,15 @@ public sealed class CommandReceiverParameter<T>(
|
||||
|
||||
internal val ANY_TYPE = typeOf<Any>()
|
||||
internal val ARRAY_OUT_ANY_TYPE = typeOf<Array<out Any?>>()
|
||||
internal val BASE_ARRAY_TYPES = mapOf(
|
||||
typeOf<ByteArray>() to typeOf<Byte>(),
|
||||
typeOf<CharArray>() to typeOf<Char>(),
|
||||
typeOf<ShortArray>() to typeOf<Short>(),
|
||||
typeOf<IntArray>() to typeOf<Int>(),
|
||||
typeOf<LongArray>() to typeOf<Long>(),
|
||||
typeOf<FloatArray>() to typeOf<Float>(),
|
||||
typeOf<DoubleArray>() to typeOf<Double>()
|
||||
)
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() {
|
||||
@ -191,8 +200,9 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
commandArgumentContext: CommandArgumentContext?
|
||||
): ArgumentAcceptance {
|
||||
if (isVararg) {
|
||||
val arrayElementType = this.type.arguments.single() // Array<T>
|
||||
return acceptingImpl(arrayElementType.type ?: ANY_TYPE, argument, commandArgumentContext)
|
||||
// BaseArray or Array<T>
|
||||
val arrayElementType = BASE_ARRAY_TYPES[this.type] ?: this.type.arguments.single().type
|
||||
return acceptingImpl(arrayElementType ?: ANY_TYPE, argument, commandArgumentContext)
|
||||
}
|
||||
|
||||
return acceptingImpl(this.type, argument, commandArgumentContext)
|
||||
@ -271,7 +281,7 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
"type.classifier must be KClass."
|
||||
}
|
||||
if (isVararg)
|
||||
check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
|
||||
check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE) || type in BASE_ARRAY_TYPES) {
|
||||
"type must be subtype of Array if vararg. Given $type."
|
||||
}
|
||||
}
|
||||
|
@ -72,42 +72,50 @@ public data class DefaultCommandValueArgument(
|
||||
public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = typeVariant.mapValue(this.value)
|
||||
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToType(): T =
|
||||
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapToType(type: KType): T =
|
||||
mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
|
||||
//@OptIn(ExperimentalStdlibApi::class)
|
||||
//@ExperimentalCommandDescriptors
|
||||
//public inline fun <reified T> CommandValueArgument.mapToType(): T =
|
||||
// mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
||||
//
|
||||
//@OptIn(ExperimentalStdlibApi::class)
|
||||
//@ExperimentalCommandDescriptors
|
||||
//public fun <T> CommandValueArgument.mapToType(type: KType): T =
|
||||
// mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? {
|
||||
if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
|
||||
val arrayElementType = expectingType.arguments.single().type ?: ANY_TYPE
|
||||
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType, context: (KType, Message) -> T?): T? {
|
||||
if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE) || expectingType in BASE_ARRAY_TYPES) {
|
||||
val arrayElementType = BASE_ARRAY_TYPES[expectingType] ?: expectingType.arguments.single().type ?: ANY_TYPE
|
||||
|
||||
val result = ArrayList<Any?>()
|
||||
|
||||
when (val value = value) {
|
||||
is MessageChain -> {
|
||||
for (message in value) {
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, message))
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, message) ?: context(arrayElementType, message))
|
||||
}
|
||||
}
|
||||
else -> { // single
|
||||
value.castOrInternalError<SingleMessage>()
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, value))
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, value) ?: context(arrayElementType, value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result.toArray(arrayElementType.createArray(result.size)) as T
|
||||
return when (expectingType) {
|
||||
typeOf<ByteArray>() -> (result as List<Byte>).toByteArray()
|
||||
typeOf<CharArray>() -> (result as List<Char>).toCharArray()
|
||||
typeOf<ShortArray>() -> (result as List<Short>).toShortArray()
|
||||
typeOf<IntArray>() -> (result as List<Int>).toIntArray()
|
||||
typeOf<LongArray>() -> (result as List<Long>).toLongArray()
|
||||
typeOf<FloatArray>() -> (result as List<Float>).toFloatArray()
|
||||
typeOf<DoubleArray>() -> (result as List<Double>).toDoubleArray()
|
||||
else -> result.toArray(arrayElementType.createArray(result.size))
|
||||
} as T?
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mapToTypeOrNullImpl(expectingType, value) as T
|
||||
return (mapToTypeOrNullImpl(expectingType, value) ?: context(expectingType, value)) as T?
|
||||
}
|
||||
|
||||
private fun KType.createArray(size: Int): Array<Any?> {
|
||||
@ -131,8 +139,8 @@ private fun CommandValueArgument.mapToTypeOrNullImpl(expectingType: KType, value
|
||||
return result.mapValue(value)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return mapToTypeOrNull(typeOf<T>())
|
||||
}
|
||||
//@ExperimentalCommandDescriptors
|
||||
//public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
// @OptIn(ExperimentalStdlibApi::class)
|
||||
// return mapToTypeOrNull(typeOf<T>())
|
||||
//}
|
@ -104,12 +104,10 @@ public class ResolvedCommandCallImpl(
|
||||
) : ResolvedCommandCall {
|
||||
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
|
||||
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
|
||||
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(
|
||||
argument.value,
|
||||
caller
|
||||
)
|
||||
?: throw NoValueArgumentMappingException(argument, parameter.type)
|
||||
// TODO: 2020/10/17 consider vararg and optional
|
||||
val value = argument.mapToTypeOrNull(parameter.type) { type, message ->
|
||||
context[type.classifierAsKClass()]?.parse(message, caller)
|
||||
} ?: throw NoValueArgumentMappingException(argument, parameter.type)
|
||||
|
||||
ResolvedCommandValueArgument(parameter.cast(), value)
|
||||
}
|
||||
}
|
||||
|
@ -676,36 +676,213 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() {
|
||||
assertEquals(1, arg1)
|
||||
Testing.ok(x)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun enum(arg1: Int, vararg y: TestEnumArgCommand.TestEnum) {
|
||||
assertEquals(1, arg1)
|
||||
Testing.ok(y)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun long(arg1: String, vararg z: Long) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun int(arg1: String, vararg z: Int) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun byte(arg1: String, vararg z: Byte) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun short(arg1: String, vararg z: Short) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun float(arg1: String, vararg z: Float) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun double(arg1: String, vararg z: Double) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun char(arg1: String, vararg z: Char) {
|
||||
assertEquals("arg1", arg1)
|
||||
Testing.ok(z)
|
||||
}
|
||||
}
|
||||
optionCommand.withRegistration {
|
||||
assertArrayEquals(
|
||||
// Array<String>
|
||||
assertContentEquals(
|
||||
emptyArray<String>(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1"))
|
||||
}
|
||||
)
|
||||
|
||||
assertArrayEquals(
|
||||
assertContentEquals(
|
||||
arrayOf("s"),
|
||||
withTesting<Array<String>> {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1 s"))
|
||||
}
|
||||
)
|
||||
assertArrayEquals(
|
||||
assertContentEquals(
|
||||
arrayOf("s", "s", "s"),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1 s s s"))
|
||||
}
|
||||
)
|
||||
// Array<TestEnum>
|
||||
assertContentEquals(
|
||||
emptyArray<TestEnumArgCommand.TestEnum>(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test enum 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
arrayOf(TestEnumArgCommand.TestEnum.V1),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test enum 1 ${TestEnumArgCommand.TestEnum.V1}"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
TestEnumArgCommand.TestEnum.values(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test enum 1 ${TestEnumArgCommand.TestEnum.values().joinToString(" ")}"))
|
||||
}
|
||||
)
|
||||
// LongArray
|
||||
assertContentEquals(
|
||||
longArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test long arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
longArrayOf(1),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test long arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
longArrayOf(1, 2, 3),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test long arg1 1 2 3"))
|
||||
}
|
||||
)
|
||||
// IntArray
|
||||
assertContentEquals(
|
||||
intArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test int arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
intArrayOf(1),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test int arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
intArrayOf(1, 2, 3),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test int arg1 1 2 3"))
|
||||
}
|
||||
)
|
||||
// ByteArray
|
||||
assertContentEquals(
|
||||
byteArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test byte arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
byteArrayOf(1),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test byte arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
byteArrayOf(1, 2, 3),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test byte arg1 1 2 3"))
|
||||
}
|
||||
)
|
||||
// ShortArray
|
||||
assertContentEquals(
|
||||
shortArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test short arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
shortArrayOf(1),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test short arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
shortArrayOf(1, 2, 3),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test short arg1 1 2 3"))
|
||||
}
|
||||
)
|
||||
// FloatArray
|
||||
assertContentEquals(
|
||||
floatArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test float arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
floatArrayOf(1.0F),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test float arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
floatArrayOf(1.0F, 1.5F, 2.0F),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test float arg1 1 1.5 2"))
|
||||
}
|
||||
)
|
||||
// DoubleArray
|
||||
assertContentEquals(
|
||||
doubleArrayOf(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test double arg1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
doubleArrayOf(1.0),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test double arg1 1"))
|
||||
}
|
||||
)
|
||||
assertContentEquals(
|
||||
doubleArrayOf(1.0, 1.5, 2.0),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test double arg1 1 1.5 2"))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> assertArrayEquals(expected: Array<out T>, actual: Array<out T>, message: String? = null) {
|
||||
asserter.assertEquals(message, expected.contentToString(), actual.contentToString())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal fun assertSuccess(result: CommandExecuteResult) {
|
||||
if (result.isFailure()) {
|
||||
|
Loading…
Reference in New Issue
Block a user