Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add
readability-container-contains
check
This commit introduces a new check `readability-container-contains` which finds usages of `container.count()` and `container.find() != container.end()` and instead recommends the `container.contains()` method introduced in C++20. For containers which permit multiple entries per key (`multimap`, `multiset`, ...), `contains` is more efficient than `count` because `count` has to do unnecessary additional work. While this this performance difference does not exist for containers with only a single entry per key (`map`, `unordered_map`, ...), `contains` still conveys the intent better. Reviewed By: xazax.hun, whisperity Differential Revision: http://reviews.llvm.org/D112646
- Loading branch information
1 parent
3e50593
commit 3696c70
Showing
8 changed files
with
450 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
//===--- ContainerContainsCheck.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 "ContainerContainsCheck.h" | ||
#include "clang/AST/ASTContext.h" | ||
#include "clang/ASTMatchers/ASTMatchFinder.h" | ||
|
||
using namespace clang::ast_matchers; | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace readability { | ||
|
||
void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) { | ||
const auto SupportedContainers = hasType( | ||
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl( | ||
hasAnyName("::std::set", "::std::unordered_set", "::std::map", | ||
"::std::unordered_map", "::std::multiset", | ||
"::std::unordered_multiset", "::std::multimap", | ||
"::std::unordered_multimap")))))); | ||
|
||
const auto CountCall = | ||
cxxMemberCallExpr(on(SupportedContainers), | ||
callee(cxxMethodDecl(hasName("count"))), | ||
argumentCountIs(1)) | ||
.bind("call"); | ||
|
||
const auto FindCall = | ||
cxxMemberCallExpr(on(SupportedContainers), | ||
callee(cxxMethodDecl(hasName("find"))), | ||
argumentCountIs(1)) | ||
.bind("call"); | ||
|
||
const auto EndCall = cxxMemberCallExpr(on(SupportedContainers), | ||
callee(cxxMethodDecl(hasName("end"))), | ||
argumentCountIs(0)); | ||
|
||
const auto Literal0 = integerLiteral(equals(0)); | ||
const auto Literal1 = integerLiteral(equals(1)); | ||
|
||
auto AddSimpleMatcher = [&](auto Matcher) { | ||
Finder->addMatcher( | ||
traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this); | ||
}; | ||
|
||
// Find membership tests which use `count()`. | ||
Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()), | ||
hasSourceExpression(CountCall)) | ||
.bind("positiveComparison"), | ||
this); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName("!="), hasRHS(Literal0)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal0), hasOperatorName("!="), hasRHS(CountCall)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName(">="), hasRHS(Literal1)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal1), hasOperatorName("<="), hasRHS(CountCall)) | ||
.bind("positiveComparison")); | ||
|
||
// Find inverted membership tests which use `count()`. | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName("=="), hasRHS(Literal0)) | ||
.bind("negativeComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal0), hasOperatorName("=="), hasRHS(CountCall)) | ||
.bind("negativeComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName("<="), hasRHS(Literal0)) | ||
.bind("negativeComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal0), hasOperatorName(">="), hasRHS(CountCall)) | ||
.bind("negativeComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1)) | ||
.bind("negativeComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall)) | ||
.bind("negativeComparison")); | ||
|
||
// Find membership tests based on `find() == end()`. | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(FindCall), hasOperatorName("!="), hasRHS(EndCall)) | ||
.bind("positiveComparison")); | ||
AddSimpleMatcher( | ||
binaryOperator(hasLHS(FindCall), hasOperatorName("=="), hasRHS(EndCall)) | ||
.bind("negativeComparison")); | ||
} | ||
|
||
void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) { | ||
// Extract the information about the match | ||
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); | ||
const auto *PositiveComparison = | ||
Result.Nodes.getNodeAs<Expr>("positiveComparison"); | ||
const auto *NegativeComparison = | ||
Result.Nodes.getNodeAs<Expr>("negativeComparison"); | ||
assert( | ||
!PositiveComparison || | ||
!NegativeComparison && | ||
"only one of PositiveComparison or NegativeComparison should be set"); | ||
bool Negated = NegativeComparison != nullptr; | ||
const auto *Comparison = Negated ? NegativeComparison : PositiveComparison; | ||
|
||
// Diagnose the issue. | ||
auto Diag = | ||
diag(Call->getExprLoc(), "use 'contains' to check for membership"); | ||
|
||
// Don't fix it if it's in a macro invocation. Leave fixing it to the user. | ||
SourceLocation FuncCallLoc = Comparison->getEndLoc(); | ||
if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID()) | ||
return; | ||
|
||
// Create the fix it. | ||
const auto *Member = cast<MemberExpr>(Call->getCallee()); | ||
Diag << FixItHint::CreateReplacement( | ||
Member->getMemberNameInfo().getSourceRange(), "contains"); | ||
SourceLocation ComparisonBegin = Comparison->getSourceRange().getBegin(); | ||
SourceLocation ComparisonEnd = Comparison->getSourceRange().getEnd(); | ||
SourceLocation CallBegin = Call->getSourceRange().getBegin(); | ||
SourceLocation CallEnd = Call->getSourceRange().getEnd(); | ||
Diag << FixItHint::CreateReplacement( | ||
CharSourceRange::getCharRange(ComparisonBegin, CallBegin), | ||
Negated ? "!" : ""); | ||
Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange( | ||
CallEnd.getLocWithOffset(1), ComparisonEnd.getLocWithOffset(1))); | ||
} | ||
|
||
} // namespace readability | ||
} // namespace tidy | ||
} // namespace clang |
40 changes: 40 additions & 0 deletions
40
clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
//===--- ContainerContainsCheck.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_CONTAINERCONTAINSCHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERCONTAINSCHECK_H | ||
|
||
#include "../ClangTidyCheck.h" | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace readability { | ||
|
||
/// Finds usages of `container.count()` and `find() == end()` which should be | ||
/// replaced by a call to the `container.contains()` method introduced in C++20. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/readability-container-contains.html | ||
class ContainerContainsCheck : public ClangTidyCheck { | ||
public: | ||
ContainerContainsCheck(StringRef Name, ClangTidyContext *Context) | ||
: ClangTidyCheck(Name, Context) {} | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) final; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) final; | ||
|
||
protected: | ||
bool isLanguageVersionSupported(const LangOptions &LO) const final { | ||
return LO.CPlusPlus20; | ||
} | ||
}; | ||
|
||
} // namespace readability | ||
} // namespace tidy | ||
} // namespace clang | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERCONTAINSCHECK_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
clang-tools-extra/docs/clang-tidy/checks/readability-container-contains.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
.. title:: clang-tidy - readability-container-contains | ||
|
||
readability-container-contains | ||
============================== | ||
|
||
Finds usages of ``container.count()`` and ``container.find() == container.end()`` which should be replaced by a call to the ``container.contains()`` method introduced in C++ 20. | ||
|
||
Whether an element is contained inside a container should be checked with ``contains`` instead of ``count``/``find`` because ``contains`` conveys the intent more clearly. Furthermore, for containers which permit multiple entries per key (``multimap``, ``multiset``, ...), ``contains`` is more efficient than ``count`` because ``count`` has to do unnecessary additional work. | ||
|
||
Examples: | ||
|
||
=========================================== ============================== | ||
Initial expression Result | ||
------------------------------------------- ------------------------------ | ||
``myMap.find(x) == myMap.end()`` ``!myMap.contains(x)`` | ||
``myMap.find(x) != myMap.end()`` ``myMap.contains(x)`` | ||
``if (myMap.count(x))`` ``if (myMap.contains(x))`` | ||
``bool exists = myMap.count(x)`` ``bool exists = myMap.contains(x)`` | ||
``bool exists = myMap.count(x) > 0`` ``bool exists = myMap.contains(x)`` | ||
``bool exists = myMap.count(x) >= 1`` ``bool exists = myMap.contains(x)`` | ||
``bool missing = myMap.count(x) == 0`` ``bool missing = !myMap.contains(x)`` | ||
=========================================== ============================== | ||
|
||
This check applies to ``std::set``, ``std::unordered_set``, ``std::map``, ``std::unordered_map`` and the corresponding multi-key variants. | ||
It is only active for C++20 and later, as the ``contains`` method was only added in C++20. |
Oops, something went wrong.