1 | //===--- DurationRewriter.cpp - clang-tidy --------------------------------===// |
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 | #include <cmath> |
10 | #include <optional> |
11 | |
12 | #include "DurationRewriter.h" |
13 | #include "clang/Tooling/FixIt.h" |
14 | #include "llvm/ADT/IndexedMap.h" |
15 | |
16 | using namespace clang::ast_matchers; |
17 | |
18 | namespace clang::tidy::abseil { |
19 | |
20 | struct DurationScale2IndexFunctor { |
21 | using argument_type = DurationScale; |
22 | unsigned operator()(DurationScale Scale) const { |
23 | return static_cast<unsigned>(Scale); |
24 | } |
25 | }; |
26 | |
27 | /// Returns an integer if the fractional part of a `FloatingLiteral` is `0`. |
28 | static std::optional<llvm::APSInt> |
29 | truncateIfIntegral(const FloatingLiteral &FloatLiteral) { |
30 | double Value = FloatLiteral.getValueAsApproximateDouble(); |
31 | if (std::fmod(x: Value, y: 1) == 0) { |
32 | if (Value >= static_cast<double>(1U << 31)) |
33 | return std::nullopt; |
34 | |
35 | return llvm::APSInt::get(X: static_cast<int64_t>(Value)); |
36 | } |
37 | return std::nullopt; |
38 | } |
39 | |
40 | const std::pair<llvm::StringRef, llvm::StringRef> & |
41 | getDurationInverseForScale(DurationScale Scale) { |
42 | static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, |
43 | DurationScale2IndexFunctor> |
44 | InverseMap = []() { |
45 | // TODO: Revisit the immediately invoked lambda technique when |
46 | // IndexedMap gets an initializer list constructor. |
47 | llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, |
48 | DurationScale2IndexFunctor> |
49 | InverseMap; |
50 | InverseMap.resize(s: 6); |
51 | InverseMap[DurationScale::Hours] = |
52 | std::make_pair(x: "::absl::ToDoubleHours" , y: "::absl::ToInt64Hours" ); |
53 | InverseMap[DurationScale::Minutes] = |
54 | std::make_pair(x: "::absl::ToDoubleMinutes" , y: "::absl::ToInt64Minutes" ); |
55 | InverseMap[DurationScale::Seconds] = |
56 | std::make_pair(x: "::absl::ToDoubleSeconds" , y: "::absl::ToInt64Seconds" ); |
57 | InverseMap[DurationScale::Milliseconds] = std::make_pair( |
58 | x: "::absl::ToDoubleMilliseconds" , y: "::absl::ToInt64Milliseconds" ); |
59 | InverseMap[DurationScale::Microseconds] = std::make_pair( |
60 | x: "::absl::ToDoubleMicroseconds" , y: "::absl::ToInt64Microseconds" ); |
61 | InverseMap[DurationScale::Nanoseconds] = std::make_pair( |
62 | x: "::absl::ToDoubleNanoseconds" , y: "::absl::ToInt64Nanoseconds" ); |
63 | return InverseMap; |
64 | }(); |
65 | |
66 | return InverseMap[Scale]; |
67 | } |
68 | |
69 | /// If `Node` is a call to the inverse of `Scale`, return that inverse's |
70 | /// argument, otherwise std::nullopt. |
71 | static std::optional<std::string> |
72 | rewriteInverseDurationCall(const MatchFinder::MatchResult &Result, |
73 | DurationScale Scale, const Expr &Node) { |
74 | const std::pair<llvm::StringRef, llvm::StringRef> &InverseFunctions = |
75 | getDurationInverseForScale(Scale); |
76 | if (const auto *MaybeCallArg = selectFirst<const Expr>( |
77 | BoundTo: "e" , |
78 | Results: match(Matcher: callExpr(callee(InnerMatcher: functionDecl(hasAnyName( |
79 | InverseFunctions.first, InverseFunctions.second))), |
80 | hasArgument(N: 0, InnerMatcher: expr().bind(ID: "e" ))), |
81 | Node, Context&: *Result.Context))) { |
82 | return tooling::fixit::getText(Node: *MaybeCallArg, Context: *Result.Context).str(); |
83 | } |
84 | |
85 | return std::nullopt; |
86 | } |
87 | |
88 | /// If `Node` is a call to the inverse of `Scale`, return that inverse's |
89 | /// argument, otherwise std::nullopt. |
90 | static std::optional<std::string> |
91 | rewriteInverseTimeCall(const MatchFinder::MatchResult &Result, |
92 | DurationScale Scale, const Expr &Node) { |
93 | llvm::StringRef InverseFunction = getTimeInverseForScale(Scale); |
94 | if (const auto *MaybeCallArg = selectFirst<const Expr>( |
95 | BoundTo: "e" , Results: match(Matcher: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: InverseFunction))), |
96 | hasArgument(N: 0, InnerMatcher: expr().bind(ID: "e" ))), |
97 | Node, Context&: *Result.Context))) { |
98 | return tooling::fixit::getText(Node: *MaybeCallArg, Context: *Result.Context).str(); |
99 | } |
100 | |
101 | return std::nullopt; |
102 | } |
103 | |
104 | /// Returns the factory function name for a given `Scale`. |
105 | llvm::StringRef getDurationFactoryForScale(DurationScale Scale) { |
106 | switch (Scale) { |
107 | case DurationScale::Hours: |
108 | return "absl::Hours" ; |
109 | case DurationScale::Minutes: |
110 | return "absl::Minutes" ; |
111 | case DurationScale::Seconds: |
112 | return "absl::Seconds" ; |
113 | case DurationScale::Milliseconds: |
114 | return "absl::Milliseconds" ; |
115 | case DurationScale::Microseconds: |
116 | return "absl::Microseconds" ; |
117 | case DurationScale::Nanoseconds: |
118 | return "absl::Nanoseconds" ; |
119 | } |
120 | llvm_unreachable("unknown scaling factor" ); |
121 | } |
122 | |
123 | llvm::StringRef getTimeFactoryForScale(DurationScale Scale) { |
124 | switch (Scale) { |
125 | case DurationScale::Hours: |
126 | return "absl::FromUnixHours" ; |
127 | case DurationScale::Minutes: |
128 | return "absl::FromUnixMinutes" ; |
129 | case DurationScale::Seconds: |
130 | return "absl::FromUnixSeconds" ; |
131 | case DurationScale::Milliseconds: |
132 | return "absl::FromUnixMillis" ; |
133 | case DurationScale::Microseconds: |
134 | return "absl::FromUnixMicros" ; |
135 | case DurationScale::Nanoseconds: |
136 | return "absl::FromUnixNanos" ; |
137 | } |
138 | llvm_unreachable("unknown scaling factor" ); |
139 | } |
140 | |
141 | /// Returns the Time factory function name for a given `Scale`. |
142 | llvm::StringRef getTimeInverseForScale(DurationScale Scale) { |
143 | switch (Scale) { |
144 | case DurationScale::Hours: |
145 | return "absl::ToUnixHours" ; |
146 | case DurationScale::Minutes: |
147 | return "absl::ToUnixMinutes" ; |
148 | case DurationScale::Seconds: |
149 | return "absl::ToUnixSeconds" ; |
150 | case DurationScale::Milliseconds: |
151 | return "absl::ToUnixMillis" ; |
152 | case DurationScale::Microseconds: |
153 | return "absl::ToUnixMicros" ; |
154 | case DurationScale::Nanoseconds: |
155 | return "absl::ToUnixNanos" ; |
156 | } |
157 | llvm_unreachable("unknown scaling factor" ); |
158 | } |
159 | |
160 | /// Returns `true` if `Node` is a value which evaluates to a literal `0`. |
161 | bool isLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node) { |
162 | auto ZeroMatcher = |
163 | anyOf(integerLiteral(equals(Value: 0)), floatLiteral(equals(Value: 0.0))); |
164 | |
165 | // Check to see if we're using a zero directly. |
166 | if (selectFirst<const clang::Expr>( |
167 | BoundTo: "val" , Results: match(Matcher: expr(ignoringImpCasts(InnerMatcher: ZeroMatcher)).bind(ID: "val" ), Node, |
168 | Context&: *Result.Context)) != nullptr) |
169 | return true; |
170 | |
171 | // Now check to see if we're using a functional cast with a scalar |
172 | // initializer expression, e.g. `int{0}`. |
173 | if (selectFirst<const clang::Expr>( |
174 | BoundTo: "val" , Results: match(Matcher: cxxFunctionalCastExpr( |
175 | hasDestinationType( |
176 | InnerMatcher: anyOf(isInteger(), realFloatingPointType())), |
177 | hasSourceExpression(InnerMatcher: initListExpr( |
178 | hasInit(N: 0, InnerMatcher: ignoringParenImpCasts(InnerMatcher: ZeroMatcher))))) |
179 | .bind(ID: "val" ), |
180 | Node, Context&: *Result.Context)) != nullptr) |
181 | return true; |
182 | |
183 | return false; |
184 | } |
185 | |
186 | std::optional<std::string> |
187 | stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result, |
188 | const Expr &Node) { |
189 | if (const Expr *MaybeCastArg = selectFirst<const Expr>( |
190 | BoundTo: "cast_arg" , |
191 | Results: match(Matcher: expr(anyOf(cxxStaticCastExpr( |
192 | hasDestinationType(InnerMatcher: realFloatingPointType()), |
193 | hasSourceExpression(InnerMatcher: expr().bind(ID: "cast_arg" ))), |
194 | cStyleCastExpr( |
195 | hasDestinationType(InnerMatcher: realFloatingPointType()), |
196 | hasSourceExpression(InnerMatcher: expr().bind(ID: "cast_arg" ))), |
197 | cxxFunctionalCastExpr( |
198 | hasDestinationType(InnerMatcher: realFloatingPointType()), |
199 | hasSourceExpression(InnerMatcher: expr().bind(ID: "cast_arg" ))))), |
200 | Node, Context&: *Result.Context))) |
201 | return tooling::fixit::getText(Node: *MaybeCastArg, Context: *Result.Context).str(); |
202 | |
203 | return std::nullopt; |
204 | } |
205 | |
206 | std::optional<std::string> |
207 | stripFloatLiteralFraction(const MatchFinder::MatchResult &Result, |
208 | const Expr &Node) { |
209 | if (const auto *LitFloat = llvm::dyn_cast<FloatingLiteral>(Val: &Node)) |
210 | // Attempt to simplify a `Duration` factory call with a literal argument. |
211 | if (std::optional<llvm::APSInt> IntValue = truncateIfIntegral(FloatLiteral: *LitFloat)) |
212 | return toString(I: *IntValue, /*radix=*/Radix: 10); |
213 | |
214 | return std::nullopt; |
215 | } |
216 | |
217 | std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result, |
218 | const Expr &Node) { |
219 | // Check for an explicit cast to `float` or `double`. |
220 | if (std::optional<std::string> MaybeArg = stripFloatCast(Result, Node)) |
221 | return *MaybeArg; |
222 | |
223 | // Check for floats without fractional components. |
224 | if (std::optional<std::string> MaybeArg = |
225 | stripFloatLiteralFraction(Result, Node)) |
226 | return *MaybeArg; |
227 | |
228 | // We couldn't simplify any further, so return the argument text. |
229 | return tooling::fixit::getText(Node, Context: *Result.Context).str(); |
230 | } |
231 | |
232 | std::optional<DurationScale> getScaleForDurationInverse(llvm::StringRef Name) { |
233 | static const llvm::StringMap<DurationScale> ScaleMap( |
234 | {{"ToDoubleHours" , DurationScale::Hours}, |
235 | {"ToInt64Hours" , DurationScale::Hours}, |
236 | {"ToDoubleMinutes" , DurationScale::Minutes}, |
237 | {"ToInt64Minutes" , DurationScale::Minutes}, |
238 | {"ToDoubleSeconds" , DurationScale::Seconds}, |
239 | {"ToInt64Seconds" , DurationScale::Seconds}, |
240 | {"ToDoubleMilliseconds" , DurationScale::Milliseconds}, |
241 | {"ToInt64Milliseconds" , DurationScale::Milliseconds}, |
242 | {"ToDoubleMicroseconds" , DurationScale::Microseconds}, |
243 | {"ToInt64Microseconds" , DurationScale::Microseconds}, |
244 | {"ToDoubleNanoseconds" , DurationScale::Nanoseconds}, |
245 | {"ToInt64Nanoseconds" , DurationScale::Nanoseconds}}); |
246 | |
247 | auto ScaleIter = ScaleMap.find(Key: std::string(Name)); |
248 | if (ScaleIter == ScaleMap.end()) |
249 | return std::nullopt; |
250 | |
251 | return ScaleIter->second; |
252 | } |
253 | |
254 | std::optional<DurationScale> getScaleForTimeInverse(llvm::StringRef Name) { |
255 | static const llvm::StringMap<DurationScale> ScaleMap( |
256 | {{"ToUnixHours" , DurationScale::Hours}, |
257 | {"ToUnixMinutes" , DurationScale::Minutes}, |
258 | {"ToUnixSeconds" , DurationScale::Seconds}, |
259 | {"ToUnixMillis" , DurationScale::Milliseconds}, |
260 | {"ToUnixMicros" , DurationScale::Microseconds}, |
261 | {"ToUnixNanos" , DurationScale::Nanoseconds}}); |
262 | |
263 | auto ScaleIter = ScaleMap.find(Key: std::string(Name)); |
264 | if (ScaleIter == ScaleMap.end()) |
265 | return std::nullopt; |
266 | |
267 | return ScaleIter->second; |
268 | } |
269 | |
270 | std::string rewriteExprFromNumberToDuration( |
271 | const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, |
272 | const Expr *Node) { |
273 | const Expr &RootNode = *Node->IgnoreParenImpCasts(); |
274 | |
275 | // First check to see if we can undo a complementary function call. |
276 | if (std::optional<std::string> MaybeRewrite = |
277 | rewriteInverseDurationCall(Result, Scale, Node: RootNode)) |
278 | return *MaybeRewrite; |
279 | |
280 | if (isLiteralZero(Result, Node: RootNode)) |
281 | return {"absl::ZeroDuration()" }; |
282 | |
283 | return (llvm::Twine(getDurationFactoryForScale(Scale)) + "(" + |
284 | simplifyDurationFactoryArg(Result, Node: RootNode) + ")" ) |
285 | .str(); |
286 | } |
287 | |
288 | std::string rewriteExprFromNumberToTime( |
289 | const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, |
290 | const Expr *Node) { |
291 | const Expr &RootNode = *Node->IgnoreParenImpCasts(); |
292 | |
293 | // First check to see if we can undo a complementary function call. |
294 | if (std::optional<std::string> MaybeRewrite = |
295 | rewriteInverseTimeCall(Result, Scale, Node: RootNode)) |
296 | return *MaybeRewrite; |
297 | |
298 | if (isLiteralZero(Result, Node: RootNode)) |
299 | return {"absl::UnixEpoch()" }; |
300 | |
301 | return (llvm::Twine(getTimeFactoryForScale(Scale)) + "(" + |
302 | tooling::fixit::getText(Node: RootNode, Context: *Result.Context) + ")" ) |
303 | .str(); |
304 | } |
305 | |
306 | bool isInMacro(const MatchFinder::MatchResult &Result, const Expr *E) { |
307 | if (!E->getBeginLoc().isMacroID()) |
308 | return false; |
309 | |
310 | SourceLocation Loc = E->getBeginLoc(); |
311 | // We want to get closer towards the initial macro typed into the source only |
312 | // if the location is being expanded as a macro argument. |
313 | while (Result.SourceManager->isMacroArgExpansion(Loc)) { |
314 | // We are calling getImmediateMacroCallerLoc, but note it is essentially |
315 | // equivalent to calling getImmediateSpellingLoc in this context according |
316 | // to Clang implementation. We are not calling getImmediateSpellingLoc |
317 | // because Clang comment says it "should not generally be used by clients." |
318 | Loc = Result.SourceManager->getImmediateMacroCallerLoc(Loc); |
319 | } |
320 | return Loc.isMacroID(); |
321 | } |
322 | |
323 | } // namespace clang::tidy::abseil |
324 | |