diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt index 578cb9630..a73e2f842 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.command 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.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME @@ -52,7 +52,7 @@ public interface Command { */ @ConsoleExperimentalApi("Property name is experimental") @ExperimentalCommandDescriptors - public val overloads: List + public val overloads: List /** * 用法说明, 用于发送给用户. [usage] 一般包含 [description]. diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt index a7582efc7..efec2b5d1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt @@ -95,8 +95,10 @@ public abstract class CompositeCommand( private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors - public final override val overloads: List by lazy { - reflector.findSubCommands() + public final override val overloads: List by lazy { + reflector.findSubCommands().also { + reflector.validate(it) + } } /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt index 56bb6a968..4b17e51e2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt @@ -56,8 +56,8 @@ public abstract class RawCommand( public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) } @ExperimentalCommandDescriptors - override val overloads: List = listOf( - CommandSignatureVariantImpl( + override val overloads: List = listOf( + CommandSignatureImpl( receiverParameter = CommandReceiverParameter(false, typeOf0()), valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired>("args", true)) ) { call -> diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 14861f717..48ce42f7c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -17,7 +17,6 @@ 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.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext @@ -67,8 +66,9 @@ public abstract class SimpleCommand( private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors - public final override val overloads: List by lazy { + public final override val overloads: List by lazy { reflector.findSubCommands().also { + reflector.validate(it) if (it.isEmpty()) throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.") } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt deleted file mode 100644 index b0bf45647..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt index 556a44e6c..b83e6907a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandDescriptor.kt @@ -26,26 +26,46 @@ import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf /** - * @see CommandSignatureVariantImpl + * 指令签名. 表示指令定义的需要的参数. + * + * @see AbstractCommandSignature */ @ExperimentalCommandDescriptors -public interface CommandSignatureVariant { +public interface CommandSignature { + /** + * 接收者参数, 为 [CommandSender] 子类 + */ @ConsoleExperimentalApi public val receiverParameter: CommandReceiverParameter? + /** + * 形式 值参数. + */ public val valueParameters: List> + /** + * 调用这个指令. + */ public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } +/** + * 来自 [KFunction] 反射得到的 [CommandSignature] + * + * @see CommandSignatureFromKFunctionImpl + */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors -public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant { +public interface CommandSignatureFromKFunction : CommandSignature { public val originFunction: KFunction<*> } +/** + * @see CommandSignatureImpl + * @see CommandSignatureFromKFunctionImpl + */ @ExperimentalCommandDescriptors -public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant { +public abstract class AbstractCommandSignature : CommandSignature { override fun toString(): String { val receiverParameter = receiverParameter return if (receiverParameter == null) { @@ -57,11 +77,11 @@ public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant } @ExperimentalCommandDescriptors -public open class CommandSignatureVariantImpl( +public open class CommandSignatureImpl( override val receiverParameter: CommandReceiverParameter?, override val valueParameters: List>, - private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariant, AbstractCommandSignatureVariant() { + private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignature, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } @@ -69,12 +89,12 @@ public open class CommandSignatureVariantImpl( @ConsoleExperimentalApi @ExperimentalCommandDescriptors -public open class CommandSignatureVariantFromKFunctionImpl( +public open class CommandSignatureFromKFunctionImpl( override val receiverParameter: CommandReceiverParameter?, override val valueParameters: List>, override val originFunction: KFunction<*>, - private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, -) : CommandSignatureVariantFromKFunction, AbstractCommandSignatureVariant() { + private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, +) : CommandSignatureFromKFunction, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt index 30d167657..b92ab8b68 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/Exceptions.kt @@ -11,7 +11,9 @@ 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.internal.data.classifierAsKClassOrNull 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}") @ExperimentalCommandDescriptors -public open class UnresolvedCommandCallException( - public val call: CommandCall, -) : CommandResolutionException("Unresolved call: $call") +public open class CommandDeclarationClashException( + public val command: Command, + public val signatures: List, +) : CommandResolutionException("Command declaration clash: \n${signatures.joinToString("\n")}") public open class CommandResolutionException : RuntimeException { public constructor() : super() @@ -38,3 +41,19 @@ public open class CommandResolutionException : RuntimeException { public constructor(message: String?, cause: Throwable?) : super(message, 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) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt index 5869352f6..97774d55c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/BuiltInCommandCallResolver.kt @@ -31,13 +31,13 @@ public object BuiltInCommandCallResolver : CommandCallResolver { return ResolvedCommandCallImpl(call.caller, callee, - signature.variant, + signature.signature, signature.zippedArguments.map { it.second }, context ?: EmptyCommandArgumentContext) } private data class ResolveData( - val variant: CommandSignatureVariant, + val signature: CommandSignature, val zippedArguments: List, CommandValueArgument>>, val argumentAcceptances: List, val remainingParameters: List>, @@ -69,7 +69,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { if (zipped.isEmpty()) { ResolveData( - variant = signature, + signature = signature, zippedArguments = emptyList(), argumentAcceptances = emptyList(), remainingParameters = remainingParameters, @@ -91,7 +91,7 @@ public object BuiltInCommandCallResolver : CommandCallResolver { } ResolveData( - variant = signature, + signature = signature, zippedArguments = zipped, argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> val accepting = parameter.accepting(argument, context) @@ -163,7 +163,7 @@ fun main() { private fun List.takeLongestMatches(): Collection { if (isEmpty()) return emptyList() 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 -> val maxMatch = m.values.maxByOrNull { it } m.filter { it.value == maxMatch }.keys diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt index 7b9a2db3b..5b48c74b9 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/resolve/ResolvedCommandCall.kt @@ -37,9 +37,9 @@ public interface ResolvedCommandCall { 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 @@ -47,7 +47,7 @@ public interface ResolvedCommandCall { public val rawValueArguments: List /** - * Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index. + * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index. * * **Implementation details**: Lazy calculation. */ @@ -73,7 +73,7 @@ public suspend inline fun ResolvedCommandCall.call() { public class ResolvedCommandCallImpl( override val caller: CommandSender, override val callee: Command, - override val calleeSignature: CommandSignatureVariant, + override val calleeSignature: CommandSignature, override val rawValueArguments: List, private val context: CommandArgumentContext, ) : ResolvedCommandCall { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt index ebba4b153..a85a7a8f8 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandReflector.kt @@ -11,6 +11,7 @@ import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.buildMessageChain import kotlin.reflect.KFunction import kotlin.reflect.KParameter +import kotlin.reflect.KType import kotlin.reflect.KVisibility import kotlin.reflect.full.* @@ -133,7 +134,7 @@ internal class CommandReflector( // if (isAbstract) illegalDeclaration("Command function cannot be abstract") } - fun generateUsage(overloads: Iterable): String { + fun generateUsage(overloads: Iterable): String { return overloads.joinToString("\n") { subcommand -> buildString { if (command.prefixOptional) { @@ -173,17 +174,45 @@ internal class CommandReflector( } } - fun validate(variants: List) { + fun validate(signatures: List) { - data class ErasedParameters( - val name: String, - val x: String, + data class ErasedParameterInfo( + val index: Int, + val name: String?, + val type: KType, // ignore nullability + val additional: String?, ) - variants + + data class ErasedVariantInfo( + val receiver: ErasedParameterInfo?, + val valueParameters: List, + ) + + 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) - fun findSubCommands(): List { + fun findSubCommands(): List { return command::class.functions // exclude static later .asSequence() .filter { it.isSubCommandFunction() } @@ -198,7 +227,7 @@ internal class CommandReflector( val functionValueParameters = function.valueParameters.associateBy { it.toUserDefinedCommandParameter() } - CommandSignatureVariantFromKFunctionImpl( + CommandSignatureFromKFunctionImpl( receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(), valueParameters = functionNameAsValueParameter + functionValueParameters.keys, originFunction = function diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt deleted file mode 100644 index 8279d9f1c..000000000 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommand.CommandParam.kt +++ /dev/null @@ -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( - /** - * 参数名. 不允许重复. - */ - 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) : this( - name, type, parameter - ) { - this._overrideParser = parser - } - - @Suppress("PropertyName") - @JvmField - internal var _overrideParser: CommandValueArgumentParser? = null - - - /** - * 覆盖的 [CommandValueArgumentParser]. - * - * 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandValueArgumentParser] - */ - val overrideParser: CommandValueArgumentParser? get() = _overrideParser -} - diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt index ac83357ac..a1ebfbf2e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/internal.kt @@ -18,13 +18,6 @@ import kotlin.math.max import kotlin.math.min -internal infix fun Array.matchesBeginning(list: List): Boolean { - this.forEachIndexed { index, any -> - if (list[index] != any) return false - } - return true -} - internal infix fun Array.intersectsIgnoringCase(other: Array): Boolean { val max = this.size.coerceAtMost(other.size) for (i in 0 until max) {