https://github.com/hjanuschka created 
https://github.com/llvm/llvm-project/pull/182085

Add new clang-tidy check that finds function parameter pairs of (pointer, size) 
that could be replaced with `std::span`.

Passing a pointer and a separate size is a common C-style pattern that 
`std::span` (C++20) was designed to replace. Using `std::span` bundles the 
pointer and size together, preventing mismatches and improving readability.

```cpp
// Before
void process(int *Data, int Size);

// After
void process(std::span<int> Data);
```

The check uses a name-based heuristic for size parameters: `size`, `len`, 
`length`, `count`, `n`, `num`, or names ending in `_size`, `_len`, etc.

Cases intentionally **not** flagged:
- `void*` or function pointers
- Non-integer size parameter types
- Size parameter names not suggesting a size
- Virtual methods
- Unnamed size parameters

Requires C++20. No fix-its are provided since changing the signature requires 
updating all call sites.

>From d18ce1a9e3cc1f4ea7b0186ab12e80a6874990fa Mon Sep 17 00:00:00 2001
From: Helmut Januschka <[email protected]>
Date: Wed, 18 Feb 2026 19:13:06 +0100
Subject: [PATCH] [clang-tidy] Add modernize-pointer-to-span check

Add new clang-tidy check that finds function parameter pairs of
(pointer, size) that could be replaced with std::span.

Passing a pointer and a separate size is a common C-style pattern that
std::span (C++20) was designed to replace. Using std::span bundles the
pointer and size together, preventing mismatches and improving readability.

For example:
```cpp
// Before
void process(int *Data, int Size);

// After
void process(std::span<int> Data);
```

The check uses a name-based heuristic to identify size parameters (names
like size, len, length, count, n, num, or names ending in _size, etc.).

Cases intentionally not flagged:
- void* or function pointers
- Non-integer size parameter types
- Size parameter names not suggesting a size
- Virtual methods
- Unnamed size parameters

Requires C++20. No fix-its (cross-TU signature changes).
---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   3 +
 .../modernize/PointerToSpanCheck.cpp          | 154 ++++++++++++++++++
 .../clang-tidy/modernize/PointerToSpanCheck.h |  34 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   6 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/modernize/pointer-to-span.rst      |  38 +++++
 .../checkers/modernize/pointer-to-span.cpp    |  41 +++++
 8 files changed, 278 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index cc4cc7a02b594..7679c4e6ad638 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -22,6 +22,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   MinMaxUseInitializerListCheck.cpp
   ModernizeTidyModule.cpp
   PassByValueCheck.cpp
+  PointerToSpanCheck.cpp
   RawStringLiteralCheck.cpp
   RedundantVoidArgCheck.cpp
   ReplaceAutoPtrCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp 
b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index fcb860d8c5298..491a9fdf59ce7 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -22,6 +22,7 @@
 #include "MakeUniqueCheck.h"
 #include "MinMaxUseInitializerListCheck.h"
 #include "PassByValueCheck.h"
+#include "PointerToSpanCheck.h"
 #include "RawStringLiteralCheck.h"
 #include "RedundantVoidArgCheck.h"
 #include "ReplaceAutoPtrCheck.h"
@@ -100,6 +101,8 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseStdNumbersCheck>(
         "modernize-use-std-numbers");
     CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
+    CheckFactories.registerCheck<PointerToSpanCheck>(
+        "modernize-pointer-to-span");
     CheckFactories.registerCheck<RawStringLiteralCheck>(
         "modernize-raw-string-literal");
     CheckFactories.registerCheck<RedundantVoidArgCheck>(
diff --git a/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp
new file mode 100644
index 0000000000000..b38a40976d768
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp
@@ -0,0 +1,154 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "PointerToSpanCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+/// Return true if \p T is an unsigned integer type commonly used for sizes
+/// (size_t, unsigned, unsigned long, etc.).
+static bool isSizeType(QualType T) {
+  T = T.getCanonicalType();
+  if (const auto *BT = T->getAs<BuiltinType>()) {
+    switch (BT->getKind()) {
+    case BuiltinType::UInt:
+    case BuiltinType::ULong:
+    case BuiltinType::ULongLong:
+    case BuiltinType::Int:
+    case BuiltinType::Long:
+    case BuiltinType::LongLong:
+      return true;
+    default:
+      return false;
+    }
+  }
+  return false;
+}
+
+/// Return true if the parameter name suggests it represents a size or count.
+static bool isSizeName(StringRef Name) {
+  const StringRef Lower = Name.lower();
+  return Lower == "size" || Lower == "len" || Lower == "length" ||
+         Lower == "count" || Lower == "n" || Lower == "num" ||
+         Lower == "nelems" || Lower == "nelem" || Lower == "num_elements" ||
+         Lower == "num_elems" || Lower == "sz" || Lower == "cnt" ||
+         Lower.ends_with("_size") || Lower.ends_with("_len") ||
+         Lower.ends_with("_length") || Lower.ends_with("_count") ||
+         Lower.ends_with("size") || Lower.ends_with("len") ||
+         Lower.ends_with("count") || Lower.ends_with("num");
+}
+
+void PointerToSpanCheck::registerMatchers(MatchFinder *Finder) {
+  // Match function declarations (not just definitions) with at least 2 params.
+  Finder->addMatcher(
+      functionDecl(
+          parameterCountIs(2),
+          hasParameter(0, parmVarDecl(hasType(pointerType())).bind("ptr")),
+          hasParameter(1, parmVarDecl().bind("size")),
+          unless(isImplicit()),
+          unless(isDeleted()))
+          .bind("func"),
+      this);
+
+  // Also match functions with more params -- look for consecutive ptr+size.
+  Finder->addMatcher(
+      functionDecl(
+          unless(parameterCountIs(2)),
+          unless(isImplicit()),
+          unless(isDeleted()))
+          .bind("funcN"),
+      this);
+}
+
+void PointerToSpanCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto &SM = *Result.SourceManager;
+
+  // Handle the exact 2-parameter case.
+  if (const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func")) {
+    const auto *PtrParam = Result.Nodes.getNodeAs<ParmVarDecl>("ptr");
+    const auto *SizeParam = Result.Nodes.getNodeAs<ParmVarDecl>("size");
+
+    if (!PtrParam || !SizeParam)
+      return;
+    if (SM.isInSystemHeader(Func->getLocation()))
+      return;
+
+    // Skip virtual methods.
+    if (const auto *MD = dyn_cast<CXXMethodDecl>(Func))
+      if (MD->isVirtual())
+        return;
+
+    // Skip main.
+    if (Func->isMain())
+      return;
+
+    // Pointer must not be void* or function pointer.
+    const auto *PT = PtrParam->getType()->getAs<PointerType>();
+    if (!PT || PT->getPointeeType()->isVoidType() ||
+        PT->getPointeeType()->isFunctionType())
+      return;
+
+    // Size param must be an integer type.
+    if (!isSizeType(SizeParam->getType()))
+      return;
+
+    // Heuristic: size param name should suggest a size.
+    // If unnamed, skip -- cannot verify intent.
+    if (!SizeParam->getIdentifier() || !isSizeName(SizeParam->getName()))
+      return;
+
+    diag(PtrParam->getLocation(),
+         "pointer and size parameters can be replaced with 'std::span'")
+        << PtrParam->getSourceRange();
+    diag(SizeParam->getLocation(),
+         "size parameter declared here", DiagnosticIDs::Note)
+        << SizeParam->getSourceRange();
+    return;
+  }
+
+  // Handle N-parameter functions: scan for consecutive (ptr, size) pairs.
+  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("funcN");
+  if (!Func)
+    return;
+  if (SM.isInSystemHeader(Func->getLocation()))
+    return;
+  if (const auto *MD = dyn_cast<CXXMethodDecl>(Func))
+    if (MD->isVirtual())
+      return;
+  if (Func->isMain())
+    return;
+
+  for (unsigned I = 0; I + 1 < Func->getNumParams(); ++I) {
+    const ParmVarDecl *PtrParam = Func->getParamDecl(I);
+    const ParmVarDecl *SizeParam = Func->getParamDecl(I + 1);
+
+    const auto *PT = PtrParam->getType()->getAs<PointerType>();
+    if (!PT || PT->getPointeeType()->isVoidType() ||
+        PT->getPointeeType()->isFunctionType())
+      continue;
+
+    if (!isSizeType(SizeParam->getType()))
+      continue;
+
+    if (!SizeParam->getIdentifier() || !isSizeName(SizeParam->getName()))
+      continue;
+
+    diag(PtrParam->getLocation(),
+         "pointer and size parameters can be replaced with 'std::span'")
+        << PtrParam->getSourceRange();
+    diag(SizeParam->getLocation(),
+         "size parameter declared here", DiagnosticIDs::Note)
+        << SizeParam->getSourceRange();
+  }
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h 
b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h
new file mode 100644
index 0000000000000..c26cb885d79a0
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MODERNIZE_POINTERTOSPANCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_POINTERTOSPANCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Finds function parameter pairs of (pointer, size) that could be replaced
+/// with std::span.
+///
+/// For the user-facing documentation see:
+/// 
https://clang.llvm.org/extra/clang-tidy/checks/modernize/pointer-to-span.html
+class PointerToSpanCheck : public ClangTidyCheck {
+public:
+  PointerToSpanCheck(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::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_POINTERTOSPANCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 68bab88146241..b0e4c356bcb6a 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -121,6 +121,12 @@ New checks
   ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
   ``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
 
+- New :doc:`modernize-pointer-to-span
+  <clang-tidy/checks/modernize/pointer-to-span>` check.
+
+  Finds function parameter pairs of (pointer, size) that could be
+  replaced with ``std::span``.
+
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst 
b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index c475870ed7b31..1c1f32ed27744 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -302,6 +302,7 @@ Clang-Tidy Checks
    :doc:`modernize-make-unique <modernize/make-unique>`, "Yes"
    :doc:`modernize-min-max-use-initializer-list 
<modernize/min-max-use-initializer-list>`, "Yes"
    :doc:`modernize-pass-by-value <modernize/pass-by-value>`, "Yes"
+   :doc:`modernize-pointer-to-span <modernize/pointer-to-span>`,
    :doc:`modernize-raw-string-literal <modernize/raw-string-literal>`, "Yes"
    :doc:`modernize-redundant-void-arg <modernize/redundant-void-arg>`, "Yes"
    :doc:`modernize-replace-auto-ptr <modernize/replace-auto-ptr>`, "Yes"
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst 
b/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst
new file mode 100644
index 0000000000000..e16fecd4d3f62
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst
@@ -0,0 +1,38 @@
+.. title:: clang-tidy - modernize-pointer-to-span
+
+modernize-pointer-to-span
+=========================
+
+Finds function parameter pairs of (pointer, size) that could be
+replaced with ``std::span``.
+
+Passing a pointer and a separate size is a common C-style pattern
+that ``std::span`` (C++20) was designed to replace. Using
+``std::span`` bundles the pointer and size together, preventing
+mismatches and improving readability.
+
+.. code-block:: c++
+
+  // Before
+  void process(int *Data, int Size);
+
+  // After
+  void process(std::span<int> Data);
+
+The check uses a name-based heuristic to identify size parameters.
+Names like ``size``, ``len``, ``length``, ``count``, ``n``, ``num``,
+or names ending in ``_size``, ``_len``, etc. are recognized.
+
+The check will not flag a parameter pair if:
+
+- the pointer is ``void*`` or a function pointer,
+- the size parameter is not an integer type,
+- the size parameter name does not suggest a size,
+- the function is a virtual method, or
+- the size parameter is unnamed.
+
+.. note::
+
+   This check requires C++20 or later and does not provide
+   fix-its because changing the signature requires updating all
+   call sites.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp 
b/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp
new file mode 100644
index 0000000000000..cc037261ce40d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp
@@ -0,0 +1,41 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-pointer-to-span %t
+
+// Positive: basic (pointer, size) pair.
+void process(int *Data, int Size);
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: pointer and size parameters can 
be replaced with 'std::span'
+
+// Positive: const pointer with unsigned long length.
+void readBuf(const char *Buf, unsigned long Len);
+// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: pointer and size parameters can 
be replaced with 'std::span'
+
+// Positive: size parameter named "count".
+void fill(float *Arr, int Count);
+// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: pointer and size parameters can 
be replaced with 'std::span'
+
+// Positive: size parameter named "n".
+void copy(int *Dst, int N);
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: pointer and size parameters can 
be replaced with 'std::span'
+
+// Positive: ptr+size in the middle of more params.
+void multi(int Id, double *Data, unsigned Len, bool Flag);
+// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: pointer and size parameters can 
be replaced with 'std::span'
+
+// Negative: second param name does not suggest a size.
+void noMatch(int *Data, int Flags);
+
+// Negative: void pointer.
+void voidPtr(void *Data, int Size);
+
+// Negative: function pointer.
+void funcPtr(void (*Fn)(int), int Size);
+
+// Negative: second param is not an integer.
+void wrongType(int *Data, double Size);
+
+// Negative: virtual method.
+struct Base {
+  virtual void vmethod(int *Data, int Size);
+};
+
+// Negative: unnamed size parameter (cannot verify intent).
+void unnamed(int *Data, int);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to