clang-tools 20.0.0git
DurationRewriter.cpp
Go to the documentation of this file.
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
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(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
40const std::pair<llvm::StringRef, llvm::StringRef> &
42 static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>,
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>,
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.
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 =
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.
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 "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`.
106 switch (Scale) {
108 return "absl::Hours";
110 return "absl::Minutes";
112 return "absl::Seconds";
114 return "absl::Milliseconds";
116 return "absl::Microseconds";
118 return "absl::Nanoseconds";
119 }
120 llvm_unreachable("unknown scaling factor");
121}
122
123llvm::StringRef getTimeFactoryForScale(DurationScale Scale) {
124 switch (Scale) {
126 return "absl::FromUnixHours";
128 return "absl::FromUnixMinutes";
130 return "absl::FromUnixSeconds";
132 return "absl::FromUnixMillis";
134 return "absl::FromUnixMicros";
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) {
145 return "absl::ToUnixHours";
147 return "absl::ToUnixMinutes";
149 return "absl::ToUnixSeconds";
151 return "absl::ToUnixMillis";
153 return "absl::ToUnixMicros";
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(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
186std::optional<std::string>
187stripFloatCast(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
206std::optional<std::string>
207stripFloatLiteralFraction(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
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 =
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
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(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(Name);
264 if (ScaleIter == ScaleMap.end())
265 return std::nullopt;
266
267 return ScaleIter->second;
268}
269
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
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
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
const Expr * E
llvm::SmallString< 256U > Name
SourceLocation Loc
::clang::DynTypedNode Node
const std::pair< llvm::StringRef, llvm::StringRef > & getDurationInverseForScale(DurationScale Scale)
Given a Scale return the fully qualified inverse functions for it.
std::optional< DurationScale > getScaleForTimeInverse(llvm::StringRef Name)
Given the name of an inverse Time function (e.g., ToUnixSeconds), return its DurationScale,...
std::optional< std::string > stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result, const Expr &Node)
Possibly strip a floating point cast expression.
std::optional< std::string > stripFloatLiteralFraction(const MatchFinder::MatchResult &Result, const Expr &Node)
std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result, const Expr &Node)
bool isLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node)
Returns true if Node is a value which evaluates to a literal 0.
llvm::StringRef getDurationFactoryForScale(DurationScale Scale)
Returns the factory function name for a given Scale.
static std::optional< std::string > rewriteInverseDurationCall(const MatchFinder::MatchResult &Result, DurationScale Scale, const Expr &Node)
If Node is a call to the inverse of Scale, return that inverse's argument, otherwise std::nullopt.
std::string rewriteExprFromNumberToTime(const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, const Expr *Node)
Assuming Node has a type int representing a time instant of Scale since The Epoch,...
DurationScale
Duration factory and conversion scales.
std::optional< DurationScale > getScaleForDurationInverse(llvm::StringRef Name)
Given the name of an inverse Duration function (e.g., ToDoubleSeconds), return its DurationScale,...
bool isInMacro(const MatchFinder::MatchResult &Result, const Expr *E)
llvm::StringRef getTimeFactoryForScale(DurationScale Scale)
Given a 'Scale', return the appropriate factory function call for constructing a Time for that scale.
std::string rewriteExprFromNumberToDuration(const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, const Expr *Node)
Assuming Node has type double or int representing a time interval of Scale, return the expression to ...
static std::optional< llvm::APSInt > truncateIfIntegral(const FloatingLiteral &FloatLiteral)
Returns an integer if the fractional part of a FloatingLiteral is 0.
static std::optional< std::string > rewriteInverseTimeCall(const MatchFinder::MatchResult &Result, DurationScale Scale, const Expr &Node)
If Node is a call to the inverse of Scale, return that inverse's argument, otherwise std::nullopt.
llvm::StringRef getTimeInverseForScale(DurationScale Scale)
Returns the Time factory function name for a given Scale.
unsigned operator()(DurationScale Scale) const