-
Notifications
You must be signed in to change notification settings - Fork 13.6k
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
Changes from 11 commits
1883d98
89be4ba
e1b65a9
042ddc3
c17b298
9aad658
2fc0938
1139ac4
f73e9f0
faf3312
5ed9896
19c4b63
8167787
3a28399
2ecd08a
00d4080
6db0820
5073650
c728a39
b8202ae
3b57307
5e31665
a2cfcdc
f5a0f12
e239576
b868d5b
0797e0c
c6028b0
6acf645
0eb4aca
d5b8dc2
a92160c
5eafb59
26bfbdc
57bccd6
61fd66a
a841ebc
7d2c085
399c4c5
abec9b5
6a0e495
274b55f
b7ca014
8bf8235
d8df8c8
8a5d585
85fb67d
1214547
6aef713
f03b2ff
5d504a7
19cc1f3
a316744
f0ef998
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
using namespace clang::ast_matchers; | ||
|
||
namespace clang::tidy::readability { | ||
|
||
void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) { | ||
Finder->addMatcher( | ||
ifStmt( | ||
has(binaryOperator( | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
anyOf(hasOperatorName("<"), hasOperatorName(">"), | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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"); | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto &Context = *Result.Context; | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (!lhsVar1 || !rhsVar1 || !lhsVar2 || !rhsVar2 || !ifStmt) | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
|
||
const BinaryOperator *binaryOp = dyn_cast<BinaryOperator>(ifStmt->getCond()); | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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), | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lhsVar2Str.str() + " = std::max(" + | ||
lhsVar1Str.str() + ", " + | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
rhsVar1Str.str() + ")"); | ||
} | ||
} | ||
} | ||
|
||
} // namespace clang::tidy::readability |
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 { | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// replaces certain conditional statements with equivalent ``std::min`` or | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// ``std::max`` expressions, improving readability and promoting the use of | ||
/// standard library functions. | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// 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 |
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, | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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(){ | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int a,b; | ||
if(a < b) | ||
a = b; | ||
} | ||
|
||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
void foo() { | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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); | ||
|
||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
11happy marked this conversation as resolved.
Show resolved
Hide resolved
11happy marked this conversation as resolved.
Show resolved
Hide resolved
11happy marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we detect the following use case too ?
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. current implementation doesn't flags this There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
11happy marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.