[llvm] [CommandLine] Better report unknown subcommands (PR #74811)
Igor Kudrin via llvm-commits
llvm-commits at lists.llvm.org
Thu Dec 7 22:32:20 PST 2023
https://github.com/igorkudrin created https://github.com/llvm/llvm-project/pull/74811
The patch improves the reporting for the first option in the command line when it looks like a subcommand name but does not match any defined.
Before the patch:
```
> prog baz
prog: Unknown command line argument 'baz'. Try: 'prog --help'
```
With the patch:
```
> prog baz
prog: Unknown subcommand 'baz'. Try: 'prog --help'
prog: Did you mean 'bar'?
```
>From 4ede8496c0b953fec04e7861eaa69ae6e8236aad Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Thu, 7 Dec 2023 22:27:37 -0800
Subject: [PATCH] [CommandLine] Better report unknown subcommands
The patch improves the reporting for the first option in the command
line when it looks like a subcommand name but does not match any
defined.
Before the patch:
```
> prog baz
prog: Unknown command line argument 'baz'. Try: 'prog --help'
```
With the patch:
```
> prog baz
prog: Unknown subcommand 'baz'. Try: 'prog --help'
prog: Did you mean 'bar'?
```
---
llvm/lib/Support/CommandLine.cpp | 45 ++++++++++++++++------
llvm/unittests/Support/CommandLineTest.cpp | 21 ++++++++++
2 files changed, 54 insertions(+), 12 deletions(-)
diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp
index a7e0cae8b855d..b76c2cc8be6da 100644
--- a/llvm/lib/Support/CommandLine.cpp
+++ b/llvm/lib/Support/CommandLine.cpp
@@ -425,7 +425,7 @@ class CommandLineParser {
return nullptr;
return Opt;
}
- SubCommand *LookupSubCommand(StringRef Name);
+ SubCommand *LookupSubCommand(StringRef Name, std::string &NearestString);
};
} // namespace
@@ -550,9 +550,13 @@ Option *CommandLineParser::LookupOption(SubCommand &Sub, StringRef &Arg,
return I->second;
}
-SubCommand *CommandLineParser::LookupSubCommand(StringRef Name) {
+SubCommand *CommandLineParser::LookupSubCommand(StringRef Name,
+ std::string &NearestString) {
if (Name.empty())
return &SubCommand::getTopLevel();
+ // Find the closest match if there is no subcommand named 'Name'.
+ SubCommand *BestMatch = nullptr;
+ unsigned BestDistance = 0;
for (auto *S : RegisteredSubCommands) {
if (S == &SubCommand::getAll())
continue;
@@ -561,7 +565,19 @@ SubCommand *CommandLineParser::LookupSubCommand(StringRef Name) {
if (StringRef(S->getName()) == StringRef(Name))
return S;
+
+ unsigned Distance =
+ Name.edit_distance(S->getName(), /*AllowReplacements=*/true,
+ /*MaxEditDistance=*/BestDistance);
+ if (!BestMatch || Distance < BestDistance) {
+ BestMatch = S;
+ BestDistance = Distance;
+ }
}
+
+ if (BestMatch)
+ NearestString = BestMatch->getName();
+
return &SubCommand::getTopLevel();
}
@@ -1527,10 +1543,12 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
int FirstArg = 1;
SubCommand *ChosenSubCommand = &SubCommand::getTopLevel();
+ std::string NearestSubCommandString;
if (argc >= 2 && argv[FirstArg][0] != '-') {
// If the first argument specifies a valid subcommand, start processing
// options from the second argument.
- ChosenSubCommand = LookupSubCommand(StringRef(argv[FirstArg]));
+ ChosenSubCommand =
+ LookupSubCommand(StringRef(argv[FirstArg]), NearestSubCommandString);
if (ChosenSubCommand != &SubCommand::getTopLevel())
FirstArg = 2;
}
@@ -1687,21 +1705,24 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
}
if (!Handler) {
- if (SinkOpts.empty()) {
+ if (!SinkOpts.empty()) {
+ for (Option *SinkOpt : SinkOpts)
+ SinkOpt->addOccurrence(i, "", StringRef(argv[i]));
+ continue;
+ } else if (i == 1 && !NearestSubCommandString.empty()) {
+ *Errs << ProgramName << ": Unknown subcommand '" << argv[i]
+ << "'. Try: '" << argv[0] << " --help'\n"
+ << ProgramName << ": Did you mean '" << NearestSubCommandString
+ << "'?\n";
+ } else {
*Errs << ProgramName << ": Unknown command line argument '" << argv[i]
<< "'. Try: '" << argv[0] << " --help'\n";
-
- if (NearestHandler) {
+ if (NearestHandler)
// If we know a near match, report it as well.
*Errs << ProgramName << ": Did you mean '"
<< PrintArg(NearestHandlerString, 0) << "'?\n";
- }
-
- ErrorParsing = true;
- } else {
- for (Option *SinkOpt : SinkOpts)
- SinkOpt->addOccurrence(i, "", StringRef(argv[i]));
}
+ ErrorParsing = true;
continue;
}
diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp
index 381fe70b6b481..4ad20cf5febdf 100644
--- a/llvm/unittests/Support/CommandLineTest.cpp
+++ b/llvm/unittests/Support/CommandLineTest.cpp
@@ -2206,4 +2206,25 @@ TEST(CommandLineTest, DefaultValue) {
EXPECT_EQ(1, StrInitOption.getNumOccurrences());
}
+TEST(CommandLineTest, UnknownSubcommand) {
+ cl::ResetCommandLineParser();
+
+ StackSubCommand SC1("foo", "Foo subcommand");
+ StackSubCommand SC2("bar", "Bar subcommand");
+ StackOption<bool> SC1Opt("f", cl::sub(SC1));
+ StackOption<bool> SC2Opt("b", cl::sub(SC2));
+ StackOption<bool> TopOpt("t");
+
+ const char *Args[] = {"prog", "baz", "-p"};
+ std::string Errs;
+ raw_string_ostream OS(Errs);
+ EXPECT_FALSE(
+ cl::ParseCommandLineOptions(std::size(Args), Args, StringRef(), &OS));
+ EXPECT_EQ(Errs,
+ "prog: Unknown subcommand 'baz'. Try: 'prog --help'\n"
+ "prog: Did you mean 'bar'?\n"
+ "prog: Unknown command line argument '-p'. Try: 'prog --help'\n"
+ "prog: Did you mean '-t'?\n");
+}
+
} // anonymous namespace
More information about the llvm-commits
mailing list