[PATCH] D56611: [clangd] A code action to swap branches of an if statement

Sam McCall via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 18 07:59:22 PST 2019


sammccall added a comment.

Cool! Adopting this one as the simplest tweak for "how should tweaks do X" questions.

This depends on helpers not (yet) present in this patch, so not commenting on them now.

Need unit tests for tweaks. Something like this should work:

  Annotations Test(R"cpp(void foo() {
    [[if]] (1) { return; } else { continue; }
  })cpp");
  auto AST = TestTU(Test.code()).build();
  auto Sel = Tweak::Selection::create(Test.Code(), AST, Test.range());
  auto T = prepareTweak("SwapIfBranches", Sel);
  ASSERT_TRUE(T) << T.takeError();
  auto Edits = T.apply(Sel);
  ASSERT_TRUE(Edits) << Edits.takeError();
  auto After = applyAllReplacements(Test.code(), *Edits);
  EXPECT_EQ(After, R"cpp(void foo() {
    if (1) { continue; } else { return; }
  })cpp");

Probably want to wrap it up into a table driven test once we have a few.



================
Comment at: clangd/refactor/tweaks/SwapIfBranches.cpp:34
+/// After:
+///   if (foo) { continue; } else { return 10; }
+class SwapIfBranches : public Tweak {
----------------
The before/after is useful, we should probably have it for all tweaks if possible.
It'd also be useful to notate where the cursor can be to trigger the action. Partly because it forces us to consider this!

e.g. (not sure if this actually matches the logic you want, just an example)
```
Before:
  if (foo) { return 10; } else { continue; }
  ^^^^^^^^^^            ^^^^^^^^           ^
After:
  ...
```


================
Comment at: clangd/refactor/tweaks/SwapIfBranches.cpp:46
+private:
+  tooling::Replacements Result;
+};
----------------
I think prepare() should just verify:
 - cursor is in the right place
 - else statement exists and isn't an else if
 - both branches are compound statements (for now)
 - (maybe) relevant locations (if keyword, else keyword, braces) are not macro expansions
and then record the relevant source locations (or just the IfStmt*)

We may be able to get away with doing all the work in `prepare()`, but it's not a good model for future tweaks. (And it is at least somewhat wasteful on a hot path).


================
Comment at: clangd/refactor/tweaks/SwapIfBranches.cpp:50
+
+class FindIfUnderCursor : public RecursiveASTVisitor<FindIfUnderCursor> {
+public:
----------------
(Mostly this is food for thought for later - we shouldn't try to solve everything in this patch)

Two efficiency problems here:
 - doing one traversal per tweak is wasteful (when we have >1 tweaks, but worth at least *thinking* about)
 - traversing the entire AST rather than just the nodes over the cursor is bad news (though "over the cursor" may not be trivial to define)

Idea for how to "framework" this problem away:
Add `vector<DynTypedNode> SelectedNodes` to the inputs, the idea being that this is the stack from the narrowest `Expr` under the cursor to the `TranslationUnitDecl` at the top.
This can be produced by a RecursiveASTVisitor (that either traverses the whole AST, or just the bits under the cursor if that's simple enough).
Tweaks would iterate over the nodes from narrowest to broadest, deciding whether to select this node for processing, continue iterating, or bail out.

Matching in checks are then pretty easy to write, we haven't removed too much flexibility in flow control, and it's pretty hard to write a slow check.

There are some complications:
 - we need access to parents somehow (e.g. consider the case of adding NS qualifiers, we want to match on names but then traverse up to the containing declrefexpr to get the nested namespace loc)
 - the interesting nodes may be a tree rather than a stack, because nodes overlap in the source. We could store a postorder traversal... but this makes e.g. "bail out when you hit a functiondecl" harder - you probably want to bail out of the current branch only.
 - things get complicated when we think about macros - depending on the semantics we want, it may be hard for the framework to prune parts of the tree that aren't under the cursor withouth traversing the whole AST.


================
Comment at: clangd/refactor/tweaks/SwapIfBranches.cpp:84
+  // to avoid changing semantics of the code (i.e. dangling else).
+  if (!llvm::dyn_cast_or_null<CompoundStmt>(If->getThen()) ||
+      !llvm::dyn_cast_or_null<CompoundStmt>(If->getElse()))
----------------
isa<CompoundStmt>


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D56611/new/

https://reviews.llvm.org/D56611





More information about the cfe-commits mailing list