//===--- Transformer.h - Transformer 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_ #define LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Tooling/Refactoring/AtomicChange.h" #include "clang/Tooling/Transformer/RewriteRule.h" #include "llvm/Support/Error.h" #include #include namespace clang { namespace tooling { namespace detail { /// Implementation details of \c Transformer with type erasure around /// \c RewriteRule as well as the corresponding consumers. class TransformerImpl { public: virtual ~TransformerImpl() = default; void onMatch(const ast_matchers::MatchFinder::MatchResult &Result); virtual std::vector buildMatchers() const = 0; protected: /// Converts a set of \c Edit into a \c AtomicChange per file modified. /// Returns an error if the edits fail to compose, e.g. overlapping edits. static llvm::Expected> convertToAtomicChanges(const llvm::SmallVectorImpl &Edits, const ast_matchers::MatchFinder::MatchResult &Result); private: virtual void onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) = 0; }; // FIXME: Use std::type_identity or backport when available. template struct type_identity { using type = T; }; } // namespace detail template struct TransformerResult { llvm::MutableArrayRef Changes; T Metadata; }; template <> struct TransformerResult { llvm::MutableArrayRef Changes; }; /// Handles the matcher and callback registration for a single `RewriteRule`, as /// defined by the arguments of the constructor. class Transformer : public ast_matchers::MatchFinder::MatchCallback { public: /// Provides the set of changes to the consumer. The callback is free to move /// or destructively consume the changes as needed. /// /// We use \c MutableArrayRef as an abstraction to provide decoupling, and we /// expect the majority of consumers to copy or move the individual values /// into a separate data structure. using ChangeSetConsumer = std::function> Changes)>; /// \param Consumer receives all rewrites for a single match, or an error. /// Will not necessarily be called for each match; for example, if the rule /// generates no edits but does not fail. Note that clients are responsible /// for handling the case that independent \c AtomicChanges conflict with each /// other. explicit Transformer(transformer::RewriteRuleWith Rule, ChangeSetConsumer Consumer) : Transformer(std::move(Rule), [Consumer = std::move(Consumer)]( llvm::Expected> Result) { if (Result) Consumer(Result->Changes); else Consumer(Result.takeError()); }) {} /// \param Consumer receives all rewrites and the associated metadata for a /// single match, or an error. Will always be called for each match, even if /// the rule generates no edits. Note that clients are responsible for /// handling the case that independent \c AtomicChanges conflict with each /// other. template explicit Transformer( transformer::RewriteRuleWith Rule, std::function::type>>)> 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: std::unique_ptr Impl; }; namespace detail { /// Runs the metadata generator on \c Rule and stuffs it into \c Result. /// @{ template llvm::Error populateMetadata(const transformer::RewriteRuleWith &Rule, size_t SelectedCase, const ast_matchers::MatchFinder::MatchResult &Match, TransformerResult &Result) { // Silence a false positive GCC -Wunused-but-set-parameter warning in constexpr // cases, by marking SelectedCase as used. See // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85827 for details. The issue is // fixed in GCC 10. (void)SelectedCase; if constexpr (!std::is_void_v) { auto Metadata = Rule.Metadata[SelectedCase]->eval(Match); if (!Metadata) return Metadata.takeError(); Result.Metadata = std::move(*Metadata); } return llvm::Error::success(); } /// @} /// Implementation when metadata is generated as a part of the rewrite. This /// happens when we have a \c RewriteRuleWith. template class WithMetadataImpl final : public TransformerImpl { transformer::RewriteRuleWith Rule; std::function>)> Consumer; public: explicit WithMetadataImpl( transformer::RewriteRuleWith R, std::function>)> Consumer) : Rule(std::move(R)), Consumer(std::move(Consumer)) { assert(llvm::all_of(Rule.Cases, [](const transformer::RewriteRuleBase::Case &Case) -> bool { return !!Case.Edits; }) && "edit generator must be provided for each rule"); if constexpr (!std::is_void_v) assert(llvm::all_of(Rule.Metadata, [](const typename transformer::Generator &Metadata) -> bool { return !!Metadata; }) && "metadata generator must be provided for each rule"); } private: void onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) final { size_t I = transformer::detail::findSelectedCase(Result, Rule); auto Transformations = Rule.Cases[I].Edits(Result); if (!Transformations) { Consumer(Transformations.takeError()); return; } llvm::SmallVector Changes; if (!Transformations->empty()) { auto C = convertToAtomicChanges(*Transformations, Result); if (C) { Changes = std::move(*C); } else { Consumer(C.takeError()); return; } } else if (std::is_void::value) { // If we don't have metadata and we don't have any edits, skip. return; } TransformerResult RewriteResult; if (auto E = populateMetadata(Rule, I, Result, RewriteResult)) { Consumer(std::move(E)); return; } RewriteResult.Changes = llvm::MutableArrayRef(Changes); Consumer(std::move(RewriteResult)); } std::vector buildMatchers() const final { return transformer::detail::buildMatchers(Rule); } }; } // namespace detail template Transformer::Transformer( transformer::RewriteRuleWith Rule, std::function::type>>)> Consumer) : Impl(std::make_unique>( std::move(Rule), std::move(Consumer))) {} } // namespace tooling } // namespace clang #endif // LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_