Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cb748c3
[clang-tidy] Add modernize-use-span-first-last check
hjanuschka Nov 29, 2024
ec5a6b0
format
hjanuschka Nov 29, 2024
5cf1b7c
format
hjanuschka Nov 29, 2024
b357f8c
format
hjanuschka Nov 29, 2024
42d1df2
format
hjanuschka Nov 29, 2024
04f3edc
format
hjanuschka Nov 29, 2024
2f9cc3e
format
hjanuschka Nov 29, 2024
24690e3
up
hjanuschka Nov 29, 2024
39833d4
up
hjanuschka Nov 29, 2024
964fadc
format
hjanuschka Nov 29, 2024
79afa9e
format
hjanuschka Nov 29, 2024
31d300f
format
hjanuschka Nov 29, 2024
c7fd4d5
format
hjanuschka Nov 29, 2024
4a9fc6e
Update clang-tools-extra/clang-tidy/readability/UseSpanFirstLastCheck…
hjanuschka Dec 2, 2024
3f604e5
Update clang-tools-extra/clang-tidy/readability/UseSpanFirstLastCheck.h
hjanuschka Dec 2, 2024
7e2aaa4
feed
hjanuschka Dec 2, 2024
b2e82cf
Merge branch 'subspan' of github.com:hjanuschka/llvm-project into sub…
hjanuschka Dec 2, 2024
c707971
up
hjanuschka Dec 2, 2024
cd2fce3
feedback
hjanuschka Dec 2, 2024
72610ac
feedback
hjanuschka Dec 2, 2024
b565ef7
feedback
hjanuschka Dec 2, 2024
3e9b0c6
feedback
hjanuschka Dec 3, 2024
2c65d52
feedback
hjanuschka Dec 3, 2024
8c16f74
up
hjanuschka Dec 4, 2024
514ac50
up
hjanuschka Dec 5, 2024
86b467b
up
hjanuschka Dec 5, 2024
6ab0051
up
hjanuschka Dec 5, 2024
39da599
up
hjanuschka Dec 11, 2024
8ba6771
feedback
hjanuschka Dec 11, 2024
79702fb
feedback
hjanuschka Dec 11, 2024
0a1d6cb
feedback
hjanuschka Dec 14, 2024
3354841
feedback
hjanuschka Dec 14, 2024
1fc1375
feedback
hjanuschka Dec 20, 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 @@ -57,6 +57,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
UseSpanFirstLastCheck.cpp
Copy link
Contributor

Choose a reason for hiding this comment

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

Please keep alphabetical order.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed, with the 2 "Use" (https://github.com/llvm/llvm-project/pull/118074/files/c7fd4d5f508d43cd7fdaf7ed9a0006687a4215ed#diff-4be36b5009d110fd7bf319b8b06cf0e86d91b04f4c30dcd8aaf94825626b2e2aL26) filenames, but there is RedundantInlineSpecifierCheck also wrong, should i fix this in the same PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think one-line change is fine in this pull request.

UseStdMinMaxCheck.cpp

LINK_LIBS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
#include "UseSpanFirstLastCheck.h"
#include "UseStdMinMaxCheck.h"

namespace clang::tidy {
Expand Down Expand Up @@ -170,6 +171,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-uppercase-literal-suffix");
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
"readability-use-anyofallof");
CheckFactories.registerCheck<UseSpanFirstLastCheck>(
"readability-use-span-first-last");
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
}
Expand Down
96 changes: 96 additions & 0 deletions clang-tools-extra/clang-tidy/readability/UseSpanFirstLastCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//===--- UseSpanFirstLastCheck.cpp - 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
//
//===----------------------------------------------------------------------===//

#include "UseSpanFirstLastCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::readability {

void UseSpanFirstLastCheck::registerMatchers(MatchFinder *Finder) {
const auto HasSpanType =
hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(
classTemplateSpecializationDecl(hasName("::std::span"))))));

// Match span.subspan(0, n) -> first(n)
Finder->addMatcher(
cxxMemberCallExpr(
callee(memberExpr(hasDeclaration(cxxMethodDecl(hasName("subspan"))))),
on(expr(HasSpanType).bind("span_object")),
hasArgument(0, integerLiteral(equals(0))),
hasArgument(1, expr().bind("count")), argumentCountIs(2))
.bind("first_subspan"),
this);

// Match span.subspan(size() - n) or span.subspan(std::ranges::size(span) - n)
// -> last(n)
const auto SizeCall = anyOf(
cxxMemberCallExpr(
callee(memberExpr(hasDeclaration(cxxMethodDecl(hasName("size")))))),
callExpr(callee(
functionDecl(hasAnyName("::std::size", "::std::ranges::size")))));

Finder->addMatcher(
cxxMemberCallExpr(
callee(memberExpr(hasDeclaration(cxxMethodDecl(hasName("subspan"))))),
on(expr(HasSpanType).bind("span_object")),
hasArgument(0, binaryOperator(hasOperatorName("-"), hasLHS(SizeCall),
hasRHS(expr().bind("count")))),
argumentCountIs(1))
.bind("last_subspan"),
this);
}

void UseSpanFirstLastCheck::check(const MatchFinder::MatchResult &Result) {
const auto *SpanObj = Result.Nodes.getNodeAs<Expr>("span_object");
if (!SpanObj)
return;

StringRef SpanText = Lexer::getSourceText(
CharSourceRange::getTokenRange(SpanObj->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

if (const auto *FirstCall =
Result.Nodes.getNodeAs<CXXMemberCallExpr>("first_subspan")) {
const auto *Count = Result.Nodes.getNodeAs<Expr>("count");
assert(Count && "Count expression must exist due to AST matcher");

StringRef CountText = Lexer::getSourceText(
CharSourceRange::getTokenRange(Count->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

std::string Replacement =
(Twine(SpanText) + ".first(" + CountText + ")").str();

diag(FirstCall->getBeginLoc(), "prefer 'span::first()' over 'subspan()'")
<< FixItHint::CreateReplacement(FirstCall->getSourceRange(),
Replacement);
}

if (const auto *LastCall =
Result.Nodes.getNodeAs<CXXMemberCallExpr>("last_subspan")) {
const auto *Count = Result.Nodes.getNodeAs<Expr>("count");
assert(Count && "Count expression must exist due to AST matcher");

StringRef CountText = Lexer::getSourceText(
CharSourceRange::getTokenRange(Count->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

std::string Replacement = SpanText.str() + ".last(" + CountText.str() + ")";

diag(LastCall->getBeginLoc(), "prefer 'span::last()' over 'subspan()'")
<< FixItHint::CreateReplacement(LastCall->getSourceRange(),
Replacement);
}
}
} // namespace clang::tidy::readability
43 changes: 43 additions & 0 deletions clang-tools-extra/clang-tidy/readability/UseSpanFirstLastCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===--- UseSpanFirstLastCheck.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_USESPANFIRSTLASTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESPANFIRSTLASTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::readability {

/// Suggests using clearer 'std::span' member functions 'first()'/'last()'
/// instead of equivalent 'subspan()' calls where applicable.
///
/// For example:
/// \code
/// std::span<int> s = ...;
/// auto sub1 = s.subspan(0, n); // -> auto sub1 = s.first(n);
/// auto sub2 = s.subspan(s.size() - n); // -> auto sub2 = s.last(n);
/// auto sub3 = s.subspan(1, n); // not changed
/// auto sub4 = s.subspan(n); // not changed
/// \endcode
///
/// The check is only active in C++20 mode.
class UseSpanFirstLastCheck : public ClangTidyCheck {
public:
UseSpanFirstLastCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}

void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus20;
}
};

} // namespace clang::tidy::readability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USESPANFIRSTLASTCHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ New checks
Finds cases when an uninstantiated virtual member function in a template class
causes cross-compiler incompatibility.

- New :doc:`readability-use-span-first-last
<clang-tidy/checks/readability/use-span-first-last>` check.

Suggests using ``std::span::first()`` and ``std::span::last()`` member functions instead of
equivalent ``subspan()``.

New check aliases
^^^^^^^^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.. title:: clang-tidy - readability-use-span-first-last

readability-use-span-first-last
===============================

Suggests using ``std::span::first()`` and ``std::span::last()`` member functions
instead of equivalent ``subspan()``. These dedicated methods were added to C++20
to provide more expressive alternatives to common subspan operations.

Covered scenarios:

=========================== ==============
Expression Replacement
--------------------------- --------------
``s.subspan(0, n)`` ``s.first(n)``
``s.subspan(s.size() - n)`` ``s.last(n)``
=========================== ==============


Non-zero offset with count (like ``subspan(1, n)``) or offset-only calls
(like ``subspan(n)``) have no clearer equivalent using ``first()`` or
``last()``, so these cases are not transformed.

This check is only active when C++20 or later is used.
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// RUN: %check_clang_tidy -std=c++20 %s readability-use-span-first-last %t

namespace std {
template <typename T>
class span {
T* ptr;
__SIZE_TYPE__ len;

public:
span(T* p, __SIZE_TYPE__ l) : ptr(p), len(l) {}

span<T> subspan(__SIZE_TYPE__ offset) const {
return span(ptr + offset, len - offset);
}

span<T> subspan(__SIZE_TYPE__ offset, __SIZE_TYPE__ count) const {
return span(ptr + offset, count);
}

span<T> first(__SIZE_TYPE__ count) const {
return span(ptr, count);
}

span<T> last(__SIZE_TYPE__ count) const {
return span(ptr + (len - count), count);
}

__SIZE_TYPE__ size() const { return len; }
};
} // namespace std

// Add here, right after the std namespace closes:
namespace std::ranges {
template<typename T>
__SIZE_TYPE__ size(const span<T>& s) { return s.size(); }
}

void test() {
int arr[] = {1, 2, 3, 4, 5};
std::span<int> s(arr, 5);

auto sub1 = s.subspan(0, 3);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::first()' over 'subspan()'
// CHECK-FIXES: auto sub1 = s.first(3);

auto sub2 = s.subspan(s.size() - 2);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub2 = s.last(2);

__SIZE_TYPE__ n = 2;
auto sub3 = s.subspan(0, n);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::first()' over 'subspan()'
// CHECK-FIXES: auto sub3 = s.first(n);

auto sub4 = s.subspan(1, 2); // No warning
auto sub5 = s.subspan(2); // No warning


#define ZERO 0
#define TWO 2
#define SIZE_MINUS(s, n) s.size() - n
#define MAKE_SUBSPAN(obj, n) obj.subspan(0, n)
#define MAKE_LAST_N(obj, n) obj.subspan(obj.size() - n)

auto sub6 = s.subspan(SIZE_MINUS(s, 2));
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub6 = s.last(2);

auto sub7 = MAKE_SUBSPAN(s, 3);
// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: prefer 'span::first()' over 'subspan()'
// CHECK-FIXES: auto sub7 = s.first(3);

auto sub8 = MAKE_LAST_N(s, 2);
// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub8 = s.last(2);

}

template <typename T>
void testTemplate() {
T arr[] = {1, 2, 3, 4, 5};
std::span<T> s(arr, 5);

auto sub1 = s.subspan(0, 3);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::first()' over 'subspan()'
// CHECK-FIXES: auto sub1 = s.first(3);

auto sub2 = s.subspan(s.size() - 2);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub2 = s.last(2);

__SIZE_TYPE__ n = 2;
auto sub3 = s.subspan(0, n);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::first()' over 'subspan()'
// CHECK-FIXES: auto sub3 = s.first(n);

auto sub4 = s.subspan(1, 2); // No warning
auto sub5 = s.subspan(2); // No warning

auto complex = s.subspan(0 + (s.size() - 2), 3); // No warning

auto complex2 = s.subspan(100 + (s.size() - 2)); // No warning
}

// Test instantiation
void testInt() {
testTemplate<int>();
}

void test_ranges() {
int arr[] = {1, 2, 3, 4, 5};
std::span<int> s(arr, 5);

auto sub1 = s.subspan(std::ranges::size(s) - 2);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub1 = s.last(2);

__SIZE_TYPE__ n = 2;
auto sub2 = s.subspan(std::ranges::size(s) - n);
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: prefer 'span::last()' over 'subspan()'
// CHECK-FIXES: auto sub2 = s.last(n);
}
Loading