Skip to content

Commit cd34d28

Browse files
authored
Merge branch 'main' into fix/add-new-checks
2 parents 763c2df + 678dcf1 commit cd34d28

File tree

100 files changed

+4934
-4938
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+4934
-4938
lines changed

.ci/all_requirements.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ ml-dtypes==0.5.1 ; python_version < "3.13" \
194194
--hash=sha256:d13755f8e8445b3870114e5b6240facaa7cb0c3361e54beba3e07fa912a6e12b \
195195
--hash=sha256:fd918d4e6a4e0c110e2e05be7a7814d10dc1b95872accbf6512b80a109b71ae1
196196
# via -r mlir/python/requirements.txt
197-
nanobind==2.7.0 \
198-
--hash=sha256:73b12d0e751d140d6c1bf4b215e18818a8debfdb374f08dc3776ad208d808e74 \
199-
--hash=sha256:f9f1b160580c50dcf37b6495a0fd5ec61dc0d95dae5f8004f87dd9ad7eb46b34
197+
nanobind==2.9.2 \
198+
--hash=sha256:c37957ffd5eac7eda349cff3622ecd32e5ee1244ecc912c99b5bc8188bafd16e \
199+
--hash=sha256:e7608472de99d375759814cab3e2c94aba3f9ec80e62cfef8ced495ca5c27d6e
200200
# via -r mlir/python/requirements.txt
201201
numpy==2.0.2 \
202202
--hash=sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a \
@@ -383,6 +383,10 @@ swig==4.3.1 \
383383
--hash=sha256:efec16327029f682f649a26da726bb0305be8800bd0f1fa3e81bf0769cf5b476 \
384384
--hash=sha256:fc496c0d600cf1bb2d91e28d3d6eae9c4301e5ea7a0dec5a4281b5efed4245a8
385385
# via -r lldb/test/requirements.txt
386+
typing-extensions==4.15.0 \
387+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
388+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
389+
# via -r mlir/python/requirements.txt
386390
urllib3==2.5.0 \
387391
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
388392
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc

.github/workflows/issue-write.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- "Check code formatting"
77
- "Check for private emails used in PRs"
88
- "PR Request Release Note"
9+
- "Code lint"
910
types:
1011
- completed
1112

.github/workflows/pr-code-lint.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: "Code lint"
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
branches:
9+
- main
10+
- 'users/**'
11+
paths:
12+
- 'clang-tools-extra/clang-tidy/**'
13+
14+
jobs:
15+
code_linter:
16+
if: github.repository_owner == 'llvm'
17+
runs-on: ubuntu-24.04
18+
defaults:
19+
run:
20+
shell: bash
21+
container:
22+
image: 'ghcr.io/llvm/ci-ubuntu-24.04:latest'
23+
timeout-minutes: 60
24+
concurrency:
25+
group: ${{ github.workflow }}-${{ github.ref }}
26+
cancel-in-progress: true
27+
steps:
28+
- name: Fetch LLVM sources
29+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
30+
with:
31+
fetch-depth: 2
32+
33+
- name: Get changed files
34+
id: changed-files
35+
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
36+
with:
37+
separator: ","
38+
skip_initial_fetch: true
39+
base_sha: 'HEAD~1'
40+
sha: 'HEAD'
41+
42+
- name: Listed files
43+
env:
44+
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
45+
run: |
46+
echo "Changed files:"
47+
echo "$CHANGED_FILES"
48+
49+
- name: Fetch code linting utils
50+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
51+
with:
52+
repository: ${{ github.repository }}
53+
ref: ${{ github.base_ref }}
54+
sparse-checkout: |
55+
llvm/utils/git/code-lint-helper.py
56+
llvm/utils/git/requirements_linting.txt
57+
clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py
58+
sparse-checkout-cone-mode: false
59+
path: code-lint-tools
60+
61+
- name: Install clang-tidy
62+
uses: aminya/setup-cpp@17c11551771948abc5752bbf3183482567c7caf0 # v1.1.1
63+
with:
64+
clang-tidy: 20.1.8
65+
66+
- name: Setup Python env
67+
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
68+
with:
69+
python-version: '3.12'
70+
71+
- name: Install Python dependencies
72+
run: python3 -m pip install -r code-lint-tools/llvm/utils/git/requirements_linting.txt
73+
74+
# TODO: create special mapping for 'codegen' targets, for now build predefined set
75+
# TODO: add entrypoint in 'compute_projects.py' that only adds a project and its direct dependencies
76+
- name: Configure and CodeGen
77+
run: |
78+
git config --global --add safe.directory '*'
79+
80+
. <(git diff --name-only HEAD~1...HEAD | python3 .ci/compute_projects.py)
81+
82+
if [[ "${projects_to_build}" == "" ]]; then
83+
echo "No projects to analyze"
84+
exit 0
85+
fi
86+
87+
cmake -G Ninja \
88+
-B build \
89+
-S llvm \
90+
-DLLVM_ENABLE_ASSERTIONS=OFF \
91+
-DLLVM_ENABLE_PROJECTS="${projects_to_build}" \
92+
-DCMAKE_CXX_COMPILER=clang++ \
93+
-DCMAKE_C_COMPILER=clang \
94+
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
95+
-DLLVM_INCLUDE_TESTS=OFF \
96+
-DCLANG_INCLUDE_TESTS=OFF \
97+
-DCMAKE_BUILD_TYPE=Release
98+
99+
ninja -C build \
100+
clang-tablegen-targets \
101+
genconfusable # for "ConfusableIdentifierCheck.h"
102+
103+
- name: Run code linter
104+
env:
105+
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
106+
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
107+
run: |
108+
echo "[]" > comments &&
109+
python3 ./code-lint-tools/llvm/utils/git/code-lint-helper.py \
110+
--token ${{ secrets.GITHUB_TOKEN }} \
111+
--issue-number $GITHUB_PR_NUMBER \
112+
--start-rev HEAD~1 \
113+
--end-rev HEAD \
114+
--verbose \
115+
--changed-files "$CHANGED_FILES"
116+
117+
- name: Upload results
118+
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 #v4.3.0
119+
if: always()
120+
with:
121+
name: workflow-args
122+
path: |
123+
comments

clang-tools-extra/clang-tidy/custom/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
99
QueryCheck.cpp
1010

1111
LINK_LIBS
12+
clangQuery
1213
clangTidy
1314
clangTidyBugproneModule
1415
clangTidyMiscModule
@@ -31,7 +32,6 @@ if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
3132
clangDynamicASTMatchers
3233
clangFrontend
3334
clangLex
34-
clangQuery
3535
clangSerialization
3636
clangTooling
3737
)

clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,43 @@
99
#include "ContainerContainsCheck.h"
1010
#include "clang/AST/ASTContext.h"
1111
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/Lex/Lexer.h"
1213

1314
using namespace clang::ast_matchers;
1415

1516
namespace clang::tidy::readability {
1617
void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
17-
const auto HasContainsMatchingParamType = hasMethod(
18-
cxxMethodDecl(isConst(), parameterCountIs(1), returns(booleanType()),
19-
hasName("contains"), unless(isDeleted()), isPublic(),
20-
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
21-
equalsBoundNode("parameterType"))))));
18+
const auto Literal0 = integerLiteral(equals(0));
19+
const auto Literal1 = integerLiteral(equals(1));
20+
21+
const auto ClassWithContains = cxxRecordDecl(
22+
hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1), isPublic(),
23+
unless(isDeleted()), returns(booleanType()),
24+
hasAnyName("contains", "Contains"))
25+
.bind("contains_fun")));
2226

2327
const auto CountCall =
24-
cxxMemberCallExpr(
25-
argumentCountIs(1),
26-
callee(cxxMethodDecl(
27-
hasName("count"),
28-
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
29-
type().bind("parameterType")))),
30-
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
28+
cxxMemberCallExpr(argumentCountIs(1),
29+
callee(cxxMethodDecl(hasAnyName("count", "Count"),
30+
ofClass(ClassWithContains))))
3131
.bind("call");
3232

3333
const auto FindCall =
34+
// Either one argument, or assume the second argument is the position to
35+
// start searching from.
3436
cxxMemberCallExpr(
35-
argumentCountIs(1),
36-
callee(cxxMethodDecl(
37-
hasName("find"),
38-
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
39-
type().bind("parameterType")))),
40-
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
37+
anyOf(argumentCountIs(1),
38+
allOf(argumentCountIs(2), hasArgument(1, Literal0))),
39+
callee(cxxMethodDecl(hasAnyName("find", "Find"),
40+
ofClass(ClassWithContains))))
4141
.bind("call");
4242

4343
const auto EndCall = cxxMemberCallExpr(
44-
argumentCountIs(0),
45-
callee(
46-
cxxMethodDecl(hasName("end"),
47-
// In the matchers below, FindCall should always appear
48-
// before EndCall so 'parameterType' is properly bound.
49-
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))));
44+
argumentCountIs(0), callee(cxxMethodDecl(hasAnyName("end", "End"),
45+
ofClass(ClassWithContains))));
5046

51-
const auto Literal0 = integerLiteral(equals(0));
52-
const auto Literal1 = integerLiteral(equals(1));
47+
const auto StringNpos = anyOf(declRefExpr(to(varDecl(hasName("npos")))),
48+
memberExpr(member(hasName("npos"))));
5349

5450
auto AddSimpleMatcher = [&](auto Matcher) {
5551
Finder->addMatcher(
@@ -94,12 +90,14 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
9490
binaryOperation(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
9591
.bind("negativeComparison"));
9692

97-
// Find membership tests based on `find() == end()`.
93+
// Find membership tests based on `find() == end()` or `find() == npos`.
9894
AddSimpleMatcher(
99-
binaryOperation(hasOperatorName("!="), hasOperands(FindCall, EndCall))
95+
binaryOperation(hasOperatorName("!="),
96+
hasOperands(FindCall, anyOf(EndCall, StringNpos)))
10097
.bind("positiveComparison"));
10198
AddSimpleMatcher(
102-
binaryOperation(hasOperatorName("=="), hasOperands(FindCall, EndCall))
99+
binaryOperation(hasOperatorName("=="),
100+
hasOperands(FindCall, anyOf(EndCall, StringNpos)))
103101
.bind("negativeComparison"));
104102
}
105103

@@ -114,29 +112,39 @@ void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
114112
"only one of PositiveComparison or NegativeComparison should be set");
115113
bool Negated = NegativeComparison != nullptr;
116114
const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
115+
const StringRef ContainsFunName =
116+
Result.Nodes.getNodeAs<CXXMethodDecl>("contains_fun")->getName();
117+
const Expr *SearchExpr = Call->getArg(0)->IgnoreParenImpCasts();
117118

118119
// Diagnose the issue.
119-
auto Diag =
120-
diag(Call->getExprLoc(), "use 'contains' to check for membership");
120+
auto Diag = diag(Call->getExprLoc(), "use '%0' to check for membership")
121+
<< ContainsFunName;
121122

122123
// Don't fix it if it's in a macro invocation. Leave fixing it to the user.
123124
SourceLocation FuncCallLoc = Comparison->getEndLoc();
124125
if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())
125126
return;
126127

127-
// Create the fix it.
128-
const auto *Member = cast<MemberExpr>(Call->getCallee());
129-
Diag << FixItHint::CreateReplacement(
130-
Member->getMemberNameInfo().getSourceRange(), "contains");
131-
SourceLocation ComparisonBegin = Comparison->getSourceRange().getBegin();
132-
SourceLocation ComparisonEnd = Comparison->getSourceRange().getEnd();
133-
SourceLocation CallBegin = Call->getSourceRange().getBegin();
134-
SourceLocation CallEnd = Call->getSourceRange().getEnd();
128+
const StringRef SearchExprText = Lexer::getSourceText(
129+
CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
130+
*Result.SourceManager, Result.Context->getLangOpts());
131+
132+
// Remove everything before the function call.
133+
Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
134+
Comparison->getBeginLoc(), Call->getBeginLoc()));
135+
136+
// Rename the function to `contains`.
137+
Diag << FixItHint::CreateReplacement(Call->getExprLoc(), ContainsFunName);
138+
139+
// Replace arguments and everything after the function call.
135140
Diag << FixItHint::CreateReplacement(
136-
CharSourceRange::getCharRange(ComparisonBegin, CallBegin),
137-
Negated ? "!" : "");
138-
Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
139-
CallEnd.getLocWithOffset(1), ComparisonEnd));
141+
CharSourceRange::getTokenRange(Call->getArg(0)->getBeginLoc(),
142+
Comparison->getEndLoc()),
143+
(SearchExprText + ")").str());
144+
145+
// Add negation if necessary.
146+
if (Negated)
147+
Diag << FixItHint::CreateInsertion(Call->getBeginLoc(), "!");
140148
}
141149

142150
} // namespace clang::tidy::readability

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ Changes in existing checks
312312
<clang-tidy/checks/portability/template-virtual-member-function>` check to
313313
avoid false positives on pure virtual member functions.
314314

315+
- Improved :doc:`readability-container-contains
316+
<clang-tidy/checks/readability/container-contains>` to support string
317+
comparisons to ``npos``. Internal changes may cause new rare false positives
318+
in non-standard containers.
319+
315320
- Improved :doc:`readability-container-size-empty
316321
<clang-tidy/checks/readability/container-size-empty>` check by correctly
317322
generating fix-it hints when size method is called from implicit ``this``,

clang-tools-extra/docs/clang-tidy/checks/readability/container-contains.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Initial expression Result
2020
-------------------------------------- -------------------------------------
2121
``myMap.find(x) == myMap.end()`` ``!myMap.contains(x)``
2222
``myMap.find(x) != myMap.end()`` ``myMap.contains(x)``
23+
``myStr.find(x) != std::string::npos`` ``myStr.contains(x)``
2324
``if (myMap.count(x))`` ``if (myMap.contains(x))``
2425
``bool exists = myMap.count(x)`` ``bool exists = myMap.contains(x)``
2526
``bool exists = myMap.count(x) > 0`` ``bool exists = myMap.contains(x)``

clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,19 @@ struct basic_string {
5050
size_type find(const _Type& str, size_type pos = 0) const;
5151
size_type find(const C* s, size_type pos = 0) const;
5252
size_type find(const C* s, size_type pos, size_type n) const;
53+
size_type find(C ch, size_type pos = 0) const;
54+
template<class StringViewLike>
55+
size_type find(const StringViewLike& t, size_type pos = 0) const;
5356

5457
size_type rfind(const _Type& str, size_type pos = npos) const;
5558
size_type rfind(const C* s, size_type pos, size_type count) const;
5659
size_type rfind(const C* s, size_type pos = npos) const;
5760
size_type rfind(C ch, size_type pos = npos) const;
5861

62+
constexpr bool contains(std::basic_string_view<C, T> sv) const noexcept;
63+
constexpr bool contains(C ch) const noexcept;
64+
constexpr bool contains(const C* s) const;
65+
5966
_Type& insert(size_type pos, const _Type& str);
6067
_Type& insert(size_type pos, const C* s);
6168
_Type& insert(size_type pos, const C* s, size_type n);
@@ -110,6 +117,10 @@ struct basic_string_view {
110117
size_type rfind(const C* s, size_type pos, size_type count) const;
111118
size_type rfind(const C* s, size_type pos = npos) const;
112119

120+
constexpr bool contains(basic_string_view sv) const noexcept;
121+
constexpr bool contains(C ch) const noexcept;
122+
constexpr bool contains(const C* s) const;
123+
113124
constexpr bool starts_with(basic_string_view sv) const noexcept;
114125
constexpr bool starts_with(C ch) const noexcept;
115126
constexpr bool starts_with(const C* s) const;

0 commit comments

Comments
 (0)