From c7e637b329eacf8dcd270b8d4fd9f429b72aa3f8 Mon Sep 17 00:00:00 2001 From: Yitzhak Mandelbaum Date: Thu, 10 Oct 2019 02:34:47 +0000 Subject: [libTooling] Move Transformer files to their own directory/library. Summary: The Transformer library has been growing inside of lib/Tooling/Refactoring. However, it's not really related to anything else in that directory. This revision moves all Transformer-related files into their own include & lib directories. A followup revision will (temporarily) add forwarding headers to help any users migrate their code to the new location. Reviewers: gribozavr Subscribers: mgorny, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D68637 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@374271 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Tooling/Refactoring/MatchConsumer.h | 58 ---- include/clang/Tooling/Refactoring/RangeSelector.h | 93 ------ include/clang/Tooling/Refactoring/SourceCode.h | 90 ----- .../clang/Tooling/Refactoring/SourceCodeBuilders.h | 86 ----- include/clang/Tooling/Refactoring/Stencil.h | 226 ------------- include/clang/Tooling/Refactoring/Transformer.h | 320 ------------------ include/clang/Tooling/Transformer/MatchConsumer.h | 58 ++++ include/clang/Tooling/Transformer/RangeSelector.h | 93 ++++++ include/clang/Tooling/Transformer/SourceCode.h | 90 +++++ .../clang/Tooling/Transformer/SourceCodeBuilders.h | 86 +++++ include/clang/Tooling/Transformer/Stencil.h | 226 +++++++++++++ include/clang/Tooling/Transformer/Transformer.h | 320 ++++++++++++++++++ lib/Tooling/CMakeLists.txt | 1 + lib/Tooling/Refactoring/CMakeLists.txt | 5 - lib/Tooling/Refactoring/RangeSelector.cpp | 312 ----------------- lib/Tooling/Refactoring/SourceCode.cpp | 65 ---- lib/Tooling/Refactoring/SourceCodeBuilders.cpp | 160 --------- lib/Tooling/Refactoring/Stencil.cpp | 369 --------------------- lib/Tooling/Refactoring/Transformer.cpp | 235 ------------- lib/Tooling/Transformer/CMakeLists.txt | 17 + lib/Tooling/Transformer/RangeSelector.cpp | 312 +++++++++++++++++ lib/Tooling/Transformer/SourceCode.cpp | 65 ++++ lib/Tooling/Transformer/SourceCodeBuilders.cpp | 160 +++++++++ lib/Tooling/Transformer/Stencil.cpp | 369 +++++++++++++++++++++ lib/Tooling/Transformer/Transformer.cpp | 235 +++++++++++++ unittests/Tooling/CMakeLists.txt | 1 + unittests/Tooling/RangeSelectorTest.cpp | 2 +- unittests/Tooling/SourceCodeBuildersTest.cpp | 2 +- unittests/Tooling/SourceCodeTest.cpp | 2 +- unittests/Tooling/StencilTest.cpp | 2 +- unittests/Tooling/TransformerTest.cpp | 4 +- 31 files changed, 2039 insertions(+), 2025 deletions(-) delete mode 100644 include/clang/Tooling/Refactoring/MatchConsumer.h delete mode 100644 include/clang/Tooling/Refactoring/RangeSelector.h delete mode 100644 include/clang/Tooling/Refactoring/SourceCode.h delete mode 100644 include/clang/Tooling/Refactoring/SourceCodeBuilders.h delete mode 100644 include/clang/Tooling/Refactoring/Stencil.h delete mode 100644 include/clang/Tooling/Refactoring/Transformer.h create mode 100644 include/clang/Tooling/Transformer/MatchConsumer.h create mode 100644 include/clang/Tooling/Transformer/RangeSelector.h create mode 100644 include/clang/Tooling/Transformer/SourceCode.h create mode 100644 include/clang/Tooling/Transformer/SourceCodeBuilders.h create mode 100644 include/clang/Tooling/Transformer/Stencil.h create mode 100644 include/clang/Tooling/Transformer/Transformer.h delete mode 100644 lib/Tooling/Refactoring/RangeSelector.cpp delete mode 100644 lib/Tooling/Refactoring/SourceCode.cpp delete mode 100644 lib/Tooling/Refactoring/SourceCodeBuilders.cpp delete mode 100644 lib/Tooling/Refactoring/Stencil.cpp delete mode 100644 lib/Tooling/Refactoring/Transformer.cpp create mode 100644 lib/Tooling/Transformer/CMakeLists.txt create mode 100644 lib/Tooling/Transformer/RangeSelector.cpp create mode 100644 lib/Tooling/Transformer/SourceCode.cpp create mode 100644 lib/Tooling/Transformer/SourceCodeBuilders.cpp create mode 100644 lib/Tooling/Transformer/Stencil.cpp create mode 100644 lib/Tooling/Transformer/Transformer.cpp diff --git a/include/clang/Tooling/Refactoring/MatchConsumer.h b/include/clang/Tooling/Refactoring/MatchConsumer.h deleted file mode 100644 index d516550d0f..0000000000 --- a/include/clang/Tooling/Refactoring/MatchConsumer.h +++ /dev/null @@ -1,58 +0,0 @@ -//===--- MatchConsumer.h - MatchConsumer abstraction ------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file This file defines the *MatchConsumer* abstraction: a computation over -/// match results, specifically the `ast_matchers::MatchFinder::MatchResult` -/// class. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_MATCH_CONSUMER_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_MATCH_CONSUMER_H_ - -#include "clang/AST/ASTTypeTraits.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" - -namespace clang { -namespace tooling { - -/// A failable computation over nodes bound by AST matchers. -/// -/// The computation should report any errors though its return value (rather -/// than terminating the program) to enable usage in interactive scenarios like -/// clang-query. -/// -/// This is a central abstraction of the Transformer framework. -template -using MatchConsumer = - std::function(const ast_matchers::MatchFinder::MatchResult &)>; - -/// Creates an error that signals that a `MatchConsumer` expected a certain node -/// to be bound by AST matchers, but it was not actually bound. -inline llvm::Error notBoundError(llvm::StringRef Id) { - return llvm::make_error(llvm::errc::invalid_argument, - "Id not bound: " + Id); -} - -/// Chooses between the two consumers, based on whether \p ID is bound in the -/// match. -template -MatchConsumer ifBound(std::string ID, MatchConsumer TrueC, - MatchConsumer FalseC) { - return [=](const ast_matchers::MatchFinder::MatchResult &Result) { - auto &Map = Result.Nodes.getMap(); - return (Map.find(ID) != Map.end() ? TrueC : FalseC)(Result); - }; -} - -} // namespace tooling -} // namespace clang -#endif // LLVM_CLANG_TOOLING_REFACTOR_MATCH_CONSUMER_H_ diff --git a/include/clang/Tooling/Refactoring/RangeSelector.h b/include/clang/Tooling/Refactoring/RangeSelector.h deleted file mode 100644 index d5b5c8fbd8..0000000000 --- a/include/clang/Tooling/Refactoring/RangeSelector.h +++ /dev/null @@ -1,93 +0,0 @@ -//===--- RangeSelector.h - Source-selection library ---------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// Defines a combinator library supporting the definition of _selectors_, -/// which select source ranges based on (bound) AST nodes. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ - -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Tooling/Refactoring/MatchConsumer.h" -#include "llvm/Support/Error.h" -#include -#include - -namespace clang { -namespace tooling { -using RangeSelector = MatchConsumer; - -inline RangeSelector charRange(CharSourceRange R) { - return [R](const ast_matchers::MatchFinder::MatchResult &) - -> Expected { return R; }; -} - -/// Selects from the start of \p Begin and to the end of \p End. -RangeSelector range(RangeSelector Begin, RangeSelector End); - -/// Convenience version of \c range where end-points are bound nodes. -RangeSelector range(std::string BeginID, std::string EndID); - -/// Selects the (empty) range [B,B) when \p Selector selects the range [B,E). -RangeSelector before(RangeSelector Selector); - -/// Selects the the point immediately following \p Selector. That is, the -/// (empty) range [E,E), when \p Selector selects either -/// * the CharRange [B,E) or -/// * the TokenRange [B,E'] where the token at E' spans the range [E,E'). -RangeSelector after(RangeSelector Selector); - -/// Selects a node, including trailing semicolon (for non-expression -/// statements). \p ID is the node's binding in the match result. -RangeSelector node(std::string ID); - -/// Selects a node, including trailing semicolon (always). Useful for selecting -/// expression statements. \p ID is the node's binding in the match result. -RangeSelector statement(std::string ID); - -/// Given a \c MemberExpr, selects the member token. \p ID is the node's -/// binding in the match result. -RangeSelector member(std::string ID); - -/// Given a node with a "name", (like \c NamedDecl, \c DeclRefExpr or \c -/// CxxCtorInitializer) selects the name's token. Only selects the final -/// identifier of a qualified name, but not any qualifiers or template -/// arguments. For example, for `::foo::bar::baz` and `::foo::bar::baz`, -/// it selects only `baz`. -/// -/// \param ID is the node's binding in the match result. -RangeSelector name(std::string ID); - -// Given a \c CallExpr (bound to \p ID), selects the arguments' source text (all -// source between the call's parentheses). -RangeSelector callArgs(std::string ID); - -// Given a \c CompoundStmt (bound to \p ID), selects the source of the -// statements (all source between the braces). -RangeSelector statements(std::string ID); - -// Given a \c InitListExpr (bound to \p ID), selects the range of the elements -// (all source between the braces). -RangeSelector initListElements(std::string ID); - -/// Given an \IfStmt (bound to \p ID), selects the range of the else branch, -/// starting from the \c else keyword. -RangeSelector elseBranch(std::string ID); - -/// Selects the range from which `S` was expanded (possibly along with other -/// source), if `S` is an expansion, and `S` itself, otherwise. Corresponds to -/// `SourceManager::getExpansionRange`. -RangeSelector expansion(RangeSelector S); -} // namespace tooling -} // namespace clang - -#endif // LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ diff --git a/include/clang/Tooling/Refactoring/SourceCode.h b/include/clang/Tooling/Refactoring/SourceCode.h deleted file mode 100644 index 72dbee4a43..0000000000 --- a/include/clang/Tooling/Refactoring/SourceCode.h +++ /dev/null @@ -1,90 +0,0 @@ -//===--- SourceCode.h - Source code manipulation routines -------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file provides functions that simplify extraction of source code. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_H -#define LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_H - -#include "clang/AST/ASTContext.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Basic/TokenKinds.h" - -namespace clang { -namespace tooling { - -/// Extends \p Range to include the token \p Next, if it immediately follows the -/// end of the range. Otherwise, returns \p Range unchanged. -CharSourceRange maybeExtendRange(CharSourceRange Range, tok::TokenKind Next, - ASTContext &Context); - -/// Returns the source range spanning the node, extended to include \p Next, if -/// it immediately follows \p Node. Otherwise, returns the normal range of \p -/// Node. See comments on `getExtendedText()` for examples. -template -CharSourceRange getExtendedRange(const T &Node, tok::TokenKind Next, - ASTContext &Context) { - return maybeExtendRange(CharSourceRange::getTokenRange(Node.getSourceRange()), - Next, Context); -} - -/// Returns the source-code text in the specified range. -StringRef getText(CharSourceRange Range, const ASTContext &Context); - -/// Returns the source-code text corresponding to \p Node. -template -StringRef getText(const T &Node, const ASTContext &Context) { - return getText(CharSourceRange::getTokenRange(Node.getSourceRange()), - Context); -} - -/// Returns the source text of the node, extended to include \p Next, if it -/// immediately follows the node. Otherwise, returns the text of just \p Node. -/// -/// For example, given statements S1 and S2 below: -/// \code -/// { -/// // S1: -/// if (!x) return foo(); -/// // S2: -/// if (!x) { return 3; } -/// } -/// \endcode -/// then -/// \code -/// getText(S1, Context) = "if (!x) return foo()" -/// getExtendedText(S1, tok::TokenKind::semi, Context) -/// = "if (!x) return foo();" -/// getExtendedText(*S1.getThen(), tok::TokenKind::semi, Context) -/// = "return foo();" -/// getExtendedText(*S2.getThen(), tok::TokenKind::semi, Context) -/// = getText(S2, Context) = "{ return 3; }" -/// \endcode -template -StringRef getExtendedText(const T &Node, tok::TokenKind Next, - ASTContext &Context) { - return getText(getExtendedRange(Node, Next, Context), Context); -} - -// Attempts to resolve the given range to one that can be edited by a rewrite; -// generally, one that starts and ends within a particular file. It supports -// a limited set of cases involving source locations in macro expansions. -llvm::Optional -getRangeForEdit(const CharSourceRange &EditRange, const SourceManager &SM, - const LangOptions &LangOpts); - -inline llvm::Optional -getRangeForEdit(const CharSourceRange &EditRange, const ASTContext &Context) { - return getRangeForEdit(EditRange, Context.getSourceManager(), - Context.getLangOpts()); -} -} // namespace tooling -} // namespace clang -#endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_H diff --git a/include/clang/Tooling/Refactoring/SourceCodeBuilders.h b/include/clang/Tooling/Refactoring/SourceCodeBuilders.h deleted file mode 100644 index 797046f3ec..0000000000 --- a/include/clang/Tooling/Refactoring/SourceCodeBuilders.h +++ /dev/null @@ -1,86 +0,0 @@ -//===--- SourceCodeBuilders.h - Source-code building facilities -*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// This file collects facilities for generating source code strings. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_ - -#include "clang/AST/ASTContext.h" -#include "clang/AST/Expr.h" -#include - -namespace clang { -namespace tooling { - -/// \name Code analysis utilities. -/// @{ -/// Ignores implicit object-construction expressions in addition to the normal -/// implicit expressions that are ignored. -const Expr *reallyIgnoreImplicit(const Expr &E); - -/// Determines whether printing this expression in *any* expression requires -/// parentheses to preserve its meaning. This analyses is necessarily -/// conservative because it lacks information about the target context. -bool mayEverNeedParens(const Expr &E); - -/// Determines whether printing this expression to the left of a dot or arrow -/// operator requires a parentheses to preserve its meaning. Given that -/// dot/arrow are (effectively) the highest precedence, this is equivalent to -/// asking whether it ever needs parens. -inline bool needParensBeforeDotOrArrow(const Expr &E) { - return mayEverNeedParens(E); -} - -/// Determines whether printing this expression to the right of a unary operator -/// requires a parentheses to preserve its meaning. -bool needParensAfterUnaryOperator(const Expr &E); -/// @} - -/// \name Basic code-string generation utilities. -/// @{ - -/// Builds source for an expression, adding parens if needed for unambiguous -/// parsing. -llvm::Optional buildParens(const Expr &E, - const ASTContext &Context); - -/// Builds idiomatic source for the dereferencing of `E`: prefix with `*` but -/// simplify when it already begins with `&`. \returns empty string on failure. -llvm::Optional buildDereference(const Expr &E, - const ASTContext &Context); - -/// Builds idiomatic source for taking the address of `E`: prefix with `&` but -/// simplify when it already begins with `*`. \returns empty string on failure. -llvm::Optional buildAddressOf(const Expr &E, - const ASTContext &Context); - -/// Adds a dot to the end of the given expression, but adds parentheses when -/// needed by the syntax, and simplifies to `->` when possible, e.g.: -/// -/// `x` becomes `x.` -/// `*a` becomes `a->` -/// `a+b` becomes `(a+b).` -llvm::Optional buildDot(const Expr &E, const ASTContext &Context); - -/// Adds an arrow to the end of the given expression, but adds parentheses -/// when needed by the syntax, and simplifies to `.` when possible, e.g.: -/// -/// `x` becomes `x->` -/// `&a` becomes `a.` -/// `a+b` becomes `(a+b)->` -llvm::Optional buildArrow(const Expr &E, - const ASTContext &Context); -/// @} - -} // namespace tooling -} // namespace clang -#endif // LLVM_CLANG_TOOLING_REFACTOR_SOURCE_CODE_BUILDERS_H_ diff --git a/include/clang/Tooling/Refactoring/Stencil.h b/include/clang/Tooling/Refactoring/Stencil.h deleted file mode 100644 index b80320d409..0000000000 --- a/include/clang/Tooling/Refactoring/Stencil.h +++ /dev/null @@ -1,226 +0,0 @@ -//===--- Stencil.h - Stencil class ------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// This file defines the *Stencil* abstraction: a code-generating object, -/// parameterized by named references to (bound) AST nodes. Given a match -/// result, a stencil can be evaluated to a string of source code. -/// -/// A stencil is similar in spirit to a format string: it is composed of a -/// series of raw text strings, references to nodes (the parameters) and helper -/// code-generation operations. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_ - -#include "clang/AST/ASTContext.h" -#include "clang/AST/ASTTypeTraits.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Tooling/Refactoring/MatchConsumer.h" -#include "clang/Tooling/Refactoring/RangeSelector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" -#include -#include - -namespace clang { -namespace tooling { - -/// A stencil is represented as a sequence of "parts" that can each individually -/// generate a code string based on a match result. The different kinds of -/// parts include (raw) text, references to bound nodes and assorted operations -/// on bound nodes. -/// -/// Users can create custom Stencil operations by implementing this interface. -class StencilPartInterface { -public: - virtual ~StencilPartInterface() = default; - - /// Evaluates this part to a string and appends it to \c Result. \c Result is - /// undefined in the case of an error. - virtual llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match, - std::string *Result) const = 0; - - virtual bool isEqual(const StencilPartInterface &other) const = 0; - - /// Constructs a string representation of the StencilPart. StencilParts - /// generated by the `selection` and `run` functions do not have a unique - /// string representation. - virtual std::string toString() const = 0; - - const void *typeId() const { return TypeId; } - -protected: - StencilPartInterface(const void *DerivedId) : TypeId(DerivedId) {} - - // Since this is an abstract class, copying/assigning only make sense for - // derived classes implementing `clone()`. - StencilPartInterface(const StencilPartInterface &) = default; - StencilPartInterface &operator=(const StencilPartInterface &) = default; - - /// Unique identifier of the concrete type of this instance. Supports safe - /// downcasting. - const void *TypeId; -}; - -/// A copyable facade for a std::unique_ptr. Copies result -/// in a copy of the underlying pointee object. -class StencilPart { -public: - explicit StencilPart(std::shared_ptr Impl) - : Impl(std::move(Impl)) {} - - /// See `StencilPartInterface::eval()`. - llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match, - std::string *Result) const { - return Impl->eval(Match, Result); - } - - bool operator==(const StencilPart &Other) const { - if (Impl == Other.Impl) - return true; - if (Impl == nullptr || Other.Impl == nullptr) - return false; - return Impl->isEqual(*Other.Impl); - } - - std::string toString() const { - if (Impl == nullptr) - return ""; - return Impl->toString(); - } - -private: - std::shared_ptr Impl; -}; - -/// A sequence of code fragments, references to parameters and code-generation -/// operations that together can be evaluated to (a fragment of) source code, -/// given a match result. -class Stencil { -public: - Stencil() = default; - - /// Composes a stencil from a series of parts. - template static Stencil cat(Ts &&... Parts) { - Stencil S; - S.Parts = {wrap(std::forward(Parts))...}; - return S; - } - - /// Appends data from a \p OtherStencil to this stencil. - void append(Stencil OtherStencil); - - // Evaluates the stencil given a match result. Requires that the nodes in the - // result includes any ids referenced in the stencil. References to missing - // nodes will result in an invalid_argument error. - llvm::Expected - eval(const ast_matchers::MatchFinder::MatchResult &Match) const; - - // Allow Stencils to operate as std::function, for compatibility with - // Transformer's TextGenerator. - llvm::Expected - operator()(const ast_matchers::MatchFinder::MatchResult &Result) const { - return eval(Result); - } - - /// Constructs a string representation of the Stencil. The string is not - /// guaranteed to be unique. - std::string toString() const { - std::vector PartStrings; - PartStrings.reserve(Parts.size()); - for (const auto &Part : Parts) - PartStrings.push_back(Part.toString()); - return llvm::join(PartStrings, ", "); - } - -private: - friend bool operator==(const Stencil &A, const Stencil &B); - static StencilPart wrap(llvm::StringRef Text); - static StencilPart wrap(RangeSelector Selector); - static StencilPart wrap(StencilPart Part) { return Part; } - - std::vector Parts; -}; - -inline bool operator==(const Stencil &A, const Stencil &B) { - return A.Parts == B.Parts; -} - -inline bool operator!=(const Stencil &A, const Stencil &B) { return !(A == B); } - -// Functions for conveniently building stencils. -namespace stencil { -/// Convenience wrapper for Stencil::cat that can be imported with a using decl. -template Stencil cat(Ts &&... Parts) { - return Stencil::cat(std::forward(Parts)...); -} - -/// \returns exactly the text provided. -StencilPart text(llvm::StringRef Text); - -/// \returns the source corresponding to the selected range. -StencilPart selection(RangeSelector Selector); - -/// \returns the source corresponding to the identified node. -/// FIXME: Deprecated. Write `selection(node(Id))` instead. -inline StencilPart node(llvm::StringRef Id) { - return selection(tooling::node(Id)); -} - -/// Generates the source of the expression bound to \p Id, wrapping it in -/// parentheses if it may parse differently depending on context. For example, a -/// binary operation is always wrapped, while a variable reference is never -/// wrapped. -StencilPart expression(llvm::StringRef Id); - -/// Constructs an idiomatic dereferencing of the expression bound to \p ExprId. -/// \p ExprId is wrapped in parentheses, if needed. -StencilPart deref(llvm::StringRef ExprId); - -/// Constructs an expression that idiomatically takes the address of the -/// expression bound to \p ExprId. \p ExprId is wrapped in parentheses, if -/// needed. -StencilPart addressOf(llvm::StringRef ExprId); - -/// Constructs a `MemberExpr` that accesses the named member (\p Member) of the -/// object bound to \p BaseId. The access is constructed idiomatically: if \p -/// BaseId is bound to `e` and \p Member identifies member `m`, then returns -/// `e->m`, when e is a pointer, `e2->m` when e = `*e2` and `e.m` otherwise. -/// Additionally, `e` is wrapped in parentheses, if needed. -StencilPart access(llvm::StringRef BaseId, StencilPart Member); -inline StencilPart access(llvm::StringRef BaseId, llvm::StringRef Member) { - return access(BaseId, text(Member)); -} - -/// Chooses between the two stencil parts, based on whether \p ID is bound in -/// the match. -StencilPart ifBound(llvm::StringRef Id, StencilPart TruePart, - StencilPart FalsePart); - -/// Chooses between the two strings, based on whether \p ID is bound in the -/// match. -inline StencilPart ifBound(llvm::StringRef Id, llvm::StringRef TrueText, - llvm::StringRef FalseText) { - return ifBound(Id, text(TrueText), text(FalseText)); -} - -/// Wraps a MatchConsumer in a StencilPart, so that it can be used in a Stencil. -/// This supports user-defined extensions to the Stencil language. -StencilPart run(MatchConsumer C); - -/// For debug use only; semantics are not guaranteed. -/// -/// \returns the string resulting from calling the node's print() method. -StencilPart dPrint(llvm::StringRef Id); -} // namespace stencil -} // namespace tooling -} // namespace clang -#endif // LLVM_CLANG_TOOLING_REFACTOR_STENCIL_H_ diff --git a/include/clang/Tooling/Refactoring/Transformer.h b/include/clang/Tooling/Refactoring/Transformer.h deleted file mode 100644 index 0971cc3e66..0000000000 --- a/include/clang/Tooling/Refactoring/Transformer.h +++ /dev/null @@ -1,320 +0,0 @@ -//===--- Transformer.h - Clang source-rewriting library ---------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// Defines a library supporting the concise specification of clang-based -/// source-to-source transformations. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ - -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/ASTMatchers/ASTMatchersInternal.h" -#include "clang/Tooling/Refactoring/AtomicChange.h" -#include "clang/Tooling/Refactoring/MatchConsumer.h" -#include "clang/Tooling/Refactoring/RangeSelector.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Error.h" -#include -#include -#include -#include -#include - -namespace clang { -namespace tooling { - -using TextGenerator = MatchConsumer; - -/// Wraps a string as a TextGenerator. -inline TextGenerator text(std::string M) { - return [M](const ast_matchers::MatchFinder::MatchResult &) - -> Expected { return M; }; -} - -// 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. -// -// * Target: the source code impacted by the rule. This identifies an AST node, -// or part thereof (\c Part), whose source range indicates the extent of the -// replacement applied by the replacement term. By default, the extent is the -// node matched by the pattern term (\c NodePart::Node). Target's are typed -// (\c Kind), which guides the determination of the node extent. -// -// * Replacement: a function that produces a replacement string for the target, -// based on the match result. -// -// * Note: (optional) a note specifically for this edit, potentially referencing -// elements of the match. This will be displayed to the user, where possible; -// for example, in clang-tidy diagnostics. Use of notes should be rare -- -// explanations of the entire rewrite should be set in the rule -// (`RewriteRule::Explanation`) instead. Notes serve the rare cases wherein -// edit-specific diagnostics are required. -// -// `ASTEdit` should be built using the `change` convenience functions. For -// example, -// \code -// change(name(fun), text("Frodo")) -// \endcode -// Or, if we use Stencil for the TextGenerator: -// \code -// using stencil::cat; -// change(statement(thenNode), cat("{", thenNode, "}")) -// change(callArgs(call), cat(x, ",", y)) -// \endcode -// Or, if you are changing the node corresponding to the rule's matcher, you can -// use the single-argument override of \c change: -// \code -// change(cat("different_expr")) -// \endcode -struct ASTEdit { - RangeSelector TargetRange; - TextGenerator Replacement; - TextGenerator Note; -}; - -/// Format of the path in an include directive -- angle brackets or quotes. -enum class IncludeFormat { - Quoted, - Angled, -}; - -/// Description of a source-code transformation. -// -// A *rewrite rule* describes a transformation of source code. A simple rule -// contains each of the following components: -// -// * Matcher: the pattern term, expressed as clang matchers (with Transformer -// extensions). -// -// * Edits: a set of Edits to the source code, described with ASTEdits. -// -// * Explanation: explanation of the rewrite. This will be displayed to the -// user, where possible; for example, in clang-tidy diagnostics. -// -// However, rules can also consist of (sub)rules, where the first that matches -// is applied and the rest are ignored. So, the above components are gathered -// as a `Case` and a rule is a list of cases. -// -// Rule cases have an additional, implicit, component: the parameters. These are -// portions of the pattern which are left unspecified, yet bound in the pattern -// so that we can reference them in the edits. -// -// The \c Transformer class can be used to apply the rewrite rule and obtain the -// corresponding replacements. -struct RewriteRule { - struct Case { - ast_matchers::internal::DynTypedMatcher Matcher; - SmallVector Edits; - TextGenerator Explanation; - // Include paths to add to the file affected by this case. These are - // bundled with the `Case`, rather than the `RewriteRule`, because each case - // might have different associated changes to the includes. - std::vector> AddedIncludes; - }; - // We expect RewriteRules will most commonly include only one case. - SmallVector Cases; - - // ID used as the default target of each match. The node described by the - // matcher is should always be bound to this id. - static constexpr llvm::StringLiteral RootID = "___root___"; -}; - -/// Convenience function for constructing a simple \c RewriteRule. -RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M, - SmallVector Edits, - TextGenerator Explanation = nullptr); - -/// Convenience overload of \c makeRule for common case of only one edit. -inline RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M, - ASTEdit Edit, - TextGenerator Explanation = nullptr) { - SmallVector Edits; - Edits.emplace_back(std::move(Edit)); - return makeRule(std::move(M), std::move(Edits), std::move(Explanation)); -} - -/// For every case in Rule, adds an include directive for the given header. The -/// common use is assumed to be a rule with only one case. For example, to -/// replace a function call and add headers corresponding to the new code, one -/// could write: -/// \code -/// auto R = makeRule(callExpr(callee(functionDecl(hasName("foo")))), -/// change(text("bar()"))); -/// AddInclude(R, "path/to/bar_header.h"); -/// AddInclude(R, "vector", IncludeFormat::Angled); -/// \endcode -void addInclude(RewriteRule &Rule, llvm::StringRef Header, - IncludeFormat Format = IncludeFormat::Quoted); - -/// Applies the first rule whose pattern matches; other rules are ignored. If -/// the matchers are independent then order doesn't matter. In that case, -/// `applyFirst` is simply joining the set of rules into one. -// -// `applyFirst` is like an `anyOf` matcher with an edit action attached to each -// of its cases. Anywhere you'd use `anyOf(m1.bind("id1"), m2.bind("id2"))` and -// then dispatch on those ids in your code for control flow, `applyFirst` lifts -// that behavior to the rule level. So, you can write `applyFirst({makeRule(m1, -// action1), makeRule(m2, action2), ...});` -// -// For example, consider a type `T` with a deterministic serialization function, -// `serialize()`. For performance reasons, we would like to make it -// non-deterministic. Therefore, we want to drop the expectation that -// `a.serialize() = b.serialize() iff a = b` (although we'll maintain -// `deserialize(a.serialize()) = a`). -// -// We have three cases to consider (for some equality function, `eq`): -// ``` -// eq(a.serialize(), b.serialize()) --> eq(a,b) -// eq(a, b.serialize()) --> eq(deserialize(a), b) -// eq(a.serialize(), b) --> eq(a, deserialize(b)) -// ``` -// -// `applyFirst` allows us to specify each independently: -// ``` -// auto eq_fun = functionDecl(...); -// auto method_call = cxxMemberCallExpr(...); -// -// auto two_calls = callExpr(callee(eq_fun), hasArgument(0, method_call), -// hasArgument(1, method_call)); -// auto left_call = -// callExpr(callee(eq_fun), callExpr(hasArgument(0, method_call))); -// auto right_call = -// callExpr(callee(eq_fun), callExpr(hasArgument(1, method_call))); -// -// RewriteRule R = applyFirst({makeRule(two_calls, two_calls_action), -// makeRule(left_call, left_call_action), -// makeRule(right_call, right_call_action)}); -// ``` -RewriteRule applyFirst(ArrayRef Rules); - -/// Replaces a portion of the source text with \p Replacement. -ASTEdit change(RangeSelector Target, TextGenerator Replacement); - -/// Replaces the entirety of a RewriteRule's match with \p Replacement. For -/// example, to replace a function call, one could write: -/// \code -/// makeRule(callExpr(callee(functionDecl(hasName("foo")))), -/// change(text("bar()"))) -/// \endcode -inline ASTEdit change(TextGenerator Replacement) { - return change(node(RewriteRule::RootID), std::move(Replacement)); -} - -/// Inserts \p Replacement before \p S, leaving the source selected by \S -/// unchanged. -inline ASTEdit insertBefore(RangeSelector S, TextGenerator Replacement) { - return change(before(std::move(S)), std::move(Replacement)); -} - -/// Inserts \p Replacement after \p S, leaving the source selected by \S -/// unchanged. -inline ASTEdit insertAfter(RangeSelector S, TextGenerator Replacement) { - return change(after(std::move(S)), std::move(Replacement)); -} - -/// Removes the source selected by \p S. -inline ASTEdit remove(RangeSelector S) { - return change(std::move(S), text("")); -} - -/// 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 -/// users. -// -// FIXME: These functions are really public, if advanced, elements of the -// RewriteRule API. Recast them as such. Or, just declare these functions -// public and well-supported and move them out of `detail`. -namespace detail { -/// Builds a single matcher for the rule, covering all of the rule's cases. -/// Only supports Rules whose cases' matchers share the same base "kind" -/// (`Stmt`, `Decl`, etc.) Deprecated: use `buildMatchers` instead, which -/// supports mixing matchers of different kinds. -ast_matchers::internal::DynTypedMatcher buildMatcher(const RewriteRule &Rule); - -/// Builds a set of matchers that cover the rule (one for each distinct node -/// matcher base kind: Stmt, Decl, etc.). Node-matchers for `QualType` and -/// `Type` are not permitted, since such nodes carry no source location -/// information and are therefore not relevant for rewriting. If any such -/// matchers are included, will return an empty vector. -std::vector -buildMatchers(const RewriteRule &Rule); - -/// Gets the beginning location of the source matched by a rewrite rule. If the -/// match occurs within a macro expansion, returns the beginning of the -/// expansion point. `Result` must come from the matching of a rewrite rule. -SourceLocation -getRuleMatchLoc(const ast_matchers::MatchFinder::MatchResult &Result); - -/// Returns the \c Case of \c Rule that was selected in the match result. -/// Assumes a matcher built with \c buildMatcher. -const RewriteRule::Case & -findSelectedCase(const ast_matchers::MatchFinder::MatchResult &Result, - const RewriteRule &Rule); - -/// A source "transformation," represented by a character range in the source to -/// be replaced and a corresponding replacement string. -struct Transformation { - CharSourceRange Range; - std::string Replacement; -}; - -/// Attempts to translate `Edits`, which are in terms of AST nodes bound in the -/// match `Result`, into Transformations, which are in terms of the source code -/// text. -/// -/// Returns an empty vector if any of the edits apply to portions of the source -/// that are ineligible for rewriting (certain interactions with macros, for -/// example). Fails if any invariants are violated relating to bound nodes in -/// the match. However, it does not fail in the case of conflicting edits -- -/// conflict handling is left to clients. We recommend use of the \c -/// AtomicChange or \c Replacements classes for assistance in detecting such -/// conflicts. -Expected> -translateEdits(const ast_matchers::MatchFinder::MatchResult &Result, - llvm::ArrayRef Edits); -} // namespace detail - -/// Handles the matcher and callback registration for a single rewrite rule, as -/// defined by the arguments of the constructor. -class Transformer : public ast_matchers::MatchFinder::MatchCallback { -public: - using ChangeConsumer = - std::function Change)>; - - /// \param Consumer Receives each rewrite or error. Will not necessarily be - /// called for each match; for example, if the rewrite is not applicable - /// because of macros, but doesn't fail. 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)) {} - - /// N.B. Passes `this` pointer to `MatchFinder`. So, this object should not - /// be moved after this call. - void registerMatchers(ast_matchers::MatchFinder *MatchFinder); - - /// Not called directly by users -- called by the framework, via base class - /// pointer. - void run(const ast_matchers::MatchFinder::MatchResult &Result) override; - -private: - RewriteRule Rule; - /// Receives each successful rewrites as an \c AtomicChange. - ChangeConsumer Consumer; -}; -} // namespace tooling -} // namespace clang - -#endif // LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ diff --git a/include/clang/Tooling/Transformer/MatchConsumer.h b/include/clang/Tooling/Transformer/MatchConsumer.h new file mode 100644 index 0000000000..51eb6af25a --- /dev/null +++ b/include/clang/Tooling/Transformer/MatchConsumer.h @@ -0,0 +1,58 @@ +//===--- MatchConsumer.h - MatchConsumer abstraction ------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file This file defines the *MatchConsumer* abstraction: a computation over +/// match results, specifically the `ast_matchers::MatchFinder::MatchResult` +/// class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_MATCH_CONSUMER_H_ +#define LLVM_CLANG_TOOLING_TRANSFORMER_MATCH_CONSUMER_H_ + +#include "clang/AST/ASTTypeTraits.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace tooling { + +/// A failable computation over nodes bound by AST matchers. +/// +/// The computation should report any errors though its return value (rather +/// than terminating the program) to enable usage in interactive scenarios like +/// clang-query. +/// +/// This is a central abstraction of the Transformer framework. +template +using MatchConsumer = + std::function(const ast_matchers::MatchFinder::MatchResult &)>; + +/// Creates an error that signals that a `MatchConsumer` expected a certain node +/// to be bound by AST matchers, but it was not actually bound. +inline llvm::Error notBoundError(llvm::StringRef Id) { + return llvm::make_error(llvm::errc::invalid_argument, + "Id not bound: " + Id); +} + +/// Chooses between the two consumers, based on whether \p ID is bound in the +/// match. +template +MatchConsumer ifBound(std::string ID, MatchConsumer TrueC, + MatchConsumer FalseC) { + return [=](const ast_matchers::MatchFinder::MatchResult &Result) { + auto &Map = Result.Nodes.getMap(); + return (Map.find(ID) != Map.end() ? TrueC : FalseC)(Result); + }; +} + +} // namespace tooling +} // namespace clang +#endif // LLVM_CLANG_TOOLING_TRANSFORMER_MATCH_CONSUMER_H_ diff --git a/include/clang/Tooling/Transformer/RangeSelector.h b/include/clang/Tooling/Transformer/RangeSelector.h new file mode 100644 index 0000000000..e178fb36a8 --- /dev/null +++ b/include/clang/Tooling/Transformer/RangeSelector.h @@ -0,0 +1,93 @@ +//===--- RangeSelector.h - Source-selection library ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Defines a combinator library supporting the definition of _selectors_, +/// which select source ranges based on (bound) AST nodes. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ +#define LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Transformer/MatchConsumer.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { +namespace tooling { +using RangeSelector = MatchConsumer; + +inline RangeSelector charRange(CharSourceRange R) { + return [R](const ast_matchers::MatchFinder::MatchResult &) + -> Expected { return R; }; +} + +/// Selects from the start of \p Begin and to the end of \p End. +RangeSelector range(RangeSelector Begin, RangeSelector End); + +/// Convenience version of \c range where end-points are bound nodes. +RangeSelector range(std::string BeginID, std::string EndID); + +/// Selects the (empty) range [B,B) when \p Selector selects the range [B,E). +RangeSelector before(RangeSelector Selector); + +/// Selects the the point immediately following \p Selector. That is, the +/// (empty) range [E,E), when \p Selector selects either +/// * the CharRange [B,E) or +/// * the TokenRange [B,E'] where the token at E' spans the range [E,E'). +RangeSelector after(RangeSelector Selector); + +/// Selects a node, including trailing semicolon (for non-expression +/// statements). \p ID is the node's binding in the match result. +RangeSelector node(std::string ID); + +/// Selects a node, including trailing semicolon (always). Useful for selecting +/// expression statements. \p ID is the node's binding in the match result. +RangeSelector statement(std::string ID); + +/// Given a \c MemberExpr, selects the member token. \p ID is the node's +/// binding in the match result. +RangeSelector member(std::string ID); + +/// Given a node with a "name", (like \c NamedDecl, \c DeclRefExpr or \c +/// CxxCtorInitializer) selects the name's token. Only selects the final +/// identifier of a qualified name, but not any qualifiers or template +/// arguments. For example, for `::foo::bar::baz` and `::foo::bar::baz`, +/// it selects only `baz`. +/// +/// \param ID is the node's binding in the match result. +RangeSelector name(std::string ID); + +// Given a \c CallExpr (bound to \p ID), selects the arguments' source text (all +// source between the call's parentheses). +RangeSelector callArgs(std::string ID); + +// Given a \c CompoundStmt (bound to \p ID), selects the source of the +// statements (all source between the braces). +RangeSelector statements(std::string ID); + +// Given a \c InitListExpr (bound to \p ID), selects the range of the elements +// (all source between the braces). +RangeSelector initListElements(std::string ID); + +/// Given an \IfStmt (bound to \p ID), selects the range of the else branch, +/// starting from the \c else keyword. +RangeSelector elseBranch(std::string ID); + +/// Selects the range from which `S` was expanded (possibly along with other +/// source), if `S` is an expansion, and `S` itself, otherwise. Corresponds to +/// `SourceManager::getExpansionRange`. +RangeSelector expansion(RangeSelector S); +} // namespace tooling +} // namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ diff --git a/include/clang/Tooling/Transformer/SourceCode.h b/include/clang/Tooling/Transformer/SourceCode.h new file mode 100644 index 0000000000..bc9cc3d2a2 --- /dev/null +++ b/include/clang/Tooling/Transformer/SourceCode.h @@ -0,0 +1,90 @@ +//===--- SourceCode.h - Source code manipulation routines -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides functions that simplify extraction of source code. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_H +#define LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_H + +#include "clang/AST/ASTContext.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" + +namespace clang { +namespace tooling { + +/// Extends \p Range to include the token \p Next, if it immediately follows the +/// end of the range. Otherwise, returns \p Range unchanged. +CharSourceRange maybeExtendRange(CharSourceRange Range, tok::TokenKind Next, + ASTContext &Context); + +/// Returns the source range spanning the node, extended to include \p Next, if +/// it immediately follows \p Node. Otherwise, returns the normal range of \p +/// Node. See comments on `getExtendedText()` for examples. +template +CharSourceRange getExtendedRange(const T &Node, tok::TokenKind Next, + ASTContext &Context) { + return maybeExtendRange(CharSourceRange::getTokenRange(Node.getSourceRange()), + Next, Context); +} + +/// Returns the source-code text in the specified range. +StringRef getText(CharSourceRange Range, const ASTContext &Context); + +/// Returns the source-code text corresponding to \p Node. +template +StringRef getText(const T &Node, const ASTContext &Context) { + return getText(CharSourceRange::getTokenRange(Node.getSourceRange()), + Context); +} + +/// Returns the source text of the node, extended to include \p Next, if it +/// immediately follows the node. Otherwise, returns the text of just \p Node. +/// +/// For example, given statements S1 and S2 below: +/// \code +/// { +/// // S1: +/// if (!x) return foo(); +/// // S2: +/// if (!x) { return 3; } +/// } +/// \endcode +/// then +/// \code +/// getText(S1, Context) = "if (!x) return foo()" +/// getExtendedText(S1, tok::TokenKind::semi, Context) +/// = "if (!x) return foo();" +/// getExtendedText(*S1.getThen(), tok::TokenKind::semi, Context) +/// = "return foo();" +/// getExtendedText(*S2.getThen(), tok::TokenKind::semi, Context) +/// = getText(S2, Context) = "{ return 3; }" +/// \endcode +template +StringRef getExtendedText(const T &Node, tok::TokenKind Next, + ASTContext &Context) { + return getText(getExtendedRange(Node, Next, Context), Context); +} + +// Attempts to resolve the given range to one that can be edited by a rewrite; +// generally, one that starts and ends within a particular file. It supports +// a limited set of cases involving source locations in macro expansions. +llvm::Optional +getRangeForEdit(const CharSourceRange &EditRange, const SourceManager &SM, + const LangOptions &LangOpts); + +inline llvm::Optional +getRangeForEdit(const CharSourceRange &EditRange, const ASTContext &Context) { + return getRangeForEdit(EditRange, Context.getSourceManager(), + Context.getLangOpts()); +} +} // namespace tooling +} // namespace clang +#endif // LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_H diff --git a/include/clang/Tooling/Transformer/SourceCodeBuilders.h b/include/clang/Tooling/Transformer/SourceCodeBuilders.h new file mode 100644 index 0000000000..6c79a7588f --- /dev/null +++ b/include/clang/Tooling/Transformer/SourceCodeBuilders.h @@ -0,0 +1,86 @@ +//===--- SourceCodeBuilders.h - Source-code building facilities -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file collects facilities for generating source code strings. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_BUILDERS_H_ +#define LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_BUILDERS_H_ + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include + +namespace clang { +namespace tooling { + +/// \name Code analysis utilities. +/// @{ +/// Ignores implicit object-construction expressions in addition to the normal +/// implicit expressions that are ignored. +const Expr *reallyIgnoreImplicit(const Expr &E); + +/// Determines whether printing this expression in *any* expression requires +/// parentheses to preserve its meaning. This analyses is necessarily +/// conservative because it lacks information about the target context. +bool mayEverNeedParens(const Expr &E); + +/// Determines whether printing this expression to the left of a dot or arrow +/// operator requires a parentheses to preserve its meaning. Given that +/// dot/arrow are (effectively) the highest precedence, this is equivalent to +/// asking whether it ever needs parens. +inline bool needParensBeforeDotOrArrow(const Expr &E) { + return mayEverNeedParens(E); +} + +/// Determines whether printing this expression to the right of a unary operator +/// requires a parentheses to preserve its meaning. +bool needParensAfterUnaryOperator(const Expr &E); +/// @} + +/// \name Basic code-string generation utilities. +/// @{ + +/// Builds source for an expression, adding parens if needed for unambiguous +/// parsing. +llvm::Optional buildParens(const Expr &E, + const ASTContext &Context); + +/// Builds idiomatic source for the dereferencing of `E`: prefix with `*` but +/// simplify when it already begins with `&`. \returns empty string on failure. +llvm::Optional buildDereference(const Expr &E, + const ASTContext &Context); + +/// Builds idiomatic source for taking the address of `E`: prefix with `&` but +/// simplify when it already begins with `*`. \returns empty string on failure. +llvm::Optional buildAddressOf(const Expr &E, + const ASTContext &Context); + +/// Adds a dot to the end of the given expression, but adds parentheses when +/// needed by the syntax, and simplifies to `->` when possible, e.g.: +/// +/// `x` becomes `x.` +/// `*a` becomes `a->` +/// `a+b` becomes `(a+b).` +llvm::Optional buildDot(const Expr &E, const ASTContext &Context); + +/// Adds an arrow to the end of the given expression, but adds parentheses +/// when needed by the syntax, and simplifies to `.` when possible, e.g.: +/// +/// `x` becomes `x->` +/// `&a` becomes `a.` +/// `a+b` becomes `(a+b)->` +llvm::Optional buildArrow(const Expr &E, + const ASTContext &Context); +/// @} + +} // namespace tooling +} // namespace clang +#endif // LLVM_CLANG_TOOLING_TRANSFORMER_SOURCE_CODE_BUILDERS_H_ diff --git a/include/clang/Tooling/Transformer/Stencil.h b/include/clang/Tooling/Transformer/Stencil.h new file mode 100644 index 0000000000..617585cacd --- /dev/null +++ b/include/clang/Tooling/Transformer/Stencil.h @@ -0,0 +1,226 @@ +//===--- Stencil.h - Stencil class ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the *Stencil* abstraction: a code-generating object, +/// parameterized by named references to (bound) AST nodes. Given a match +/// result, a stencil can be evaluated to a string of source code. +/// +/// A stencil is similar in spirit to a format string: it is composed of a +/// series of raw text strings, references to nodes (the parameters) and helper +/// code-generation operations. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_STENCIL_H_ +#define LLVM_CLANG_TOOLING_TRANSFORMER_STENCIL_H_ + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Transformer/MatchConsumer.h" +#include "clang/Tooling/Transformer/RangeSelector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { +namespace tooling { + +/// A stencil is represented as a sequence of "parts" that can each individually +/// generate a code string based on a match result. The different kinds of +/// parts include (raw) text, references to bound nodes and assorted operations +/// on bound nodes. +/// +/// Users can create custom Stencil operations by implementing this interface. +class StencilPartInterface { +public: + virtual ~StencilPartInterface() = default; + + /// Evaluates this part to a string and appends it to \c Result. \c Result is + /// undefined in the case of an error. + virtual llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match, + std::string *Result) const = 0; + + virtual bool isEqual(const StencilPartInterface &other) const = 0; + + /// Constructs a string representation of the StencilPart. StencilParts + /// generated by the `selection` and `run` functions do not have a unique + /// string representation. + virtual std::string toString() const = 0; + + const void *typeId() const { return TypeId; } + +protected: + StencilPartInterface(const void *DerivedId) : TypeId(DerivedId) {} + + // Since this is an abstract class, copying/assigning only make sense for + // derived classes implementing `clone()`. + StencilPartInterface(const StencilPartInterface &) = default; + StencilPartInterface &operator=(const StencilPartInterface &) = default; + + /// Unique identifier of the concrete type of this instance. Supports safe + /// downcasting. + const void *TypeId; +}; + +/// A copyable facade for a std::unique_ptr. Copies result +/// in a copy of the underlying pointee object. +class StencilPart { +public: + explicit StencilPart(std::shared_ptr Impl) + : Impl(std::move(Impl)) {} + + /// See `StencilPartInterface::eval()`. + llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &Match, + std::string *Result) const { + return Impl->eval(Match, Result); + } + + bool operator==(const StencilPart &Other) const { + if (Impl == Other.Impl) + return true; + if (Impl == nullptr || Other.Impl == nullptr) + return false; + return Impl->isEqual(*Other.Impl); + } + + std::string toString() const { + if (Impl == nullptr) + return ""; + return Impl->toString(); + } + +private: + std::shared_ptr Impl; +}; + +/// A sequence of code fragments, references to parameters and code-generation +/// operations that together can be evaluated to (a fragment of) source code, +/// given a match result. +class Stencil { +public: + Stencil() = default; + + /// Composes a stencil from a series of parts. + template static Stencil cat(Ts &&... Parts) { + Stencil S; + S.Parts = {wrap(std::forward(Parts))...}; + return S; + } + + /// Appends data from a \p OtherStencil to this stencil. + void append(Stencil OtherStencil); + + // Evaluates the stencil given a match result. Requires that the nodes in the + // result includes any ids referenced in the stencil. References to missing + // nodes will result in an invalid_argument error. + llvm::Expected + eval(const ast_matchers::MatchFinder::MatchResult &Match) const; + + // Allow Stencils to operate as std::function, for compatibility with + // Transformer's TextGenerator. + llvm::Expected + operator()(const ast_matchers::MatchFinder::MatchResult &Result) const { + return eval(Result); + } + + /// Constructs a string representation of the Stencil. The string is not + /// guaranteed to be unique. + std::string toString() const { + std::vector PartStrings; + PartStrings.reserve(Parts.size()); + for (const auto &Part : Parts) + PartStrings.push_back(Part.toString()); + return llvm::join(PartStrings, ", "); + } + +private: + friend bool operator==(const Stencil &A, const Stencil &B); + static StencilPart wrap(llvm::StringRef Text); + static StencilPart wrap(RangeSelector Selector); + static StencilPart wrap(StencilPart Part) { return Part; } + + std::vector Parts; +}; + +inline bool operator==(const Stencil &A, const Stencil &B) { + return A.Parts == B.Parts; +} + +inline bool operator!=(const Stencil &A, const Stencil &B) { return !(A == B); } + +// Functions for conveniently building stencils. +namespace stencil { +/// Convenience wrapper for Stencil::cat that can be imported with a using decl. +template Stencil cat(Ts &&... Parts) { + return Stencil::cat(std::forward(Parts)...); +} + +/// \returns exactly the text provided. +StencilPart text(llvm::StringRef Text); + +/// \returns the source corresponding to the selected range. +StencilPart selection(RangeSelector Selector); + +/// \returns the source corresponding to the identified node. +/// FIXME: Deprecated. Write `selection(node(Id))` instead. +inline StencilPart node(llvm::StringRef Id) { + return selection(tooling::node(Id)); +} + +/// Generates the source of the expression bound to \p Id, wrapping it in +/// parentheses if it may parse differently depending on context. For example, a +/// binary operation is always wrapped, while a variable reference is never +/// wrapped. +StencilPart expression(llvm::StringRef Id); + +/// Constructs an idiomatic dereferencing of the expression bound to \p ExprId. +/// \p ExprId is wrapped in parentheses, if needed. +StencilPart deref(llvm::StringRef ExprId); + +/// Constructs an expression that idiomatically takes the address of the +/// expression bound to \p ExprId. \p ExprId is wrapped in parentheses, if +/// needed. +StencilPart addressOf(llvm::StringRef ExprId); + +/// Constructs a `MemberExpr` that accesses the named member (\p Member) of the +/// object bound to \p BaseId. The access is constructed idiomatically: if \p +/// BaseId is bound to `e` and \p Member identifies member `m`, then returns +/// `e->m`, when e is a pointer, `e2->m` when e = `*e2` and `e.m` otherwise. +/// Additionally, `e` is wrapped in parentheses, if needed. +StencilPart access(llvm::StringRef BaseId, StencilPart Member); +inline StencilPart access(llvm::StringRef BaseId, llvm::StringRef Member) { + return access(BaseId, text(Member)); +} + +/// Chooses between the two stencil parts, based on whether \p ID is bound in +/// the match. +StencilPart ifBound(llvm::StringRef Id, StencilPart TruePart, + StencilPart FalsePart); + +/// Chooses between the two strings, based on whether \p ID is bound in the +/// match. +inline StencilPart ifBound(llvm::StringRef Id, llvm::StringRef TrueText, + llvm::StringRef FalseText) { + return ifBound(Id, text(TrueText), text(FalseText)); +} + +/// Wraps a MatchConsumer in a StencilPart, so that it can be used in a Stencil. +/// This supports user-defined extensions to the Stencil language. +StencilPart run(MatchConsumer C); + +/// For debug use only; semantics are not guaranteed. +/// +/// \returns the string resulting from calling the node's print() method. +StencilPart dPrint(llvm::StringRef Id); +} // namespace stencil +} // namespace tooling +} // namespace clang +#endif // LLVM_CLANG_TOOLING_TRANSFORMER_STENCIL_H_ diff --git a/include/clang/Tooling/Transformer/Transformer.h b/include/clang/Tooling/Transformer/Transformer.h new file mode 100644 index 0000000000..0dc1e820e2 --- /dev/null +++ b/include/clang/Tooling/Transformer/Transformer.h @@ -0,0 +1,320 @@ +//===--- Transformer.h - Clang source-rewriting library ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Defines a library supporting the concise specification of clang-based +/// source-to-source transformations. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_ +#define LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_ + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Transformer/MatchConsumer.h" +#include "clang/Tooling/Transformer/RangeSelector.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Error.h" +#include +#include +#include +#include +#include + +namespace clang { +namespace tooling { + +using TextGenerator = MatchConsumer; + +/// Wraps a string as a TextGenerator. +inline TextGenerator text(std::string M) { + return [M](const ast_matchers::MatchFinder::MatchResult &) + -> Expected { return M; }; +} + +// 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. +// +// * Target: the source code impacted by the rule. This identifies an AST node, +// or part thereof (\c Part), whose source range indicates the extent of the +// replacement applied by the replacement term. By default, the extent is the +// node matched by the pattern term (\c NodePart::Node). Target's are typed +// (\c Kind), which guides the determination of the node extent. +// +// * Replacement: a function that produces a replacement string for the target, +// based on the match result. +// +// * Note: (optional) a note specifically for this edit, potentially referencing +// elements of the match. This will be displayed to the user, where possible; +// for example, in clang-tidy diagnostics. Use of notes should be rare -- +// explanations of the entire rewrite should be set in the rule +// (`RewriteRule::Explanation`) instead. Notes serve the rare cases wherein +// edit-specific diagnostics are required. +// +// `ASTEdit` should be built using the `change` convenience functions. For +// example, +// \code +// change(name(fun), text("Frodo")) +// \endcode +// Or, if we use Stencil for the TextGenerator: +// \code +// using stencil::cat; +// change(statement(thenNode), cat("{", thenNode, "}")) +// change(callArgs(call), cat(x, ",", y)) +// \endcode +// Or, if you are changing the node corresponding to the rule's matcher, you can +// use the single-argument override of \c change: +// \code +// change(cat("different_expr")) +// \endcode +struct ASTEdit { + RangeSelector TargetRange; + TextGenerator Replacement; + TextGenerator Note; +}; + +/// Format of the path in an include directive -- angle brackets or quotes. +enum class IncludeFormat { + Quoted, + Angled, +}; + +/// Description of a source-code transformation. +// +// A *rewrite rule* describes a transformation of source code. A simple rule +// contains each of the following components: +// +// * Matcher: the pattern term, expressed as clang matchers (with Transformer +// extensions). +// +// * Edits: a set of Edits to the source code, described with ASTEdits. +// +// * Explanation: explanation of the rewrite. This will be displayed to the +// user, where possible; for example, in clang-tidy diagnostics. +// +// However, rules can also consist of (sub)rules, where the first that matches +// is applied and the rest are ignored. So, the above components are gathered +// as a `Case` and a rule is a list of cases. +// +// Rule cases have an additional, implicit, component: the parameters. These are +// portions of the pattern which are left unspecified, yet bound in the pattern +// so that we can reference them in the edits. +// +// The \c Transformer class can be used to apply the rewrite rule and obtain the +// corresponding replacements. +struct RewriteRule { + struct Case { + ast_matchers::internal::DynTypedMatcher Matcher; + SmallVector Edits; + TextGenerator Explanation; + // Include paths to add to the file affected by this case. These are + // bundled with the `Case`, rather than the `RewriteRule`, because each case + // might have different associated changes to the includes. + std::vector> AddedIncludes; + }; + // We expect RewriteRules will most commonly include only one case. + SmallVector Cases; + + // ID used as the default target of each match. The node described by the + // matcher is should always be bound to this id. + static constexpr llvm::StringLiteral RootID = "___root___"; +}; + +/// Convenience function for constructing a simple \c RewriteRule. +RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M, + SmallVector Edits, + TextGenerator Explanation = nullptr); + +/// Convenience overload of \c makeRule for common case of only one edit. +inline RewriteRule makeRule(ast_matchers::internal::DynTypedMatcher M, + ASTEdit Edit, + TextGenerator Explanation = nullptr) { + SmallVector Edits; + Edits.emplace_back(std::move(Edit)); + return makeRule(std::move(M), std::move(Edits), std::move(Explanation)); +} + +/// For every case in Rule, adds an include directive for the given header. The +/// common use is assumed to be a rule with only one case. For example, to +/// replace a function call and add headers corresponding to the new code, one +/// could write: +/// \code +/// auto R = makeRule(callExpr(callee(functionDecl(hasName("foo")))), +/// change(text("bar()"))); +/// AddInclude(R, "path/to/bar_header.h"); +/// AddInclude(R, "vector", IncludeFormat::Angled); +/// \endcode +void addInclude(RewriteRule &Rule, llvm::StringRef Header, + IncludeFormat Format = IncludeFormat::Quoted); + +/// Applies the first rule whose pattern matches; other rules are ignored. If +/// the matchers are independent then order doesn't matter. In that case, +/// `applyFirst` is simply joining the set of rules into one. +// +// `applyFirst` is like an `anyOf` matcher with an edit action attached to each +// of its cases. Anywhere you'd use `anyOf(m1.bind("id1"), m2.bind("id2"))` and +// then dispatch on those ids in your code for control flow, `applyFirst` lifts +// that behavior to the rule level. So, you can write `applyFirst({makeRule(m1, +// action1), makeRule(m2, action2), ...});` +// +// For example, consider a type `T` with a deterministic serialization function, +// `serialize()`. For performance reasons, we would like to make it +// non-deterministic. Therefore, we want to drop the expectation that +// `a.serialize() = b.serialize() iff a = b` (although we'll maintain +// `deserialize(a.serialize()) = a`). +// +// We have three cases to consider (for some equality function, `eq`): +// ``` +// eq(a.serialize(), b.serialize()) --> eq(a,b) +// eq(a, b.serialize()) --> eq(deserialize(a), b) +// eq(a.serialize(), b) --> eq(a, deserialize(b)) +// ``` +// +// `applyFirst` allows us to specify each independently: +// ``` +// auto eq_fun = functionDecl(...); +// auto method_call = cxxMemberCallExpr(...); +// +// auto two_calls = callExpr(callee(eq_fun), hasArgument(0, method_call), +// hasArgument(1, method_call)); +// auto left_call = +// callExpr(callee(eq_fun), callExpr(hasArgument(0, method_call))); +// auto right_call = +// callExpr(callee(eq_fun), callExpr(hasArgument(1, method_call))); +// +// RewriteRule R = applyFirst({makeRule(two_calls, two_calls_action), +// makeRule(left_call, left_call_action), +// makeRule(right_call, right_call_action)}); +// ``` +RewriteRule applyFirst(ArrayRef Rules); + +/// Replaces a portion of the source text with \p Replacement. +ASTEdit change(RangeSelector Target, TextGenerator Replacement); + +/// Replaces the entirety of a RewriteRule's match with \p Replacement. For +/// example, to replace a function call, one could write: +/// \code +/// makeRule(callExpr(callee(functionDecl(hasName("foo")))), +/// change(text("bar()"))) +/// \endcode +inline ASTEdit change(TextGenerator Replacement) { + return change(node(RewriteRule::RootID), std::move(Replacement)); +} + +/// Inserts \p Replacement before \p S, leaving the source selected by \S +/// unchanged. +inline ASTEdit insertBefore(RangeSelector S, TextGenerator Replacement) { + return change(before(std::move(S)), std::move(Replacement)); +} + +/// Inserts \p Replacement after \p S, leaving the source selected by \S +/// unchanged. +inline ASTEdit insertAfter(RangeSelector S, TextGenerator Replacement) { + return change(after(std::move(S)), std::move(Replacement)); +} + +/// Removes the source selected by \p S. +inline ASTEdit remove(RangeSelector S) { + return change(std::move(S), text("")); +} + +/// 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 +/// users. +// +// FIXME: These functions are really public, if advanced, elements of the +// RewriteRule API. Recast them as such. Or, just declare these functions +// public and well-supported and move them out of `detail`. +namespace detail { +/// Builds a single matcher for the rule, covering all of the rule's cases. +/// Only supports Rules whose cases' matchers share the same base "kind" +/// (`Stmt`, `Decl`, etc.) Deprecated: use `buildMatchers` instead, which +/// supports mixing matchers of different kinds. +ast_matchers::internal::DynTypedMatcher buildMatcher(const RewriteRule &Rule); + +/// Builds a set of matchers that cover the rule (one for each distinct node +/// matcher base kind: Stmt, Decl, etc.). Node-matchers for `QualType` and +/// `Type` are not permitted, since such nodes carry no source location +/// information and are therefore not relevant for rewriting. If any such +/// matchers are included, will return an empty vector. +std::vector +buildMatchers(const RewriteRule &Rule); + +/// Gets the beginning location of the source matched by a rewrite rule. If the +/// match occurs within a macro expansion, returns the beginning of the +/// expansion point. `Result` must come from the matching of a rewrite rule. +SourceLocation +getRuleMatchLoc(const ast_matchers::MatchFinder::MatchResult &Result); + +/// Returns the \c Case of \c Rule that was selected in the match result. +/// Assumes a matcher built with \c buildMatcher. +const RewriteRule::Case & +findSelectedCase(const ast_matchers::MatchFinder::MatchResult &Result, + const RewriteRule &Rule); + +/// A source "transformation," represented by a character range in the source to +/// be replaced and a corresponding replacement string. +struct Transformation { + CharSourceRange Range; + std::string Replacement; +}; + +/// Attempts to translate `Edits`, which are in terms of AST nodes bound in the +/// match `Result`, into Transformations, which are in terms of the source code +/// text. +/// +/// Returns an empty vector if any of the edits apply to portions of the source +/// that are ineligible for rewriting (certain interactions with macros, for +/// example). Fails if any invariants are violated relating to bound nodes in +/// the match. However, it does not fail in the case of conflicting edits -- +/// conflict handling is left to clients. We recommend use of the \c +/// AtomicChange or \c Replacements classes for assistance in detecting such +/// conflicts. +Expected> +translateEdits(const ast_matchers::MatchFinder::MatchResult &Result, + llvm::ArrayRef Edits); +} // namespace detail + +/// Handles the matcher and callback registration for a single rewrite rule, as +/// defined by the arguments of the constructor. +class Transformer : public ast_matchers::MatchFinder::MatchCallback { +public: + using ChangeConsumer = + std::function Change)>; + + /// \param Consumer Receives each rewrite or error. Will not necessarily be + /// called for each match; for example, if the rewrite is not applicable + /// because of macros, but doesn't fail. 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)) {} + + /// N.B. Passes `this` pointer to `MatchFinder`. So, this object should not + /// be moved after this call. + void registerMatchers(ast_matchers::MatchFinder *MatchFinder); + + /// Not called directly by users -- called by the framework, via base class + /// pointer. + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + RewriteRule Rule; + /// Receives each successful rewrites as an \c AtomicChange. + ChangeConsumer Consumer; +}; +} // namespace tooling +} // namespace clang + +#endif // LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_ diff --git a/lib/Tooling/CMakeLists.txt b/lib/Tooling/CMakeLists.txt index 81185d7417..05061f0a10 100644 --- a/lib/Tooling/CMakeLists.txt +++ b/lib/Tooling/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(Refactoring) add_subdirectory(ASTDiff) add_subdirectory(Syntax) add_subdirectory(DependencyScanning) +add_subdirectory(Transformer) add_clang_library(clangTooling AllTUsExecution.cpp diff --git a/lib/Tooling/Refactoring/CMakeLists.txt b/lib/Tooling/Refactoring/CMakeLists.txt index e3961db284..db889d2a06 100644 --- a/lib/Tooling/Refactoring/CMakeLists.txt +++ b/lib/Tooling/Refactoring/CMakeLists.txt @@ -6,17 +6,12 @@ add_clang_library(clangToolingRefactoring AtomicChange.cpp Extract/Extract.cpp Extract/SourceExtraction.cpp - RangeSelector.cpp RefactoringActions.cpp Rename/RenamingAction.cpp Rename/SymbolOccurrences.cpp Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp - SourceCode.cpp - SourceCodeBuilders.cpp - Stencil.cpp - Transformer.cpp LINK_LIBS clangAST diff --git a/lib/Tooling/Refactoring/RangeSelector.cpp b/lib/Tooling/Refactoring/RangeSelector.cpp deleted file mode 100644 index 972c7e6554..0000000000 --- a/lib/Tooling/Refactoring/RangeSelector.cpp +++ /dev/null @@ -1,312 +0,0 @@ -//===--- RangeSelector.cpp - RangeSelector implementations ------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Tooling/Refactoring/RangeSelector.h" -#include "clang/AST/Expr.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Lex/Lexer.h" -#include "clang/Tooling/Refactoring/SourceCode.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" -#include -#include -#include - -using namespace clang; -using namespace tooling; - -using ast_matchers::MatchFinder; -using ast_type_traits::ASTNodeKind; -using ast_type_traits::DynTypedNode; -using llvm::Error; -using llvm::StringError; - -using MatchResult = MatchFinder::MatchResult; - -static Error invalidArgumentError(Twine Message) { - return llvm::make_error(llvm::errc::invalid_argument, Message); -} - -static Error typeError(StringRef ID, const ASTNodeKind &Kind) { - return invalidArgumentError("mismatched type (node id=" + ID + - " kind=" + Kind.asStringRef() + ")"); -} - -static Error typeError(StringRef ID, const ASTNodeKind &Kind, - Twine ExpectedType) { - return invalidArgumentError("mismatched type: expected one of " + - ExpectedType + " (node id=" + ID + - " kind=" + Kind.asStringRef() + ")"); -} - -static Error missingPropertyError(StringRef ID, Twine Description, - StringRef Property) { - return invalidArgumentError(Description + " requires property '" + Property + - "' (node id=" + ID + ")"); -} - -static Expected getNode(const ast_matchers::BoundNodes &Nodes, - StringRef ID) { - auto &NodesMap = Nodes.getMap(); - auto It = NodesMap.find(ID); - if (It == NodesMap.end()) - return invalidArgumentError("ID not bound: " + ID); - return It->second; -} - -// FIXME: handling of macros should be configurable. -static SourceLocation findPreviousTokenStart(SourceLocation Start, - const SourceManager &SM, - const LangOptions &LangOpts) { - if (Start.isInvalid() || Start.isMacroID()) - return SourceLocation(); - - SourceLocation BeforeStart = Start.getLocWithOffset(-1); - if (BeforeStart.isInvalid() || BeforeStart.isMacroID()) - return SourceLocation(); - - return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts); -} - -// Finds the start location of the previous token of kind \p TK. -// FIXME: handling of macros should be configurable. -static SourceLocation findPreviousTokenKind(SourceLocation Start, - const SourceManager &SM, - const LangOptions &LangOpts, - tok::TokenKind TK) { - while (true) { - SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); - if (L.isInvalid() || L.isMacroID()) - return SourceLocation(); - - Token T; - if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) - return SourceLocation(); - - if (T.is(TK)) - return T.getLocation(); - - Start = L; - } -} - -static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM, - const LangOptions &LangOpts) { - SourceLocation EndLoc = - E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc(); - return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren); -} - -RangeSelector tooling::before(RangeSelector Selector) { - return [Selector](const MatchResult &Result) -> Expected { - Expected SelectedRange = Selector(Result); - if (!SelectedRange) - return SelectedRange.takeError(); - return CharSourceRange::getCharRange(SelectedRange->getBegin()); - }; -} - -RangeSelector tooling::after(RangeSelector Selector) { - return [Selector](const MatchResult &Result) -> Expected { - Expected SelectedRange = Selector(Result); - if (!SelectedRange) - return SelectedRange.takeError(); - if (SelectedRange->isCharRange()) - return CharSourceRange::getCharRange(SelectedRange->getEnd()); - return CharSourceRange::getCharRange(Lexer::getLocForEndOfToken( - SelectedRange->getEnd(), 0, Result.Context->getSourceManager(), - Result.Context->getLangOpts())); - }; -} - -RangeSelector tooling::node(std::string ID) { - return [ID](const MatchResult &Result) -> Expected { - Expected Node = getNode(Result.Nodes, ID); - if (!Node) - return Node.takeError(); - return Node->get() != nullptr && Node->get() == nullptr - ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context) - : CharSourceRange::getTokenRange(Node->getSourceRange()); - }; -} - -RangeSelector tooling::statement(std::string ID) { - return [ID](const MatchResult &Result) -> Expected { - Expected Node = getNode(Result.Nodes, ID); - if (!Node) - return Node.takeError(); - return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context); - }; -} - -RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { - return [Begin, End](const MatchResult &Result) -> Expected { - Expected BeginRange = Begin(Result); - if (!BeginRange) - return BeginRange.takeError(); - Expected EndRange = End(Result); - if (!EndRange) - return EndRange.takeError(); - SourceLocation B = BeginRange->getBegin(); - SourceLocation E = EndRange->getEnd(); - // Note: we are precluding the possibility of sub-token ranges in the case - // that EndRange is a token range. - if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) { - return invalidArgumentError("Bad range: out of order"); - } - return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange()); - }; -} - -RangeSelector tooling::range(std::string BeginID, std::string EndID) { - return tooling::range(node(std::move(BeginID)), node(std::move(EndID))); -} - -RangeSelector tooling::member(std::string ID) { - return [ID](const MatchResult &Result) -> Expected { - Expected Node = getNode(Result.Nodes, ID); - if (!Node) - return Node.takeError(); - if (auto *M = Node->get()) - return CharSourceRange::getTokenRange( - M->getMemberNameInfo().getSourceRange()); - return typeError(ID, Node->getNodeKind(), "MemberExpr"); - }; -} - -RangeSelector tooling::name(std::string ID) { - return [ID](const MatchResult &Result) -> Expected { - Expected N = getNode(Result.Nodes, ID); - if (!N) - return N.takeError(); - auto &Node = *N; - if (const auto *D = Node.get()) { - if (!D->getDeclName().isIdentifier()) - return missingPropertyError(ID, "name", "identifier"); - SourceLocation L = D->getLocation(); - auto R = CharSourceRange::getTokenRange(L, L); - // Verify that the range covers exactly the name. - // FIXME: extend this code to support cases like `operator +` or - // `foo` for which this range will be too short. Doing so will - // require subcasing `NamedDecl`, because it doesn't provide virtual - // access to the \c DeclarationNameInfo. - if (getText(R, *Result.Context) != D->getName()) - return CharSourceRange(); - return R; - } - if (const auto *E = Node.get()) { - if (!E->getNameInfo().getName().isIdentifier()) - return missingPropertyError(ID, "name", "identifier"); - SourceLocation L = E->getLocation(); - return CharSourceRange::getTokenRange(L, L); - } - if (const auto *I = Node.get()) { - if (!I->isMemberInitializer() && I->isWritten()) - return missingPropertyError(ID, "name", "explicit member initializer"); - SourceLocation L = I->getMemberLocation(); - return CharSourceRange::getTokenRange(L, L); - } - return typeError(ID, Node.getNodeKind(), - "DeclRefExpr, NamedDecl, CXXCtorInitializer"); - }; -} - -namespace { -// FIXME: make this available in the public API for users to easily create their -// own selectors. - -// Creates a selector from a range-selection function \p Func, which selects a -// range that is relative to a bound node id. \c T is the node type expected by -// \p Func. -template -class RelativeSelector { - std::string ID; - -public: - RelativeSelector(std::string ID) : ID(std::move(ID)) {} - - Expected operator()(const MatchResult &Result) { - Expected N = getNode(Result.Nodes, ID); - if (!N) - return N.takeError(); - if (const auto *Arg = N->get()) - return Func(Result, *Arg); - return typeError(ID, N->getNodeKind()); - } -}; -} // namespace - -// FIXME: Change the following functions from being in an anonymous namespace -// to static functions, after the minimum Visual C++ has _MSC_VER >= 1915 -// (equivalent to Visual Studio 2017 v15.8 or higher). Using the anonymous -// namespace works around a bug in earlier versions. -namespace { -// Returns the range of the statements (all source between the braces). -CharSourceRange getStatementsRange(const MatchResult &, - const CompoundStmt &CS) { - return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1), - CS.getRBracLoc()); -} -} // namespace - -RangeSelector tooling::statements(std::string ID) { - return RelativeSelector(std::move(ID)); -} - -namespace { -// Returns the range of the source between the call's parentheses. -CharSourceRange getCallArgumentsRange(const MatchResult &Result, - const CallExpr &CE) { - return CharSourceRange::getCharRange( - findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts()) - .getLocWithOffset(1), - CE.getRParenLoc()); -} -} // namespace - -RangeSelector tooling::callArgs(std::string ID) { - return RelativeSelector(std::move(ID)); -} - -namespace { -// Returns the range of the elements of the initializer list. Includes all -// source between the braces. -CharSourceRange getElementsRange(const MatchResult &, - const InitListExpr &E) { - return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1), - E.getRBraceLoc()); -} -} // namespace - -RangeSelector tooling::initListElements(std::string ID) { - return RelativeSelector(std::move(ID)); -} - -namespace { -// Returns the range of the else branch, including the `else` keyword. -CharSourceRange getElseRange(const MatchResult &Result, const IfStmt &S) { - return maybeExtendRange( - CharSourceRange::getTokenRange(S.getElseLoc(), S.getEndLoc()), - tok::TokenKind::semi, *Result.Context); -} -} // namespace - -RangeSelector tooling::elseBranch(std::string ID) { - return RelativeSelector(std::move(ID)); -} - -RangeSelector tooling::expansion(RangeSelector S) { - return [S](const MatchResult &Result) -> Expected { - Expected SRange = S(Result); - if (!SRange) - return SRange.takeError(); - return Result.SourceManager->getExpansionRange(*SRange); - }; -} diff --git a/lib/Tooling/Refactoring/SourceCode.cpp b/lib/Tooling/Refactoring/SourceCode.cpp deleted file mode 100644 index cee8f43f3e..0000000000 --- a/lib/Tooling/Refactoring/SourceCode.cpp +++ /dev/null @@ -1,65 +0,0 @@ -//===--- SourceCode.cpp - Source code manipulation routines -----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file provides functions that simplify extraction of source code. -// -//===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/SourceCode.h" -#include "clang/Lex/Lexer.h" - -using namespace clang; - -StringRef clang::tooling::getText(CharSourceRange Range, - const ASTContext &Context) { - return Lexer::getSourceText(Range, Context.getSourceManager(), - Context.getLangOpts()); -} - -CharSourceRange clang::tooling::maybeExtendRange(CharSourceRange Range, - tok::TokenKind Next, - ASTContext &Context) { - Optional Tok = Lexer::findNextToken( - Range.getEnd(), Context.getSourceManager(), Context.getLangOpts()); - if (!Tok || !Tok->is(Next)) - return Range; - return CharSourceRange::getTokenRange(Range.getBegin(), Tok->getLocation()); -} - -llvm::Optional -clang::tooling::getRangeForEdit(const CharSourceRange &EditRange, - const SourceManager &SM, - const LangOptions &LangOpts) { - // FIXME: makeFileCharRange() has the disadvantage of stripping off "identity" - // macros. For example, if we're looking to rewrite the int literal 3 to 6, - // and we have the following definition: - // #define DO_NOTHING(x) x - // then - // foo(DO_NOTHING(3)) - // will be rewritten to - // foo(6) - // rather than the arguably better - // foo(DO_NOTHING(6)) - // Decide whether the current behavior is desirable and modify if not. - CharSourceRange Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); - if (Range.isInvalid()) - return None; - - if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) - return None; - if (SM.isInSystemHeader(Range.getBegin()) || - SM.isInSystemHeader(Range.getEnd())) - return None; - - std::pair BeginInfo = SM.getDecomposedLoc(Range.getBegin()); - std::pair EndInfo = SM.getDecomposedLoc(Range.getEnd()); - if (BeginInfo.first != EndInfo.first || - BeginInfo.second > EndInfo.second) - return None; - - return Range; -} diff --git a/lib/Tooling/Refactoring/SourceCodeBuilders.cpp b/lib/Tooling/Refactoring/SourceCodeBuilders.cpp deleted file mode 100644 index 2499c0f1eb..0000000000 --- a/lib/Tooling/Refactoring/SourceCodeBuilders.cpp +++ /dev/null @@ -1,160 +0,0 @@ -//===--- SourceCodeBuilder.cpp ----------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Tooling/Refactoring/SourceCodeBuilders.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Expr.h" -#include "clang/AST/ExprCXX.h" -#include "clang/Tooling/Refactoring/SourceCode.h" -#include "llvm/ADT/Twine.h" -#include - -using namespace clang; -using namespace tooling; - -const Expr *tooling::reallyIgnoreImplicit(const Expr &E) { - const Expr *Expr = E.IgnoreImplicit(); - if (const auto *CE = dyn_cast(Expr)) { - if (CE->getNumArgs() > 0 && - CE->getArg(0)->getSourceRange() == Expr->getSourceRange()) - return CE->getArg(0)->IgnoreImplicit(); - } - return Expr; -} - -bool tooling::mayEverNeedParens(const Expr &E) { - const Expr *Expr = reallyIgnoreImplicit(E); - // We always want parens around unary, binary, and ternary operators, because - // they are lower precedence. - if (isa(Expr) || isa(Expr) || - isa(Expr)) - return true; - - // We need parens around calls to all overloaded operators except: function - // calls, subscripts, and expressions that are already part of an (implicit) - // call to operator->. These latter are all in the same precedence level as - // dot/arrow and that level is left associative, so they don't need parens - // when appearing on the left. - if (const auto *Op = dyn_cast(Expr)) - return Op->getOperator() != OO_Call && Op->getOperator() != OO_Subscript && - Op->getOperator() != OO_Arrow; - - return false; -} - -bool tooling::needParensAfterUnaryOperator(const Expr &E) { - const Expr *Expr = reallyIgnoreImplicit(E); - if (isa(Expr) || isa(Expr)) - return true; - - if (const auto *Op = dyn_cast(Expr)) - return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && - Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && - Op->getOperator() != OO_Subscript; - - return false; -} - -llvm::Optional tooling::buildParens(const Expr &E, - const ASTContext &Context) { - StringRef Text = getText(E, Context); - if (Text.empty()) - return llvm::None; - if (mayEverNeedParens(E)) - return ("(" + Text + ")").str(); - return Text.str(); -} - -llvm::Optional -tooling::buildDereference(const Expr &E, const ASTContext &Context) { - if (const auto *Op = dyn_cast(&E)) - if (Op->getOpcode() == UO_AddrOf) { - // Strip leading '&'. - StringRef Text = - getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); - if (Text.empty()) - return llvm::None; - return Text.str(); - } - - StringRef Text = getText(E, Context); - if (Text.empty()) - return llvm::None; - // Add leading '*'. - if (needParensAfterUnaryOperator(E)) - return ("*(" + Text + ")").str(); - return ("*" + Text).str(); -} - -llvm::Optional tooling::buildAddressOf(const Expr &E, - const ASTContext &Context) { - if (const auto *Op = dyn_cast(&E)) - if (Op->getOpcode() == UO_Deref) { - // Strip leading '*'. - StringRef Text = - getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); - if (Text.empty()) - return llvm::None; - return Text.str(); - } - // Add leading '&'. - StringRef Text = getText(E, Context); - if (Text.empty()) - return llvm::None; - if (needParensAfterUnaryOperator(E)) { - return ("&(" + Text + ")").str(); - } - return ("&" + Text).str(); -} - -llvm::Optional tooling::buildDot(const Expr &E, - const ASTContext &Context) { - if (const auto *Op = llvm::dyn_cast(&E)) - if (Op->getOpcode() == UO_Deref) { - // Strip leading '*', add following '->'. - const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); - StringRef DerefText = getText(*SubExpr, Context); - if (DerefText.empty()) - return llvm::None; - if (needParensBeforeDotOrArrow(*SubExpr)) - return ("(" + DerefText + ")->").str(); - return (DerefText + "->").str(); - } - - // Add following '.'. - StringRef Text = getText(E, Context); - if (Text.empty()) - return llvm::None; - if (needParensBeforeDotOrArrow(E)) { - return ("(" + Text + ").").str(); - } - return (Text + ".").str(); -} - -llvm::Optional tooling::buildArrow(const Expr &E, - const ASTContext &Context) { - if (const auto *Op = llvm::dyn_cast(&E)) - if (Op->getOpcode() == UO_AddrOf) { - // Strip leading '&', add following '.'. - const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); - StringRef DerefText = getText(*SubExpr, Context); - if (DerefText.empty()) - return llvm::None; - if (needParensBeforeDotOrArrow(*SubExpr)) - return ("(" + DerefText + ").").str(); - return (DerefText + ".").str(); - } - - // Add following '->'. - StringRef Text = getText(E, Context); - if (Text.empty()) - return llvm::None; - if (needParensBeforeDotOrArrow(E)) - return ("(" + Text + ")->").str(); - return (Text + "->").str(); -} diff --git a/lib/Tooling/Refactoring/Stencil.cpp b/lib/Tooling/Refactoring/Stencil.cpp deleted file mode 100644 index ebfe78099d..0000000000 --- a/lib/Tooling/Refactoring/Stencil.cpp +++ /dev/null @@ -1,369 +0,0 @@ -//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Tooling/Refactoring/Stencil.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/ASTTypeTraits.h" -#include "clang/AST/Expr.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Lex/Lexer.h" -#include "clang/Tooling/Refactoring/SourceCode.h" -#include "clang/Tooling/Refactoring/SourceCodeBuilders.h" -#include "llvm/ADT/Twine.h" -#include "llvm/Support/Errc.h" -#include -#include -#include - -using namespace clang; -using namespace tooling; - -using ast_matchers::MatchFinder; -using ast_type_traits::DynTypedNode; -using llvm::errc; -using llvm::Error; -using llvm::Expected; -using llvm::StringError; - -// A down_cast function to safely down cast a StencilPartInterface to a subclass -// D. Returns nullptr if P is not an instance of D. -template const D *down_cast(const StencilPartInterface *P) { - if (P == nullptr || D::typeId() != P->typeId()) - return nullptr; - return static_cast(P); -} - -static llvm::Expected -getNode(const ast_matchers::BoundNodes &Nodes, StringRef Id) { - auto &NodesMap = Nodes.getMap(); - auto It = NodesMap.find(Id); - if (It == NodesMap.end()) - return llvm::make_error(llvm::errc::invalid_argument, - "Id not bound: " + Id); - return It->second; -} - -namespace { -// An arbitrary fragment of code within a stencil. -struct RawTextData { - explicit RawTextData(std::string T) : Text(std::move(T)) {} - std::string Text; -}; - -// A debugging operation to dump the AST for a particular (bound) AST node. -struct DebugPrintNodeData { - explicit DebugPrintNodeData(std::string S) : Id(std::move(S)) {} - std::string Id; -}; - -// Operators that take a single node Id as an argument. -enum class UnaryNodeOperator { - Parens, - Deref, - Address, -}; - -// Generic container for stencil operations with a (single) node-id argument. -struct UnaryOperationData { - UnaryOperationData(UnaryNodeOperator Op, std::string Id) - : Op(Op), Id(std::move(Id)) {} - UnaryNodeOperator Op; - std::string Id; -}; - -// The fragment of code corresponding to the selected range. -struct SelectorData { - explicit SelectorData(RangeSelector S) : Selector(std::move(S)) {} - RangeSelector Selector; -}; - -// A stencil operation to build a member access `e.m` or `e->m`, as appropriate. -struct AccessData { - AccessData(StringRef BaseId, StencilPart Member) - : BaseId(BaseId), Member(std::move(Member)) {} - std::string BaseId; - StencilPart Member; -}; - -struct IfBoundData { - IfBoundData(StringRef Id, StencilPart TruePart, StencilPart FalsePart) - : Id(Id), TruePart(std::move(TruePart)), FalsePart(std::move(FalsePart)) { - } - std::string Id; - StencilPart TruePart; - StencilPart FalsePart; -}; - -bool isEqualData(const RawTextData &A, const RawTextData &B) { - return A.Text == B.Text; -} - -bool isEqualData(const DebugPrintNodeData &A, const DebugPrintNodeData &B) { - return A.Id == B.Id; -} - -bool isEqualData(const UnaryOperationData &A, const UnaryOperationData &B) { - return A.Op == B.Op && A.Id == B.Id; -} - -// Equality is not (yet) defined for \c RangeSelector. -bool isEqualData(const SelectorData &, const SelectorData &) { return false; } - -bool isEqualData(const AccessData &A, const AccessData &B) { - return A.BaseId == B.BaseId && A.Member == B.Member; -} - -bool isEqualData(const IfBoundData &A, const IfBoundData &B) { - return A.Id == B.Id && A.TruePart == B.TruePart && A.FalsePart == B.FalsePart; -} - -// Equality is not defined over MatchConsumers, which are opaque. -bool isEqualData(const MatchConsumer &A, - const MatchConsumer &B) { - return false; -} - -std::string toStringData(const RawTextData &Data) { - std::string Result; - llvm::raw_string_ostream OS(Result); - OS << "\""; - OS.write_escaped(Data.Text); - OS << "\""; - OS.flush(); - return Result; -} - -std::string toStringData(const DebugPrintNodeData &Data) { - return (llvm::Twine("dPrint(\"") + Data.Id + "\")").str(); -} - -std::string toStringData(const UnaryOperationData &Data) { - StringRef OpName; - switch (Data.Op) { - case UnaryNodeOperator::Parens: - OpName = "expression"; - break; - case UnaryNodeOperator::Deref: - OpName = "deref"; - break; - case UnaryNodeOperator::Address: - OpName = "addressOf"; - break; - } - return (OpName + "(\"" + Data.Id + "\")").str(); -} - -std::string toStringData(const SelectorData &) { return "SelectorData()"; } - -std::string toStringData(const AccessData &Data) { - return (llvm::Twine("access(\"") + Data.BaseId + "\", " + - Data.Member.toString() + ")") - .str(); -} - -std::string toStringData(const IfBoundData &Data) { - return (llvm::Twine("ifBound(\"") + Data.Id + "\", " + - Data.TruePart.toString() + ", " + Data.FalsePart.toString() + ")") - .str(); -} - -std::string toStringData(const MatchConsumer &) { - return "MatchConsumer()"; -} - -// The `evalData()` overloads evaluate the given stencil data to a string, given -// the match result, and append it to `Result`. We define an overload for each -// type of stencil data. - -Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &, - std::string *Result) { - Result->append(Data.Text); - return Error::success(); -} - -Error evalData(const DebugPrintNodeData &Data, - const MatchFinder::MatchResult &Match, std::string *Result) { - std::string Output; - llvm::raw_string_ostream Os(Output); - auto NodeOrErr = getNode(Match.Nodes, Data.Id); - if (auto Err = NodeOrErr.takeError()) - return Err; - NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts())); - *Result += Os.str(); - return Error::success(); -} - -Error evalData(const UnaryOperationData &Data, - const MatchFinder::MatchResult &Match, std::string *Result) { - const auto *E = Match.Nodes.getNodeAs(Data.Id); - if (E == nullptr) - return llvm::make_error( - errc::invalid_argument, "Id not bound or not Expr: " + Data.Id); - llvm::Optional Source; - switch (Data.Op) { - case UnaryNodeOperator::Parens: - Source = buildParens(*E, *Match.Context); - break; - case UnaryNodeOperator::Deref: - Source = buildDereference(*E, *Match.Context); - break; - case UnaryNodeOperator::Address: - Source = buildAddressOf(*E, *Match.Context); - break; - } - if (!Source) - return llvm::make_error( - errc::invalid_argument, - "Could not construct expression source from ID: " + Data.Id); - *Result += *Source; - return Error::success(); -} - -Error evalData(const SelectorData &Data, const MatchFinder::MatchResult &Match, - std::string *Result) { - auto Range = Data.Selector(Match); - if (!Range) - return Range.takeError(); - *Result += getText(*Range, *Match.Context); - return Error::success(); -} - -Error evalData(const AccessData &Data, const MatchFinder::MatchResult &Match, - std::string *Result) { - const auto *E = Match.Nodes.getNodeAs(Data.BaseId); - if (E == nullptr) - return llvm::make_error(errc::invalid_argument, - "Id not bound: " + Data.BaseId); - if (!E->isImplicitCXXThis()) { - if (llvm::Optional S = E->getType()->isAnyPointerType() - ? buildArrow(*E, *Match.Context) - : buildDot(*E, *Match.Context)) - *Result += *S; - else - return llvm::make_error( - errc::invalid_argument, - "Could not construct object text from ID: " + Data.BaseId); - } - return Data.Member.eval(Match, Result); -} - -Error evalData(const IfBoundData &Data, const MatchFinder::MatchResult &Match, - std::string *Result) { - auto &M = Match.Nodes.getMap(); - return (M.find(Data.Id) != M.end() ? Data.TruePart : Data.FalsePart) - .eval(Match, Result); -} - -Error evalData(const MatchConsumer &Fn, - const MatchFinder::MatchResult &Match, std::string *Result) { - Expected Value = Fn(Match); - if (!Value) - return Value.takeError(); - *Result += *Value; - return Error::success(); -} - -template -class StencilPartImpl : public StencilPartInterface { - T Data; - -public: - template - explicit StencilPartImpl(Ps &&... Args) - : StencilPartInterface(StencilPartImpl::typeId()), - Data(std::forward(Args)...) {} - - // Generates a unique identifier for this class (specifically, one per - // instantiation of the template). - static const void* typeId() { - static bool b; - return &b; - } - - Error eval(const MatchFinder::MatchResult &Match, - std::string *Result) const override { - return evalData(Data, Match, Result); - } - - bool isEqual(const StencilPartInterface &Other) const override { - if (const auto *OtherPtr = down_cast(&Other)) - return isEqualData(Data, OtherPtr->Data); - return false; - } - - std::string toString() const override { return toStringData(Data); } -}; -} // namespace - -StencilPart Stencil::wrap(StringRef Text) { - return stencil::text(Text); -} - -StencilPart Stencil::wrap(RangeSelector Selector) { - return stencil::selection(std::move(Selector)); -} - -void Stencil::append(Stencil OtherStencil) { - for (auto &Part : OtherStencil.Parts) - Parts.push_back(std::move(Part)); -} - -llvm::Expected -Stencil::eval(const MatchFinder::MatchResult &Match) const { - std::string Result; - for (const auto &Part : Parts) - if (auto Err = Part.eval(Match, &Result)) - return std::move(Err); - return Result; -} - -StencilPart stencil::text(StringRef Text) { - return StencilPart(std::make_shared>(Text)); -} - -StencilPart stencil::selection(RangeSelector Selector) { - return StencilPart( - std::make_shared>(std::move(Selector))); -} - -StencilPart stencil::dPrint(StringRef Id) { - return StencilPart(std::make_shared>(Id)); -} - -StencilPart stencil::expression(llvm::StringRef Id) { - return StencilPart(std::make_shared>( - UnaryNodeOperator::Parens, Id)); -} - -StencilPart stencil::deref(llvm::StringRef ExprId) { - return StencilPart(std::make_shared>( - UnaryNodeOperator::Deref, ExprId)); -} - -StencilPart stencil::addressOf(llvm::StringRef ExprId) { - return StencilPart(std::make_shared>( - UnaryNodeOperator::Address, ExprId)); -} - -StencilPart stencil::access(StringRef BaseId, StencilPart Member) { - return StencilPart( - std::make_shared>(BaseId, std::move(Member))); -} - -StencilPart stencil::ifBound(StringRef Id, StencilPart TruePart, - StencilPart FalsePart) { - return StencilPart(std::make_shared>( - Id, std::move(TruePart), std::move(FalsePart))); -} - -StencilPart stencil::run(MatchConsumer Fn) { - return StencilPart( - std::make_shared>>( - std::move(Fn))); -} diff --git a/lib/Tooling/Refactoring/Transformer.cpp b/lib/Tooling/Refactoring/Transformer.cpp deleted file mode 100644 index 905d594444..0000000000 --- a/lib/Tooling/Refactoring/Transformer.cpp +++ /dev/null @@ -1,235 +0,0 @@ -//===--- Transformer.cpp - Transformer library implementation ---*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/Tooling/Refactoring/Transformer.h" -#include "clang/AST/Expr.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/Refactoring/AtomicChange.h" -#include "clang/Tooling/Refactoring/SourceCode.h" -#include "llvm/ADT/Optional.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" -#include -#include -#include -#include - -using namespace clang; -using namespace tooling; - -using ast_matchers::MatchFinder; -using ast_matchers::internal::DynTypedMatcher; -using ast_type_traits::ASTNodeKind; -using ast_type_traits::DynTypedNode; -using llvm::Error; -using llvm::StringError; - -using MatchResult = MatchFinder::MatchResult; - -Expected> -tooling::detail::translateEdits(const MatchResult &Result, - llvm::ArrayRef Edits) { - SmallVector Transformations; - for (const auto &Edit : Edits) { - Expected Range = Edit.TargetRange(Result); - if (!Range) - return Range.takeError(); - llvm::Optional EditRange = - getRangeForEdit(*Range, *Result.Context); - // FIXME: let user specify whether to treat this case as an error or ignore - // it as is currently done. - if (!EditRange) - return SmallVector(); - auto Replacement = Edit.Replacement(Result); - if (!Replacement) - return Replacement.takeError(); - tooling::detail::Transformation T; - T.Range = *EditRange; - T.Replacement = std::move(*Replacement); - Transformations.push_back(std::move(T)); - } - return Transformations; -} - -ASTEdit tooling::change(RangeSelector S, TextGenerator Replacement) { - ASTEdit E; - E.TargetRange = std::move(S); - E.Replacement = std::move(Replacement); - return E; -} - -RewriteRule tooling::makeRule(DynTypedMatcher M, SmallVector Edits, - TextGenerator Explanation) { - return RewriteRule{{RewriteRule::Case{ - std::move(M), std::move(Edits), std::move(Explanation), {}}}}; -} - -void tooling::addInclude(RewriteRule &Rule, StringRef Header, - IncludeFormat Format) { - for (auto &Case : Rule.Cases) - Case.AddedIncludes.emplace_back(Header.str(), Format); -} - -#ifndef NDEBUG -// Filters for supported matcher kinds. FIXME: Explicitly list the allowed kinds -// (all node matcher types except for `QualType` and `Type`), rather than just -// banning `QualType` and `Type`. -static bool hasValidKind(const DynTypedMatcher &M) { - return !M.canConvertTo(); -} -#endif - -// Binds each rule's matcher to a unique (and deterministic) tag based on -// `TagBase` and the id paired with the case. -static std::vector taggedMatchers( - StringRef TagBase, - const SmallVectorImpl> &Cases) { - std::vector Matchers; - Matchers.reserve(Cases.size()); - for (const auto &Case : Cases) { - std::string Tag = (TagBase + Twine(Case.first)).str(); - // HACK: Many matchers are not bindable, so ensure that tryBind will work. - DynTypedMatcher BoundMatcher(Case.second.Matcher); - BoundMatcher.setAllowBind(true); - auto M = BoundMatcher.tryBind(Tag); - Matchers.push_back(*std::move(M)); - } - return Matchers; -} - -// Simply gathers the contents of the various rules into a single rule. The -// actual work to combine these into an ordered choice is deferred to matcher -// registration. -RewriteRule tooling::applyFirst(ArrayRef Rules) { - RewriteRule R; - for (auto &Rule : Rules) - R.Cases.append(Rule.Cases.begin(), Rule.Cases.end()); - return R; -} - -std::vector -tooling::detail::buildMatchers(const RewriteRule &Rule) { - // Map the cases into buckets of matchers -- one for each "root" AST kind, - // which guarantees that they can be combined in a single anyOf matcher. Each - // case is paired with an identifying number that is converted to a string id - // in `taggedMatchers`. - std::map, 1>> - Buckets; - const SmallVectorImpl &Cases = Rule.Cases; - for (int I = 0, N = Cases.size(); I < N; ++I) { - assert(hasValidKind(Cases[I].Matcher) && - "Matcher must be non-(Qual)Type node matcher"); - Buckets[Cases[I].Matcher.getSupportedKind()].emplace_back(I, Cases[I]); - } - - std::vector Matchers; - for (const auto &Bucket : Buckets) { - DynTypedMatcher M = DynTypedMatcher::constructVariadic( - DynTypedMatcher::VO_AnyOf, Bucket.first, - taggedMatchers("Tag", Bucket.second)); - M.setAllowBind(true); - // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true. - Matchers.push_back(*M.tryBind(RewriteRule::RootID)); - } - return Matchers; -} - -DynTypedMatcher tooling::detail::buildMatcher(const RewriteRule &Rule) { - std::vector Ms = buildMatchers(Rule); - assert(Ms.size() == 1 && "Cases must have compatible matchers."); - return Ms[0]; -} - -SourceLocation tooling::detail::getRuleMatchLoc(const MatchResult &Result) { - auto &NodesMap = Result.Nodes.getMap(); - auto Root = NodesMap.find(RewriteRule::RootID); - assert(Root != NodesMap.end() && "Transformation failed: missing root node."); - llvm::Optional RootRange = getRangeForEdit( - CharSourceRange::getTokenRange(Root->second.getSourceRange()), - *Result.Context); - if (RootRange) - return RootRange->getBegin(); - // The match doesn't have a coherent range, so fall back to the expansion - // location as the "beginning" of the match. - return Result.SourceManager->getExpansionLoc( - Root->second.getSourceRange().getBegin()); -} - -// Finds the case that was "selected" -- that is, whose matcher triggered the -// `MatchResult`. -const RewriteRule::Case & -tooling::detail::findSelectedCase(const MatchResult &Result, - const RewriteRule &Rule) { - if (Rule.Cases.size() == 1) - return Rule.Cases[0]; - - auto &NodesMap = Result.Nodes.getMap(); - for (size_t i = 0, N = Rule.Cases.size(); i < N; ++i) { - std::string Tag = ("Tag" + Twine(i)).str(); - if (NodesMap.find(Tag) != NodesMap.end()) - return Rule.Cases[i]; - } - llvm_unreachable("No tag found for this rule."); -} - -constexpr llvm::StringLiteral RewriteRule::RootID; - -void Transformer::registerMatchers(MatchFinder *MatchFinder) { - for (auto &Matcher : tooling::detail::buildMatchers(Rule)) - MatchFinder->addDynamicMatcher(Matcher, this); -} - -void Transformer::run(const MatchResult &Result) { - if (Result.Context->getDiagnostics().hasErrorOccurred()) - return; - - RewriteRule::Case Case = tooling::detail::findSelectedCase(Result, Rule); - auto Transformations = tooling::detail::translateEdits(Result, Case.Edits); - if (!Transformations) { - Consumer(Transformations.takeError()); - return; - } - - if (Transformations->empty()) { - // No rewrite applied (but no error encountered either). - detail::getRuleMatchLoc(Result).print( - llvm::errs() << "note: skipping match at loc ", *Result.SourceManager); - llvm::errs() << "\n"; - return; - } - - // Record the results in the AtomicChange, anchored at the location of the - // first change. - AtomicChange AC(*Result.SourceManager, - (*Transformations)[0].Range.getBegin()); - for (const auto &T : *Transformations) { - if (auto Err = AC.replace(*Result.SourceManager, T.Range, T.Replacement)) { - Consumer(std::move(Err)); - return; - } - } - - for (const auto &I : Case.AddedIncludes) { - auto &Header = I.first; - switch (I.second) { - case IncludeFormat::Quoted: - AC.addHeader(Header); - break; - case IncludeFormat::Angled: - AC.addHeader((llvm::Twine("<") + Header + ">").str()); - break; - } - } - - Consumer(std::move(AC)); -} diff --git a/lib/Tooling/Transformer/CMakeLists.txt b/lib/Tooling/Transformer/CMakeLists.txt new file mode 100644 index 0000000000..2e9ba58862 --- /dev/null +++ b/lib/Tooling/Transformer/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS Support) + +add_clang_library(clangTransformer + RangeSelector.cpp + SourceCode.cpp + SourceCodeBuilders.cpp + Stencil.cpp + Transformer.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangToolingCore + clangToolingRefactoring + ) diff --git a/lib/Tooling/Transformer/RangeSelector.cpp b/lib/Tooling/Transformer/RangeSelector.cpp new file mode 100644 index 0000000000..496c2d987c --- /dev/null +++ b/lib/Tooling/Transformer/RangeSelector.cpp @@ -0,0 +1,312 @@ +//===--- RangeSelector.cpp - RangeSelector implementations ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Transformer/RangeSelector.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include +#include +#include + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using ast_type_traits::ASTNodeKind; +using ast_type_traits::DynTypedNode; +using llvm::Error; +using llvm::StringError; + +using MatchResult = MatchFinder::MatchResult; + +static Error invalidArgumentError(Twine Message) { + return llvm::make_error(llvm::errc::invalid_argument, Message); +} + +static Error typeError(StringRef ID, const ASTNodeKind &Kind) { + return invalidArgumentError("mismatched type (node id=" + ID + + " kind=" + Kind.asStringRef() + ")"); +} + +static Error typeError(StringRef ID, const ASTNodeKind &Kind, + Twine ExpectedType) { + return invalidArgumentError("mismatched type: expected one of " + + ExpectedType + " (node id=" + ID + + " kind=" + Kind.asStringRef() + ")"); +} + +static Error missingPropertyError(StringRef ID, Twine Description, + StringRef Property) { + return invalidArgumentError(Description + " requires property '" + Property + + "' (node id=" + ID + ")"); +} + +static Expected getNode(const ast_matchers::BoundNodes &Nodes, + StringRef ID) { + auto &NodesMap = Nodes.getMap(); + auto It = NodesMap.find(ID); + if (It == NodesMap.end()) + return invalidArgumentError("ID not bound: " + ID); + return It->second; +} + +// FIXME: handling of macros should be configurable. +static SourceLocation findPreviousTokenStart(SourceLocation Start, + const SourceManager &SM, + const LangOptions &LangOpts) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); + + SourceLocation BeforeStart = Start.getLocWithOffset(-1); + if (BeforeStart.isInvalid() || BeforeStart.isMacroID()) + return SourceLocation(); + + return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts); +} + +// Finds the start location of the previous token of kind \p TK. +// FIXME: handling of macros should be configurable. +static SourceLocation findPreviousTokenKind(SourceLocation Start, + const SourceManager &SM, + const LangOptions &LangOpts, + tok::TokenKind TK) { + while (true) { + SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); + if (L.isInvalid() || L.isMacroID()) + return SourceLocation(); + + Token T; + if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) + return SourceLocation(); + + if (T.is(TK)) + return T.getLocation(); + + Start = L; + } +} + +static SourceLocation findOpenParen(const CallExpr &E, const SourceManager &SM, + const LangOptions &LangOpts) { + SourceLocation EndLoc = + E.getNumArgs() == 0 ? E.getRParenLoc() : E.getArg(0)->getBeginLoc(); + return findPreviousTokenKind(EndLoc, SM, LangOpts, tok::TokenKind::l_paren); +} + +RangeSelector tooling::before(RangeSelector Selector) { + return [Selector](const MatchResult &Result) -> Expected { + Expected SelectedRange = Selector(Result); + if (!SelectedRange) + return SelectedRange.takeError(); + return CharSourceRange::getCharRange(SelectedRange->getBegin()); + }; +} + +RangeSelector tooling::after(RangeSelector Selector) { + return [Selector](const MatchResult &Result) -> Expected { + Expected SelectedRange = Selector(Result); + if (!SelectedRange) + return SelectedRange.takeError(); + if (SelectedRange->isCharRange()) + return CharSourceRange::getCharRange(SelectedRange->getEnd()); + return CharSourceRange::getCharRange(Lexer::getLocForEndOfToken( + SelectedRange->getEnd(), 0, Result.Context->getSourceManager(), + Result.Context->getLangOpts())); + }; +} + +RangeSelector tooling::node(std::string ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + return Node->get() != nullptr && Node->get() == nullptr + ? getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context) + : CharSourceRange::getTokenRange(Node->getSourceRange()); + }; +} + +RangeSelector tooling::statement(std::string ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + return getExtendedRange(*Node, tok::TokenKind::semi, *Result.Context); + }; +} + +RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { + return [Begin, End](const MatchResult &Result) -> Expected { + Expected BeginRange = Begin(Result); + if (!BeginRange) + return BeginRange.takeError(); + Expected EndRange = End(Result); + if (!EndRange) + return EndRange.takeError(); + SourceLocation B = BeginRange->getBegin(); + SourceLocation E = EndRange->getEnd(); + // Note: we are precluding the possibility of sub-token ranges in the case + // that EndRange is a token range. + if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) { + return invalidArgumentError("Bad range: out of order"); + } + return CharSourceRange(SourceRange(B, E), EndRange->isTokenRange()); + }; +} + +RangeSelector tooling::range(std::string BeginID, std::string EndID) { + return tooling::range(node(std::move(BeginID)), node(std::move(EndID))); +} + +RangeSelector tooling::member(std::string ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected Node = getNode(Result.Nodes, ID); + if (!Node) + return Node.takeError(); + if (auto *M = Node->get()) + return CharSourceRange::getTokenRange( + M->getMemberNameInfo().getSourceRange()); + return typeError(ID, Node->getNodeKind(), "MemberExpr"); + }; +} + +RangeSelector tooling::name(std::string ID) { + return [ID](const MatchResult &Result) -> Expected { + Expected N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + auto &Node = *N; + if (const auto *D = Node.get()) { + if (!D->getDeclName().isIdentifier()) + return missingPropertyError(ID, "name", "identifier"); + SourceLocation L = D->getLocation(); + auto R = CharSourceRange::getTokenRange(L, L); + // Verify that the range covers exactly the name. + // FIXME: extend this code to support cases like `operator +` or + // `foo` for which this range will be too short. Doing so will + // require subcasing `NamedDecl`, because it doesn't provide virtual + // access to the \c DeclarationNameInfo. + if (getText(R, *Result.Context) != D->getName()) + return CharSourceRange(); + return R; + } + if (const auto *E = Node.get()) { + if (!E->getNameInfo().getName().isIdentifier()) + return missingPropertyError(ID, "name", "identifier"); + SourceLocation L = E->getLocation(); + return CharSourceRange::getTokenRange(L, L); + } + if (const auto *I = Node.get()) { + if (!I->isMemberInitializer() && I->isWritten()) + return missingPropertyError(ID, "name", "explicit member initializer"); + SourceLocation L = I->getMemberLocation(); + return CharSourceRange::getTokenRange(L, L); + } + return typeError(ID, Node.getNodeKind(), + "DeclRefExpr, NamedDecl, CXXCtorInitializer"); + }; +} + +namespace { +// FIXME: make this available in the public API for users to easily create their +// own selectors. + +// Creates a selector from a range-selection function \p Func, which selects a +// range that is relative to a bound node id. \c T is the node type expected by +// \p Func. +template +class RelativeSelector { + std::string ID; + +public: + RelativeSelector(std::string ID) : ID(std::move(ID)) {} + + Expected operator()(const MatchResult &Result) { + Expected N = getNode(Result.Nodes, ID); + if (!N) + return N.takeError(); + if (const auto *Arg = N->get()) + return Func(Result, *Arg); + return typeError(ID, N->getNodeKind()); + } +}; +} // namespace + +// FIXME: Change the following functions from being in an anonymous namespace +// to static functions, after the minimum Visual C++ has _MSC_VER >= 1915 +// (equivalent to Visual Studio 2017 v15.8 or higher). Using the anonymous +// namespace works around a bug in earlier versions. +namespace { +// Returns the range of the statements (all source between the braces). +CharSourceRange getStatementsRange(const MatchResult &, + const CompoundStmt &CS) { + return CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1), + CS.getRBracLoc()); +} +} // namespace + +RangeSelector tooling::statements(std::string ID) { + return RelativeSelector(std::move(ID)); +} + +namespace { +// Returns the range of the source between the call's parentheses. +CharSourceRange getCallArgumentsRange(const MatchResult &Result, + const CallExpr &CE) { + return CharSourceRange::getCharRange( + findOpenParen(CE, *Result.SourceManager, Result.Context->getLangOpts()) + .getLocWithOffset(1), + CE.getRParenLoc()); +} +} // namespace + +RangeSelector tooling::callArgs(std::string ID) { + return RelativeSelector(std::move(ID)); +} + +namespace { +// Returns the range of the elements of the initializer list. Includes all +// source between the braces. +CharSourceRange getElementsRange(const MatchResult &, + const InitListExpr &E) { + return CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1), + E.getRBraceLoc()); +} +} // namespace + +RangeSelector tooling::initListElements(std::string ID) { + return RelativeSelector(std::move(ID)); +} + +namespace { +// Returns the range of the else branch, including the `else` keyword. +CharSourceRange getElseRange(const MatchResult &Result, const IfStmt &S) { + return maybeExtendRange( + CharSourceRange::getTokenRange(S.getElseLoc(), S.getEndLoc()), + tok::TokenKind::semi, *Result.Context); +} +} // namespace + +RangeSelector tooling::elseBranch(std::string ID) { + return RelativeSelector(std::move(ID)); +} + +RangeSelector tooling::expansion(RangeSelector S) { + return [S](const MatchResult &Result) -> Expected { + Expected SRange = S(Result); + if (!SRange) + return SRange.takeError(); + return Result.SourceManager->getExpansionRange(*SRange); + }; +} diff --git a/lib/Tooling/Transformer/SourceCode.cpp b/lib/Tooling/Transformer/SourceCode.cpp new file mode 100644 index 0000000000..836401d1e6 --- /dev/null +++ b/lib/Tooling/Transformer/SourceCode.cpp @@ -0,0 +1,65 @@ +//===--- SourceCode.cpp - Source code manipulation routines -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides functions that simplify extraction of source code. +// +//===----------------------------------------------------------------------===// +#include "clang/Tooling/Transformer/SourceCode.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; + +StringRef clang::tooling::getText(CharSourceRange Range, + const ASTContext &Context) { + return Lexer::getSourceText(Range, Context.getSourceManager(), + Context.getLangOpts()); +} + +CharSourceRange clang::tooling::maybeExtendRange(CharSourceRange Range, + tok::TokenKind Next, + ASTContext &Context) { + Optional Tok = Lexer::findNextToken( + Range.getEnd(), Context.getSourceManager(), Context.getLangOpts()); + if (!Tok || !Tok->is(Next)) + return Range; + return CharSourceRange::getTokenRange(Range.getBegin(), Tok->getLocation()); +} + +llvm::Optional +clang::tooling::getRangeForEdit(const CharSourceRange &EditRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + // FIXME: makeFileCharRange() has the disadvantage of stripping off "identity" + // macros. For example, if we're looking to rewrite the int literal 3 to 6, + // and we have the following definition: + // #define DO_NOTHING(x) x + // then + // foo(DO_NOTHING(3)) + // will be rewritten to + // foo(6) + // rather than the arguably better + // foo(DO_NOTHING(6)) + // Decide whether the current behavior is desirable and modify if not. + CharSourceRange Range = Lexer::makeFileCharRange(EditRange, SM, LangOpts); + if (Range.isInvalid()) + return None; + + if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) + return None; + if (SM.isInSystemHeader(Range.getBegin()) || + SM.isInSystemHeader(Range.getEnd())) + return None; + + std::pair BeginInfo = SM.getDecomposedLoc(Range.getBegin()); + std::pair EndInfo = SM.getDecomposedLoc(Range.getEnd()); + if (BeginInfo.first != EndInfo.first || + BeginInfo.second > EndInfo.second) + return None; + + return Range; +} diff --git a/lib/Tooling/Transformer/SourceCodeBuilders.cpp b/lib/Tooling/Transformer/SourceCodeBuilders.cpp new file mode 100644 index 0000000000..56ec45e8fd --- /dev/null +++ b/lib/Tooling/Transformer/SourceCodeBuilders.cpp @@ -0,0 +1,160 @@ +//===--- SourceCodeBuilder.cpp ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Transformer/SourceCodeBuilders.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "llvm/ADT/Twine.h" +#include + +using namespace clang; +using namespace tooling; + +const Expr *tooling::reallyIgnoreImplicit(const Expr &E) { + const Expr *Expr = E.IgnoreImplicit(); + if (const auto *CE = dyn_cast(Expr)) { + if (CE->getNumArgs() > 0 && + CE->getArg(0)->getSourceRange() == Expr->getSourceRange()) + return CE->getArg(0)->IgnoreImplicit(); + } + return Expr; +} + +bool tooling::mayEverNeedParens(const Expr &E) { + const Expr *Expr = reallyIgnoreImplicit(E); + // We always want parens around unary, binary, and ternary operators, because + // they are lower precedence. + if (isa(Expr) || isa(Expr) || + isa(Expr)) + return true; + + // We need parens around calls to all overloaded operators except: function + // calls, subscripts, and expressions that are already part of an (implicit) + // call to operator->. These latter are all in the same precedence level as + // dot/arrow and that level is left associative, so they don't need parens + // when appearing on the left. + if (const auto *Op = dyn_cast(Expr)) + return Op->getOperator() != OO_Call && Op->getOperator() != OO_Subscript && + Op->getOperator() != OO_Arrow; + + return false; +} + +bool tooling::needParensAfterUnaryOperator(const Expr &E) { + const Expr *Expr = reallyIgnoreImplicit(E); + if (isa(Expr) || isa(Expr)) + return true; + + if (const auto *Op = dyn_cast(Expr)) + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + + return false; +} + +llvm::Optional tooling::buildParens(const Expr &E, + const ASTContext &Context) { + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (mayEverNeedParens(E)) + return ("(" + Text + ")").str(); + return Text.str(); +} + +llvm::Optional +tooling::buildDereference(const Expr &E, const ASTContext &Context) { + if (const auto *Op = dyn_cast(&E)) + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + StringRef Text = + getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); + if (Text.empty()) + return llvm::None; + return Text.str(); + } + + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + // Add leading '*'. + if (needParensAfterUnaryOperator(E)) + return ("*(" + Text + ")").str(); + return ("*" + Text).str(); +} + +llvm::Optional tooling::buildAddressOf(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = dyn_cast(&E)) + if (Op->getOpcode() == UO_Deref) { + // Strip leading '*'. + StringRef Text = + getText(*Op->getSubExpr()->IgnoreParenImpCasts(), Context); + if (Text.empty()) + return llvm::None; + return Text.str(); + } + // Add leading '&'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensAfterUnaryOperator(E)) { + return ("&(" + Text + ")").str(); + } + return ("&" + Text).str(); +} + +llvm::Optional tooling::buildDot(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = llvm::dyn_cast(&E)) + if (Op->getOpcode() == UO_Deref) { + // Strip leading '*', add following '->'. + const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); + StringRef DerefText = getText(*SubExpr, Context); + if (DerefText.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(*SubExpr)) + return ("(" + DerefText + ")->").str(); + return (DerefText + "->").str(); + } + + // Add following '.'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(E)) { + return ("(" + Text + ").").str(); + } + return (Text + ".").str(); +} + +llvm::Optional tooling::buildArrow(const Expr &E, + const ASTContext &Context) { + if (const auto *Op = llvm::dyn_cast(&E)) + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&', add following '.'. + const Expr *SubExpr = Op->getSubExpr()->IgnoreParenImpCasts(); + StringRef DerefText = getText(*SubExpr, Context); + if (DerefText.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(*SubExpr)) + return ("(" + DerefText + ").").str(); + return (DerefText + ".").str(); + } + + // Add following '->'. + StringRef Text = getText(E, Context); + if (Text.empty()) + return llvm::None; + if (needParensBeforeDotOrArrow(E)) + return ("(" + Text + ")->").str(); + return (Text + "->").str(); +} diff --git a/lib/Tooling/Transformer/Stencil.cpp b/lib/Tooling/Transformer/Stencil.cpp new file mode 100644 index 0000000000..82fde2bc4d --- /dev/null +++ b/lib/Tooling/Transformer/Stencil.cpp @@ -0,0 +1,369 @@ +//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Transformer/Stencil.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "clang/Tooling/Transformer/SourceCodeBuilders.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Errc.h" +#include +#include +#include + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using ast_type_traits::DynTypedNode; +using llvm::errc; +using llvm::Error; +using llvm::Expected; +using llvm::StringError; + +// A down_cast function to safely down cast a StencilPartInterface to a subclass +// D. Returns nullptr if P is not an instance of D. +template const D *down_cast(const StencilPartInterface *P) { + if (P == nullptr || D::typeId() != P->typeId()) + return nullptr; + return static_cast(P); +} + +static llvm::Expected +getNode(const ast_matchers::BoundNodes &Nodes, StringRef Id) { + auto &NodesMap = Nodes.getMap(); + auto It = NodesMap.find(Id); + if (It == NodesMap.end()) + return llvm::make_error(llvm::errc::invalid_argument, + "Id not bound: " + Id); + return It->second; +} + +namespace { +// An arbitrary fragment of code within a stencil. +struct RawTextData { + explicit RawTextData(std::string T) : Text(std::move(T)) {} + std::string Text; +}; + +// A debugging operation to dump the AST for a particular (bound) AST node. +struct DebugPrintNodeData { + explicit DebugPrintNodeData(std::string S) : Id(std::move(S)) {} + std::string Id; +}; + +// Operators that take a single node Id as an argument. +enum class UnaryNodeOperator { + Parens, + Deref, + Address, +}; + +// Generic container for stencil operations with a (single) node-id argument. +struct UnaryOperationData { + UnaryOperationData(UnaryNodeOperator Op, std::string Id) + : Op(Op), Id(std::move(Id)) {} + UnaryNodeOperator Op; + std::string Id; +}; + +// The fragment of code corresponding to the selected range. +struct SelectorData { + explicit SelectorData(RangeSelector S) : Selector(std::move(S)) {} + RangeSelector Selector; +}; + +// A stencil operation to build a member access `e.m` or `e->m`, as appropriate. +struct AccessData { + AccessData(StringRef BaseId, StencilPart Member) + : BaseId(BaseId), Member(std::move(Member)) {} + std::string BaseId; + StencilPart Member; +}; + +struct IfBoundData { + IfBoundData(StringRef Id, StencilPart TruePart, StencilPart FalsePart) + : Id(Id), TruePart(std::move(TruePart)), FalsePart(std::move(FalsePart)) { + } + std::string Id; + StencilPart TruePart; + StencilPart FalsePart; +}; + +bool isEqualData(const RawTextData &A, const RawTextData &B) { + return A.Text == B.Text; +} + +bool isEqualData(const DebugPrintNodeData &A, const DebugPrintNodeData &B) { + return A.Id == B.Id; +} + +bool isEqualData(const UnaryOperationData &A, const UnaryOperationData &B) { + return A.Op == B.Op && A.Id == B.Id; +} + +// Equality is not (yet) defined for \c RangeSelector. +bool isEqualData(const SelectorData &, const SelectorData &) { return false; } + +bool isEqualData(const AccessData &A, const AccessData &B) { + return A.BaseId == B.BaseId && A.Member == B.Member; +} + +bool isEqualData(const IfBoundData &A, const IfBoundData &B) { + return A.Id == B.Id && A.TruePart == B.TruePart && A.FalsePart == B.FalsePart; +} + +// Equality is not defined over MatchConsumers, which are opaque. +bool isEqualData(const MatchConsumer &A, + const MatchConsumer &B) { + return false; +} + +std::string toStringData(const RawTextData &Data) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << "\""; + OS.write_escaped(Data.Text); + OS << "\""; + OS.flush(); + return Result; +} + +std::string toStringData(const DebugPrintNodeData &Data) { + return (llvm::Twine("dPrint(\"") + Data.Id + "\")").str(); +} + +std::string toStringData(const UnaryOperationData &Data) { + StringRef OpName; + switch (Data.Op) { + case UnaryNodeOperator::Parens: + OpName = "expression"; + break; + case UnaryNodeOperator::Deref: + OpName = "deref"; + break; + case UnaryNodeOperator::Address: + OpName = "addressOf"; + break; + } + return (OpName + "(\"" + Data.Id + "\")").str(); +} + +std::string toStringData(const SelectorData &) { return "SelectorData()"; } + +std::string toStringData(const AccessData &Data) { + return (llvm::Twine("access(\"") + Data.BaseId + "\", " + + Data.Member.toString() + ")") + .str(); +} + +std::string toStringData(const IfBoundData &Data) { + return (llvm::Twine("ifBound(\"") + Data.Id + "\", " + + Data.TruePart.toString() + ", " + Data.FalsePart.toString() + ")") + .str(); +} + +std::string toStringData(const MatchConsumer &) { + return "MatchConsumer()"; +} + +// The `evalData()` overloads evaluate the given stencil data to a string, given +// the match result, and append it to `Result`. We define an overload for each +// type of stencil data. + +Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &, + std::string *Result) { + Result->append(Data.Text); + return Error::success(); +} + +Error evalData(const DebugPrintNodeData &Data, + const MatchFinder::MatchResult &Match, std::string *Result) { + std::string Output; + llvm::raw_string_ostream Os(Output); + auto NodeOrErr = getNode(Match.Nodes, Data.Id); + if (auto Err = NodeOrErr.takeError()) + return Err; + NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts())); + *Result += Os.str(); + return Error::success(); +} + +Error evalData(const UnaryOperationData &Data, + const MatchFinder::MatchResult &Match, std::string *Result) { + const auto *E = Match.Nodes.getNodeAs(Data.Id); + if (E == nullptr) + return llvm::make_error( + errc::invalid_argument, "Id not bound or not Expr: " + Data.Id); + llvm::Optional Source; + switch (Data.Op) { + case UnaryNodeOperator::Parens: + Source = buildParens(*E, *Match.Context); + break; + case UnaryNodeOperator::Deref: + Source = buildDereference(*E, *Match.Context); + break; + case UnaryNodeOperator::Address: + Source = buildAddressOf(*E, *Match.Context); + break; + } + if (!Source) + return llvm::make_error( + errc::invalid_argument, + "Could not construct expression source from ID: " + Data.Id); + *Result += *Source; + return Error::success(); +} + +Error evalData(const SelectorData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + auto Range = Data.Selector(Match); + if (!Range) + return Range.takeError(); + *Result += getText(*Range, *Match.Context); + return Error::success(); +} + +Error evalData(const AccessData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + const auto *E = Match.Nodes.getNodeAs(Data.BaseId); + if (E == nullptr) + return llvm::make_error(errc::invalid_argument, + "Id not bound: " + Data.BaseId); + if (!E->isImplicitCXXThis()) { + if (llvm::Optional S = E->getType()->isAnyPointerType() + ? buildArrow(*E, *Match.Context) + : buildDot(*E, *Match.Context)) + *Result += *S; + else + return llvm::make_error( + errc::invalid_argument, + "Could not construct object text from ID: " + Data.BaseId); + } + return Data.Member.eval(Match, Result); +} + +Error evalData(const IfBoundData &Data, const MatchFinder::MatchResult &Match, + std::string *Result) { + auto &M = Match.Nodes.getMap(); + return (M.find(Data.Id) != M.end() ? Data.TruePart : Data.FalsePart) + .eval(Match, Result); +} + +Error evalData(const MatchConsumer &Fn, + const MatchFinder::MatchResult &Match, std::string *Result) { + Expected Value = Fn(Match); + if (!Value) + return Value.takeError(); + *Result += *Value; + return Error::success(); +} + +template +class StencilPartImpl : public StencilPartInterface { + T Data; + +public: + template + explicit StencilPartImpl(Ps &&... Args) + : StencilPartInterface(StencilPartImpl::typeId()), + Data(std::forward(Args)...) {} + + // Generates a unique identifier for this class (specifically, one per + // instantiation of the template). + static const void* typeId() { + static bool b; + return &b; + } + + Error eval(const MatchFinder::MatchResult &Match, + std::string *Result) const override { + return evalData(Data, Match, Result); + } + + bool isEqual(const StencilPartInterface &Other) const override { + if (const auto *OtherPtr = down_cast(&Other)) + return isEqualData(Data, OtherPtr->Data); + return false; + } + + std::string toString() const override { return toStringData(Data); } +}; +} // namespace + +StencilPart Stencil::wrap(StringRef Text) { + return stencil::text(Text); +} + +StencilPart Stencil::wrap(RangeSelector Selector) { + return stencil::selection(std::move(Selector)); +} + +void Stencil::append(Stencil OtherStencil) { + for (auto &Part : OtherStencil.Parts) + Parts.push_back(std::move(Part)); +} + +llvm::Expected +Stencil::eval(const MatchFinder::MatchResult &Match) const { + std::string Result; + for (const auto &Part : Parts) + if (auto Err = Part.eval(Match, &Result)) + return std::move(Err); + return Result; +} + +StencilPart stencil::text(StringRef Text) { + return StencilPart(std::make_shared>(Text)); +} + +StencilPart stencil::selection(RangeSelector Selector) { + return StencilPart( + std::make_shared>(std::move(Selector))); +} + +StencilPart stencil::dPrint(StringRef Id) { + return StencilPart(std::make_shared>(Id)); +} + +StencilPart stencil::expression(llvm::StringRef Id) { + return StencilPart(std::make_shared>( + UnaryNodeOperator::Parens, Id)); +} + +StencilPart stencil::deref(llvm::StringRef ExprId) { + return StencilPart(std::make_shared>( + UnaryNodeOperator::Deref, ExprId)); +} + +StencilPart stencil::addressOf(llvm::StringRef ExprId) { + return StencilPart(std::make_shared>( + UnaryNodeOperator::Address, ExprId)); +} + +StencilPart stencil::access(StringRef BaseId, StencilPart Member) { + return StencilPart( + std::make_shared>(BaseId, std::move(Member))); +} + +StencilPart stencil::ifBound(StringRef Id, StencilPart TruePart, + StencilPart FalsePart) { + return StencilPart(std::make_shared>( + Id, std::move(TruePart), std::move(FalsePart))); +} + +StencilPart stencil::run(MatchConsumer Fn) { + return StencilPart( + std::make_shared>>( + std::move(Fn))); +} diff --git a/lib/Tooling/Transformer/Transformer.cpp b/lib/Tooling/Transformer/Transformer.cpp new file mode 100644 index 0000000000..1aecf6ab8e --- /dev/null +++ b/lib/Tooling/Transformer/Transformer.cpp @@ -0,0 +1,235 @@ +//===--- Transformer.cpp - Transformer library implementation ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Transformer/Transformer.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/Transformer/SourceCode.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include +#include +#include +#include + +using namespace clang; +using namespace tooling; + +using ast_matchers::MatchFinder; +using ast_matchers::internal::DynTypedMatcher; +using ast_type_traits::ASTNodeKind; +using ast_type_traits::DynTypedNode; +using llvm::Error; +using llvm::StringError; + +using MatchResult = MatchFinder::MatchResult; + +Expected> +tooling::detail::translateEdits(const MatchResult &Result, + llvm::ArrayRef Edits) { + SmallVector Transformations; + for (const auto &Edit : Edits) { + Expected Range = Edit.TargetRange(Result); + if (!Range) + return Range.takeError(); + llvm::Optional EditRange = + getRangeForEdit(*Range, *Result.Context); + // FIXME: let user specify whether to treat this case as an error or ignore + // it as is currently done. + if (!EditRange) + return SmallVector(); + auto Replacement = Edit.Replacement(Result); + if (!Replacement) + return Replacement.takeError(); + tooling::detail::Transformation T; + T.Range = *EditRange; + T.Replacement = std::move(*Replacement); + Transformations.push_back(std::move(T)); + } + return Transformations; +} + +ASTEdit tooling::change(RangeSelector S, TextGenerator Replacement) { + ASTEdit E; + E.TargetRange = std::move(S); + E.Replacement = std::move(Replacement); + return E; +} + +RewriteRule tooling::makeRule(DynTypedMatcher M, SmallVector Edits, + TextGenerator Explanation) { + return RewriteRule{{RewriteRule::Case{ + std::move(M), std::move(Edits), std::move(Explanation), {}}}}; +} + +void tooling::addInclude(RewriteRule &Rule, StringRef Header, + IncludeFormat Format) { + for (auto &Case : Rule.Cases) + Case.AddedIncludes.emplace_back(Header.str(), Format); +} + +#ifndef NDEBUG +// Filters for supported matcher kinds. FIXME: Explicitly list the allowed kinds +// (all node matcher types except for `QualType` and `Type`), rather than just +// banning `QualType` and `Type`. +static bool hasValidKind(const DynTypedMatcher &M) { + return !M.canConvertTo(); +} +#endif + +// Binds each rule's matcher to a unique (and deterministic) tag based on +// `TagBase` and the id paired with the case. +static std::vector taggedMatchers( + StringRef TagBase, + const SmallVectorImpl> &Cases) { + std::vector Matchers; + Matchers.reserve(Cases.size()); + for (const auto &Case : Cases) { + std::string Tag = (TagBase + Twine(Case.first)).str(); + // HACK: Many matchers are not bindable, so ensure that tryBind will work. + DynTypedMatcher BoundMatcher(Case.second.Matcher); + BoundMatcher.setAllowBind(true); + auto M = BoundMatcher.tryBind(Tag); + Matchers.push_back(*std::move(M)); + } + return Matchers; +} + +// Simply gathers the contents of the various rules into a single rule. The +// actual work to combine these into an ordered choice is deferred to matcher +// registration. +RewriteRule tooling::applyFirst(ArrayRef Rules) { + RewriteRule R; + for (auto &Rule : Rules) + R.Cases.append(Rule.Cases.begin(), Rule.Cases.end()); + return R; +} + +std::vector +tooling::detail::buildMatchers(const RewriteRule &Rule) { + // Map the cases into buckets of matchers -- one for each "root" AST kind, + // which guarantees that they can be combined in a single anyOf matcher. Each + // case is paired with an identifying number that is converted to a string id + // in `taggedMatchers`. + std::map, 1>> + Buckets; + const SmallVectorImpl &Cases = Rule.Cases; + for (int I = 0, N = Cases.size(); I < N; ++I) { + assert(hasValidKind(Cases[I].Matcher) && + "Matcher must be non-(Qual)Type node matcher"); + Buckets[Cases[I].Matcher.getSupportedKind()].emplace_back(I, Cases[I]); + } + + std::vector Matchers; + for (const auto &Bucket : Buckets) { + DynTypedMatcher M = DynTypedMatcher::constructVariadic( + DynTypedMatcher::VO_AnyOf, Bucket.first, + taggedMatchers("Tag", Bucket.second)); + M.setAllowBind(true); + // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true. + Matchers.push_back(*M.tryBind(RewriteRule::RootID)); + } + return Matchers; +} + +DynTypedMatcher tooling::detail::buildMatcher(const RewriteRule &Rule) { + std::vector Ms = buildMatchers(Rule); + assert(Ms.size() == 1 && "Cases must have compatible matchers."); + return Ms[0]; +} + +SourceLocation tooling::detail::getRuleMatchLoc(const MatchResult &Result) { + auto &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(RewriteRule::RootID); + assert(Root != NodesMap.end() && "Transformation failed: missing root node."); + llvm::Optional RootRange = getRangeForEdit( + CharSourceRange::getTokenRange(Root->second.getSourceRange()), + *Result.Context); + if (RootRange) + return RootRange->getBegin(); + // The match doesn't have a coherent range, so fall back to the expansion + // location as the "beginning" of the match. + return Result.SourceManager->getExpansionLoc( + Root->second.getSourceRange().getBegin()); +} + +// Finds the case that was "selected" -- that is, whose matcher triggered the +// `MatchResult`. +const RewriteRule::Case & +tooling::detail::findSelectedCase(const MatchResult &Result, + const RewriteRule &Rule) { + if (Rule.Cases.size() == 1) + return Rule.Cases[0]; + + auto &NodesMap = Result.Nodes.getMap(); + for (size_t i = 0, N = Rule.Cases.size(); i < N; ++i) { + std::string Tag = ("Tag" + Twine(i)).str(); + if (NodesMap.find(Tag) != NodesMap.end()) + return Rule.Cases[i]; + } + llvm_unreachable("No tag found for this rule."); +} + +constexpr llvm::StringLiteral RewriteRule::RootID; + +void Transformer::registerMatchers(MatchFinder *MatchFinder) { + for (auto &Matcher : tooling::detail::buildMatchers(Rule)) + MatchFinder->addDynamicMatcher(Matcher, this); +} + +void Transformer::run(const MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + RewriteRule::Case Case = tooling::detail::findSelectedCase(Result, Rule); + auto Transformations = tooling::detail::translateEdits(Result, Case.Edits); + if (!Transformations) { + Consumer(Transformations.takeError()); + return; + } + + if (Transformations->empty()) { + // No rewrite applied (but no error encountered either). + detail::getRuleMatchLoc(Result).print( + llvm::errs() << "note: skipping match at loc ", *Result.SourceManager); + llvm::errs() << "\n"; + return; + } + + // Record the results in the AtomicChange, anchored at the location of the + // first change. + AtomicChange AC(*Result.SourceManager, + (*Transformations)[0].Range.getBegin()); + for (const auto &T : *Transformations) { + if (auto Err = AC.replace(*Result.SourceManager, T.Range, T.Replacement)) { + Consumer(std::move(Err)); + return; + } + } + + for (const auto &I : Case.AddedIncludes) { + auto &Header = I.first; + switch (I.second) { + case IncludeFormat::Quoted: + AC.addHeader(Header); + break; + case IncludeFormat::Angled: + AC.addHeader((llvm::Twine("<") + Header + ">").str()); + break; + } + } + + Consumer(std::move(AC)); +} diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt index 2b35302c7b..5cef154926 100644 --- a/unittests/Tooling/CMakeLists.txt +++ b/unittests/Tooling/CMakeLists.txt @@ -75,6 +75,7 @@ clang_target_link_libraries(ToolingTests clangToolingCore clangToolingInclusions clangToolingRefactoring + clangTransformer ) target_link_libraries(ToolingTests diff --git a/unittests/Tooling/RangeSelectorTest.cpp b/unittests/Tooling/RangeSelectorTest.cpp index 58ce63cbd7..29a20a9f18 100644 --- a/unittests/Tooling/RangeSelectorTest.cpp +++ b/unittests/Tooling/RangeSelectorTest.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/RangeSelector.h" +#include "clang/Tooling/Transformer/RangeSelector.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Tooling/FixIt.h" diff --git a/unittests/Tooling/SourceCodeBuildersTest.cpp b/unittests/Tooling/SourceCodeBuildersTest.cpp index 2bf50ffad5..9b5e7bf3ba 100644 --- a/unittests/Tooling/SourceCodeBuildersTest.cpp +++ b/unittests/Tooling/SourceCodeBuildersTest.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/SourceCodeBuilders.h" +#include "clang/Tooling/Transformer/SourceCodeBuilders.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" diff --git a/unittests/Tooling/SourceCodeTest.cpp b/unittests/Tooling/SourceCodeTest.cpp index e3da9bf14b..eb28d7cf27 100644 --- a/unittests/Tooling/SourceCodeTest.cpp +++ b/unittests/Tooling/SourceCodeTest.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/SourceCode.h" +#include "clang/Tooling/Transformer/SourceCode.h" #include "TestVisitor.h" #include "clang/Basic/Diagnostic.h" #include "llvm/Testing/Support/Annotations.h" diff --git a/unittests/Tooling/StencilTest.cpp b/unittests/Tooling/StencilTest.cpp index 0bd3d90f83..2b9aa68e34 100644 --- a/unittests/Tooling/StencilTest.cpp +++ b/unittests/Tooling/StencilTest.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/Stencil.h" +#include "clang/Tooling/Transformer/Stencil.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/FixIt.h" #include "clang/Tooling/Tooling.h" diff --git a/unittests/Tooling/TransformerTest.cpp b/unittests/Tooling/TransformerTest.cpp index 5d55182f82..feae0c649a 100644 --- a/unittests/Tooling/TransformerTest.cpp +++ b/unittests/Tooling/TransformerTest.cpp @@ -6,9 +6,9 @@ // //===----------------------------------------------------------------------===// -#include "clang/Tooling/Refactoring/Transformer.h" +#include "clang/Tooling/Transformer/Transformer.h" #include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Tooling/Refactoring/RangeSelector.h" +#include "clang/Tooling/Transformer/RangeSelector.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" -- cgit v1.2.1