diff --git a/clang/include/clang/Tooling/Transformer/RewriteRule.h b/clang/include/clang/Tooling/Transformer/RewriteRule.h --- a/clang/include/clang/Tooling/Transformer/RewriteRule.h +++ b/clang/include/clang/Tooling/Transformer/RewriteRule.h @@ -47,6 +47,8 @@ using TextGenerator = std::shared_ptr>; +using AnyGenerator = MatchConsumer; + // Description of a source-code edit, expressed in terms of an AST node. // Includes: an ID for the (bound) node, a selector for source related to the // node, a replacement and, optionally, an explanation for the edit. @@ -87,7 +89,11 @@ RangeSelector TargetRange; TextGenerator Replacement; TextGenerator Note; - llvm::Any Metadata; + // Not all transformations will want or need to attach metadata and therefore + // should not be required to do so. + AnyGenerator Metadata = [](const ast_matchers::MatchFinder::MatchResult &) { + return llvm::Any(); + }; }; /// Lifts a list of `ASTEdit`s into an `EditGenerator`. @@ -261,9 +267,27 @@ /// Removes the source selected by \p S. ASTEdit remove(RangeSelector S); -inline ASTEdit withMetadata(ASTEdit edit, llvm::Any Metadata) { - edit.Metadata = std::move(Metadata); - return edit; +// FIXME: If `Metadata` returns an `llvm::Expected` the `AnyGenerator` will +// construct an `llvm::Expected` where no error is present but the +// `llvm::Any` holds the error. This is unlikely but potentially surprising. +// Perhaps the `llvm::Expected` should be unwrapped, or perhaps this should be a +// compile-time error. No solution here is perfect. +// +// Note: This function template accepts any type callable with a MatchResult +// rather than a `std::function` because the return-type needs to be deduced. If +// it accepted a `std::function`, lambdas or other callable +// types would not be able to deduce `R`, and users would be forced to specify +// explicitly the type they intended to return by wrapping the lambda at the +// call-site. +template +inline ASTEdit withMetadata(ASTEdit Edit, Callable Metadata) { + Edit.Metadata = + [Gen = std::move(Metadata)]( + const ast_matchers::MatchFinder::MatchResult &R) -> llvm::Any { + return Gen(R); + }; + + return Edit; } /// The following three functions are a low-level part of the RewriteRule diff --git a/clang/lib/Tooling/Transformer/RewriteRule.cpp b/clang/lib/Tooling/Transformer/RewriteRule.cpp --- a/clang/lib/Tooling/Transformer/RewriteRule.cpp +++ b/clang/lib/Tooling/Transformer/RewriteRule.cpp @@ -44,10 +44,13 @@ auto Replacement = E.Replacement->eval(Result); if (!Replacement) return Replacement.takeError(); + auto Metadata = E.Metadata(Result); + if (!Metadata) + return Metadata.takeError(); transformer::Edit T; T.Range = *EditRange; T.Replacement = std::move(*Replacement); - T.Metadata = E.Metadata; + T.Metadata = std::move(*Metadata); Edits.push_back(std::move(T)); } return Edits; diff --git a/clang/unittests/Tooling/TransformerTest.cpp b/clang/unittests/Tooling/TransformerTest.cpp --- a/clang/unittests/Tooling/TransformerTest.cpp +++ b/clang/unittests/Tooling/TransformerTest.cpp @@ -440,6 +440,12 @@ } TEST_F(TransformerTest, WithMetadata) { + auto makeMetadata = [](const MatchFinder::MatchResult &R) -> llvm::Any { + int N = + R.Nodes.getNodeAs("int")->getValue().getLimitedValue(); + return N; + }; + std::string Input = R"cc( int f() { int x = 5; @@ -448,8 +454,11 @@ )cc"; Transformer T( - makeRule(declStmt().bind("decl"), - withMetadata(remove(statement(std::string("decl"))), 17)), + makeRule( + declStmt(containsDeclaration(0, varDecl(hasInitializer( + integerLiteral().bind("int"))))) + .bind("decl"), + withMetadata(remove(statement(std::string("decl"))), makeMetadata)), consumer()); T.registerMatchers(&MatchFinder); auto Factory = newFrontendActionFactory(&MatchFinder); @@ -459,7 +468,7 @@ ASSERT_EQ(Changes.size(), 1u); const llvm::Any &Metadata = Changes[0].getMetadata(); ASSERT_TRUE(llvm::any_isa(Metadata)); - EXPECT_THAT(llvm::any_cast(Metadata), 17); + EXPECT_THAT(llvm::any_cast(Metadata), 5); } TEST_F(TransformerTest, MultiChange) {