| //===--- DurationRewriter.cpp - clang-tidy --------------------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include <cmath> | 
 | #include <optional> | 
 |  | 
 | #include "DurationRewriter.h" | 
 | #include "clang/Tooling/FixIt.h" | 
 | #include "llvm/ADT/IndexedMap.h" | 
 |  | 
 | using namespace clang::ast_matchers; | 
 |  | 
 | namespace clang::tidy::abseil { | 
 |  | 
 | struct DurationScale2IndexFunctor { | 
 |   using argument_type = DurationScale; | 
 |   unsigned operator()(DurationScale Scale) const { | 
 |     return static_cast<unsigned>(Scale); | 
 |   } | 
 | }; | 
 |  | 
 | /// Returns an integer if the fractional part of a `FloatingLiteral` is `0`. | 
 | static std::optional<llvm::APSInt> | 
 | truncateIfIntegral(const FloatingLiteral &FloatLiteral) { | 
 |   double Value = FloatLiteral.getValueAsApproximateDouble(); | 
 |   if (std::fmod(Value, 1) == 0) { | 
 |     if (Value >= static_cast<double>(1U << 31)) | 
 |       return std::nullopt; | 
 |  | 
 |     return llvm::APSInt::get(static_cast<int64_t>(Value)); | 
 |   } | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | const std::pair<llvm::StringRef, llvm::StringRef> & | 
 | getDurationInverseForScale(DurationScale Scale) { | 
 |   static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, | 
 |                                 DurationScale2IndexFunctor> | 
 |       InverseMap = []() { | 
 |         // TODO: Revisit the immediately invoked lambda technique when | 
 |         // IndexedMap gets an initializer list constructor. | 
 |         llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>, | 
 |                          DurationScale2IndexFunctor> | 
 |             InverseMap; | 
 |         InverseMap.resize(6); | 
 |         InverseMap[DurationScale::Hours] = | 
 |             std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours"); | 
 |         InverseMap[DurationScale::Minutes] = | 
 |             std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes"); | 
 |         InverseMap[DurationScale::Seconds] = | 
 |             std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds"); | 
 |         InverseMap[DurationScale::Milliseconds] = std::make_pair( | 
 |             "::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds"); | 
 |         InverseMap[DurationScale::Microseconds] = std::make_pair( | 
 |             "::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds"); | 
 |         InverseMap[DurationScale::Nanoseconds] = std::make_pair( | 
 |             "::absl::ToDoubleNanoseconds", "::absl::ToInt64Nanoseconds"); | 
 |         return InverseMap; | 
 |       }(); | 
 |  | 
 |   return InverseMap[Scale]; | 
 | } | 
 |  | 
 | /// If `Node` is a call to the inverse of `Scale`, return that inverse's | 
 | /// argument, otherwise std::nullopt. | 
 | static std::optional<std::string> | 
 | rewriteInverseDurationCall(const MatchFinder::MatchResult &Result, | 
 |                            DurationScale Scale, const Expr &Node) { | 
 |   const std::pair<llvm::StringRef, llvm::StringRef> &InverseFunctions = | 
 |       getDurationInverseForScale(Scale); | 
 |   if (const auto *MaybeCallArg = selectFirst<const Expr>( | 
 |           "e", | 
 |           match(callExpr(callee(functionDecl(hasAnyName( | 
 |                              InverseFunctions.first, InverseFunctions.second))), | 
 |                          hasArgument(0, expr().bind("e"))), | 
 |                 Node, *Result.Context))) { | 
 |     return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str(); | 
 |   } | 
 |  | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | /// If `Node` is a call to the inverse of `Scale`, return that inverse's | 
 | /// argument, otherwise std::nullopt. | 
 | static std::optional<std::string> | 
 | rewriteInverseTimeCall(const MatchFinder::MatchResult &Result, | 
 |                        DurationScale Scale, const Expr &Node) { | 
 |   llvm::StringRef InverseFunction = getTimeInverseForScale(Scale); | 
 |   if (const auto *MaybeCallArg = selectFirst<const Expr>( | 
 |           "e", match(callExpr(callee(functionDecl(hasName(InverseFunction))), | 
 |                               hasArgument(0, expr().bind("e"))), | 
 |                      Node, *Result.Context))) { | 
 |     return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str(); | 
 |   } | 
 |  | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | /// Returns the factory function name for a given `Scale`. | 
 | llvm::StringRef getDurationFactoryForScale(DurationScale Scale) { | 
 |   switch (Scale) { | 
 |   case DurationScale::Hours: | 
 |     return "absl::Hours"; | 
 |   case DurationScale::Minutes: | 
 |     return "absl::Minutes"; | 
 |   case DurationScale::Seconds: | 
 |     return "absl::Seconds"; | 
 |   case DurationScale::Milliseconds: | 
 |     return "absl::Milliseconds"; | 
 |   case DurationScale::Microseconds: | 
 |     return "absl::Microseconds"; | 
 |   case DurationScale::Nanoseconds: | 
 |     return "absl::Nanoseconds"; | 
 |   } | 
 |   llvm_unreachable("unknown scaling factor"); | 
 | } | 
 |  | 
 | llvm::StringRef getTimeFactoryForScale(DurationScale Scale) { | 
 |   switch (Scale) { | 
 |   case DurationScale::Hours: | 
 |     return "absl::FromUnixHours"; | 
 |   case DurationScale::Minutes: | 
 |     return "absl::FromUnixMinutes"; | 
 |   case DurationScale::Seconds: | 
 |     return "absl::FromUnixSeconds"; | 
 |   case DurationScale::Milliseconds: | 
 |     return "absl::FromUnixMillis"; | 
 |   case DurationScale::Microseconds: | 
 |     return "absl::FromUnixMicros"; | 
 |   case DurationScale::Nanoseconds: | 
 |     return "absl::FromUnixNanos"; | 
 |   } | 
 |   llvm_unreachable("unknown scaling factor"); | 
 | } | 
 |  | 
 | /// Returns the Time factory function name for a given `Scale`. | 
 | llvm::StringRef getTimeInverseForScale(DurationScale Scale) { | 
 |   switch (Scale) { | 
 |   case DurationScale::Hours: | 
 |     return "absl::ToUnixHours"; | 
 |   case DurationScale::Minutes: | 
 |     return "absl::ToUnixMinutes"; | 
 |   case DurationScale::Seconds: | 
 |     return "absl::ToUnixSeconds"; | 
 |   case DurationScale::Milliseconds: | 
 |     return "absl::ToUnixMillis"; | 
 |   case DurationScale::Microseconds: | 
 |     return "absl::ToUnixMicros"; | 
 |   case DurationScale::Nanoseconds: | 
 |     return "absl::ToUnixNanos"; | 
 |   } | 
 |   llvm_unreachable("unknown scaling factor"); | 
 | } | 
 |  | 
 | /// Returns `true` if `Node` is a value which evaluates to a literal `0`. | 
 | bool isLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node) { | 
 |   auto ZeroMatcher = | 
 |       anyOf(integerLiteral(equals(0)), floatLiteral(equals(0.0))); | 
 |  | 
 |   // Check to see if we're using a zero directly. | 
 |   if (selectFirst<const clang::Expr>( | 
 |           "val", match(expr(ignoringImpCasts(ZeroMatcher)).bind("val"), Node, | 
 |                        *Result.Context)) != nullptr) | 
 |     return true; | 
 |  | 
 |   // Now check to see if we're using a functional cast with a scalar | 
 |   // initializer expression, e.g. `int{0}`. | 
 |   if (selectFirst<const clang::Expr>( | 
 |           "val", match(cxxFunctionalCastExpr( | 
 |                            hasDestinationType( | 
 |                                anyOf(isInteger(), realFloatingPointType())), | 
 |                            hasSourceExpression(initListExpr( | 
 |                                hasInit(0, ignoringParenImpCasts(ZeroMatcher))))) | 
 |                            .bind("val"), | 
 |                        Node, *Result.Context)) != nullptr) | 
 |     return true; | 
 |  | 
 |   return false; | 
 | } | 
 |  | 
 | std::optional<std::string> | 
 | stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result, | 
 |                const Expr &Node) { | 
 |   if (const Expr *MaybeCastArg = selectFirst<const Expr>( | 
 |           "cast_arg", | 
 |           match(expr(anyOf(cxxStaticCastExpr( | 
 |                                hasDestinationType(realFloatingPointType()), | 
 |                                hasSourceExpression(expr().bind("cast_arg"))), | 
 |                            cStyleCastExpr( | 
 |                                hasDestinationType(realFloatingPointType()), | 
 |                                hasSourceExpression(expr().bind("cast_arg"))), | 
 |                            cxxFunctionalCastExpr( | 
 |                                hasDestinationType(realFloatingPointType()), | 
 |                                hasSourceExpression(expr().bind("cast_arg"))))), | 
 |                 Node, *Result.Context))) | 
 |     return tooling::fixit::getText(*MaybeCastArg, *Result.Context).str(); | 
 |  | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | std::optional<std::string> | 
 | stripFloatLiteralFraction(const MatchFinder::MatchResult &Result, | 
 |                           const Expr &Node) { | 
 |   if (const auto *LitFloat = llvm::dyn_cast<FloatingLiteral>(&Node)) | 
 |     // Attempt to simplify a `Duration` factory call with a literal argument. | 
 |     if (std::optional<llvm::APSInt> IntValue = truncateIfIntegral(*LitFloat)) | 
 |       return toString(*IntValue, /*radix=*/10); | 
 |  | 
 |   return std::nullopt; | 
 | } | 
 |  | 
 | std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result, | 
 |                                        const Expr &Node) { | 
 |   // Check for an explicit cast to `float` or `double`. | 
 |   if (std::optional<std::string> MaybeArg = stripFloatCast(Result, Node)) | 
 |     return *MaybeArg; | 
 |  | 
 |   // Check for floats without fractional components. | 
 |   if (std::optional<std::string> MaybeArg = | 
 |           stripFloatLiteralFraction(Result, Node)) | 
 |     return *MaybeArg; | 
 |  | 
 |   // We couldn't simplify any further, so return the argument text. | 
 |   return tooling::fixit::getText(Node, *Result.Context).str(); | 
 | } | 
 |  | 
 | std::optional<DurationScale> getScaleForDurationInverse(llvm::StringRef Name) { | 
 |   static const llvm::StringMap<DurationScale> ScaleMap( | 
 |       {{"ToDoubleHours", DurationScale::Hours}, | 
 |        {"ToInt64Hours", DurationScale::Hours}, | 
 |        {"ToDoubleMinutes", DurationScale::Minutes}, | 
 |        {"ToInt64Minutes", DurationScale::Minutes}, | 
 |        {"ToDoubleSeconds", DurationScale::Seconds}, | 
 |        {"ToInt64Seconds", DurationScale::Seconds}, | 
 |        {"ToDoubleMilliseconds", DurationScale::Milliseconds}, | 
 |        {"ToInt64Milliseconds", DurationScale::Milliseconds}, | 
 |        {"ToDoubleMicroseconds", DurationScale::Microseconds}, | 
 |        {"ToInt64Microseconds", DurationScale::Microseconds}, | 
 |        {"ToDoubleNanoseconds", DurationScale::Nanoseconds}, | 
 |        {"ToInt64Nanoseconds", DurationScale::Nanoseconds}}); | 
 |  | 
 |   auto ScaleIter = ScaleMap.find(Name); | 
 |   if (ScaleIter == ScaleMap.end()) | 
 |     return std::nullopt; | 
 |  | 
 |   return ScaleIter->second; | 
 | } | 
 |  | 
 | std::optional<DurationScale> getScaleForTimeInverse(llvm::StringRef Name) { | 
 |   static const llvm::StringMap<DurationScale> ScaleMap( | 
 |       {{"ToUnixHours", DurationScale::Hours}, | 
 |        {"ToUnixMinutes", DurationScale::Minutes}, | 
 |        {"ToUnixSeconds", DurationScale::Seconds}, | 
 |        {"ToUnixMillis", DurationScale::Milliseconds}, | 
 |        {"ToUnixMicros", DurationScale::Microseconds}, | 
 |        {"ToUnixNanos", DurationScale::Nanoseconds}}); | 
 |  | 
 |   auto ScaleIter = ScaleMap.find(Name); | 
 |   if (ScaleIter == ScaleMap.end()) | 
 |     return std::nullopt; | 
 |  | 
 |   return ScaleIter->second; | 
 | } | 
 |  | 
 | std::string rewriteExprFromNumberToDuration( | 
 |     const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, | 
 |     const Expr *Node) { | 
 |   const Expr &RootNode = *Node->IgnoreParenImpCasts(); | 
 |  | 
 |   // First check to see if we can undo a complementary function call. | 
 |   if (std::optional<std::string> MaybeRewrite = | 
 |           rewriteInverseDurationCall(Result, Scale, RootNode)) | 
 |     return *MaybeRewrite; | 
 |  | 
 |   if (isLiteralZero(Result, RootNode)) | 
 |     return {"absl::ZeroDuration()"}; | 
 |  | 
 |   return (llvm::Twine(getDurationFactoryForScale(Scale)) + "(" + | 
 |           simplifyDurationFactoryArg(Result, RootNode) + ")") | 
 |       .str(); | 
 | } | 
 |  | 
 | std::string rewriteExprFromNumberToTime( | 
 |     const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale, | 
 |     const Expr *Node) { | 
 |   const Expr &RootNode = *Node->IgnoreParenImpCasts(); | 
 |  | 
 |   // First check to see if we can undo a complementary function call. | 
 |   if (std::optional<std::string> MaybeRewrite = | 
 |           rewriteInverseTimeCall(Result, Scale, RootNode)) | 
 |     return *MaybeRewrite; | 
 |  | 
 |   if (isLiteralZero(Result, RootNode)) | 
 |     return {"absl::UnixEpoch()"}; | 
 |  | 
 |   return (llvm::Twine(getTimeFactoryForScale(Scale)) + "(" + | 
 |           tooling::fixit::getText(RootNode, *Result.Context) + ")") | 
 |       .str(); | 
 | } | 
 |  | 
 | bool isInMacro(const MatchFinder::MatchResult &Result, const Expr *E) { | 
 |   if (!E->getBeginLoc().isMacroID()) | 
 |     return false; | 
 |  | 
 |   SourceLocation Loc = E->getBeginLoc(); | 
 |   // We want to get closer towards the initial macro typed into the source only | 
 |   // if the location is being expanded as a macro argument. | 
 |   while (Result.SourceManager->isMacroArgExpansion(Loc)) { | 
 |     // We are calling getImmediateMacroCallerLoc, but note it is essentially | 
 |     // equivalent to calling getImmediateSpellingLoc in this context according | 
 |     // to Clang implementation. We are not calling getImmediateSpellingLoc | 
 |     // because Clang comment says it "should not generally be used by clients." | 
 |     Loc = Result.SourceManager->getImmediateMacroCallerLoc(Loc); | 
 |   } | 
 |   return Loc.isMacroID(); | 
 | } | 
 |  | 
 | } // namespace clang::tidy::abseil |