1//===--- Transformer.h - Transformer class ----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_
10#define LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_
11
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Tooling/Refactoring/AtomicChange.h"
14#include "clang/Tooling/Transformer/RewriteRule.h"
15#include "llvm/Support/Error.h"
16#include <functional>
17#include <utility>
18
19namespace clang {
20namespace tooling {
21
22namespace detail {
23/// Implementation details of \c Transformer with type erasure around
24/// \c RewriteRule<T> as well as the corresponding consumers.
25class TransformerImpl {
26public:
27 virtual ~TransformerImpl() = default;
28
29 void onMatch(const ast_matchers::MatchFinder::MatchResult &Result);
30
31 virtual std::vector<ast_matchers::internal::DynTypedMatcher>
32 buildMatchers() const = 0;
33
34protected:
35 /// Converts a set of \c Edit into a \c AtomicChange per file modified.
36 /// Returns an error if the edits fail to compose, e.g. overlapping edits.
37 static llvm::Expected<llvm::SmallVector<AtomicChange, 1>>
38 convertToAtomicChanges(const llvm::SmallVectorImpl<transformer::Edit> &Edits,
39 const ast_matchers::MatchFinder::MatchResult &Result);
40
41private:
42 virtual void
43 onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) = 0;
44};
45} // namespace detail
46
47template <typename T> struct TransformerResult {
48 llvm::MutableArrayRef<AtomicChange> Changes;
49 T Metadata;
50};
51
52template <> struct TransformerResult<void> {
53 llvm::MutableArrayRef<AtomicChange> Changes;
54};
55
56/// Handles the matcher and callback registration for a single `RewriteRule`, as
57/// defined by the arguments of the constructor.
58class Transformer : public ast_matchers::MatchFinder::MatchCallback {
59public:
60 /// Provides the set of changes to the consumer. The callback is free to move
61 /// or destructively consume the changes as needed.
62 ///
63 /// We use \c MutableArrayRef as an abstraction to provide decoupling, and we
64 /// expect the majority of consumers to copy or move the individual values
65 /// into a separate data structure.
66 using ChangeSetConsumer = std::function<void(
67 Expected<llvm::MutableArrayRef<AtomicChange>> Changes)>;
68
69 /// \param Consumer receives all rewrites for a single match, or an error.
70 /// Will not necessarily be called for each match; for example, if the rule
71 /// generates no edits but does not fail. Note that clients are responsible
72 /// for handling the case that independent \c AtomicChanges conflict with each
73 /// other.
74 explicit Transformer(transformer::RewriteRuleWith<void> Rule,
75 ChangeSetConsumer Consumer)
76 : Transformer(std::move(Rule),
77 [Consumer = std::move(Consumer)](
78 llvm::Expected<TransformerResult<void>> Result) {
79 if (Result)
80 Consumer(Result->Changes);
81 else
82 Consumer(Result.takeError());
83 }) {}
84
85 /// \param Consumer receives all rewrites and the associated metadata for a
86 /// single match, or an error. Will always be called for each match, even if
87 /// the rule generates no edits. Note that clients are responsible for
88 /// handling the case that independent \c AtomicChanges conflict with each
89 /// other.
90 template <typename MetadataT>
91 explicit Transformer(
92 transformer::RewriteRuleWith<MetadataT> Rule,
93 std::function<void(
94 llvm::Expected<TransformerResult<llvm::type_identity_t<MetadataT>>>)>
95 Consumer);
96
97 /// N.B. Passes `this` pointer to `MatchFinder`. So, this object should not
98 /// be moved after this call.
99 void registerMatchers(ast_matchers::MatchFinder *MatchFinder);
100
101 /// Not called directly by users -- called by the framework, via base class
102 /// pointer.
103 void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
104
105private:
106 std::unique_ptr<detail::TransformerImpl> Impl;
107};
108
109namespace detail {
110/// Runs the metadata generator on \c Rule and stuffs it into \c Result.
111/// @{
112template <typename T>
113llvm::Error
114populateMetadata(const transformer::RewriteRuleWith<T> &Rule,
115 size_t SelectedCase,
116 const ast_matchers::MatchFinder::MatchResult &Match,
117 TransformerResult<T> &Result) {
118 // Silence a false positive GCC -Wunused-but-set-parameter warning in constexpr
119 // cases, by marking SelectedCase as used. See
120 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85827 for details. The issue is
121 // fixed in GCC 10.
122 (void)SelectedCase;
123 if constexpr (!std::is_void_v<T>) {
124 auto Metadata = Rule.Metadata[SelectedCase]->eval(Match);
125 if (!Metadata)
126 return Metadata.takeError();
127 Result.Metadata = std::move(*Metadata);
128 }
129 return llvm::Error::success();
130}
131/// @}
132
133/// Implementation when metadata is generated as a part of the rewrite. This
134/// happens when we have a \c RewriteRuleWith<T>.
135template <typename T> class WithMetadataImpl final : public TransformerImpl {
136 transformer::RewriteRuleWith<T> Rule;
137 std::function<void(llvm::Expected<TransformerResult<T>>)> Consumer;
138
139public:
140 explicit WithMetadataImpl(
141 transformer::RewriteRuleWith<T> R,
142 std::function<void(llvm::Expected<TransformerResult<T>>)> Consumer)
143 : Rule(std::move(R)), Consumer(std::move(Consumer)) {
144 assert(llvm::all_of(Rule.Cases,
145 [](const transformer::RewriteRuleBase::Case &Case)
146 -> bool { return !!Case.Edits; }) &&
147 "edit generator must be provided for each rule");
148 if constexpr (!std::is_void_v<T>)
149 assert(llvm::all_of(Rule.Metadata,
150 [](const typename transformer::Generator<T> &Metadata)
151 -> bool { return !!Metadata; }) &&
152 "metadata generator must be provided for each rule");
153 }
154
155private:
156 void onMatchImpl(const ast_matchers::MatchFinder::MatchResult &Result) final {
157 size_t I = transformer::detail::findSelectedCase(Result, Rule);
158 auto Transformations = Rule.Cases[I].Edits(Result);
159 if (!Transformations) {
160 Consumer(Transformations.takeError());
161 return;
162 }
163
164 llvm::SmallVector<AtomicChange, 1> Changes;
165 if (!Transformations->empty()) {
166 auto C = convertToAtomicChanges(Edits: *Transformations, Result);
167 if (C) {
168 Changes = std::move(*C);
169 } else {
170 Consumer(C.takeError());
171 return;
172 }
173 } else if (std::is_void<T>::value) {
174 // If we don't have metadata and we don't have any edits, skip.
175 return;
176 }
177
178 TransformerResult<T> RewriteResult;
179 if (auto E = populateMetadata(Rule, I, Result, RewriteResult)) {
180 Consumer(std::move(E));
181 return;
182 }
183
184 RewriteResult.Changes = llvm::MutableArrayRef<AtomicChange>(Changes);
185 Consumer(std::move(RewriteResult));
186 }
187
188 std::vector<ast_matchers::internal::DynTypedMatcher>
189 buildMatchers() const final {
190 return transformer::detail::buildMatchers(Rule);
191 }
192};
193} // namespace detail
194
195template <typename MetadataT>
196Transformer::Transformer(
197 transformer::RewriteRuleWith<MetadataT> Rule,
198 std::function<void(
199 llvm::Expected<TransformerResult<llvm::type_identity_t<MetadataT>>>)>
200 Consumer)
201 : Impl(std::make_unique<detail::WithMetadataImpl<MetadataT>>(
202 std::move(Rule), std::move(Consumer))) {}
203
204} // namespace tooling
205} // namespace clang
206
207#endif // LLVM_CLANG_TOOLING_TRANSFORMER_TRANSFORMER_H_
208

source code of clang/include/clang/Tooling/Transformer/Transformer.h