Skip to content

Add clang-tidy check to suggest replacement of conditional statement with std::min/std::max #77816

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
1883d98
Add readability check to suggest replacement of conditional statement…
11happy Jan 11, 2024
89be4ba
Formatted the code
11happy Jan 11, 2024
e1b65a9
small fix
11happy Jan 11, 2024
042ddc3
Revert "small fix"
11happy Jan 12, 2024
c17b298
Revert "Formatted the code"
11happy Jan 12, 2024
9aad658
Revert "Add readability check to suggest replacement of conditional s…
11happy Jan 12, 2024
2fc0938
Added Suggested Changes
11happy Jan 12, 2024
1139ac4
Formatted the Code
11happy Jan 12, 2024
f73e9f0
Added Release Notes
11happy Jan 12, 2024
faf3312
corrected file name
11happy Jan 12, 2024
5ed9896
corrected title length in docs
11happy Jan 12, 2024
19c4b63
Suggested Changes
11happy Jan 12, 2024
8167787
body indentation
11happy Jan 12, 2024
3a28399
added intialisation,constexpr tests
11happy Jan 13, 2024
2ecd08a
small fixes
11happy Jan 13, 2024
00d4080
Addded Suggested Changes
11happy Jan 15, 2024
6db0820
formatted the code
11happy Jan 15, 2024
5073650
Added Include Check
11happy Jan 15, 2024
c728a39
Formatted the code
11happy Jan 15, 2024
b8202ae
Added tests for macro & if with brackets
11happy Jan 17, 2024
3b57307
Fixed merge Conflict
11happy Jan 17, 2024
5e31665
Added suggested changes
11happy Jan 19, 2024
a2cfcdc
Formatted the code
11happy Jan 19, 2024
f5a0f12
Converted to Lambdas,placed at sorted location
11happy Jan 19, 2024
e239576
Single Lambda
11happy Jan 20, 2024
b868d5b
CamelCase,const auto*
11happy Jan 20, 2024
0797e0c
fixes
11happy Jan 20, 2024
c6028b0
few nits
11happy Jan 21, 2024
6acf645
formatted the code
11happy Jan 21, 2024
0eb4aca
extra checks
11happy Jan 21, 2024
d5b8dc2
ensure if has no else if
11happy Jan 22, 2024
a92160c
correct style issue
11happy Jan 22, 2024
5eafb59
correct docs
11happy Jan 22, 2024
26bfbdc
correct docs
11happy Jan 22, 2024
57bccd6
static functions
11happy Jan 22, 2024
61fd66a
small fix
11happy Jan 22, 2024
a841ebc
style issues
11happy Jan 23, 2024
7d2c085
corrrect style issue
11happy Jan 23, 2024
399c4c5
Merge branch 'SecondPR' of https://github.com/11happy/llvm-project in…
11happy Jan 23, 2024
abec9b5
correct functional bugs
11happy Jan 26, 2024
6a0e495
pass dependent types
11happy Jan 26, 2024
274b55f
filter implicit cast expr
11happy Jan 26, 2024
b7ca014
resolved merge conflict
11happy Jan 26, 2024
8bf8235
Add mising type, optimise
11happy Jan 27, 2024
d8df8c8
removed whitespace
11happy Jan 27, 2024
8a5d585
remove unsed initialisation
11happy Jan 27, 2024
85fb67d
small change
11happy Jan 27, 2024
1214547
simplify
11happy Jan 29, 2024
6aef713
small fix
11happy Jan 30, 2024
f03b2ff
non template alias
11happy Feb 1, 2024
5d504a7
simplify pointer dereference
11happy Feb 5, 2024
19cc1f3
remove extra parentheses
11happy Feb 5, 2024
a316744
use spefici type instead of auto
11happy Feb 5, 2024
f0ef998
use auto for cast kind
11happy Feb 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/readability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ add_clang_library(clangTidyReadabilityModule
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
UseStdMinMaxCheck.cpp

LINK_LIBS
clangTidy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
#include "UseStdMinMaxCheck.h"

namespace clang::tidy {
namespace readability {
Expand Down Expand Up @@ -163,6 +164,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-uppercase-literal-suffix");
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
"readability-use-anyofallof");
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
}
};

Expand Down
189 changes: 189 additions & 0 deletions clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//===--- UseStdMinMaxCheck.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 "UseStdMinMaxCheck.h"
#include "../utils/ASTUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Preprocessor.h"

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

namespace {

// Ignore if statements that are inside macros.
AST_MATCHER(IfStmt, isIfInMacro) {
return Node.getIfLoc().isMacroID() || Node.getEndLoc().isMacroID();
}

} // namespace

static const llvm::StringRef AlgorithmHeader("<algorithm>");

static bool minCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs,
const Expr *CondRhs, const Expr *AssignLhs,
const Expr *AssignRhs, const ASTContext &Context) {
if ((Op == BO_LT || Op == BO_LE) &&
(tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) &&
tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context)))
return true;

if ((Op == BO_GT || Op == BO_GE) &&
(tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) &&
tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context)))
return true;

return false;
}

static bool maxCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs,
const Expr *CondRhs, const Expr *AssignLhs,
const Expr *AssignRhs, const ASTContext &Context) {
if ((Op == BO_LT || Op == BO_LE) &&
(tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) &&
tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context)))
return true;

if ((Op == BO_GT || Op == BO_GE) &&
(tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) &&
tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context)))
return true;

return false;
}

QualType getNonTemplateAlias(QualType QT) {
while (true) {
// cast to a TypedefType
if (const TypedefType *TT = dyn_cast<TypedefType>(QT)) {
// check if the typedef is a template and if it is dependent
if (!TT->getDecl()->getDescribedTemplate() &&
!TT->getDecl()->getDeclContext()->isDependentContext())
return QT;
QT = TT->getDecl()->getUnderlyingType();
}
// cast to elaborated type
else if (const ElaboratedType *ET = dyn_cast<ElaboratedType>(QT)) {
QT = ET->getNamedType();
} else {
break;
}
}
return QT;
}

static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs,
const Expr *AssignLhs,
const SourceManager &Source,
const LangOptions &LO,
StringRef FunctionName,
const BinaryOperator *BO) {
const llvm::StringRef CondLhsStr = Lexer::getSourceText(
Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO);
const llvm::StringRef CondRhsStr = Lexer::getSourceText(
Source.getExpansionRange(CondRhs->getSourceRange()), Source, LO);
const llvm::StringRef AssignLhsStr = Lexer::getSourceText(
Source.getExpansionRange(AssignLhs->getSourceRange()), Source, LO);

clang::QualType GlobalImplicitCastType;
clang::QualType LhsType = CondLhs->getType()
.getCanonicalType()
.getNonReferenceType()
.getUnqualifiedType();
clang::QualType RhsType = CondRhs->getType()
.getCanonicalType()
.getNonReferenceType()
.getUnqualifiedType();
if (LhsType != RhsType) {
GlobalImplicitCastType = getNonTemplateAlias(BO->getLHS()->getType());
}

return (AssignLhsStr + " = " + FunctionName +
(!GlobalImplicitCastType.isNull()
? "<" + GlobalImplicitCastType.getAsString() + ">("
: "(") +
CondLhsStr + ", " + CondRhsStr + ");")
.str();
}

UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM),
areDiagsSelfContained()) {}

void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
}

void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) {
auto AssignOperator =
binaryOperator(hasOperatorName("="),
hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")),
hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs")));
auto BinaryOperator =
binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="),
hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")),
hasRHS(expr(unless(isTypeDependent())).bind("CondRhs")))
.bind("binaryOp");
Finder->addMatcher(
ifStmt(stmt().bind("if"), unless(isIfInMacro()),
unless(hasElse(stmt())), // Ensure `if` has no `else`
hasCondition(BinaryOperator),
hasThen(
anyOf(stmt(AssignOperator),
compoundStmt(statementCountIs(1), has(AssignOperator)))),
hasParent(stmt(unless(ifStmt(hasElse(
equalsBoundNode("if"))))))), // Ensure `if` has no `else if`
this);
}

void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
}

void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) {
const clang::IfStmt *If = Result.Nodes.getNodeAs<IfStmt>("if");
const clang::LangOptions &LO = Result.Context->getLangOpts();
const clang::Expr *CondLhs = Result.Nodes.getNodeAs<Expr>("CondLhs");
const clang::Expr *CondRhs = Result.Nodes.getNodeAs<Expr>("CondRhs");
const clang::Expr *AssignLhs = Result.Nodes.getNodeAs<Expr>("AssignLhs");
const clang::Expr *AssignRhs = Result.Nodes.getNodeAs<Expr>("AssignRhs");
const clang::BinaryOperator *BinaryOp =
Result.Nodes.getNodeAs<BinaryOperator>("binaryOp");
const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode();
const SourceLocation IfLocation = If->getIfLoc();
const SourceLocation ThenLocation = If->getEndLoc();

auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) {
const SourceManager &Source = *Result.SourceManager;
diag(IfLocation, "use `%0` instead of `%1`")
<< FunctionName << BinaryOp->getOpcodeStr()
<< FixItHint::CreateReplacement(
SourceRange(IfLocation, Lexer::getLocForEndOfToken(
ThenLocation, 0, Source, LO)),
createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO,
FunctionName, BinaryOp))
<< IncludeInserter.createIncludeInsertion(
Source.getFileID(If->getBeginLoc()), AlgorithmHeader);
};

if (minCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
(*Result.Context))) {
ReplaceAndDiagnose("std::min");
} else if (maxCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
(*Result.Context))) {
ReplaceAndDiagnose("std::max");
}
}

} // namespace clang::tidy::readability
42 changes: 42 additions & 0 deletions clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===--- UseStdMinMaxCheck.h - clang-tidy -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESTDMINMAXCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESTDMINMAXCHECK_H

#include "../ClangTidyCheck.h"
#include "../utils/IncludeInserter.h"

namespace clang::tidy::readability {

/// Replaces certain conditional statements with equivalent calls to
/// ``std::min`` or ``std::max``.
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/UseStdMinMax.html
class UseStdMinMaxCheck : public ClangTidyCheck {
public:
UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}

private:
utils::IncludeInserter IncludeInserter;
};

} // namespace clang::tidy::readability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESTDMINMAXCHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`readability-use-std-min-max
<clang-tidy/checks/readability/use-std-min-max>` check.

Replaces certain conditional statements with equivalent calls to
``std::min`` or ``std::max``.

New check aliases
^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ Clang-Tidy Checks
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
:doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
:doc:`readability-use-anyofallof <readability/use-anyofallof>`,
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
:doc:`zircon-temporary-objects <zircon/temporary-objects>`,


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. title:: clang-tidy - readability-use-std-min-max

readability-use-std-min-max
===========================

Replaces certain conditional statements with equivalent calls to
``std::min`` or ``std::max``.
Note: This may impact performance in critical code due to potential
additional stores compared to the original if statement.

Before:

.. code-block:: c++

void foo() {
int a = 2, b = 3;
if (a < b)
a = b;
}


After:

.. code-block:: c++

void foo() {
int a = 2, b = 3;
a = std::max(a, b);
}
Loading