Repository navigation
[llvm] Add subcommand support for OptTable - #155026
Conversation
TODO: Add tests.
| HelloSubOptTable T; | ||
| unsigned MissingArgIndex, MissingArgCount; | ||
|
|
||
| auto HandleMultipleSubcommands = [](const ArrayRef<StringRef> SubCommands) { |
There was a problem hiding this comment.
const might not be needed since ArrayRef is already immutable I believe. Same with the other const ArrayRefs below.
| // The option CommandIDsOffset. | ||
| OS << ", "; | ||
| if (R.getValue("CommandGroup") != nullptr) { | ||
| std::vector<const Record *> CommandGroup = | ||
| R.getValueAsListOfDefs("CommandGroup"); | ||
| CommandKeyT CommandKey; | ||
| for (const auto &Command : CommandGroup) | ||
| CommandKey.push_back(Command->getName()); |
There was a problem hiding this comment.
Maybe move this into its own function since it's reused below.
| for (auto SC : SubCommands) { | ||
| llvm::errs() << " `" << SC << "`\n"; | ||
| } |
There was a problem hiding this comment.
Omit braces for single-line bodies: https://llvm.org/docs/CodingStandards.html#don-t-use-braces-on-simple-single-statement-bodies-of-if-else-loop-statements
Same for other bodies below
| OptTable(const StringTable &StrTable, | ||
| ArrayRef<StringTable::Offset> PrefixesTable, | ||
| ArrayRef<Info> OptionInfos, bool IgnoreCase = false); |
There was a problem hiding this comment.
Could probably just add
ArrayRef<Command> Commands = {},
ArrayRef<unsigned> CommandIDsTable = {},
so you don't need to make another ctor. Same with other OpTables below.
| } | ||
| if (SubCommands.size() == 1) | ||
| return SubCommands.front(); | ||
| return SubCommand; |
There was a problem hiding this comment.
I think SubCommand is still just {} here so maybe just return {}.
| std::function<void(ArrayRef<StringRef>)> HandleMultipleSubcommands, | ||
| std::function<void(ArrayRef<StringRef>)> HandleOtherPositionals) const; |
There was a problem hiding this comment.
Maybe add comments above briefly explaining what these callbacks are for. Mostly requesting since the implication to me (based on the example usage) is that a user might want to just throw an error and exit. If this is the primary/only desirable case, then that might be worth noting. I personally can't think of a reason one might not want to print help and exit, but maybe someone else might have a reason to. We could probably discourage that with comments or maybe saying the callback is NoReturn if we want.
| for (const auto &C : Commands) { | ||
| if (FirstArg == C.Name) { | ||
| ActiveCommand = &C; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
nit: Just a personal preference so feel free to ignore, but I think this looks cleaner
auto found = std::find_if(Commands.begin(), Commands.end(), [&](const auto &C){
return FirstArg == C.Name
});
ActiveCommand = found == Commands.end() ? nullptr : *found;
There was a problem hiding this comment.
Alternatively, it looks like you have getActiveCommand below so maybe you could reuse it here.
There was a problem hiding this comment.
Reusing getActiveCommand. Using find_if in getActiveCommand implementation.
Done. PTAL.
| // This loop prints subcommands list and sets ActiveCommand to | ||
| // TopLevelCommand while iterating over all commands. | ||
| for (const auto &C : Commands) { | ||
| if (C.Name == TopLevelCommandName) { |
There was a problem hiding this comment.
Coud probably just do C.Name == "TopLevelCommandName" if it's not changed elsewhere and stick it as a constexpr global in OptTable.h if it's used elsewhere.
There was a problem hiding this comment.
warning: result of comparison against a string literal is unspecified (use an explicit string comparison function instead) [-Wstring-compare]
804 | if (C.Name == "TopLevelCommand") {
| ^ ~~~~~~~~~~~~~~~~~
| assert((CurIndex == 0 || !Command.empty()) && | ||
| "Only first command set should be empty!"); | ||
| for (const auto &CommandKey : Command) { | ||
| auto It = llvm::find_if(Commands, [&](const Record *R) { |
There was a problem hiding this comment.
We can use std::find_if directly. I think llvm::find_if just calls it.
|
@PiJoules -- Thank you for the review! I've addressed all the comments from the last version you reviewed. PTAL. |
| class HelloSubOptTable : public GenericOptTable { | ||
| public: | ||
| HelloSubOptTable() | ||
| : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable, false, |
There was a problem hiding this comment.
https://llvm.org/docs/CodingStandards.html#comment-formatting
/*IgnoreCase=*/false
| if (Subcommand.empty()) { | ||
| if (Args.hasArg(OPT_version)) { | ||
| llvm::outs() << "LLVM Hello Subcommand Example 1.0\n"; | ||
| } |
There was a problem hiding this comment.
if (Args.hasArg(OPT_version))
llvm::outs() << "LLVM Hello Subcommand Example 1.0\n";
|
|
||
| size_t OldSize = SubCommands.size(); | ||
| for (const OptTable::Command &CMD : Commands) { | ||
| if (StringRef(CMD.Name) == "TopLevelCommand") |
There was a problem hiding this comment.
Maybe extract "TopLevelCommand" into a header and make it a global rather than using it as a literal between different cpp.
There was a problem hiding this comment.
Done. Thank you. It's added to OptTable.h file
| // Compile time representation for top level command (aka toolname). | ||
| // Offers backward compatibility with existing Option class definitions before | ||
| // introduction of commandGroup in Option class to support subcommands. | ||
| def TopLevelCommand : Command<"TopLevelCommand">; |
There was a problem hiding this comment.
What happens if a user would define a Subcommand with the name "TopLevelCommand"? I'm assuming there should only be one with this name assuming it's like a sentinel. If only one should be defined, are there any checks that would assert this or diagnose if more than one was provided?
There was a problem hiding this comment.
That's a good point. I'll add the check. Maybe it's worth changing the name to something that is probably not going to be useful as a user defined subcommand name. "TopLevelCommand" -> "TOPLEVELCOMMAND". Other option could be a "Command" type object with empty string for name to depict the top level command. I can make sure all the subcommands have a non empty string name.
There was a problem hiding this comment.
Ok I did a refactoring which changes the design a bit. There is no abstraction for "TopLevelCommand" anymore which I always felt weird about maintaining. Now we only have subcommands that can be registered by the user. All regression tests pass and my llvm-hello-sub example behaves correctly. PTAL. I am working on adding more tests to cover subcommand use cases.
| return internalParseOneArg(Args, Index, [VisibilityMask](const Option &Opt) { | ||
| return !Opt.hasVisibilityFlag(VisibilityMask); | ||
| }); | ||
| return internalParseOneArg(Args, Index, nullptr, |
There was a problem hiding this comment.
/*ActiveCommand=*/nullptr
| unsigned FlagsToExclude) const { | ||
| return internalParseOneArg( | ||
| Args, Index, [FlagsToInclude, FlagsToExclude](const Option &Opt) { | ||
| Args, Index, nullptr, |
| FlagsToExclude &= ~HelpHidden; | ||
| return internalPrintHelp( | ||
| OS, Usage, Title, ShowHidden, ShowAllAliases, | ||
| OS, Usage, Title, {}, ShowHidden, ShowAllAliases, |
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
| StringRef SubCommand) const { | ||
| assert(!SubCommand.empty() && | ||
| "This helper is only for valid registered subcommands."); | ||
| typename ArrayRef<OptTable::SubCommand>::iterator SCIT = |
There was a problem hiding this comment.
Can probably save a line and just use auto here since it's probably well known that std::find* functions return an iterator.
There was a problem hiding this comment.
Done. But it didn't save a line unfortunately :)
| /// Return the string table used for option names. | ||
| const StringTable &getStrTable() const { return *StrTable; } | ||
|
|
||
| const ArrayRef<SubCommand> getSubCommands() const { return SubCommands; } |
There was a problem hiding this comment.
ArrayRef is already immutable so const might not be needed here.
| // pairs. | ||
| std::map<std::string, std::vector<OptionInfo>> GroupedOptionHelp; | ||
|
|
||
| typename ArrayRef<OptTable::SubCommand>::iterator ActiveSubCommand = |
There was a problem hiding this comment.
Can probably use auto here also
Thank you @PiJoules I'll wait for a day to see if there are any objections before landing. |
this is for unblocking eld upstream : llvm/llvm-project#155026 Signed-off-by: Shankar Easwaran <seaswara@quicinc.com>
this is for unblocking eld upstream : llvm/llvm-project#155026 Signed-off-by: Shankar Easwaran <seaswara@quicinc.com>
There was a problem hiding this comment.
Rather than having a directory in examples dedicated to just subcommands, I think we should a directory for Option and subcommands should be just one of the examples in there.
There was a problem hiding this comment.
I'll start a new PR for that change.
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/23/builds/14465 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/168/builds/16524 Here is the relevant piece of the build log for the reference |
## What changed Upgrades the prebuilt LLVM dependency from 21.1.8 to 22.1.8 and adapts clice to the LLVM 22 API. Highlights: - **NNS redesign / ElaboratedType removal** (llvm/llvm-project#147835): `NestedNameSpecifier` is a value type and types carry their elaborated keyword and qualifier themselves. Rewrote NNS handling in the template resolver, semantic visitor, unifier, display, and USR generation; `rewrite_specifier` collapses to a single type-component rewrite since prefixes now live inside type nodes. - **DependentTemplateSpecializationType removed** (llvm/llvm-project#158109): dependent specializations are `TemplateSpecializationType`s with a `DependentTemplateName`. The resolver's DTST lookup/rewrite/pseudo-SFINAE paths merged into the TST paths. - **CompilerInstance owns its VFS** (llvm/llvm-project#158381): compilation and scan now seed the instance VFS before creating diagnostics and the file manager. - **clangOptions split** (llvm/llvm-project#167374) and the new `SUBCOMMANDIDS_OFFSET` OPTION column (llvm/llvm-project#155026): include-path and macro updates in the argument parser. - Assorted renames: `getCanonicalTagType`, `getCanonicalTemplateSpecializationType`, `UsingType::getDecl`, `sys::path::make_absolute`, `clang::GetResourcesPath`. Behavioral changes visible in features (all matching clang/clangd 22 rendering, pinned in snapshots): `__size_t (aka unsigned long)` sugar, `(unnamed enum)` naming, namespace-qualified canonical class types in hover, and converted (qualified) template arguments in hover titles for implicit variable template specializations. One deliberate divergence from clangd: inlay type hints keep written class scopes (`S2::Nested<int>`) that LLVM 22's `SuppressScope` would now drop — restored by printing the outer node's written qualifier. Also cherry-picks the release-triple normalization + prebuilt-respin fixes (`/MT`, macOS deployment target) that the 22.1.8 prebuilt was built with, and adds an `LLVM 21 → 22` section to the LLVM changelog documenting every breaking change with upstream references. Note: CI stays red until the pruned 22.1.8 `clice-llvm` release is published (release-llvm is running against this branch); the version pin in `cmake/package.cmake` will be bumped in a follow-up commit on this branch once it is up. ## Tests All four suites pass locally against the 22.1.8 prebuilt: unit (1174), integration (341), smoke (3/3), snap (393). Snapshot updates are limited to the upstream rendering changes listed above; one selection-tree unit test was re-marked because dependent qualifier chains are now `DependentNameTypeLoc` components with name-only ranges.
Implement support for
subcommandsin OptTable to attain feature parity withcl.Design overview: https://discourse.llvm.org/t/subcommand-feature-support-in-llvm-opttable/88098
Issue: #108307