Review command:

Add `CommandReflector.validate` to check declaration clashes;
Rename CommandSignatureVariant to CommandSignature;
Add docs;
Cleanup code;
This commit is contained in:
Him188 2020-10-25 14:49:08 +08:00
parent d1ebe44f3e
commit 291035f978
12 changed files with 109 additions and 140 deletions

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant import net.mamoe.mirai.console.command.descriptor.CommandSignature
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
@ -52,7 +52,7 @@ public interface Command {
*/ */
@ConsoleExperimentalApi("Property name is experimental") @ConsoleExperimentalApi("Property name is experimental")
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public val overloads: List<CommandSignatureVariant> public val overloads: List<CommandSignature>
/** /**
* 用法说明, 用于发送给用户. [usage] 一般包含 [description]. * 用法说明, 用于发送给用户. [usage] 一般包含 [description].

View File

@ -95,8 +95,10 @@ public abstract class CompositeCommand(
private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) } private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { public final override val overloads: List<CommandSignatureFromKFunction> by lazy {
reflector.findSubCommands() reflector.findSubCommands().also {
reflector.validate(it)
}
} }
/** /**

View File

@ -56,8 +56,8 @@ public abstract class RawCommand(
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
override val overloads: List<CommandSignatureVariant> = listOf( override val overloads: List<CommandSignature> = listOf(
CommandSignatureVariantImpl( CommandSignatureImpl(
receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()), receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()),
valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>("args", true)) valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>("args", true))
) { call -> ) { call ->

View File

@ -17,7 +17,6 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext
@ -67,8 +66,9 @@ public abstract class SimpleCommand(
private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy { public final override val overloads: List<CommandSignatureFromKFunction> by lazy {
reflector.findSubCommands().also { reflector.findSubCommands().also {
reflector.validate(it)
if (it.isEmpty()) if (it.isEmpty())
throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.") throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.")
} }

View File

@ -1,31 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.IllegalCommandArgumentException
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
*
* [message] 将会发送给指令调用方.
*
* @see IllegalCommandArgumentException
* @see CommandValueArgumentParser
* @see AbstractCommandValueArgumentParser.illegalArgument
*/
public class CommandArgumentParserException : IllegalCommandArgumentException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -26,26 +26,46 @@ import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
/** /**
* @see CommandSignatureVariantImpl * 指令签名. 表示指令定义的需要的参数.
*
* @see AbstractCommandSignature
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandSignatureVariant { public interface CommandSignature {
/**
* 接收者参数, [CommandSender] 子类
*/
@ConsoleExperimentalApi @ConsoleExperimentalApi
public val receiverParameter: CommandReceiverParameter<out CommandSender>? public val receiverParameter: CommandReceiverParameter<out CommandSender>?
/**
* 形式 值参数.
*/
public val valueParameters: List<AbstractCommandValueParameter<*>> public val valueParameters: List<AbstractCommandValueParameter<*>>
/**
* 调用这个指令.
*/
public suspend fun call(resolvedCommandCall: ResolvedCommandCall) public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
} }
/**
* 来自 [KFunction] 反射得到的 [CommandSignature]
*
* @see CommandSignatureFromKFunctionImpl
*/
@ConsoleExperimentalApi @ConsoleExperimentalApi
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant { public interface CommandSignatureFromKFunction : CommandSignature {
public val originFunction: KFunction<*> public val originFunction: KFunction<*>
} }
/**
* @see CommandSignatureImpl
* @see CommandSignatureFromKFunctionImpl
*/
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant { public abstract class AbstractCommandSignature : CommandSignature {
override fun toString(): String { override fun toString(): String {
val receiverParameter = receiverParameter val receiverParameter = receiverParameter
return if (receiverParameter == null) { return if (receiverParameter == null) {
@ -57,11 +77,11 @@ public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public open class CommandSignatureVariantImpl( public open class CommandSignatureImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?, override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>, override val valueParameters: List<AbstractCommandValueParameter<*>>,
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignatureVariant, AbstractCommandSignatureVariant() { ) : CommandSignature, AbstractCommandSignature() {
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
return onCall(resolvedCommandCall) return onCall(resolvedCommandCall)
} }
@ -69,12 +89,12 @@ public open class CommandSignatureVariantImpl(
@ConsoleExperimentalApi @ConsoleExperimentalApi
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public open class CommandSignatureVariantFromKFunctionImpl( public open class CommandSignatureFromKFunctionImpl(
override val receiverParameter: CommandReceiverParameter<out CommandSender>?, override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
override val valueParameters: List<AbstractCommandValueParameter<*>>, override val valueParameters: List<AbstractCommandValueParameter<*>>,
override val originFunction: KFunction<*>, override val originFunction: KFunction<*>,
private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
) : CommandSignatureVariantFromKFunction, AbstractCommandSignatureVariant() { ) : CommandSignatureFromKFunction, AbstractCommandSignature() {
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
return onCall(resolvedCommandCall) return onCall(resolvedCommandCall)
} }

View File

@ -11,7 +11,9 @@
package net.mamoe.mirai.console.command.descriptor package net.mamoe.mirai.console.command.descriptor
import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.IllegalCommandArgumentException
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument
import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
@ -28,9 +30,10 @@ public open class NoValueArgumentMappingException(
) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}") ) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}")
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public open class UnresolvedCommandCallException( public open class CommandDeclarationClashException(
public val call: CommandCall, public val command: Command,
) : CommandResolutionException("Unresolved call: $call") public val signatures: List<CommandSignature>,
) : CommandResolutionException("Command declaration clash: \n${signatures.joinToString("\n")}")
public open class CommandResolutionException : RuntimeException { public open class CommandResolutionException : RuntimeException {
public constructor() : super() public constructor() : super()
@ -38,3 +41,19 @@ public open class CommandResolutionException : RuntimeException {
public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause) public constructor(cause: Throwable?) : super(cause)
} }
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
*
* [message] 将会发送给指令调用方.
*
* @see IllegalCommandArgumentException
* @see CommandValueArgumentParser
* @see AbstractCommandValueArgumentParser.illegalArgument
*/
public class CommandArgumentParserException : IllegalCommandArgumentException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -31,13 +31,13 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
return ResolvedCommandCallImpl(call.caller, return ResolvedCommandCallImpl(call.caller,
callee, callee,
signature.variant, signature.signature,
signature.zippedArguments.map { it.second }, signature.zippedArguments.map { it.second },
context ?: EmptyCommandArgumentContext) context ?: EmptyCommandArgumentContext)
} }
private data class ResolveData( private data class ResolveData(
val variant: CommandSignatureVariant, val signature: CommandSignature,
val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>, val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>,
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
val remainingParameters: List<AbstractCommandValueParameter<*>>, val remainingParameters: List<AbstractCommandValueParameter<*>>,
@ -69,7 +69,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
if (zipped.isEmpty()) { if (zipped.isEmpty()) {
ResolveData( ResolveData(
variant = signature, signature = signature,
zippedArguments = emptyList(), zippedArguments = emptyList(),
argumentAcceptances = emptyList(), argumentAcceptances = emptyList(),
remainingParameters = remainingParameters, remainingParameters = remainingParameters,
@ -91,7 +91,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
} }
ResolveData( ResolveData(
variant = signature, signature = signature,
zippedArguments = zipped, zippedArguments = zipped,
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context) val accepting = parameter.accepting(argument, context)
@ -163,7 +163,7 @@ fun main() {
private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> { private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> {
if (isEmpty()) return emptyList() if (isEmpty()) return emptyList()
return associateWith { return associateWith {
it.variant.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults. it.signature.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults.
}.let { m -> }.let { m ->
val maxMatch = m.values.maxByOrNull { it } val maxMatch = m.values.maxByOrNull { it }
m.filter { it.value == maxMatch }.keys m.filter { it.value == maxMatch }.keys

View File

@ -37,9 +37,9 @@ public interface ResolvedCommandCall {
public val callee: Command public val callee: Command
/** /**
* The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand] * The callee [CommandSignature], specifically a sub command from [CompositeCommand]
*/ */
public val calleeSignature: CommandSignatureVariant public val calleeSignature: CommandSignature
/** /**
* Original arguments * Original arguments
@ -47,7 +47,7 @@ public interface ResolvedCommandCall {
public val rawValueArguments: List<CommandValueArgument> public val rawValueArguments: List<CommandValueArgument>
/** /**
* Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index.
* *
* **Implementation details**: Lazy calculation. * **Implementation details**: Lazy calculation.
*/ */
@ -73,7 +73,7 @@ public suspend inline fun ResolvedCommandCall.call() {
public class ResolvedCommandCallImpl( public class ResolvedCommandCallImpl(
override val caller: CommandSender, override val caller: CommandSender,
override val callee: Command, override val callee: Command,
override val calleeSignature: CommandSignatureVariant, override val calleeSignature: CommandSignature,
override val rawValueArguments: List<CommandValueArgument>, override val rawValueArguments: List<CommandValueArgument>,
private val context: CommandArgumentContext, private val context: CommandArgumentContext,
) : ResolvedCommandCall { ) : ResolvedCommandCall {

View File

@ -11,6 +11,7 @@ import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.message.data.buildMessageChain
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.KParameter import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KVisibility import kotlin.reflect.KVisibility
import kotlin.reflect.full.* import kotlin.reflect.full.*
@ -133,7 +134,7 @@ internal class CommandReflector(
// if (isAbstract) illegalDeclaration("Command function cannot be abstract") // if (isAbstract) illegalDeclaration("Command function cannot be abstract")
} }
fun generateUsage(overloads: Iterable<CommandSignatureVariantFromKFunction>): String { fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
return overloads.joinToString("\n") { subcommand -> return overloads.joinToString("\n") { subcommand ->
buildString { buildString {
if (command.prefixOptional) { if (command.prefixOptional) {
@ -173,17 +174,45 @@ internal class CommandReflector(
} }
} }
fun validate(variants: List<CommandSignatureVariantFromKFunctionImpl>) { fun validate(signatures: List<CommandSignatureFromKFunctionImpl>) {
data class ErasedParameters( data class ErasedParameterInfo(
val name: String, val index: Int,
val x: String, val name: String?,
val type: KType, // ignore nullability
val additional: String?,
) )
variants
data class ErasedVariantInfo(
val receiver: ErasedParameterInfo?,
val valueParameters: List<ErasedParameterInfo>,
)
fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo {
return ErasedParameterInfo(index,
this.name,
this.type.withNullability(false),
if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null)
}
val candidates = signatures.map { variant ->
variant to ErasedVariantInfo(
variant.receiverParameter?.toErasedParameterInfo(0),
variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) }
)
}
val groups = candidates.groupBy { it.second }
val clashes = groups.entries.find { (_, value) ->
value.size > 1
} ?: return
throw CommandDeclarationClashException(command, clashes.value.map { it.first })
} }
@Throws(IllegalCommandDeclarationException::class) @Throws(IllegalCommandDeclarationException::class)
fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> { fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> {
return command::class.functions // exclude static later return command::class.functions // exclude static later
.asSequence() .asSequence()
.filter { it.isSubCommandFunction() } .filter { it.isSubCommandFunction() }
@ -198,7 +227,7 @@ internal class CommandReflector(
val functionValueParameters = val functionValueParameters =
function.valueParameters.associateBy { it.toUserDefinedCommandParameter() } function.valueParameters.associateBy { it.toUserDefinedCommandParameter() }
CommandSignatureVariantFromKFunctionImpl( CommandSignatureFromKFunctionImpl(
receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(), receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(),
valueParameters = functionNameAsValueParameter + functionValueParameters.keys, valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
originFunction = function originFunction = function

View File

@ -1,63 +0,0 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
import kotlin.reflect.KParameter
import kotlin.reflect.KType
/*
internal fun Parameter.toCommandParam(): CommandParameter<*> {
val name = getAnnotation(CompositeCommand.Name::class.java)
return CommandParameter(
name?.value ?: this.name
?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
this.type.kotlin,
null
)
}
*/
/**
* 指令形式参数.
*/
internal data class CommandParameter<T : Any>(
/**
* 参数名. 不允许重复.
*/
val name: String,
/**
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
*/
val type: KType, // exact type
val parameter: KParameter, // source parameter
) {
constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
name, type, parameter
) {
this._overrideParser = parser
}
@Suppress("PropertyName")
@JvmField
internal var _overrideParser: CommandValueArgumentParser<T>? = null
/**
* 覆盖的 [CommandValueArgumentParser].
*
* 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandValueArgumentParser]
*/
val overrideParser: CommandValueArgumentParser<T>? get() = _overrideParser
}

View File

@ -18,13 +18,6 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
this.forEachIndexed { index, any ->
if (list[index] != any) return false
}
return true
}
internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean { internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean {
val max = this.size.coerceAtMost(other.size) val max = this.size.coerceAtMost(other.size)
for (i in 0 until max) { for (i in 0 until max) {