276 lines · cpp
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-exception6//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 const 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> &34getDurationInverseForScale(DurationScale Scale) {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's49/// 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 =54 getDurationInverseForScale(Scale);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's68/// argument, otherwise std::nullopt.69static std::optional<std::string>70rewriteInverseTimeCall(const MatchFinder::MatchResult &Result,71 DurationScale Scale, const Expr &Node) {72 const 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`.84llvm::StringRef getDurationFactoryForScale(DurationScale 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 scalar124 // 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 222std::string rewriteExprFromNumberToDuration(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 240std::string rewriteExprFromNumberToTime(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 only264 // 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 essentially267 // equivalent to calling getImmediateSpellingLoc in this context according268 // to Clang implementation. We are not calling getImmediateSpellingLoc269 // 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::abseil276