[clang] cf42877 - [libTooling] Add assorted `EditGenerator` combinators.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 24 06:14:45 PDT 2020
Author: Yitzhak Mandelbaum
Date: 2020-07-24T12:51:54Z
New Revision: cf428778128fed5eacee884964af53bf4a9f74f2
URL: https://github.com/llvm/llvm-project/commit/cf428778128fed5eacee884964af53bf4a9f74f2
DIFF: https://github.com/llvm/llvm-project/commit/cf428778128fed5eacee884964af53bf4a9f74f2.diff
LOG: [libTooling] Add assorted `EditGenerator` combinators.
Summary:
This patch adds various combinators that help in constructing `EditGenerator`s:
* `noEdits`
* `ifBound`, specialized to `ASTEdit`
* `flatten` and `flattenVector` which allow for easy construction from a set
of sub edits.
* `shrinkTo`, which generates edits to shrink a given range to another that
it encloses.
Reviewers: asoffer, gribozavr2
Subscribers: cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D84310
Added:
Modified:
clang/include/clang/Tooling/Transformer/MatchConsumer.h
clang/include/clang/Tooling/Transformer/RewriteRule.h
clang/lib/Tooling/Transformer/RewriteRule.cpp
clang/unittests/Tooling/TransformerTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Tooling/Transformer/MatchConsumer.h b/clang/include/clang/Tooling/Transformer/MatchConsumer.h
index f407ffce3d25..cb0a5f684b7d 100644
--- a/clang/include/clang/Tooling/Transformer/MatchConsumer.h
+++ b/clang/include/clang/Tooling/Transformer/MatchConsumer.h
@@ -99,11 +99,5 @@ llvm::Expected<T> MatchComputation<T>::eval(
return Output;
}
} // namespace transformer
-
-namespace tooling {
-// DEPRECATED: Temporary alias supporting client migration to the `transformer`
-// namespace.
-using transformer::ifBound;
-} // namespace tooling
} // namespace clang
#endif // LLVM_CLANG_TOOLING_TRANSFORMER_MATCH_CONSUMER_H_
diff --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h
index 1be572736460..c22a2da81fe6 100644
--- a/clang/include/clang/Tooling/Transformer/RewriteRule.h
+++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h
@@ -107,9 +107,42 @@ struct ASTEdit {
/// clients. We recommend use of the \c AtomicChange or \c Replacements classes
/// for assistance in detecting such conflicts.
EditGenerator editList(llvm::SmallVector<ASTEdit, 1> Edits);
-// Convenience form of `editList` for a single edit.
+/// Convenience form of `editList` for a single edit.
EditGenerator edit(ASTEdit);
+/// Convenience generator for a no-op edit generator.
+inline EditGenerator noEdits() { return editList({}); }
+
+/// Convenience version of `ifBound` specialized to `ASTEdit`.
+inline EditGenerator ifBound(std::string ID, ASTEdit TrueEdit,
+ ASTEdit FalseEdit) {
+ return ifBound(std::move(ID), edit(std::move(TrueEdit)),
+ edit(std::move(FalseEdit)));
+}
+
+/// Convenience version of `ifBound` that has no "False" branch. If the node is
+/// not bound, then no edits are produced.
+inline EditGenerator ifBound(std::string ID, ASTEdit TrueEdit) {
+ return ifBound(std::move(ID), edit(std::move(TrueEdit)), noEdits());
+}
+
+/// Flattens a list of generators into a single generator whose elements are the
+/// concatenation of the results of the argument generators.
+EditGenerator flattenVector(SmallVector<EditGenerator, 2> Generators);
+
+namespace detail {
+/// Convenience function to construct an \c EditGenerator. Overloaded for common
+/// cases so that user doesn't need to specify which factory function to
+/// use. This pattern gives benefits similar to implicit constructors, while
+/// maintaing a higher degree of explicitness.
+inline EditGenerator injectEdits(ASTEdit E) { return edit(std::move(E)); }
+inline EditGenerator injectEdits(EditGenerator G) { return G; }
+} // namespace detail
+
+template <typename... Ts> EditGenerator flatten(Ts &&...Edits) {
+ return flattenVector({detail::injectEdits(std::forward<Ts>(Edits))...});
+}
+
/// Format of the path in an include directive -- angle brackets or quotes.
enum class IncludeFormat {
Quoted,
@@ -291,6 +324,14 @@ inline ASTEdit withMetadata(ASTEdit Edit, Callable Metadata) {
return Edit;
}
+/// Assuming that the inner range is enclosed by the outer range, creates
+/// precision edits to remove the parts of the outer range that are not included
+/// in the inner range.
+inline EditGenerator shrinkTo(RangeSelector outer, RangeSelector inner) {
+ return editList({remove(enclose(before(outer), before(inner))),
+ remove(enclose(after(inner), after(outer)))});
+}
+
/// The following three functions are a low-level part of the RewriteRule
/// API. We expose them for use in implementing the fixtures that interpret
/// RewriteRule, like Transformer and TransfomerTidy, or for more advanced
diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp
index a212a868c81d..c145895af7ab 100644
--- a/clang/lib/Tooling/Transformer/RewriteRule.cpp
+++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp
@@ -68,6 +68,24 @@ EditGenerator transformer::edit(ASTEdit Edit) {
};
}
+EditGenerator
+transformer::flattenVector(SmallVector<EditGenerator, 2> Generators) {
+ if (Generators.size() == 1)
+ return std::move(Generators[0]);
+ return
+ [Gs = std::move(Generators)](
+ const MatchResult &Result) -> llvm::Expected<SmallVector<Edit, 1>> {
+ SmallVector<Edit, 1> AllEdits;
+ for (const auto &G : Gs) {
+ llvm::Expected<SmallVector<Edit, 1>> Edits = G(Result);
+ if (!Edits)
+ return Edits.takeError();
+ AllEdits.append(Edits->begin(), Edits->end());
+ }
+ return AllEdits;
+ };
+}
+
ASTEdit transformer::changeTo(RangeSelector Target, TextGenerator Replacement) {
ASTEdit E;
E.TargetRange = std::move(Target);
diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp
index 59b334b0ea5a..1a68eb1d172a 100644
--- a/clang/unittests/Tooling/TransformerTest.cpp
+++ b/clang/unittests/Tooling/TransformerTest.cpp
@@ -10,6 +10,7 @@
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/Transformer/RangeSelector.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/Stencil.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
@@ -378,6 +379,41 @@ TEST_F(TransformerTest, NodePartMemberMultiToken) {
Input, Expected);
}
+TEST_F(TransformerTest, NoEdits) {
+ using transformer::noEdits;
+ std::string Input = "int f(int x) { return x; }";
+ testRule(makeRule(returnStmt().bind("return"), noEdits()), Input, Input);
+}
+
+TEST_F(TransformerTest, IfBound2Args) {
+ using transformer::ifBound;
+ std::string Input = "int f(int x) { return x; }";
+ std::string Expected = "int f(int x) { CHANGE; }";
+ testRule(makeRule(returnStmt().bind("return"),
+ ifBound("return", changeTo(cat("CHANGE;")))),
+ Input, Expected);
+}
+
+TEST_F(TransformerTest, IfBound3Args) {
+ using transformer::ifBound;
+ std::string Input = "int f(int x) { return x; }";
+ std::string Expected = "int f(int x) { CHANGE; }";
+ testRule(makeRule(returnStmt().bind("return"),
+ ifBound("nothing", changeTo(cat("ERROR")),
+ changeTo(cat("CHANGE;")))),
+ Input, Expected);
+}
+
+TEST_F(TransformerTest, ShrinkTo) {
+ using transformer::shrinkTo;
+ std::string Input = "int f(int x) { return x; }";
+ std::string Expected = "return x;";
+ testRule(makeRule(functionDecl(hasDescendant(returnStmt().bind("return")))
+ .bind("function"),
+ shrinkTo(node("function"), node("return"))),
+ Input, Expected);
+}
+
TEST_F(TransformerTest, InsertBeforeEdit) {
std::string Input = R"cc(
int f() {
@@ -497,6 +533,90 @@ TEST_F(TransformerTest, MultiChange) {
Input, Expected);
}
+TEST_F(TransformerTest, EditList) {
+ using clang::transformer::editList;
+ std::string Input = R"cc(
+ void foo() {
+ if (10 > 1.0)
+ log(1) << "oh no!";
+ else
+ log(0) << "ok";
+ }
+ )cc";
+ std::string Expected = R"(
+ void foo() {
+ if (true) { /* then */ }
+ else { /* else */ }
+ }
+ )";
+
+ StringRef C = "C", T = "T", E = "E";
+ testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
+ hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
+ editList({changeTo(node(std::string(C)), cat("true")),
+ changeTo(statement(std::string(T)),
+ cat("{ /* then */ }")),
+ changeTo(statement(std::string(E)),
+ cat("{ /* else */ }"))})),
+ Input, Expected);
+}
+
+TEST_F(TransformerTest, Flatten) {
+ using clang::transformer::editList;
+ std::string Input = R"cc(
+ void foo() {
+ if (10 > 1.0)
+ log(1) << "oh no!";
+ else
+ log(0) << "ok";
+ }
+ )cc";
+ std::string Expected = R"(
+ void foo() {
+ if (true) { /* then */ }
+ else { /* else */ }
+ }
+ )";
+
+ StringRef C = "C", T = "T", E = "E";
+ testRule(
+ makeRule(
+ ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)),
+ hasElse(stmt().bind(E))),
+ flatten(changeTo(node(std::string(C)), cat("true")),
+ changeTo(statement(std::string(T)), cat("{ /* then */ }")),
+ changeTo(statement(std::string(E)), cat("{ /* else */ }")))),
+ Input, Expected);
+}
+
+TEST_F(TransformerTest, FlattenWithMixedArgs) {
+ using clang::transformer::editList;
+ std::string Input = R"cc(
+ void foo() {
+ if (10 > 1.0)
+ log(1) << "oh no!";
+ else
+ log(0) << "ok";
+ }
+ )cc";
+ std::string Expected = R"(
+ void foo() {
+ if (true) { /* then */ }
+ else { /* else */ }
+ }
+ )";
+
+ StringRef C = "C", T = "T", E = "E";
+ testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
+ hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
+ flatten(changeTo(node(std::string(C)), cat("true")),
+ edit(changeTo(statement(std::string(T)),
+ cat("{ /* then */ }"))),
+ editList({changeTo(statement(std::string(E)),
+ cat("{ /* else */ }"))}))),
+ Input, Expected);
+}
+
TEST_F(TransformerTest, OrderedRuleUnrelated) {
StringRef Flag = "flag";
RewriteRule FlagRule = makeRule(
More information about the cfe-commits
mailing list