xref: /llvm-project/clang-tools-extra/clang-tidy/abseil/DurationRewriter.cpp (revision c4dfa03f9f44fa183daabdd4e6d760a432ef6531)
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(Value, 1) == 0) {
32     if (Value >= static_cast<double>(1U << 31))
33       return std::nullopt;
34 
35     return llvm::APSInt::get(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(6);
51         InverseMap[DurationScale::Hours] =
52             std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours");
53         InverseMap[DurationScale::Minutes] =
54             std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes");
55         InverseMap[DurationScale::Seconds] =
56             std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds");
57         InverseMap[DurationScale::Milliseconds] = std::make_pair(
58             "::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds");
59         InverseMap[DurationScale::Microseconds] = std::make_pair(
60             "::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds");
61         InverseMap[DurationScale::Nanoseconds] = std::make_pair(
62             "::absl::ToDoubleNanoseconds", "::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           "e",
78           match(callExpr(callee(functionDecl(hasAnyName(
79                              InverseFunctions.first, InverseFunctions.second))),
80                          hasArgument(0, expr().bind("e"))),
81                 Node, *Result.Context))) {
82     return tooling::fixit::getText(*MaybeCallArg, *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           "e", match(callExpr(callee(functionDecl(hasName(InverseFunction))),
96                               hasArgument(0, expr().bind("e"))),
97                      Node, *Result.Context))) {
98     return tooling::fixit::getText(*MaybeCallArg, *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(0)), floatLiteral(equals(0.0)));
164 
165   // Check to see if we're using a zero directly.
166   if (selectFirst<const clang::Expr>(
167           "val", match(expr(ignoringImpCasts(ZeroMatcher)).bind("val"), Node,
168                        *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           "val", match(cxxFunctionalCastExpr(
175                            hasDestinationType(
176                                anyOf(isInteger(), realFloatingPointType())),
177                            hasSourceExpression(initListExpr(
178                                hasInit(0, ignoringParenImpCasts(ZeroMatcher)))))
179                            .bind("val"),
180                        Node, *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           "cast_arg",
191           match(expr(anyOf(cxxStaticCastExpr(
192                                hasDestinationType(realFloatingPointType()),
193                                hasSourceExpression(expr().bind("cast_arg"))),
194                            cStyleCastExpr(
195                                hasDestinationType(realFloatingPointType()),
196                                hasSourceExpression(expr().bind("cast_arg"))),
197                            cxxFunctionalCastExpr(
198                                hasDestinationType(realFloatingPointType()),
199                                hasSourceExpression(expr().bind("cast_arg"))))),
200                 Node, *Result.Context)))
201     return tooling::fixit::getText(*MaybeCastArg, *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>(&Node))
210     // Attempt to simplify a `Duration` factory call with a literal argument.
211     if (std::optional<llvm::APSInt> IntValue = truncateIfIntegral(*LitFloat))
212       return toString(*IntValue, /*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, *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(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(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, RootNode))
278     return *MaybeRewrite;
279 
280   if (isLiteralZero(Result, RootNode))
281     return {"absl::ZeroDuration()"};
282 
283   return (llvm::Twine(getDurationFactoryForScale(Scale)) + "(" +
284           simplifyDurationFactoryArg(Result, 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, RootNode))
296     return *MaybeRewrite;
297 
298   if (isLiteralZero(Result, RootNode))
299     return {"absl::UnixEpoch()"};
300 
301   return (llvm::Twine(getTimeFactoryForScale(Scale)) + "(" +
302           tooling::fixit::getText(RootNode, *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