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 11 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 @@ -52,6 +52,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 @@ -55,13 +55,16 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
#include "UseStdMinMaxCheck.h"

namespace clang::tidy {
namespace readability {

class ReadabilityModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
CheckFactories.registerCheck<AvoidConstParamsInDecls>(
"readability-avoid-const-params-in-decls");
CheckFactories.registerCheck<AvoidReturnWithVoidValueCheck>(
Expand Down
97 changes: 97 additions & 0 deletions clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===--- 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 "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Preprocessor.h"
#include <optional>

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
ifStmt(
has(binaryOperator(
anyOf(hasOperatorName("<"), hasOperatorName(">"),
hasOperatorName("<="), hasOperatorName(">=")),
hasLHS(expr().bind("lhsVar1")), hasRHS(expr().bind("rhsVar1")))),
hasThen(stmt(binaryOperator(hasOperatorName("="),
hasLHS(expr().bind("lhsVar2")),
hasRHS(expr().bind("rhsVar2"))))))
.bind("ifStmt"),
this);
}

void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) {
const auto *lhsVar1 = Result.Nodes.getNodeAs<Expr>("lhsVar1");
const auto *rhsVar1 = Result.Nodes.getNodeAs<Expr>("rhsVar1");
const auto *lhsVar2 = Result.Nodes.getNodeAs<Expr>("lhsVar2");
const auto *rhsVar2 = Result.Nodes.getNodeAs<Expr>("rhsVar2");
const auto *ifStmt = Result.Nodes.getNodeAs<IfStmt>("ifStmt");
auto &Context = *Result.Context;

if (!lhsVar1 || !rhsVar1 || !lhsVar2 || !rhsVar2 || !ifStmt)
return;

const BinaryOperator *binaryOp = dyn_cast<BinaryOperator>(ifStmt->getCond());
if (!binaryOp)
return;

SourceLocation ifLocation = ifStmt->getIfLoc();
SourceLocation thenLocation = ifStmt->getEndLoc();
auto lhsVar1Str = Lexer::getSourceText(
CharSourceRange::getTokenRange(lhsVar1->getSourceRange()),
Context.getSourceManager(), Context.getLangOpts());

auto lhsVar2Str = Lexer::getSourceText(
CharSourceRange::getTokenRange(lhsVar2->getSourceRange()),
Context.getSourceManager(), Context.getLangOpts());

auto rhsVar1Str = Lexer::getSourceText(
CharSourceRange::getTokenRange(rhsVar1->getSourceRange()),
Context.getSourceManager(), Context.getLangOpts());

auto rhsVar2Str = Lexer::getSourceText(
CharSourceRange::getTokenRange(rhsVar2->getSourceRange()),
Context.getSourceManager(), Context.getLangOpts());

if (binaryOp->getOpcode() == BO_LT) {
if (lhsVar1Str == lhsVar2Str && rhsVar1Str == rhsVar2Str) {
diag(ifStmt->getIfLoc(), "use `std::max` instead of `<`")
<< FixItHint::CreateReplacement(SourceRange(ifLocation, thenLocation),
lhsVar2Str.str() + " = std::max(" +
lhsVar1Str.str() + ", " +
rhsVar1Str.str() + ")");
} else if (lhsVar1Str == rhsVar2Str && rhsVar1Str == lhsVar2Str) {
diag(ifStmt->getIfLoc(), "use `std::min` instead of `<`")
<< FixItHint::CreateReplacement(SourceRange(ifLocation, thenLocation),
lhsVar2Str.str() + " = std::min(" +
lhsVar1Str.str() + ", " +
rhsVar1Str.str() + ")");
}
} else if (binaryOp->getOpcode() == BO_GT) {
if (lhsVar1Str == lhsVar2Str && rhsVar1Str == rhsVar2Str) {
diag(ifStmt->getIfLoc(), "use `std::min` instead of `>`")
<< FixItHint::CreateReplacement(SourceRange(ifLocation, thenLocation),
lhsVar2Str.str() + " = std::min(" +
lhsVar1Str.str() + ", " +
rhsVar1Str.str() + ")");
} else if (lhsVar1Str == rhsVar2Str && rhsVar1Str == lhsVar2Str) {
diag(ifStmt->getIfLoc(), "use `std::max` instead of `>`")
<< FixItHint::CreateReplacement(SourceRange(ifLocation, thenLocation),
lhsVar2Str.str() + " = std::max(" +
lhsVar1Str.str() + ", " +
rhsVar1Str.str() + ")");
}
}
}

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

namespace clang::tidy::readability {

/// replaces certain conditional statements with equivalent ``std::min`` or
/// ``std::max`` expressions, improving readability and promoting the use of
/// standard library functions.
///
/// 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)
: ClangTidyCheck(Name, Context) {}
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
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;
}
};

} // namespace clang::tidy::readability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESTDMINMAXCHECK_H
7 changes: 7 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ New checks
Recommends the smallest possible underlying type for an ``enum`` or ``enum``
class based on the range of its enumerators.

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

Replaces certain conditional statements with equivalent ``std::min`` or
``std::max`` expressions, improving readability and promoting the use of
standard library functions.

- New :doc:`readability-reference-to-constructed-temporary
<clang-tidy/checks/readability/reference-to-constructed-temporary>` check.

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 @@ -336,6 +336,7 @@ Clang-Tidy Checks
:doc:`portability-restrict-system-includes <portability/restrict-system-includes>`, "Yes"
:doc:`portability-simd-intrinsics <portability/simd-intrinsics>`,
:doc:`portability-std-allocator-const <portability/std-allocator-const>`,
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
:doc:`readability-avoid-const-params-in-decls <readability/avoid-const-params-in-decls>`, "Yes"
:doc:`readability-avoid-return-with-void-value <readability/avoid-return-with-void-value>`,
:doc:`readability-avoid-unconditional-preprocessor-if <readability/avoid-unconditional-preprocessor-if>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.. title:: clang-tidy - readability-use-std-min-max

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

Replaces certain conditional statements with equivalent ``std::min`` or ``std::max`` expressions,
improving readability and promoting the use of standard library functions.
Note: While this transformation improves code clarity, it may not be
suitable for performance-critical code. Using ``std::min`` or ``std::max`` can
introduce additional stores, potentially impacting performance compared to
the original if statement that only assigns when the value needs to change.

Examples:

Before:

.. code-block:: c++

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


After:

.. code-block:: c++

void foo(){
a = std::max(a, b);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy %s readability-use-std-min-max %t

void foo() {
int value1,value2,value3;
short value4;

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
if (value1 < value2)
value1 = value2; // CHECK-FIXES: value1 = std::max(value1, value2);

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
if (value1 < value2)
value2 = value1; // CHECK-FIXES: value2 = std::min(value1, value2);

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::min` instead of `>` [readability-use-std-min-max]
if (value2 > value1)
value2 = value1; // CHECK-FIXES: value2 = std::min(value2, value1);

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::max` instead of `>` [readability-use-std-min-max
if (value2 > value1)
value1 = value2; // CHECK-FIXES: value1 = std::max(value2, value1);

// No suggestion needed here
if (value1 == value2)
value1 = value2;

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
if(value1<value4)
value1=value4; // CHECK-FIXES: value1 = std::max(value1, value4);

// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
if(value1+value2<value3)
value3 = value1+value2; // CHECK-FIXES: value3 = std::min(value1+value2, value3);

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we detect the following use case too ?

int myMin = var1 < var2 ? var1 : var2;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this we should not add this, I believe its already pretty formatted with readability in mind. But sure we can add, whats your opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current implementation doesn't flags this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the proposed ternary operator does exactly what std::min / std::max does and there are no advantages in using this over std::min/max IMO.
But I could understand that someone might want to keep it as is and only flags if statements. Maybe we could have an option to enable / disable this detection. Something like DiagnoseTernaryOperators? (I'm not convinced by the name of the parameter, but that's all I have for now)