This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Expose SelectionTree to code tweaks, and use it for swap if branches.
ClosedPublic

Authored by sammccall on Feb 1 2019, 1:27 AM.

Details

Summary

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).

Diff Detail

Repository
rL LLVM

Event Timeline

sammccall created this revision.Feb 1 2019, 1:27 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2019, 1:27 AM

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?
There's too much noise with those getters currently.

unittests/clangd/TweakTests.cpp
56 ↗(On Diff #184690)

NIT: split into two declarations?

96 ↗(On Diff #184690)

NIT: split into two declaration?

sammccall marked 3 inline comments as done.Feb 1 2019, 2:40 AM

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?

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.
I forgot *some* of these can be default-constructed.

unittests/clangd/TweakTests.cpp
56 ↗(On Diff #184690)

(clearly we should chat about the two-decls thing :-) on the other review?)

sammccall updated this revision to Diff 184707.Feb 1 2019, 2:41 AM
sammccall marked an inline comment as done.

Simplify Selection constructor.
Don't traverse up through compoundstmt.
Add more tests.

ilya-biryukov marked an inline comment as done.Feb 1 2019, 2:50 AM

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.

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 :-)

ilya-biryukov added inline comments.Feb 1 2019, 2:52 AM
clangd/refactor/tweaks/SwapIfBranches.cpp
60 ↗(On Diff #184707)

NIT: drop llvm:: for consistency.

ilya-biryukov accepted this revision.Feb 1 2019, 5:56 AM

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.

This revision is now accepted and ready to land.Feb 1 2019, 5:56 AM
sammccall updated this revision to Diff 184734.Feb 1 2019, 5:56 AM
sammccall marked 3 inline comments as done.

Split declarations, add test for two-if-statements.

This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2019, 7:09 AM

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.

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.

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.