Skip to content

Commit 1fcd5fe

Browse files
committed
Add modernize-use-span
This linter check recommends using std::span over const references to std::array or std::vector.
1 parent 34be80a commit 1fcd5fe

File tree

8 files changed

+371
-0
lines changed

8 files changed

+371
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ add_clang_library(clangTidyModernizeModule STATIC
4242
UseNullptrCheck.cpp
4343
UseOverrideCheck.cpp
4444
UseRangesCheck.cpp
45+
UseSpanCheck.cpp
4546
UseStartsEndsWithCheck.cpp
4647
UseStdFormatCheck.cpp
4748
UseStdNumbersCheck.cpp

clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "UseNullptrCheck.h"
4444
#include "UseOverrideCheck.h"
4545
#include "UseRangesCheck.h"
46+
#include "UseSpanCheck.h"
4647
#include "UseStartsEndsWithCheck.h"
4748
#include "UseStdFormatCheck.h"
4849
#include "UseStdNumbersCheck.h"
@@ -80,6 +81,8 @@ class ModernizeModule : public ClangTidyModule {
8081
CheckFactories.registerCheck<UseIntegerSignComparisonCheck>(
8182
"modernize-use-integer-sign-comparison");
8283
CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
84+
CheckFactories.registerCheck<UseSpanCheck>(
85+
"modernize-use-span");
8386
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
8487
"modernize-use-starts-ends-with");
8588
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//===--- UseSpanCheck.cpp - clang-tidy ------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "UseSpanCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/Lex/Lexer.h"
13+
#include "../utils/IncludeInserter.h"
14+
#include <string>
15+
16+
using namespace clang::ast_matchers;
17+
18+
namespace clang::tidy::modernize {
19+
20+
namespace {
21+
AST_MATCHER(QualType, isRefToVectorOrArray) {
22+
if (!Node->isReferenceType())
23+
return false;
24+
25+
QualType PointeeType = Node->getPointeeType();
26+
27+
const Type *UnqualifiedType = PointeeType.getTypePtr()->getUnqualifiedDesugaredType();
28+
if (!UnqualifiedType || !UnqualifiedType->isRecordType())
29+
return false;
30+
31+
const CXXRecordDecl *Record = UnqualifiedType->getAsCXXRecordDecl();
32+
if (!Record)
33+
return false;
34+
35+
const std::string Name = Record->getQualifiedNameAsString();
36+
return Name == "std::vector" || Name == "std::array";
37+
}
38+
} // namespace
39+
40+
UseSpanCheck::UseSpanCheck(StringRef Name, ClangTidyContext *Context)
41+
: ClangTidyCheck(Name, Context),
42+
Inserter(utils::IncludeSorter::IS_LLVM, areDiagsSelfContained()) {}
43+
44+
void UseSpanCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
45+
Preprocessor *ModuleExpanderPP) {
46+
Inserter.registerPreprocessor(PP);
47+
}
48+
49+
void UseSpanCheck::registerMatchers(MatchFinder *Finder) {
50+
Finder->addMatcher(
51+
functionDecl(
52+
forEachDescendant(
53+
parmVarDecl(hasType(qualType(isRefToVectorOrArray())))
54+
.bind("param"))),
55+
this);
56+
}
57+
58+
void UseSpanCheck::check(const MatchFinder::MatchResult &Result) {
59+
const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
60+
if (!Param)
61+
return;
62+
63+
QualType ParamType = Param->getType();
64+
if (!ParamType->isReferenceType())
65+
return;
66+
67+
// Get the pointee type (the vector/array type)
68+
QualType PointeeType = ParamType->getPointeeType();
69+
bool IsConst = PointeeType.isConstQualified();
70+
71+
const Type *UnqualifiedType = PointeeType.getTypePtr()->getUnqualifiedDesugaredType();
72+
if (!UnqualifiedType || !UnqualifiedType->isRecordType())
73+
return;
74+
75+
const CXXRecordDecl *Record = UnqualifiedType->getAsCXXRecordDecl();
76+
if (!Record)
77+
return;
78+
79+
const std::string RecordName = Record->getQualifiedNameAsString();
80+
if (RecordName != "std::vector" && RecordName != "std::array")
81+
return;
82+
83+
// Check if it's a template specialization
84+
if (!isa<ClassTemplateSpecializationDecl>(Record))
85+
return;
86+
87+
// Get the template arguments
88+
const auto *TemplateSpecRecord = cast<ClassTemplateSpecializationDecl>(Record);
89+
const TemplateArgumentList &Args = TemplateSpecRecord->getTemplateArgs();
90+
if (Args.size() < 1)
91+
return;
92+
93+
// Get the element type from the first template argument
94+
const TemplateArgument &Arg = Args[0];
95+
if (Arg.getKind() != TemplateArgument::Type)
96+
return;
97+
98+
QualType ElementType = Arg.getAsType();
99+
100+
// Get the source range for the parameter type
101+
TypeSourceInfo *TSI = Param->getTypeSourceInfo();
102+
TypeLoc TL = TSI->getTypeLoc();
103+
104+
// Get the source range for the entire type, including qualifiers
105+
SourceRange TypeRange = TL.getSourceRange();
106+
107+
// Create the diagnostic
108+
auto Diag = diag(Param->getBeginLoc(),
109+
"parameter %0 is reference to %1; consider using std::span instead")
110+
<< Param
111+
<< (RecordName == "std::vector" ? "std::vector" : "std::array");
112+
113+
// Create the fix-it hint
114+
std::string SpanType;
115+
std::string ElementTypeWithConst = IsConst ? "const " + ElementType.getAsString() : ElementType.getAsString();
116+
117+
// For std::array, we should preserve the size in the span
118+
if (RecordName == "std::array" && Args.size() >= 2) {
119+
const TemplateArgument &SizeArg = Args[1];
120+
if (SizeArg.getKind() == TemplateArgument::Integral) {
121+
llvm::APSInt Size = SizeArg.getAsIntegral();
122+
// Convert APSInt to string
123+
SmallString<16> SizeStr;
124+
Size.toString(SizeStr, 10);
125+
SpanType = "std::span<" + ElementTypeWithConst + ", " + SizeStr.str().str() + ">";
126+
} else {
127+
SpanType = "std::span<" + ElementTypeWithConst + ">";
128+
}
129+
} else {
130+
SpanType = "std::span<" + ElementTypeWithConst + ">";
131+
}
132+
133+
// Create the replacement for the entire type including qualifiers
134+
Diag << FixItHint::CreateReplacement(TypeRange, SpanType);
135+
136+
// Add the include for <span>
137+
Diag << Inserter.createIncludeInsertion(
138+
Result.SourceManager->getFileID(Param->getBeginLoc()),
139+
"<span>");
140+
}
141+
142+
} // namespace clang::tidy::modernize
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===--- UseSpanCheck.h - clang-tidy ----------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESPANCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESPANCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include "../utils/IncludeInserter.h"
14+
15+
namespace clang::tidy::modernize {
16+
17+
/// Suggests replacing const references to std::vector and std::array with
18+
/// std::span.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-span.html
22+
class UseSpanCheck : public ClangTidyCheck {
23+
public:
24+
UseSpanCheck(StringRef Name, ClangTidyContext *Context);
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
28+
Preprocessor *ModuleExpanderPP) override;
29+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
30+
return LangOpts.CPlusPlus20;
31+
}
32+
33+
private:
34+
utils::IncludeInserter Inserter;
35+
};
36+
37+
} // namespace clang::tidy::modernize
38+
39+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESPANCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ New checks
136136
Finds potentially erroneous calls to ``reset`` method on smart pointers when
137137
the pointee type also has a ``reset`` method.
138138

139+
- New :doc:`modernize-use-span
140+
<clang-tidy/checks/modernize/use-span>` check.
141+
142+
Suggests replacing const references to std::vector and std::array with
143+
std::span.
144+
139145
New check aliases
140146
^^^^^^^^^^^^^^^^^
141147

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ Clang-Tidy Checks
310310
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
311311
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
312312
:doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
313+
:doc:`modernize-use-span <modernize/use-span>`, "Yes"
313314
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
314315
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
315316
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.. title:: clang-tidy - modernize-use-span
2+
3+
modernize-use-span
4+
==================
5+
6+
This check suggests using ``std::span`` in function parameters when taking a
7+
range of contiguous elements by reference. ``std::span`` can accept vectors,
8+
arrays, and C-style arrays, so the function is no longer bound to a specific
9+
container type.
10+
11+
This check implements `R.14<https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-ap>``
12+
from the C++ Core Guidelines.
13+
14+
This check requires C++20 or later.
15+
16+
Examples
17+
--------
18+
19+
.. code-block:: c++
20+
21+
// Before
22+
void process(const std::vector<int>& vec) {
23+
for (const auto& val : vec) {
24+
// Process val
25+
}
26+
}
27+
28+
void analyze(const std::array<double, 5>& arr) {
29+
for (const auto& val : arr) {
30+
// Analyze val
31+
}
32+
}
33+
34+
// After
35+
void process(std::span<const int> vec) {
36+
for (const auto& val : vec) {
37+
// Process val
38+
}
39+
}
40+
41+
void analyze(std::span<const double, 5> arr) {
42+
for (const auto& val : arr) {
43+
// Analyze val
44+
}
45+
}
46+
47+
The transformed code can now accept any contiguous container of the appropriate
48+
element type and optionally specified length.

0 commit comments

Comments
 (0)