1 //===--- UseStdMinMaxCheck.cpp - clang-tidy -------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UseStdMinMaxCheck.h"
10 #include "../utils/ASTUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Preprocessor.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::readability
{
21 // Ignore if statements that are inside macros.
22 AST_MATCHER(IfStmt
, isIfInMacro
) {
23 return Node
.getIfLoc().isMacroID() || Node
.getEndLoc().isMacroID();
28 static const llvm::StringRef
AlgorithmHeader("<algorithm>");
30 static bool minCondition(const BinaryOperator::Opcode Op
, const Expr
*CondLhs
,
31 const Expr
*CondRhs
, const Expr
*AssignLhs
,
32 const Expr
*AssignRhs
, const ASTContext
&Context
) {
33 if ((Op
== BO_LT
|| Op
== BO_LE
) &&
34 (tidy::utils::areStatementsIdentical(CondLhs
, AssignRhs
, Context
) &&
35 tidy::utils::areStatementsIdentical(CondRhs
, AssignLhs
, Context
)))
38 if ((Op
== BO_GT
|| Op
== BO_GE
) &&
39 (tidy::utils::areStatementsIdentical(CondLhs
, AssignLhs
, Context
) &&
40 tidy::utils::areStatementsIdentical(CondRhs
, AssignRhs
, Context
)))
46 static bool maxCondition(const BinaryOperator::Opcode Op
, const Expr
*CondLhs
,
47 const Expr
*CondRhs
, const Expr
*AssignLhs
,
48 const Expr
*AssignRhs
, const ASTContext
&Context
) {
49 if ((Op
== BO_LT
|| Op
== BO_LE
) &&
50 (tidy::utils::areStatementsIdentical(CondLhs
, AssignLhs
, Context
) &&
51 tidy::utils::areStatementsIdentical(CondRhs
, AssignRhs
, Context
)))
54 if ((Op
== BO_GT
|| Op
== BO_GE
) &&
55 (tidy::utils::areStatementsIdentical(CondLhs
, AssignRhs
, Context
) &&
56 tidy::utils::areStatementsIdentical(CondRhs
, AssignLhs
, Context
)))
62 QualType
getNonTemplateAlias(QualType QT
) {
64 // cast to a TypedefType
65 if (const TypedefType
*TT
= dyn_cast
<TypedefType
>(QT
)) {
66 // check if the typedef is a template and if it is dependent
67 if (!TT
->getDecl()->getDescribedTemplate() &&
68 !TT
->getDecl()->getDeclContext()->isDependentContext())
70 QT
= TT
->getDecl()->getUnderlyingType();
72 // cast to elaborated type
73 else if (const ElaboratedType
*ET
= dyn_cast
<ElaboratedType
>(QT
)) {
74 QT
= ET
->getNamedType();
82 static std::string
createReplacement(const Expr
*CondLhs
, const Expr
*CondRhs
,
83 const Expr
*AssignLhs
,
84 const SourceManager
&Source
,
85 const LangOptions
&LO
,
86 StringRef FunctionName
,
87 const BinaryOperator
*BO
) {
88 const llvm::StringRef CondLhsStr
= Lexer::getSourceText(
89 Source
.getExpansionRange(CondLhs
->getSourceRange()), Source
, LO
);
90 const llvm::StringRef CondRhsStr
= Lexer::getSourceText(
91 Source
.getExpansionRange(CondRhs
->getSourceRange()), Source
, LO
);
92 const llvm::StringRef AssignLhsStr
= Lexer::getSourceText(
93 Source
.getExpansionRange(AssignLhs
->getSourceRange()), Source
, LO
);
95 clang::QualType GlobalImplicitCastType
;
96 clang::QualType LhsType
= CondLhs
->getType()
98 .getNonReferenceType()
99 .getUnqualifiedType();
100 clang::QualType RhsType
= CondRhs
->getType()
102 .getNonReferenceType()
103 .getUnqualifiedType();
104 if (LhsType
!= RhsType
) {
105 GlobalImplicitCastType
= getNonTemplateAlias(BO
->getLHS()->getType());
108 return (AssignLhsStr
+ " = " + FunctionName
+
109 (!GlobalImplicitCastType
.isNull()
110 ? "<" + GlobalImplicitCastType
.getAsString() + ">("
112 CondLhsStr
+ ", " + CondRhsStr
+ ");")
116 UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name
, ClangTidyContext
*Context
)
117 : ClangTidyCheck(Name
, Context
),
118 IncludeInserter(Options
.getLocalOrGlobal("IncludeStyle",
119 utils::IncludeSorter::IS_LLVM
),
120 areDiagsSelfContained()) {}
122 void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
123 Options
.store(Opts
, "IncludeStyle", IncludeInserter
.getStyle());
126 void UseStdMinMaxCheck::registerMatchers(MatchFinder
*Finder
) {
127 auto AssignOperator
=
128 binaryOperator(hasOperatorName("="),
129 hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")),
130 hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs")));
131 auto BinaryOperator
=
132 binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="),
133 hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")),
134 hasRHS(expr(unless(isTypeDependent())).bind("CondRhs")))
137 ifStmt(stmt().bind("if"), unless(isIfInMacro()),
138 unless(hasElse(stmt())), // Ensure `if` has no `else`
139 hasCondition(BinaryOperator
),
141 anyOf(stmt(AssignOperator
),
142 compoundStmt(statementCountIs(1), has(AssignOperator
)))),
143 hasParent(stmt(unless(ifStmt(hasElse(
144 equalsBoundNode("if"))))))), // Ensure `if` has no `else if`
148 void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager
&SM
,
150 Preprocessor
*ModuleExpanderPP
) {
151 IncludeInserter
.registerPreprocessor(PP
);
154 void UseStdMinMaxCheck::check(const MatchFinder::MatchResult
&Result
) {
155 const auto *If
= Result
.Nodes
.getNodeAs
<IfStmt
>("if");
156 const clang::LangOptions
&LO
= Result
.Context
->getLangOpts();
157 const auto *CondLhs
= Result
.Nodes
.getNodeAs
<Expr
>("CondLhs");
158 const auto *CondRhs
= Result
.Nodes
.getNodeAs
<Expr
>("CondRhs");
159 const auto *AssignLhs
= Result
.Nodes
.getNodeAs
<Expr
>("AssignLhs");
160 const auto *AssignRhs
= Result
.Nodes
.getNodeAs
<Expr
>("AssignRhs");
161 const auto *BinaryOp
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("binaryOp");
162 const clang::BinaryOperatorKind BinaryOpcode
= BinaryOp
->getOpcode();
163 const SourceLocation IfLocation
= If
->getIfLoc();
164 const SourceLocation ThenLocation
= If
->getEndLoc();
166 auto ReplaceAndDiagnose
= [&](const llvm::StringRef FunctionName
) {
167 const SourceManager
&Source
= *Result
.SourceManager
;
168 diag(IfLocation
, "use `%0` instead of `%1`")
169 << FunctionName
<< BinaryOp
->getOpcodeStr()
170 << FixItHint::CreateReplacement(
171 SourceRange(IfLocation
, Lexer::getLocForEndOfToken(
172 ThenLocation
, 0, Source
, LO
)),
173 createReplacement(CondLhs
, CondRhs
, AssignLhs
, Source
, LO
,
174 FunctionName
, BinaryOp
))
175 << IncludeInserter
.createIncludeInsertion(
176 Source
.getFileID(If
->getBeginLoc()), AlgorithmHeader
);
179 if (minCondition(BinaryOpcode
, CondLhs
, CondRhs
, AssignLhs
, AssignRhs
,
180 (*Result
.Context
))) {
181 ReplaceAndDiagnose("std::min");
182 } else if (maxCondition(BinaryOpcode
, CondLhs
, CondRhs
, AssignLhs
, AssignRhs
,
183 (*Result
.Context
))) {
184 ReplaceAndDiagnose("std::max");
188 } // namespace clang::tidy::readability