• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
10 #include "DurationUnnecessaryConversionCheck.h"
11 #include "DurationRewriter.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Tooling/FixIt.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace abseil {
21 
registerMatchers(MatchFinder * Finder)22 void DurationUnnecessaryConversionCheck::registerMatchers(MatchFinder *Finder) {
23   for (const auto &Scale : {"Hours", "Minutes", "Seconds", "Milliseconds",
24                             "Microseconds", "Nanoseconds"}) {
25     std::string DurationFactory = (llvm::Twine("::absl::") + Scale).str();
26     std::string FloatConversion =
27         (llvm::Twine("::absl::ToDouble") + Scale).str();
28     std::string IntegerConversion =
29         (llvm::Twine("::absl::ToInt64") + Scale).str();
30 
31     // Matcher which matches the current scale's factory with a `1` argument,
32     // e.g. `absl::Seconds(1)`.
33     auto factory_matcher = ignoringElidableConstructorCall(
34         callExpr(callee(functionDecl(hasName(DurationFactory))),
35                  hasArgument(0, ignoringImpCasts(integerLiteral(equals(1))))));
36 
37     // Matcher which matches either inverse function and binds its argument,
38     // e.g. `absl::ToDoubleSeconds(dur)`.
39     auto inverse_function_matcher = callExpr(
40         callee(functionDecl(hasAnyName(FloatConversion, IntegerConversion))),
41         hasArgument(0, expr().bind("arg")));
42 
43     // Matcher which matches a duration divided by the factory_matcher above,
44     // e.g. `dur / absl::Seconds(1)`.
45     auto division_operator_matcher = cxxOperatorCallExpr(
46         hasOverloadedOperatorName("/"), hasArgument(0, expr().bind("arg")),
47         hasArgument(1, factory_matcher));
48 
49     // Matcher which matches a duration argument to `FDivDuration`,
50     // e.g. `absl::FDivDuration(dur, absl::Seconds(1))`
51     auto fdiv_matcher = callExpr(
52         callee(functionDecl(hasName("::absl::FDivDuration"))),
53         hasArgument(0, expr().bind("arg")), hasArgument(1, factory_matcher));
54 
55     // Matcher which matches a duration argument being scaled,
56     // e.g. `absl::ToDoubleSeconds(dur) * 2`
57     auto scalar_matcher = ignoringImpCasts(
58         binaryOperator(hasOperatorName("*"),
59                        hasEitherOperand(expr(ignoringParenImpCasts(
60                            callExpr(callee(functionDecl(hasAnyName(
61                                         FloatConversion, IntegerConversion))),
62                                     hasArgument(0, expr().bind("arg")))
63                                .bind("inner_call")))))
64             .bind("binop"));
65 
66     Finder->addMatcher(
67         callExpr(callee(functionDecl(hasName(DurationFactory))),
68                  hasArgument(0, anyOf(inverse_function_matcher,
69                                       division_operator_matcher, fdiv_matcher,
70                                       scalar_matcher)))
71             .bind("call"),
72         this);
73   }
74 }
75 
check(const MatchFinder::MatchResult & Result)76 void DurationUnnecessaryConversionCheck::check(
77     const MatchFinder::MatchResult &Result) {
78   const auto *OuterCall = Result.Nodes.getNodeAs<Expr>("call");
79 
80   if (isInMacro(Result, OuterCall))
81     return;
82 
83   FixItHint Hint;
84   if (const auto *Binop = Result.Nodes.getNodeAs<BinaryOperator>("binop")) {
85     const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
86     const auto *InnerCall = Result.Nodes.getNodeAs<Expr>("inner_call");
87     const Expr *LHS = Binop->getLHS();
88     const Expr *RHS = Binop->getRHS();
89 
90     if (LHS->IgnoreParenImpCasts() == InnerCall) {
91       Hint = FixItHint::CreateReplacement(
92           OuterCall->getSourceRange(),
93           (llvm::Twine(tooling::fixit::getText(*Arg, *Result.Context)) + " * " +
94            tooling::fixit::getText(*RHS, *Result.Context))
95               .str());
96     } else {
97       assert(RHS->IgnoreParenImpCasts() == InnerCall &&
98              "Inner call should be find on the RHS");
99 
100       Hint = FixItHint::CreateReplacement(
101           OuterCall->getSourceRange(),
102           (llvm::Twine(tooling::fixit::getText(*LHS, *Result.Context)) + " * " +
103            tooling::fixit::getText(*Arg, *Result.Context))
104               .str());
105     }
106   } else if (const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg")) {
107     Hint = FixItHint::CreateReplacement(
108         OuterCall->getSourceRange(),
109         tooling::fixit::getText(*Arg, *Result.Context));
110   }
111   diag(OuterCall->getBeginLoc(),
112        "remove unnecessary absl::Duration conversions")
113       << Hint;
114 }
115 
116 } // namespace abseil
117 } // namespace tidy
118 } // namespace clang
119