DurationUnnecessaryConversionCheck.cpp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. //===--- DurationUnnecessaryConversionCheck.cpp - clang-tidy
  2. //-----------------------===//
  3. //
  4. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  5. // See https://llvm.org/LICENSE.txt for license information.
  6. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  7. //
  8. //===----------------------------------------------------------------------===//
  9. #include "DurationUnnecessaryConversionCheck.h"
  10. #include "DurationRewriter.h"
  11. #include "clang/AST/ASTContext.h"
  12. #include "clang/ASTMatchers/ASTMatchFinder.h"
  13. #include "clang/Tooling/FixIt.h"
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::abseil {
  16. void DurationUnnecessaryConversionCheck::registerMatchers(MatchFinder *Finder) {
  17. for (const auto &Scale : {"Hours", "Minutes", "Seconds", "Milliseconds",
  18. "Microseconds", "Nanoseconds"}) {
  19. std::string DurationFactory = (llvm::Twine("::absl::") + Scale).str();
  20. std::string FloatConversion =
  21. (llvm::Twine("::absl::ToDouble") + Scale).str();
  22. std::string IntegerConversion =
  23. (llvm::Twine("::absl::ToInt64") + Scale).str();
  24. // Matcher which matches the current scale's factory with a `1` argument,
  25. // e.g. `absl::Seconds(1)`.
  26. auto FactoryMatcher = ignoringElidableConstructorCall(
  27. callExpr(callee(functionDecl(hasName(DurationFactory))),
  28. hasArgument(0, ignoringImpCasts(integerLiteral(equals(1))))));
  29. // Matcher which matches either inverse function and binds its argument,
  30. // e.g. `absl::ToDoubleSeconds(dur)`.
  31. auto InverseFunctionMatcher = callExpr(
  32. callee(functionDecl(hasAnyName(FloatConversion, IntegerConversion))),
  33. hasArgument(0, expr().bind("arg")));
  34. // Matcher which matches a duration divided by the factory_matcher above,
  35. // e.g. `dur / absl::Seconds(1)`.
  36. auto DivisionOperatorMatcher = cxxOperatorCallExpr(
  37. hasOverloadedOperatorName("/"), hasArgument(0, expr().bind("arg")),
  38. hasArgument(1, FactoryMatcher));
  39. // Matcher which matches a duration argument to `FDivDuration`,
  40. // e.g. `absl::FDivDuration(dur, absl::Seconds(1))`
  41. auto FdivMatcher = callExpr(
  42. callee(functionDecl(hasName("::absl::FDivDuration"))),
  43. hasArgument(0, expr().bind("arg")), hasArgument(1, FactoryMatcher));
  44. // Matcher which matches a duration argument being scaled,
  45. // e.g. `absl::ToDoubleSeconds(dur) * 2`
  46. auto ScalarMatcher = ignoringImpCasts(
  47. binaryOperator(hasOperatorName("*"),
  48. hasEitherOperand(expr(ignoringParenImpCasts(
  49. callExpr(callee(functionDecl(hasAnyName(
  50. FloatConversion, IntegerConversion))),
  51. hasArgument(0, expr().bind("arg")))
  52. .bind("inner_call")))))
  53. .bind("binop"));
  54. Finder->addMatcher(
  55. callExpr(callee(functionDecl(hasName(DurationFactory))),
  56. hasArgument(0, anyOf(InverseFunctionMatcher,
  57. DivisionOperatorMatcher, FdivMatcher,
  58. ScalarMatcher)))
  59. .bind("call"),
  60. this);
  61. }
  62. }
  63. void DurationUnnecessaryConversionCheck::check(
  64. const MatchFinder::MatchResult &Result) {
  65. const auto *OuterCall = Result.Nodes.getNodeAs<Expr>("call");
  66. if (isInMacro(Result, OuterCall))
  67. return;
  68. FixItHint Hint;
  69. if (const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop")) {
  70. const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
  71. const auto *InnerCall = Result.Nodes.getNodeAs<Expr>("inner_call");
  72. const Expr *LHS = Binop->getLHS();
  73. const Expr *RHS = Binop->getRHS();
  74. if (LHS->IgnoreParenImpCasts() == InnerCall) {
  75. Hint = FixItHint::CreateReplacement(
  76. OuterCall->getSourceRange(),
  77. (llvm::Twine(tooling::fixit::getText(*Arg, *Result.Context)) + " * " +
  78. tooling::fixit::getText(*RHS, *Result.Context))
  79. .str());
  80. } else {
  81. assert(RHS->IgnoreParenImpCasts() == InnerCall &&
  82. "Inner call should be find on the RHS");
  83. Hint = FixItHint::CreateReplacement(
  84. OuterCall->getSourceRange(),
  85. (llvm::Twine(tooling::fixit::getText(*LHS, *Result.Context)) + " * " +
  86. tooling::fixit::getText(*Arg, *Result.Context))
  87. .str());
  88. }
  89. } else if (const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg")) {
  90. Hint = FixItHint::CreateReplacement(
  91. OuterCall->getSourceRange(),
  92. tooling::fixit::getText(*Arg, *Result.Context));
  93. }
  94. diag(OuterCall->getBeginLoc(),
  95. "remove unnecessary absl::Duration conversions")
  96. << Hint;
  97. }
  98. } // namespace clang::tidy::abseil