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