| //===--- DurationUnnecessaryConversionCheck.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 "DurationUnnecessaryConversionCheck.h" |
| #include "DurationRewriter.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Tooling/FixIt.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace abseil { |
| |
| void DurationUnnecessaryConversionCheck::registerMatchers(MatchFinder *Finder) { |
| for (const auto &Scale : {"Hours", "Minutes", "Seconds", "Milliseconds", |
| "Microseconds", "Nanoseconds"}) { |
| std::string DurationFactory = (llvm::Twine("::absl::") + Scale).str(); |
| std::string FloatConversion = |
| (llvm::Twine("::absl::ToDouble") + Scale).str(); |
| std::string IntegerConversion = |
| (llvm::Twine("::absl::ToInt64") + Scale).str(); |
| |
| // Matcher which matches the current scale's factory with a `1` argument, |
| // e.g. `absl::Seconds(1)`. |
| auto FactoryMatcher = ignoringElidableConstructorCall( |
| callExpr(callee(functionDecl(hasName(DurationFactory))), |
| hasArgument(0, ignoringImpCasts(integerLiteral(equals(1)))))); |
| |
| // Matcher which matches either inverse function and binds its argument, |
| // e.g. `absl::ToDoubleSeconds(dur)`. |
| auto InverseFunctionMatcher = callExpr( |
| callee(functionDecl(hasAnyName(FloatConversion, IntegerConversion))), |
| hasArgument(0, expr().bind("arg"))); |
| |
| // Matcher which matches a duration divided by the factory_matcher above, |
| // e.g. `dur / absl::Seconds(1)`. |
| auto DivisionOperatorMatcher = cxxOperatorCallExpr( |
| hasOverloadedOperatorName("/"), hasArgument(0, expr().bind("arg")), |
| hasArgument(1, FactoryMatcher)); |
| |
| // Matcher which matches a duration argument to `FDivDuration`, |
| // e.g. `absl::FDivDuration(dur, absl::Seconds(1))` |
| auto FdivMatcher = callExpr( |
| callee(functionDecl(hasName("::absl::FDivDuration"))), |
| hasArgument(0, expr().bind("arg")), hasArgument(1, FactoryMatcher)); |
| |
| // Matcher which matches a duration argument being scaled, |
| // e.g. `absl::ToDoubleSeconds(dur) * 2` |
| auto ScalarMatcher = ignoringImpCasts( |
| binaryOperator(hasOperatorName("*"), |
| hasEitherOperand(expr(ignoringParenImpCasts( |
| callExpr(callee(functionDecl(hasAnyName( |
| FloatConversion, IntegerConversion))), |
| hasArgument(0, expr().bind("arg"))) |
| .bind("inner_call"))))) |
| .bind("binop")); |
| |
| Finder->addMatcher( |
| callExpr(callee(functionDecl(hasName(DurationFactory))), |
| hasArgument(0, anyOf(InverseFunctionMatcher, |
| DivisionOperatorMatcher, FdivMatcher, |
| ScalarMatcher))) |
| .bind("call"), |
| this); |
| } |
| } |
| |
| void DurationUnnecessaryConversionCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *OuterCall = Result.Nodes.getNodeAs<Expr>("call"); |
| |
| if (isInMacro(Result, OuterCall)) |
| return; |
| |
| FixItHint Hint; |
| if (const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop")) { |
| const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg"); |
| const auto *InnerCall = Result.Nodes.getNodeAs<Expr>("inner_call"); |
| const Expr *LHS = Binop->getLHS(); |
| const Expr *RHS = Binop->getRHS(); |
| |
| if (LHS->IgnoreParenImpCasts() == InnerCall) { |
| Hint = FixItHint::CreateReplacement( |
| OuterCall->getSourceRange(), |
| (llvm::Twine(tooling::fixit::getText(*Arg, *Result.Context)) + " * " + |
| tooling::fixit::getText(*RHS, *Result.Context)) |
| .str()); |
| } else { |
| assert(RHS->IgnoreParenImpCasts() == InnerCall && |
| "Inner call should be find on the RHS"); |
| |
| Hint = FixItHint::CreateReplacement( |
| OuterCall->getSourceRange(), |
| (llvm::Twine(tooling::fixit::getText(*LHS, *Result.Context)) + " * " + |
| tooling::fixit::getText(*Arg, *Result.Context)) |
| .str()); |
| } |
| } else if (const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg")) { |
| Hint = FixItHint::CreateReplacement( |
| OuterCall->getSourceRange(), |
| tooling::fixit::getText(*Arg, *Result.Context)); |
| } |
| diag(OuterCall->getBeginLoc(), |
| "remove unnecessary absl::Duration conversions") |
| << Hint; |
| } |
| |
| } // namespace abseil |
| } // namespace tidy |
| } // namespace clang |