支持 vararg 和原生类型数组命令参数 (#1760)

* fix: vararg

* marge

* add: other array test

* api dump
This commit is contained in:
cssxsh 2022-07-31 15:56:14 +08:00 committed by GitHub
parent eeb10cc89a
commit 6bb2bf23aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 233 additions and 41 deletions

View File

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

View File

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

View File

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

View File

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

View File

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