1 //===--- DurationRewriter.cpp - clang-tidy --------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "DurationRewriter.h"
10 #include "clang/Tooling/FixIt.h"
11 #include "llvm/ADT/IndexedMap.h"
13 using namespace clang::ast_matchers
;
19 struct DurationScale2IndexFunctor
{
20 using argument_type
= DurationScale
;
21 unsigned operator()(DurationScale Scale
) const {
22 return static_cast<unsigned>(Scale
);
26 /// Returns an integer if the fractional part of a `FloatingLiteral` is `0`.
27 static llvm::Optional
<llvm::APSInt
>
28 truncateIfIntegral(const FloatingLiteral
&FloatLiteral
) {
29 double Value
= FloatLiteral
.getValueAsApproximateDouble();
30 if (std::fmod(Value
, 1) == 0) {
31 if (Value
>= static_cast<double>(1u << 31))
34 return llvm::APSInt::get(static_cast<int64_t>(Value
));
39 const std::pair
<llvm::StringRef
, llvm::StringRef
> &
40 getDurationInverseForScale(DurationScale Scale
) {
41 static const llvm::IndexedMap
<std::pair
<llvm::StringRef
, llvm::StringRef
>,
42 DurationScale2IndexFunctor
>
44 // TODO: Revisit the immediately invoked lambda technique when
45 // IndexedMap gets an initializer list constructor.
46 llvm::IndexedMap
<std::pair
<llvm::StringRef
, llvm::StringRef
>,
47 DurationScale2IndexFunctor
>
50 InverseMap
[DurationScale::Hours
] =
51 std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours");
52 InverseMap
[DurationScale::Minutes
] =
53 std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes");
54 InverseMap
[DurationScale::Seconds
] =
55 std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds");
56 InverseMap
[DurationScale::Milliseconds
] = std::make_pair(
57 "::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds");
58 InverseMap
[DurationScale::Microseconds
] = std::make_pair(
59 "::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds");
60 InverseMap
[DurationScale::Nanoseconds
] = std::make_pair(
61 "::absl::ToDoubleNanoseconds", "::absl::ToInt64Nanoseconds");
65 return InverseMap
[Scale
];
68 /// If `Node` is a call to the inverse of `Scale`, return that inverse's
69 /// argument, otherwise None.
70 static llvm::Optional
<std::string
>
71 rewriteInverseDurationCall(const MatchFinder::MatchResult
&Result
,
72 DurationScale Scale
, const Expr
&Node
) {
73 const std::pair
<llvm::StringRef
, llvm::StringRef
> &InverseFunctions
=
74 getDurationInverseForScale(Scale
);
75 if (const auto *MaybeCallArg
= selectFirst
<const Expr
>(
77 match(callExpr(callee(functionDecl(hasAnyName(
78 InverseFunctions
.first
, InverseFunctions
.second
))),
79 hasArgument(0, expr().bind("e"))),
80 Node
, *Result
.Context
))) {
81 return tooling::fixit::getText(*MaybeCallArg
, *Result
.Context
).str();
87 /// If `Node` is a call to the inverse of `Scale`, return that inverse's
88 /// argument, otherwise None.
89 static llvm::Optional
<std::string
>
90 rewriteInverseTimeCall(const MatchFinder::MatchResult
&Result
,
91 DurationScale Scale
, const Expr
&Node
) {
92 llvm::StringRef InverseFunction
= getTimeInverseForScale(Scale
);
93 if (const auto *MaybeCallArg
= selectFirst
<const Expr
>(
94 "e", match(callExpr(callee(functionDecl(hasName(InverseFunction
))),
95 hasArgument(0, expr().bind("e"))),
96 Node
, *Result
.Context
))) {
97 return tooling::fixit::getText(*MaybeCallArg
, *Result
.Context
).str();
103 /// Returns the factory function name for a given `Scale`.
104 llvm::StringRef
getDurationFactoryForScale(DurationScale Scale
) {
106 case DurationScale::Hours
:
107 return "absl::Hours";
108 case DurationScale::Minutes
:
109 return "absl::Minutes";
110 case DurationScale::Seconds
:
111 return "absl::Seconds";
112 case DurationScale::Milliseconds
:
113 return "absl::Milliseconds";
114 case DurationScale::Microseconds
:
115 return "absl::Microseconds";
116 case DurationScale::Nanoseconds
:
117 return "absl::Nanoseconds";
119 llvm_unreachable("unknown scaling factor");
122 llvm::StringRef
getTimeFactoryForScale(DurationScale Scale
) {
124 case DurationScale::Hours
:
125 return "absl::FromUnixHours";
126 case DurationScale::Minutes
:
127 return "absl::FromUnixMinutes";
128 case DurationScale::Seconds
:
129 return "absl::FromUnixSeconds";
130 case DurationScale::Milliseconds
:
131 return "absl::FromUnixMillis";
132 case DurationScale::Microseconds
:
133 return "absl::FromUnixMicros";
134 case DurationScale::Nanoseconds
:
135 return "absl::FromUnixNanos";
137 llvm_unreachable("unknown scaling factor");
140 /// Returns the Time factory function name for a given `Scale`.
141 llvm::StringRef
getTimeInverseForScale(DurationScale Scale
) {
143 case DurationScale::Hours
:
144 return "absl::ToUnixHours";
145 case DurationScale::Minutes
:
146 return "absl::ToUnixMinutes";
147 case DurationScale::Seconds
:
148 return "absl::ToUnixSeconds";
149 case DurationScale::Milliseconds
:
150 return "absl::ToUnixMillis";
151 case DurationScale::Microseconds
:
152 return "absl::ToUnixMicros";
153 case DurationScale::Nanoseconds
:
154 return "absl::ToUnixNanos";
156 llvm_unreachable("unknown scaling factor");
159 /// Returns `true` if `Node` is a value which evaluates to a literal `0`.
160 bool isLiteralZero(const MatchFinder::MatchResult
&Result
, const Expr
&Node
) {
162 anyOf(integerLiteral(equals(0)), floatLiteral(equals(0.0)));
164 // Check to see if we're using a zero directly.
165 if (selectFirst
<const clang::Expr
>(
166 "val", match(expr(ignoringImpCasts(ZeroMatcher
)).bind("val"), Node
,
167 *Result
.Context
)) != nullptr)
170 // Now check to see if we're using a functional cast with a scalar
171 // initializer expression, e.g. `int{0}`.
172 if (selectFirst
<const clang::Expr
>(
173 "val", match(cxxFunctionalCastExpr(
175 anyOf(isInteger(), realFloatingPointType())),
176 hasSourceExpression(initListExpr(
177 hasInit(0, ignoringParenImpCasts(ZeroMatcher
)))))
179 Node
, *Result
.Context
)) != nullptr)
185 llvm::Optional
<std::string
>
186 stripFloatCast(const ast_matchers::MatchFinder::MatchResult
&Result
,
188 if (const Expr
*MaybeCastArg
= selectFirst
<const Expr
>(
190 match(expr(anyOf(cxxStaticCastExpr(
191 hasDestinationType(realFloatingPointType()),
192 hasSourceExpression(expr().bind("cast_arg"))),
194 hasDestinationType(realFloatingPointType()),
195 hasSourceExpression(expr().bind("cast_arg"))),
196 cxxFunctionalCastExpr(
197 hasDestinationType(realFloatingPointType()),
198 hasSourceExpression(expr().bind("cast_arg"))))),
199 Node
, *Result
.Context
)))
200 return tooling::fixit::getText(*MaybeCastArg
, *Result
.Context
).str();
205 llvm::Optional
<std::string
>
206 stripFloatLiteralFraction(const MatchFinder::MatchResult
&Result
,
208 if (const auto *LitFloat
= llvm::dyn_cast
<FloatingLiteral
>(&Node
))
209 // Attempt to simplify a `Duration` factory call with a literal argument.
210 if (llvm::Optional
<llvm::APSInt
> IntValue
= truncateIfIntegral(*LitFloat
))
211 return toString(*IntValue
, /*radix=*/10);
216 std::string
simplifyDurationFactoryArg(const MatchFinder::MatchResult
&Result
,
218 // Check for an explicit cast to `float` or `double`.
219 if (llvm::Optional
<std::string
> MaybeArg
= stripFloatCast(Result
, Node
))
222 // Check for floats without fractional components.
223 if (llvm::Optional
<std::string
> MaybeArg
=
224 stripFloatLiteralFraction(Result
, Node
))
227 // We couldn't simplify any further, so return the argument text.
228 return tooling::fixit::getText(Node
, *Result
.Context
).str();
231 llvm::Optional
<DurationScale
> getScaleForDurationInverse(llvm::StringRef Name
) {
232 static const llvm::StringMap
<DurationScale
> ScaleMap(
233 {{"ToDoubleHours", DurationScale::Hours
},
234 {"ToInt64Hours", DurationScale::Hours
},
235 {"ToDoubleMinutes", DurationScale::Minutes
},
236 {"ToInt64Minutes", DurationScale::Minutes
},
237 {"ToDoubleSeconds", DurationScale::Seconds
},
238 {"ToInt64Seconds", DurationScale::Seconds
},
239 {"ToDoubleMilliseconds", DurationScale::Milliseconds
},
240 {"ToInt64Milliseconds", DurationScale::Milliseconds
},
241 {"ToDoubleMicroseconds", DurationScale::Microseconds
},
242 {"ToInt64Microseconds", DurationScale::Microseconds
},
243 {"ToDoubleNanoseconds", DurationScale::Nanoseconds
},
244 {"ToInt64Nanoseconds", DurationScale::Nanoseconds
}});
246 auto ScaleIter
= ScaleMap
.find(std::string(Name
));
247 if (ScaleIter
== ScaleMap
.end())
250 return ScaleIter
->second
;
253 llvm::Optional
<DurationScale
> getScaleForTimeInverse(llvm::StringRef Name
) {
254 static const llvm::StringMap
<DurationScale
> ScaleMap(
255 {{"ToUnixHours", DurationScale::Hours
},
256 {"ToUnixMinutes", DurationScale::Minutes
},
257 {"ToUnixSeconds", DurationScale::Seconds
},
258 {"ToUnixMillis", DurationScale::Milliseconds
},
259 {"ToUnixMicros", DurationScale::Microseconds
},
260 {"ToUnixNanos", DurationScale::Nanoseconds
}});
262 auto ScaleIter
= ScaleMap
.find(std::string(Name
));
263 if (ScaleIter
== ScaleMap
.end())
266 return ScaleIter
->second
;
269 std::string
rewriteExprFromNumberToDuration(
270 const ast_matchers::MatchFinder::MatchResult
&Result
, DurationScale Scale
,
272 const Expr
&RootNode
= *Node
->IgnoreParenImpCasts();
274 // First check to see if we can undo a complimentary function call.
275 if (llvm::Optional
<std::string
> MaybeRewrite
=
276 rewriteInverseDurationCall(Result
, Scale
, RootNode
))
277 return *MaybeRewrite
;
279 if (isLiteralZero(Result
, RootNode
))
280 return std::string("absl::ZeroDuration()");
282 return (llvm::Twine(getDurationFactoryForScale(Scale
)) + "(" +
283 simplifyDurationFactoryArg(Result
, RootNode
) + ")")
287 std::string
rewriteExprFromNumberToTime(
288 const ast_matchers::MatchFinder::MatchResult
&Result
, DurationScale Scale
,
290 const Expr
&RootNode
= *Node
->IgnoreParenImpCasts();
292 // First check to see if we can undo a complimentary function call.
293 if (llvm::Optional
<std::string
> MaybeRewrite
=
294 rewriteInverseTimeCall(Result
, Scale
, RootNode
))
295 return *MaybeRewrite
;
297 if (isLiteralZero(Result
, RootNode
))
298 return std::string("absl::UnixEpoch()");
300 return (llvm::Twine(getTimeFactoryForScale(Scale
)) + "(" +
301 tooling::fixit::getText(RootNode
, *Result
.Context
) + ")")
305 bool isInMacro(const MatchFinder::MatchResult
&Result
, const Expr
*E
) {
306 if (!E
->getBeginLoc().isMacroID())
309 SourceLocation Loc
= E
->getBeginLoc();
310 // We want to get closer towards the initial macro typed into the source only
311 // if the location is being expanded as a macro argument.
312 while (Result
.SourceManager
->isMacroArgExpansion(Loc
)) {
313 // We are calling getImmediateMacroCallerLoc, but note it is essentially
314 // equivalent to calling getImmediateSpellingLoc in this context according
315 // to Clang implementation. We are not calling getImmediateSpellingLoc
316 // because Clang comment says it "should not generally be used by clients."
317 Loc
= Result
.SourceManager
->getImmediateMacroCallerLoc(Loc
);
319 return Loc
.isMacroID();
322 } // namespace abseil