https://github.com/hjanuschka updated 
https://github.com/llvm/llvm-project/pull/182023

>From 2ddc82162ae926efdafef9746ff7ca07b6a40af6 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <[email protected]>
Date: Wed, 18 Feb 2026 14:30:08 +0100
Subject: [PATCH 1/2] [clang-tidy] Add modernize-use-size-type check

Suggests changing signed integer variables to size_t when they are initialized 
from unsigned sources and only used in unsigned-compatible contexts.
---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   2 +
 .../clang-tidy/modernize/UseSizeTypeCheck.cpp | 158 ++++++++++++++++++
 .../clang-tidy/modernize/UseSizeTypeCheck.h   |  36 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   7 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/modernize/use-size-type.rst        |  38 +++++
 .../checkers/modernize/use-size-type.cpp      |  78 +++++++++
 8 files changed, 321 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index cc4cc7a02b594..22ebd20a66211 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -46,6 +46,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseOverrideCheck.cpp
   UseRangesCheck.cpp
   UseScopedLockCheck.cpp
+  UseSizeTypeCheck.cpp
   UseStartsEndsWithCheck.cpp
   UseStdFormatCheck.cpp
   UseStdNumbersCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp 
b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index fcb860d8c5298..38e539adaeec8 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -46,6 +46,7 @@
 #include "UseOverrideCheck.h"
 #include "UseRangesCheck.h"
 #include "UseScopedLockCheck.h"
+#include "UseSizeTypeCheck.h"
 #include "UseStartsEndsWithCheck.h"
 #include "UseStdFormatCheck.h"
 #include "UseStdNumbersCheck.h"
@@ -94,6 +95,7 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
     CheckFactories.registerCheck<UseScopedLockCheck>(
         "modernize-use-scoped-lock");
+    CheckFactories.registerCheck<UseSizeTypeCheck>("modernize-use-size-type");
     CheckFactories.registerCheck<UseStartsEndsWithCheck>(
         "modernize-use-starts-ends-with");
     
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
new file mode 100644
index 0000000000000..036fdfdc94e4c
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
@@ -0,0 +1,158 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseSizeTypeCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+void UseSizeTypeCheck::registerMatchers(MatchFinder *Finder) {
+  // Match local variables with signed integer type initialized from an
+  // unsigned expression (via implicit cast).
+  Finder->addMatcher(
+      varDecl(hasLocalStorage(),
+              hasType(qualType(hasCanonicalType(isSignedInteger()))),
+              hasInitializer(ignoringImplicit(expr(
+                  hasType(qualType(hasCanonicalType(isUnsignedInteger())))))),
+              unless(isConstexpr()),
+              hasParent(declStmt(hasParent(compoundStmt().bind("scope")))))
+          .bind("var"),
+      this);
+}
+
+/// Return true if every use of \p VD within \p Scope is in a context that
+/// expects or is compatible with an unsigned / size_t type.
+static bool allUsesAreUnsignedCompatible(const VarDecl *VD,
+                                         const CompoundStmt *Scope,
+                                         ASTContext &Ctx) {
+  auto Refs =
+      match(findAll(declRefExpr(to(varDecl(equalsNode(VD)))).bind("ref")),
+            *Scope, Ctx);
+
+  if (Refs.empty())
+    return false;
+
+  for (const auto &Ref : Refs) {
+    const auto *DRE = Ref.getNodeAs<DeclRefExpr>("ref");
+    if (!DRE)
+      return false;
+
+    const auto Parents = Ctx.getParents(*DRE);
+    if (Parents.empty())
+      return false;
+
+    // Walk up through implicit casts to find the "real" parent context.
+    // The DeclRefExpr is typically wrapped in LValueToRValue and
+    // IntegralCast implicit casts.
+    const Expr *Current = DRE;
+    DynTypedNodeList CurrentParents = Parents;
+    while (CurrentParents.size() == 1) {
+      if (const auto *ICE = CurrentParents[0].get<ImplicitCastExpr>()) {
+        Current = ICE;
+        CurrentParents = Ctx.getParents(*ICE);
+        continue;
+      }
+      break;
+    }
+
+    bool UsageOk = false;
+    for (const auto &Parent : CurrentParents) {
+      // Used in binary comparison with unsigned operand.
+      if (const auto *BO = Parent.get<BinaryOperator>()) {
+        if (BO->isComparisonOp()) {
+          UsageOk = true;
+          break;
+        }
+        // Also accept arithmetic where the result feeds into an
+        // unsigned context (but not standalone).
+      }
+
+      // Used as function argument (CallExpr or CXXMemberCallExpr).
+      if (const auto *CE = Parent.get<CallExpr>()) {
+        for (unsigned I = 0; I < CE->getNumArgs(); ++I) {
+          if (CE->getArg(I)->IgnoreParenImpCasts() == DRE) {
+            if (const auto *FD = CE->getDirectCallee()) {
+              if (I < FD->getNumParams()) {
+                const QualType PT =
+                    FD->getParamDecl(I)->getType().getCanonicalType();
+                if (PT->isUnsignedIntegerType())
+                  UsageOk = true;
+              }
+            }
+            break;
+          }
+        }
+        if (UsageOk)
+          break;
+      }
+
+      // Used as array subscript index.
+      if (const auto *ASE = Parent.get<ArraySubscriptExpr>()) {
+        if (ASE->getIdx()->IgnoreParenImpCasts() == DRE) {
+          UsageOk = true;
+          break;
+        }
+      }
+
+      // Used in operator[] (CXXOperatorCallExpr).
+      if (const auto *OCE = Parent.get<CXXOperatorCallExpr>()) {
+        if (OCE->getOperator() == OO_Subscript && OCE->getNumArgs() > 1 &&
+            OCE->getArg(1)->IgnoreParenImpCasts() == DRE) {
+          UsageOk = true;
+          break;
+        }
+        if (UsageOk)
+          break;
+      }
+    }
+
+    if (!UsageOk)
+      return false;
+  }
+
+  return true;
+}
+
+void UseSizeTypeCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
+  const auto *Scope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
+  if (!VD || !Scope)
+    return;
+
+  // Skip dependent types.
+  if (VD->getType()->isDependentType())
+    return;
+
+  // Skip macros.
+  if (VD->getLocation().isMacroID())
+    return;
+
+  // Check all uses are unsigned-compatible.
+  if (!allUsesAreUnsignedCompatible(VD, Scope, *Result.Context))
+    return;
+
+  // Get the type specifier source range to replace.
+  const SourceLocation TypeStart = VD->getTypeSpecStartLoc();
+  const SourceLocation TypeEnd = VD->getTypeSpecEndLoc();
+  if (TypeStart.isInvalid() || TypeEnd.isInvalid())
+    return;
+  if (TypeStart.isMacroID() || TypeEnd.isMacroID())
+    return;
+
+  diag(VD->getLocation(),
+       "variable %0 is of signed type %1 but is initialized from and "
+       "used as an unsigned value; consider using 'size_t'")
+      << VD << VD->getType()
+      << FixItHint::CreateReplacement(
+             CharSourceRange::getTokenRange(TypeStart, TypeEnd), "size_t");
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h 
b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h
new file mode 100644
index 0000000000000..f692e55c77b34
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_USESIZETYPECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESIZETYPECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Finds local variables declared as signed integer types that are initialized
+/// from an unsigned/``size_t`` source (e.g. ``container.size()``) and only 
used
+/// in contexts expecting unsigned types, and suggests changing the type to
+/// ``size_t``.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-size-type.html
+class UseSizeTypeCheck : public ClangTidyCheck {
+public:
+  UseSizeTypeCheck(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.CPlusPlus11;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESIZETYPECHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 68bab88146241..0dc391644e88e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -121,6 +121,13 @@ 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-use-size-type
+  <clang-tidy/checks/modernize/use-size-type>` check.
+
+  Finds local variables declared as signed integer types initialized
+  from unsigned sources and only used in unsigned contexts, and
+  suggests changing the type to ``size_t``.
+
 - 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..a4138f3c282f3 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -326,6 +326,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-override <modernize/use-override>`, "Yes"
    :doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
    :doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes"
+   :doc:`modernize-use-size-type <modernize/use-size-type>`, "Yes"
    :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, 
"Yes"
    :doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
    :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst 
b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
new file mode 100644
index 0000000000000..9fc7b780ae016
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
@@ -0,0 +1,38 @@
+.. title:: clang-tidy - modernize-use-size-type
+
+modernize-use-size-type
+=======================
+
+Finds local variables declared as signed integer types that are
+initialized from an unsigned/``size_t`` source (e.g.
+``container.size()``) and only used in contexts expecting unsigned
+types, and suggests changing the type to ``size_t``.
+
+Storing a ``size_t`` value in a signed ``int`` can cause implicit
+narrowing conversions and sign-comparison warnings. Using ``size_t``
+directly avoids these issues.
+
+For example:
+
+.. code-block:: c++
+
+  std::vector<int> v;
+  int n = v.size();
+  v.resize(n);
+
+  // transforms to:
+
+  std::vector<int> v;
+  size_t n = v.size();
+  v.resize(n);
+
+The check only triggers when all of the following are true:
+
+- The variable is a local, non-static variable.
+- The variable has a signed integer type (e.g. ``int``).
+- The initializer is an unsigned integer expression.
+- Every use of the variable is in an unsigned-compatible context
+  (comparison, function argument expecting unsigned, array
+  subscript, or implicit cast to unsigned).
+- The variable is not ``constexpr``.
+- The declaration is not in a macro.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp 
b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp
new file mode 100644
index 0000000000000..da23ba1b630af
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp
@@ -0,0 +1,78 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-size-type %t
+
+using size_t = decltype(sizeof(0));
+
+namespace std {
+template <typename T>
+struct vector {
+  size_t size() const;
+  T &operator[](size_t);
+  const T &operator[](size_t) const;
+  void resize(size_t);
+};
+
+template <typename T>
+struct basic_string {
+  size_t size() const;
+  size_t length() const;
+  char &operator[](size_t);
+};
+using string = basic_string<char>;
+} // namespace std
+
+// Positive: int from .size(), used in comparison and subscript
+void test_size_comparison(std::vector<int> &v) {
+  int n = v.size();
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'n' is of signed type 
'int' but is initialized from and used as an unsigned value; consider using 
'size_t' [modernize-use-size-type]
+  // CHECK-FIXES: size_t n = v.size();
+  for (int i = 0; i < n; ++i) {
+  }
+}
+
+// Positive: int from .size(), used in resize()
+void test_size_resize(std::vector<int> &v) {
+  int s = v.size();
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 's' is of signed type 
'int'
+  // CHECK-FIXES: size_t s = v.size();
+  v.resize(s);
+}
+
+// Positive: int from .length(), used in subscript
+void test_length_subscript(std::string &s) {
+  int len = s.length();
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'len' is of signed type 
'int'
+  // CHECK-FIXES: size_t len = s.length();
+  for (int i = 0; i < len; ++i) {
+    char c = s[i];
+  }
+}
+
+// Negative: variable used in signed context (subtraction producing
+// potentially negative value)
+void test_signed_arithmetic(std::vector<int> &v) {
+  int n = v.size();
+  int x = n - 10; // Used in signed arithmetic
+}
+
+// Negative: variable not initialized from unsigned
+void test_signed_init() {
+  int n = 42;
+  size_t s = n;
+}
+
+// Negative: variable is already unsigned
+void test_already_unsigned(std::vector<int> &v) {
+  size_t n = v.size();
+}
+
+// Negative: variable used in a context expecting signed
+void negative_signed_param(int x);
+void test_signed_param(std::vector<int> &v) {
+  int n = v.size();
+  negative_signed_param(n);
+}
+
+// Negative: unused variable (no uses to check)
+void test_unused(std::vector<int> &v) {
+  int n = v.size();
+}

>From 89f4ae68241f5e9539e4a017b7175e8b540c5ef9 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <[email protected]>
Date: Wed, 18 Feb 2026 17:25:58 +0100
Subject: [PATCH 2/2] Update
 clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst

Co-authored-by: EugeneZelenko <[email protected]>
---
 .../clang-tidy/modernize/UseSizeTypeCheck.cpp              | 2 --
 clang-tools-extra/docs/ReleaseNotes.rst                    | 7 ++++---
 .../docs/clang-tidy/checks/modernize/use-size-type.rst     | 6 +++---
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
index 036fdfdc94e4c..67cfe59083488 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp
@@ -52,11 +52,9 @@ static bool allUsesAreUnsignedCompatible(const VarDecl *VD,
     // Walk up through implicit casts to find the "real" parent context.
     // The DeclRefExpr is typically wrapped in LValueToRValue and
     // IntegralCast implicit casts.
-    const Expr *Current = DRE;
     DynTypedNodeList CurrentParents = Parents;
     while (CurrentParents.size() == 1) {
       if (const auto *ICE = CurrentParents[0].get<ImplicitCastExpr>()) {
-        Current = ICE;
         CurrentParents = Ctx.getParents(*ICE);
         continue;
       }
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 0dc391644e88e..ccf544ded3777 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -124,9 +124,10 @@ New checks
 - New :doc:`modernize-use-size-type
   <clang-tidy/checks/modernize/use-size-type>` check.
 
-  Finds local variables declared as signed integer types initialized
-  from unsigned sources and only used in unsigned contexts, and
-  suggests changing the type to ``size_t``.
+  Finds local variables declared as signed integer types that are
+  initialized from an unsigned/``size_t`` source (e.g.
+  ``container.size()``) and only used in contexts expecting unsigned
+  types, and suggests changing the type to ``size_t``.
 
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst 
b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
index 9fc7b780ae016..199dcbcac3aad 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst
@@ -31,8 +31,8 @@ The check only triggers when all of the following are true:
 - The variable is a local, non-static variable.
 - The variable has a signed integer type (e.g. ``int``).
 - The initializer is an unsigned integer expression.
-- Every use of the variable is in an unsigned-compatible context
-  (comparison, function argument expecting unsigned, array
-  subscript, or implicit cast to unsigned).
+- Every use of the variable is in an unsigned-compatible context (comparison,
+  function argument expecting unsigned, array subscript, or implicit cast to
+  unsigned).
 - The variable is not ``constexpr``.
 - The declaration is not in a macro.

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

Reply via email to