This reduces the per-check implementation burden and redundant work.
It also makes checks range-aware by default (treating the commonAncestor
as if it were a point selection should be good baseline behavior).
Details
- Reviewers
ilya-biryukov - Commits
- rG9c8f432617ff: [clangd] Expose SelectionTree to code tweaks, and use it for swap if branches.
rCTE352876: [clangd] Expose SelectionTree to code tweaks, and use it for swap if branches.
rL352876: [clangd] Expose SelectionTree to code tweaks, and use it for swap if branches.
Diff Detail
- Repository
- rL LLVM
Event Timeline
No real urgency here (though it may simplify the pending tweaks) - mostly this is motivation for D57562.
Preserving the ability to fire on the condition and its subexpressions, but not the if/else branches, is pretty arbitrary and mostly serves to demontrate a nontrivial traversal.
The selection-matching code is shorter but more importantly I think it's a bit less error-prone - it should be harder to introduce off-by-one errors and index miscalculations without SourceLocations everywhere.
I wonder if this makes the check available in too many places?
void test() { if (callFunc([]() { for (int i = 0; i < 100; ++i) { /* compute something */; } // action also available here... }) { } else {} }
This look a bit artificial for this check as conditions are rarely large, but could be more significant for other checks, where it could be more common to have declarations of classes, functions and lambdas lambdas recursively inside other declarations.
Maybe we should consider stopping the traversals up at some points, e.g. at lambda decls or something similar to limit this class of problems in the future?
clangd/refactor/Tweak.cpp | ||
---|---|---|
43 ↗ | (On Diff #184690) | NIT: maybe move the code into the ctor body and save AST.getASTContext() and getSourceManager() into local vars for readability? |
unittests/clangd/TweakTests.cpp | ||
56 ↗ | (On Diff #184690) | NIT: split into two declarations? |
96 ↗ | (On Diff #184690) | NIT: split into two declaration? |
Heh, this actually makes it easier to implement - we can break at a compoundstmt, and since we require both if and then clauses to be braced, this takes care of only alllowing subexpressions of cond.
Maybe we should consider stopping the traversals up at some points, e.g. at lambda decls or something similar to limit this class of problems in the future?
If you mean in some frameworky sort of way... yeah, it's quite likely we need something. At least to make the Node* -> DynTypedNode -> Stmt -> IfStmt dance less clunky.
I've deliberately avoided adding too much in the way of helpers for now in favor of the raw data structures, mostly wanting to see what the common patterns are - we can afford to write a few of these by hand.
clangd/refactor/Tweak.cpp | ||
---|---|---|
43 ↗ | (On Diff #184690) | Whoops, much better. |
unittests/clangd/TweakTests.cpp | ||
56 ↗ | (On Diff #184690) | (clearly we should chat about the two-decls thing :-) on the other review?) |
Simplify Selection constructor.
Don't traverse up through compoundstmt.
Add more tests.
Exactly, the code dealing with typed nodes there is definitely too verbose. Thinking about helpers like findAncestor<IfStmt>().
And yeah, totally fine to keep it as is for now, it's great that the change is focused.
Another concern that came to mind: what should we do if two if statements are selected? IIUC the commonAncestor would be their parent and we don't have the action available, right? This LG, the alternatives are showing multiple tweaks or just one of them and inevitably confusing the users in some cases (either applying the tweak to the other than expected if statement or showing two tweaks with identical descriptions and no ways to distinguish between those).
Could we add a test for this case?
unittests/clangd/TweakTests.cpp | ||
---|---|---|
56 ↗ | (On Diff #184690) | Done :-) |
clangd/refactor/tweaks/SwapIfBranches.cpp | ||
---|---|---|
60 ↗ | (On Diff #184707) | NIT: drop llvm:: for consistency. |
LGTM.
See the NIT about llvm::, though. Also adding a test with multiple selected ifs would be reasonable, to document and validate behaviour in such cases.
e.g. findAncestor is nice but here we want to walk up but break if we hit a limit node (e.g. compoundstmt).
If that turns out to be the normal case, then a plain findAncestor isn't such a useful primitive.
Some other thoughts:
- In a draft I did have a tree traversal where the node visitor could return {continue, abort, prune} - seemed cool but I didn't actually have a use for it
- just some direct casting operators (Node->as<IfStmt>()) would help, probably.
Another concern that came to mind: what should we do if two if statements are selected? IIUC the commonAncestor would be their parent and we don't have the action available, right?
Correct. Ranges are confusing, I think we need to mostly treat them as "a target to perform an action on" rather than "a selection of targets to perform actions on". Maybe some exceptions like maybe bulk actions on includes or member function decls, but the simple model should work for most tweaks.
Could we add a test for this case?
Done.