From 6bb2bf23aa8d02ca1f7594ae58e6f5e2a88ba7a8 Mon Sep 17 00:00:00 2001 From: cssxsh <cssxsh@gmail.com> Date: Sun, 31 Jul 2022 15:56:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20vararg=20=E5=92=8C?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E7=B1=BB=E5=9E=8B=E6=95=B0=E7=BB=84=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=8F=82=E6=95=B0=20(#1760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: vararg * marge * add: other array test * api dump --- .../compatibility-validation/jvm/api/jvm.api | 3 +- .../command/descriptor/CommandParameter.kt | 16 +- .../src/command/parse/CommandValueArgument.kt | 52 +++-- .../command/resolve/ResolvedCommandCall.kt | 10 +- .../test/command/InstanceTestCommand.kt | 193 +++++++++++++++++- 5 files changed, 233 insertions(+), 41 deletions(-) diff --git a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api index c8b20d5a4..1973b4879 100644 --- a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api +++ b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api @@ -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; } diff --git a/mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt index df3bbf6ee..be6c5dc83 100644 --- a/mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt +++ b/mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt @@ -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." } } diff --git a/mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt b/mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt index 267424ac7..64f1e09f8 100644 --- a/mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt +++ b/mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt @@ -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>()) -} \ No newline at end of file +//@ExperimentalCommandDescriptors +//public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? { +// @OptIn(ExperimentalStdlibApi::class) +// return mapToTypeOrNull(typeOf<T>()) +//} \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt b/mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt index 401330c21..641094d06 100644 --- a/mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt +++ b/mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt @@ -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) } } diff --git a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt index b065f13f7..2cc6f3fd9 100644 --- a/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt +++ b/mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt @@ -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()) {