[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clang-tidy / abseil / TimeSubtractionCheck.cpp
blob58ce1f4be622ed2a449116d22456dabba5966f95
1 //===--- TimeSubtractionCheck.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 //===----------------------------------------------------------------------===//
9 #include "TimeSubtractionCheck.h"
10 #include "DurationRewriter.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 #include "clang/Tooling/FixIt.h"
16 using namespace clang::ast_matchers;
18 namespace clang {
19 namespace tidy {
20 namespace abseil {
22 // Returns `true` if `Range` is inside a macro definition.
23 static bool insideMacroDefinition(const MatchFinder::MatchResult &Result,
24 SourceRange Range) {
25 return !clang::Lexer::makeFileCharRange(
26 clang::CharSourceRange::getCharRange(Range),
27 *Result.SourceManager, Result.Context->getLangOpts())
28 .isValid();
31 static bool isConstructorAssignment(const MatchFinder::MatchResult &Result,
32 const Expr *Node) {
33 // For C++14 and earlier there are elidable constructors that must be matched
34 // in hasParent. The elidable constructors do not exist in C++17 and later and
35 // therefore an additional check that does not match against the elidable
36 // constructors are needed for this case.
37 return selectFirst<const Expr>(
38 "e",
39 match(expr(anyOf(
40 callExpr(hasParent(materializeTemporaryExpr(hasParent(
41 cxxConstructExpr(hasParent(exprWithCleanups(
42 hasParent(varDecl()))))))))
43 .bind("e"),
44 callExpr(hasParent(varDecl())).bind("e"))),
45 *Node, *Result.Context)) != nullptr;
48 static bool isArgument(const MatchFinder::MatchResult &Result,
49 const Expr *Node) {
50 // For the same reason as in isConstructorAssignment two AST shapes need to be
51 // matched here.
52 return selectFirst<const Expr>(
53 "e",
54 match(
55 expr(anyOf(
56 expr(hasParent(materializeTemporaryExpr(
57 hasParent(cxxConstructExpr(
58 hasParent(callExpr()),
59 unless(hasParent(cxxOperatorCallExpr())))))))
60 .bind("e"),
61 expr(hasParent(callExpr()),
62 unless(hasParent(cxxOperatorCallExpr())))
63 .bind("e"))),
64 *Node, *Result.Context)) != nullptr;
67 static bool isReturn(const MatchFinder::MatchResult &Result, const Expr *Node) {
68 // For the same reason as in isConstructorAssignment two AST shapes need to be
69 // matched here.
70 return selectFirst<const Expr>(
71 "e",
72 match(expr(anyOf(
73 expr(hasParent(materializeTemporaryExpr(hasParent(
74 cxxConstructExpr(hasParent(exprWithCleanups(
75 hasParent(returnStmt()))))))))
76 .bind("e"),
77 expr(hasParent(returnStmt())).bind("e"))),
78 *Node, *Result.Context)) != nullptr;
81 static bool parensRequired(const MatchFinder::MatchResult &Result,
82 const Expr *Node) {
83 // TODO: Figure out any more contexts in which we can omit the surrounding
84 // parentheses.
85 return !(isConstructorAssignment(Result, Node) || isArgument(Result, Node) ||
86 isReturn(Result, Node));
89 void TimeSubtractionCheck::emitDiagnostic(const Expr *Node,
90 llvm::StringRef Replacement) {
91 diag(Node->getBeginLoc(), "perform subtraction in the time domain")
92 << FixItHint::CreateReplacement(Node->getSourceRange(), Replacement);
95 void TimeSubtractionCheck::registerMatchers(MatchFinder *Finder) {
96 for (const char *ScaleName :
97 {"Hours", "Minutes", "Seconds", "Millis", "Micros", "Nanos"}) {
98 std::string TimeInverse = (llvm::Twine("ToUnix") + ScaleName).str();
99 llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(TimeInverse);
100 assert(Scale && "Unknown scale encountered");
102 auto TimeInverseMatcher = callExpr(callee(
103 functionDecl(hasName((llvm::Twine("::absl::") + TimeInverse).str()))
104 .bind("func_decl")));
106 // Match the cases where we know that the result is a 'Duration' and the
107 // first argument is a 'Time'. Just knowing the type of the first operand
108 // is not sufficient, since the second operand could be either a 'Time' or
109 // a 'Duration'. If we know the result is a 'Duration', we can then infer
110 // that the second operand must be a 'Time'.
111 auto CallMatcher =
112 callExpr(
113 callee(functionDecl(hasName(getDurationFactoryForScale(*Scale)))),
114 hasArgument(0, binaryOperator(hasOperatorName("-"),
115 hasLHS(TimeInverseMatcher))
116 .bind("binop")))
117 .bind("outer_call");
118 Finder->addMatcher(CallMatcher, this);
120 // Match cases where we know the second operand is a 'Time'. Since
121 // subtracting a 'Time' from a 'Duration' is not defined, in these cases,
122 // we always know the first operand is a 'Time' if the second is a 'Time'.
123 auto OperandMatcher =
124 binaryOperator(hasOperatorName("-"), hasRHS(TimeInverseMatcher))
125 .bind("binop");
126 Finder->addMatcher(OperandMatcher, this);
130 void TimeSubtractionCheck::check(const MatchFinder::MatchResult &Result) {
131 const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");
132 std::string InverseName =
133 Result.Nodes.getNodeAs<FunctionDecl>("func_decl")->getNameAsString();
134 if (insideMacroDefinition(Result, BinOp->getSourceRange()))
135 return;
137 llvm::Optional<DurationScale> Scale = getScaleForTimeInverse(InverseName);
138 if (!Scale)
139 return;
141 const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");
142 if (OuterCall) {
143 if (insideMacroDefinition(Result, OuterCall->getSourceRange()))
144 return;
146 // We're working with the first case of matcher, and need to replace the
147 // entire 'Duration' factory call. (Which also means being careful about
148 // our order-of-operations and optionally putting in some parenthesis.
149 bool NeedParens = parensRequired(Result, OuterCall);
151 emitDiagnostic(
152 OuterCall,
153 (llvm::Twine(NeedParens ? "(" : "") +
154 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) + " - " +
155 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
156 (NeedParens ? ")" : ""))
157 .str());
158 } else {
159 // We're working with the second case of matcher, and either just need to
160 // change the arguments, or perhaps remove an outer function call. In the
161 // latter case (addressed first), we also need to worry about parenthesis.
162 const auto *MaybeCallArg = selectFirst<const CallExpr>(
163 "arg", match(expr(hasAncestor(
164 callExpr(callee(functionDecl(hasName(
165 getDurationFactoryForScale(*Scale)))))
166 .bind("arg"))),
167 *BinOp, *Result.Context));
168 if (MaybeCallArg && MaybeCallArg->getArg(0)->IgnoreImpCasts() == BinOp &&
169 !insideMacroDefinition(Result, MaybeCallArg->getSourceRange())) {
170 // Handle the case where the matched expression is inside a call which
171 // converts it from the inverse to a Duration. In this case, we replace
172 // the outer with just the subtraction expression, which gives the right
173 // type and scale, taking care again about parenthesis.
174 bool NeedParens = parensRequired(Result, MaybeCallArg);
176 emitDiagnostic(
177 MaybeCallArg,
178 (llvm::Twine(NeedParens ? "(" : "") +
179 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
180 " - " +
181 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) +
182 (NeedParens ? ")" : ""))
183 .str());
184 } else {
185 // In the last case, just convert the arguments and wrap the result in
186 // the correct inverse function.
187 emitDiagnostic(
188 BinOp,
189 (llvm::Twine(
190 getDurationInverseForScale(*Scale).second.str().substr(2)) +
191 "(" + rewriteExprFromNumberToTime(Result, *Scale, BinOp->getLHS()) +
192 " - " +
193 rewriteExprFromNumberToTime(Result, *Scale, BinOp->getRHS()) + ")")
194 .str());
199 } // namespace abseil
200 } // namespace tidy
201 } // namespace clang