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