diff --git a/clang/include/clang/Tooling/Refactoring/Transformer.h b/clang/include/clang/Tooling/Refactoring/Transformer.h --- a/clang/include/clang/Tooling/Refactoring/Transformer.h +++ b/clang/include/clang/Tooling/Refactoring/Transformer.h @@ -44,12 +44,21 @@ Name, }; -using TextGenerator = - std::function; +// \c TextGenerator may fail, because it processes dynamically-bound match +// results. For example, a typo in the name of a bound node or a mismatch in +// the node's type can lead to a failure in the string generation code. We +// allow the generator to return \c Expected, rather than assert on such +// failures, so that the Transformer client can choose how to handle the error. +// For example, if used in a UI (for example, clang-query or a web app), in +// which the user specifies the rewrite rule, the backend might choose to return +// a diagnostic error, rather than crash. +using TextGenerator = std::function( + const ast_matchers::MatchFinder::MatchResult &)>; -/// Wraps a string as a TextGenerator. +/// Wraps a string as a (trivially successful) TextGenerator. inline TextGenerator text(std::string M) { - return [M](const ast_matchers::MatchFinder::MatchResult &) { return M; }; + return [M](const ast_matchers::MatchFinder::MatchResult &) + -> Expected { return M; }; } // Description of a source-code edit, expressed in terms of an AST node. @@ -224,9 +233,11 @@ using ChangeConsumer = std::function; - /// \param Consumer Receives each successful rewrite as an \c AtomicChange. - /// Note that clients are responsible for handling the case that independent - /// \c AtomicChanges conflict with each other. + /// \param Consumer Receives each rewrite as an \c AtomicChange. If an error + /// occurs in constructing a single \c AtomicChange then the change is still + /// passed to \c Consumer, but it's error will be set. Note that clients are + /// responsible for handling the case that independent \c AtomicChanges + /// conflict with each other. Transformer(RewriteRule Rule, ChangeConsumer Consumer) : Rule(std::move(Rule)), Consumer(std::move(Consumer)) {} diff --git a/clang/lib/Tooling/Refactoring/Transformer.cpp b/clang/lib/Tooling/Refactoring/Transformer.cpp --- a/clang/lib/Tooling/Refactoring/Transformer.cpp +++ b/clang/lib/Tooling/Refactoring/Transformer.cpp @@ -157,12 +157,16 @@ Edit.Target, It->second, Edit.Kind, Edit.Part, *Result.Context); if (auto Err = RangeOrErr.takeError()) return std::move(Err); - Transformation T; - T.Range = *RangeOrErr; - if (T.Range.isInvalid() || - isOriginMacroBody(*Result.SourceManager, T.Range.getBegin())) + auto &Range = *RangeOrErr; + if (Range.isInvalid() || + isOriginMacroBody(*Result.SourceManager, Range.getBegin())) return SmallVector(); - T.Replacement = Edit.Replacement(Result); + auto ReplacementOrErr = Edit.Replacement(Result); + if (auto Err = ReplacementOrErr.takeError()) + return std::move(Err); + Transformation T; + T.Range = Range; + T.Replacement = std::move(*ReplacementOrErr); Transformations.push_back(std::move(T)); } return Transformations; @@ -194,12 +198,15 @@ Root->second.getSourceRange().getBegin()); assert(RootLoc.isValid() && "Invalid location for Root node of match."); + AtomicChange AC(*Result.SourceManager, RootLoc); + auto TransformationsOrErr = translateEdits(Result, Rule.Edits); if (auto Err = TransformationsOrErr.takeError()) { - llvm::errs() << "Transformation failed: " << llvm::toString(std::move(Err)) - << "\n"; + AC.setError(llvm::toString(std::move(Err))); + Consumer(AC); return; } + auto &Transformations = *TransformationsOrErr; if (Transformations.empty()) { // No rewrite applied (but no error encountered either). @@ -209,8 +216,7 @@ return; } - // Convert the result to an AtomicChange. - AtomicChange AC(*Result.SourceManager, RootLoc); + // Record the results in the AtomicChange. for (const auto &T : Transformations) { if (auto Err = AC.replace(*Result.SourceManager, T.Range, T.Replacement)) { AC.setError(llvm::toString(std::move(Err))); 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 @@ -10,6 +10,8 @@ #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -356,6 +358,27 @@ // Negative tests (where we expect no transformation to occur). // +// Tests for a conflict in edits from a single match for a rule. +TEST_F(TransformerTest, TextGeneratorFailure) { + std::string Input = "int conflictOneRule() { return 3 + 7; }"; + // Try to change the whole binary-operator expression AND one its operands: + StringRef O = "O"; + auto AlwaysFail = [](const ast_matchers::MatchFinder::MatchResult &) + -> llvm::Expected { + return llvm::make_error(llvm::errc::invalid_argument, + "ERROR"); + }; + Transformer T(makeRule(binaryOperator().bind(O), change(O, AlwaysFail)), + [this](const AtomicChange &C) { Changes.push_back(C); }); + T.registerMatchers(&MatchFinder); + // Rewriting doesn't change the input, but produces an AtomicChange that + // carries an error (`rewrite()` doesn't fail outright, because it doesn't + // check for errors in the changes). + compareSnippets(Input, rewrite(Input)); + ASSERT_EQ(Changes.size(), 1u); + EXPECT_TRUE(Changes[0].hasError()); +} + // Tests for a conflict in edits from a single match for a rule. TEST_F(TransformerTest, OverlappingEditsInRule) { std::string Input = "int conflictOneRule() { return 3 + 7; }";