| 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(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 | |
| 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(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. |
| 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 | 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. |
| 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 | 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`. |
| 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(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 | |
| 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 | 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 | |
| 206 | std::optional<std::string> |
| 207 | stripFloatLiteralFraction(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 | |
| 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, Context: *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(Key: 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(Key: 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, 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 | |
| 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, 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 | |
| 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 | |