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
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::abseil {
19
20struct 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`.
28static std::optional<llvm::APSInt>
29truncateIfIntegral(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
40const std::pair<llvm::StringRef, llvm::StringRef> &
41getDurationInverseForScale(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.
71static std::optional<std::string>
72rewriteInverseDurationCall(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.
90static std::optional<std::string>
91rewriteInverseTimeCall(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`.
105llvm::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
123llvm::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`.
142llvm::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`.
161bool 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
186std::optional<std::string>
187stripFloatCast(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
206std::optional<std::string>
207stripFloatLiteralFraction(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
217std::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
232std::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
254std::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
270std::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
288std::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
306bool 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

source code of clang-tools-extra/clang-tidy/abseil/DurationRewriter.cpp