[llvm] [CommandLine] Better report unknown subcommands (PR #74811)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Dec 7 22:32:34 PST 2023
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-support
Author: Igor Kudrin (igorkudrin)
<details>
<summary>Changes</summary>
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'?
```
---
Full diff: https://github.com/llvm/llvm-project/pull/74811.diff
2 Files Affected:
- (modified) llvm/lib/Support/CommandLine.cpp (+33-12)
- (modified) llvm/unittests/Support/CommandLineTest.cpp (+21)
``````````diff
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/74811
More information about the llvm-commits
mailing list