llvm-project
323 строки · 12.2 Кб
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
16using namespace clang::ast_matchers;17
18namespace clang::tidy::abseil {19
20struct DurationScale2IndexFunctor {21using argument_type = DurationScale;22unsigned operator()(DurationScale Scale) const {23return static_cast<unsigned>(Scale);24}25};26
27/// Returns an integer if the fractional part of a `FloatingLiteral` is `0`.
28static std::optional<llvm::APSInt>29truncateIfIntegral(const FloatingLiteral &FloatLiteral) {30double Value = FloatLiteral.getValueAsApproximateDouble();31if (std::fmod(Value, 1) == 0) {32if (Value >= static_cast<double>(1U << 31))33return std::nullopt;34
35return llvm::APSInt::get(static_cast<int64_t>(Value));36}37return std::nullopt;38}
39
40const std::pair<llvm::StringRef, llvm::StringRef> &41getDurationInverseForScale(DurationScale Scale) {42static const llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>,43DurationScale2IndexFunctor>44InverseMap = []() {45// TODO: Revisit the immediately invoked lambda technique when46// IndexedMap gets an initializer list constructor.47llvm::IndexedMap<std::pair<llvm::StringRef, llvm::StringRef>,48DurationScale2IndexFunctor>49InverseMap;50InverseMap.resize(6);51InverseMap[DurationScale::Hours] =52std::make_pair("::absl::ToDoubleHours", "::absl::ToInt64Hours");53InverseMap[DurationScale::Minutes] =54std::make_pair("::absl::ToDoubleMinutes", "::absl::ToInt64Minutes");55InverseMap[DurationScale::Seconds] =56std::make_pair("::absl::ToDoubleSeconds", "::absl::ToInt64Seconds");57InverseMap[DurationScale::Milliseconds] = std::make_pair(58"::absl::ToDoubleMilliseconds", "::absl::ToInt64Milliseconds");59InverseMap[DurationScale::Microseconds] = std::make_pair(60"::absl::ToDoubleMicroseconds", "::absl::ToInt64Microseconds");61InverseMap[DurationScale::Nanoseconds] = std::make_pair(62"::absl::ToDoubleNanoseconds", "::absl::ToInt64Nanoseconds");63return InverseMap;64}();65
66return InverseMap[Scale];67}
68
69/// If `Node` is a call to the inverse of `Scale`, return that inverse's
70/// argument, otherwise std::nullopt.
71static std::optional<std::string>72rewriteInverseDurationCall(const MatchFinder::MatchResult &Result,73DurationScale Scale, const Expr &Node) {74const std::pair<llvm::StringRef, llvm::StringRef> &InverseFunctions =75getDurationInverseForScale(Scale);76if (const auto *MaybeCallArg = selectFirst<const Expr>(77"e",78match(callExpr(callee(functionDecl(hasAnyName(79InverseFunctions.first, InverseFunctions.second))),80hasArgument(0, expr().bind("e"))),81Node, *Result.Context))) {82return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str();83}84
85return std::nullopt;86}
87
88/// If `Node` is a call to the inverse of `Scale`, return that inverse's
89/// argument, otherwise std::nullopt.
90static std::optional<std::string>91rewriteInverseTimeCall(const MatchFinder::MatchResult &Result,92DurationScale Scale, const Expr &Node) {93llvm::StringRef InverseFunction = getTimeInverseForScale(Scale);94if (const auto *MaybeCallArg = selectFirst<const Expr>(95"e", match(callExpr(callee(functionDecl(hasName(InverseFunction))),96hasArgument(0, expr().bind("e"))),97Node, *Result.Context))) {98return tooling::fixit::getText(*MaybeCallArg, *Result.Context).str();99}100
101return std::nullopt;102}
103
104/// Returns the factory function name for a given `Scale`.
105llvm::StringRef getDurationFactoryForScale(DurationScale Scale) {106switch (Scale) {107case DurationScale::Hours:108return "absl::Hours";109case DurationScale::Minutes:110return "absl::Minutes";111case DurationScale::Seconds:112return "absl::Seconds";113case DurationScale::Milliseconds:114return "absl::Milliseconds";115case DurationScale::Microseconds:116return "absl::Microseconds";117case DurationScale::Nanoseconds:118return "absl::Nanoseconds";119}120llvm_unreachable("unknown scaling factor");121}
122
123llvm::StringRef getTimeFactoryForScale(DurationScale Scale) {124switch (Scale) {125case DurationScale::Hours:126return "absl::FromUnixHours";127case DurationScale::Minutes:128return "absl::FromUnixMinutes";129case DurationScale::Seconds:130return "absl::FromUnixSeconds";131case DurationScale::Milliseconds:132return "absl::FromUnixMillis";133case DurationScale::Microseconds:134return "absl::FromUnixMicros";135case DurationScale::Nanoseconds:136return "absl::FromUnixNanos";137}138llvm_unreachable("unknown scaling factor");139}
140
141/// Returns the Time factory function name for a given `Scale`.
142llvm::StringRef getTimeInverseForScale(DurationScale Scale) {143switch (Scale) {144case DurationScale::Hours:145return "absl::ToUnixHours";146case DurationScale::Minutes:147return "absl::ToUnixMinutes";148case DurationScale::Seconds:149return "absl::ToUnixSeconds";150case DurationScale::Milliseconds:151return "absl::ToUnixMillis";152case DurationScale::Microseconds:153return "absl::ToUnixMicros";154case DurationScale::Nanoseconds:155return "absl::ToUnixNanos";156}157llvm_unreachable("unknown scaling factor");158}
159
160/// Returns `true` if `Node` is a value which evaluates to a literal `0`.
161bool isLiteralZero(const MatchFinder::MatchResult &Result, const Expr &Node) {162auto ZeroMatcher =163anyOf(integerLiteral(equals(0)), floatLiteral(equals(0.0)));164
165// Check to see if we're using a zero directly.166if (selectFirst<const clang::Expr>(167"val", match(expr(ignoringImpCasts(ZeroMatcher)).bind("val"), Node,168*Result.Context)) != nullptr)169return true;170
171// Now check to see if we're using a functional cast with a scalar172// initializer expression, e.g. `int{0}`.173if (selectFirst<const clang::Expr>(174"val", match(cxxFunctionalCastExpr(175hasDestinationType(176anyOf(isInteger(), realFloatingPointType())),177hasSourceExpression(initListExpr(178hasInit(0, ignoringParenImpCasts(ZeroMatcher)))))179.bind("val"),180Node, *Result.Context)) != nullptr)181return true;182
183return false;184}
185
186std::optional<std::string>187stripFloatCast(const ast_matchers::MatchFinder::MatchResult &Result,188const Expr &Node) {189if (const Expr *MaybeCastArg = selectFirst<const Expr>(190"cast_arg",191match(expr(anyOf(cxxStaticCastExpr(192hasDestinationType(realFloatingPointType()),193hasSourceExpression(expr().bind("cast_arg"))),194cStyleCastExpr(195hasDestinationType(realFloatingPointType()),196hasSourceExpression(expr().bind("cast_arg"))),197cxxFunctionalCastExpr(198hasDestinationType(realFloatingPointType()),199hasSourceExpression(expr().bind("cast_arg"))))),200Node, *Result.Context)))201return tooling::fixit::getText(*MaybeCastArg, *Result.Context).str();202
203return std::nullopt;204}
205
206std::optional<std::string>207stripFloatLiteralFraction(const MatchFinder::MatchResult &Result,208const Expr &Node) {209if (const auto *LitFloat = llvm::dyn_cast<FloatingLiteral>(&Node))210// Attempt to simplify a `Duration` factory call with a literal argument.211if (std::optional<llvm::APSInt> IntValue = truncateIfIntegral(*LitFloat))212return toString(*IntValue, /*radix=*/10);213
214return std::nullopt;215}
216
217std::string simplifyDurationFactoryArg(const MatchFinder::MatchResult &Result,218const Expr &Node) {219// Check for an explicit cast to `float` or `double`.220if (std::optional<std::string> MaybeArg = stripFloatCast(Result, Node))221return *MaybeArg;222
223// Check for floats without fractional components.224if (std::optional<std::string> MaybeArg =225stripFloatLiteralFraction(Result, Node))226return *MaybeArg;227
228// We couldn't simplify any further, so return the argument text.229return tooling::fixit::getText(Node, *Result.Context).str();230}
231
232std::optional<DurationScale> getScaleForDurationInverse(llvm::StringRef Name) {233static 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
247auto ScaleIter = ScaleMap.find(std::string(Name));248if (ScaleIter == ScaleMap.end())249return std::nullopt;250
251return ScaleIter->second;252}
253
254std::optional<DurationScale> getScaleForTimeInverse(llvm::StringRef Name) {255static 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
263auto ScaleIter = ScaleMap.find(std::string(Name));264if (ScaleIter == ScaleMap.end())265return std::nullopt;266
267return ScaleIter->second;268}
269
270std::string rewriteExprFromNumberToDuration(271const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale,272const Expr *Node) {273const Expr &RootNode = *Node->IgnoreParenImpCasts();274
275// First check to see if we can undo a complementary function call.276if (std::optional<std::string> MaybeRewrite =277rewriteInverseDurationCall(Result, Scale, RootNode))278return *MaybeRewrite;279
280if (isLiteralZero(Result, RootNode))281return {"absl::ZeroDuration()"};282
283return (llvm::Twine(getDurationFactoryForScale(Scale)) + "(" +284simplifyDurationFactoryArg(Result, RootNode) + ")")285.str();286}
287
288std::string rewriteExprFromNumberToTime(289const ast_matchers::MatchFinder::MatchResult &Result, DurationScale Scale,290const Expr *Node) {291const Expr &RootNode = *Node->IgnoreParenImpCasts();292
293// First check to see if we can undo a complementary function call.294if (std::optional<std::string> MaybeRewrite =295rewriteInverseTimeCall(Result, Scale, RootNode))296return *MaybeRewrite;297
298if (isLiteralZero(Result, RootNode))299return {"absl::UnixEpoch()"};300
301return (llvm::Twine(getTimeFactoryForScale(Scale)) + "(" +302tooling::fixit::getText(RootNode, *Result.Context) + ")")303.str();304}
305
306bool isInMacro(const MatchFinder::MatchResult &Result, const Expr *E) {307if (!E->getBeginLoc().isMacroID())308return false;309
310SourceLocation Loc = E->getBeginLoc();311// We want to get closer towards the initial macro typed into the source only312// if the location is being expanded as a macro argument.313while (Result.SourceManager->isMacroArgExpansion(Loc)) {314// We are calling getImmediateMacroCallerLoc, but note it is essentially315// equivalent to calling getImmediateSpellingLoc in this context according316// to Clang implementation. We are not calling getImmediateSpellingLoc317// because Clang comment says it "should not generally be used by clients."318Loc = Result.SourceManager->getImmediateMacroCallerLoc(Loc);319}320return Loc.isMacroID();321}
322
323} // namespace clang::tidy::abseil324