[clang-tools-extra] [clang-tidy] Add new check 'misc-scope-reduction' (PR #175429)

via cfe-commits cfe-commits at lists.llvm.org
Sat Jan 24 11:31:08 PST 2026


https://github.com/vabridgers updated https://github.com/llvm/llvm-project/pull/175429

>From 00cc929839e84706d396406ef599e80d48d4074d Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 12:21:43 +0100
Subject: [PATCH 01/38] [clang-tidy] Add new check 'misc-scope-reduction'

Adds a new clang-tidy check that does a scope reduction analysis,
supporting SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and MISRA
+C:2012 Rule 8-9.
---
 .../clang-tidy/misc/CMakeLists.txt            |   1 +
 .../clang-tidy/misc/MiscTidyModule.cpp        |   2 +
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 282 ++++++++++++++++++
 .../clang-tidy/misc/ScopeReductionCheck.h     |  28 ++
 clang-tools-extra/docs/ReleaseNotes.rst       |   5 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/misc/scope-reduction.rst           |  50 ++++
 .../checkers/misc/scope-reduction.cpp         | 181 +++++++++++
 8 files changed, 550 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index e34b0cf687be3..c1c3dd656b811 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -37,6 +37,7 @@ add_clang_library(clangTidyMiscModule STATIC
   OverrideWithDifferentVisibilityCheck.cpp
   PredictableRandCheck.cpp
   RedundantExpressionCheck.cpp
+  ScopeReductionCheck.cpp
   StaticAssertCheck.cpp
   ThrowByValueCatchByReferenceCheck.cpp
   UnconventionalAssignOperatorCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index f8550b30b9789..899c27033ba3b 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -26,6 +26,7 @@
 #include "OverrideWithDifferentVisibilityCheck.h"
 #include "PredictableRandCheck.h"
 #include "RedundantExpressionCheck.h"
+#include "ScopeReductionCheck.h"
 #include "StaticAssertCheck.h"
 #include "ThrowByValueCatchByReferenceCheck.h"
 #include "UnconventionalAssignOperatorCheck.h"
@@ -75,6 +76,7 @@ class MiscModule : public ClangTidyModule {
     CheckFactories.registerCheck<PredictableRandCheck>("misc-predictable-rand");
     CheckFactories.registerCheck<RedundantExpressionCheck>(
         "misc-redundant-expression");
+    CheckFactories.registerCheck<ScopeReductionCheck>("misc-scope-reduction");
     CheckFactories.registerCheck<StaticAssertCheck>("misc-static-assert");
     CheckFactories.registerCheck<ThrowByValueCatchByReferenceCheck>(
         "misc-throw-by-value-catch-by-reference");
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
new file mode 100644
index 0000000000000..51fb0598fa765
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -0,0 +1,282 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// This checker uses a 7-step algorithm to accomplish scope analysis of a
+// variable and determine if it's in too large a scope. Note that the
+// clang-tidy framework is aimed mainly at supporting text-manipulation,
+// diagnostics, or common AST patterns. Scope reduction analysis is
+// quite specialized, and there's not much support specifically for
+// those steps. Perhaps someone else knows better and can help simplify
+// this code in a more concrete way other than simply suggesting it can
+// be simpler.
+//
+// The 7-step algorithm used by this checker for scope reduction analysis is:
+// 1) Filter out variables declared in for-loop initializations
+//    - Those variables are already in optimal scope, and can be skipped
+// 2) Collect variable uses
+//    - find all DeclRefExpr nodes that reference the variable
+// 3) Build scope chains
+//    - for each use, find all compound statements that contain it (from
+//      innermost to outermost)
+// 4) Find the innermost compound statement that contains all uses
+//    - This is the smallest scope where the variable could be declared
+// 5) Find declaration scope
+//    - Locate the compound statement containing the variable declaration
+// 6) Verify nesting
+//    - Ensure the usage scope is actually nested within the declaration scope
+// 7) Alternate analysis - check for for-loop initialization opportunity
+//    - This is only run if compound stmt analysis didn't find smaller scope
+//    - Only check local variables, not parameters
+//    - Determine if all uses are within the same for-loop and suggest
+//      for-loop initialization
+//
+// The algo works by finding the smallest scope that could contain the variable
+// declaration while still encompassing all it's uses.
+
+#include "ScopeReductionCheck.h"
+#include "../utils/ASTUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+static void
+collectVariableUses(const Stmt *S, const VarDecl *Var,
+                    llvm::SmallVector<const DeclRefExpr *, 8> &Uses) {
+  if (!S)
+    return;
+
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) {
+    if (DRE->getDecl() == Var)
+      Uses.push_back(DRE);
+  }
+
+  for (const Stmt *Child : S->children())
+    collectVariableUses(Child, Var, Uses);
+}
+
+void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(varDecl(hasLocalStorage()).bind("var"), this);
+}
+
+void ScopeReductionCheck::check(
+    const ast_matchers::MatchFinder::MatchResult &Result) {
+  const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var");
+  if (!Var)
+    return;
+
+  // Step 1: Filter out variables declared in for-loop initializations
+  // These variables are already in their optimal scope and shouldn't be
+  // analyzed
+  auto &Parents = Result.Context->getParentMapContext();
+  auto ParentNodes = Parents.getParents(DynTypedNode::create(*Var));
+
+  if (!ParentNodes.empty()) {
+    if (const auto *Parent = ParentNodes[0].get<Stmt>()) {
+      if (isa<DeclStmt>(Parent)) {
+        // Check if DeclStmt's parent is ForStmt
+        auto GrandParentNodes = Parents.getParents(*Parent);
+        if (!GrandParentNodes.empty()) {
+          if (const auto *GrandParent = GrandParentNodes[0].get<Stmt>()) {
+            if (isa<ForStmt>(GrandParent))
+              return; // Skip for-loop declared variables
+          }
+        }
+      }
+    }
+  }
+
+  // auto *Context = Result.Context;
+  auto *Function = dyn_cast<FunctionDecl>(Var->getDeclContext());
+  if (!Function || !Function->hasBody())
+    return;
+
+  // Step 2: Collect all uses of this variable within the function
+  llvm::SmallVector<const DeclRefExpr *, 8> Uses;
+  collectVariableUses(Function->getBody(), Var, Uses);
+
+  // No uses, return with no diagnostics
+  if (Uses.empty())
+    return;
+
+  // Step 3: For each variable use, find all compound statements that contain it
+  // This builds a "scope chain" from innermost to outermost for each use
+  const CompoundStmt *InnermostScope = nullptr;
+
+  // For each use, find all compound statements that contain it
+  llvm::SmallVector<llvm::SmallVector<const CompoundStmt *, 4>, 8>
+      UseScopeChains;
+
+  for (const auto *Use : Uses) {
+    llvm::SmallVector<const CompoundStmt *, 4> ScopeChain;
+    const Stmt *Current = Use;
+
+    // Walk up the AST from this use to fins all containing compound stmts
+    while (Current) {
+      auto ParentNodes = Parents.getParents(*Current);
+      if (ParentNodes.empty())
+        break;
+
+      const Stmt *Parent = ParentNodes[0].get<Stmt>();
+      if (!Parent) {
+        // Try to get Decl parent and continue from there
+        if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
+          auto DeclParentNodes = Parents.getParents(*DeclParent);
+          if (!DeclParentNodes.empty())
+            Parent = DeclParentNodes[0].get<Stmt>();
+        }
+        if (!Parent)
+          break;
+      }
+
+      if (const auto *CS = dyn_cast<CompoundStmt>(Parent))
+        ScopeChain.push_back(CS);
+
+      Current = Parent;
+    }
+
+    if (!ScopeChain.empty())
+      UseScopeChains.push_back(ScopeChain);
+  }
+
+  // Step 4: Find the innermost scope that contains all uses
+  //         This is the smallest scope where var could be declared
+  if (!UseScopeChains.empty()) {
+    // Start with first use's innermost scope
+    InnermostScope = UseScopeChains[0][0];
+
+    // For each subsequent use, find common ancestor scope
+    for (size_t i = 1; i < UseScopeChains.size(); ++i) {
+      const CompoundStmt *CommonScope = nullptr;
+
+      // Find first scope that appears in both chains (common ancestor)
+      for (const auto *Scope1 : UseScopeChains[0]) {
+        for (const auto *Scope2 : UseScopeChains[i]) {
+          if (Scope1 == Scope2) {
+            CommonScope = Scope1;
+            break;
+          }
+        }
+        if (CommonScope)
+          break;
+      }
+
+      if (CommonScope)
+        InnermostScope = CommonScope;
+    }
+  }
+
+  // Step 5: Check if current var declaration is broader than necessary
+  if (InnermostScope) {
+    // Find the compound statement containing the variable declaration
+    const DynTypedNode Current = DynTypedNode::create(*Var);
+    const CompoundStmt *VarScope = nullptr;
+
+    auto ParentNodes = Parents.getParents(Current);
+    while (!ParentNodes.empty()) {
+      const Stmt *Parent = ParentNodes[0].get<Stmt>();
+      if (!Parent)
+        break;
+
+      if (const auto *CS = dyn_cast<CompoundStmt>(Parent)) {
+        VarScope = CS;
+        break;
+      }
+      ParentNodes = Parents.getParents(*Parent);
+    }
+
+    // Step 6: Verify that usage scope is nested within decl scope
+    if (VarScope && VarScope != InnermostScope) {
+      // Walk up from innermost usage to see if the decl scope is reached
+      const Stmt *CheckScope = InnermostScope;
+      bool IsNested = false;
+
+      while (CheckScope) {
+        auto CheckParents = Parents.getParents(*CheckScope);
+        if (CheckParents.empty())
+          break;
+
+        const Stmt *CheckParent = CheckParents[0].get<Stmt>();
+        if (CheckParent == VarScope) {
+          IsNested = true;
+          break;
+        }
+        CheckScope = CheckParent;
+      }
+
+      // Only report if the usage scope is truly nested within the decl scope
+      if (IsNested) {
+        diag(Var->getLocation(),
+             "variable '%0' can be declared in a smaller scope")
+            << Var->getName();
+        return; // early exit
+      }
+    }
+  }
+
+  // Step 7: Alternative analysis - check for for-loop initialization
+  // opportunity This only runs if the compound statement analysis didn't find a
+  // smaller scope Only check local variables, not parameters
+  if (!isa<ParmVarDecl>(Var)) {
+    const ForStmt *CommonForLoop = nullptr;
+    bool AllUsesInSameForLoop = true;
+
+    for (const auto *Use : Uses) {
+      const ForStmt *ContainingForLoop = nullptr;
+      const Stmt *Current = Use;
+
+      // Walk up the AST to find a containing ForStmt
+      while (Current) {
+        auto ParentNodes = Parents.getParents(*Current);
+        if (ParentNodes.empty())
+          break;
+
+        if (const auto *FS = ParentNodes[0].get<ForStmt>()) {
+          ContainingForLoop = FS;
+          break;
+        }
+
+        const Stmt *Parent = ParentNodes[0].get<Stmt>();
+        if (!Parent) {
+          // Handle Decl parents like we do in the existing logic
+          if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
+            auto DeclParentNodes = Parents.getParents(*DeclParent);
+            if (!DeclParentNodes.empty())
+              Parent = DeclParentNodes[0].get<Stmt>();
+          }
+          if (!Parent)
+            break;
+        }
+        Current = Parent;
+      }
+
+      if (!ContainingForLoop) {
+        AllUsesInSameForLoop = false;
+        break;
+      }
+
+      if (!CommonForLoop) {
+        CommonForLoop = ContainingForLoop;
+      } else if (CommonForLoop != ContainingForLoop) {
+        AllUsesInSameForLoop = false;
+        break;
+      }
+    }
+
+    if (AllUsesInSameForLoop && CommonForLoop) {
+      diag(Var->getLocation(),
+           "variable '%0' can be declared in for-loop initialization")
+          << Var->getName();
+      return;
+    }
+  }
+}
+
+} // namespace clang::tidy::misc
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
new file mode 100644
index 0000000000000..690f58feb93f7
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MISC_SCOPEREDUCTIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDCUTIONCHECK_H
+
+#include "../../clang-tidy/utils/DeclRefExprUtils.h"
+#include "../ClangTidyCheck.h"
+#include "clang/AST/ASTContext.h"
+
+namespace clang::tidy::misc {
+
+class ScopeReductionCheck : public ClangTidyCheck {
+public:
+  ScopeReductionCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDCUTIONCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 5cfb6476dcc1f..b8c8279d3c907 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,11 @@ Improvements to clang-tidy
 New checks
 ^^^^^^^^^^
 
+- New :doc:`misc-scope-reduction
+  <clang-tidy/checks/misc/scope-reduction>` check.
+
+  Checks for opportunities to minimize scope of local variables.
+
 - 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 a34fade4b8027..93829a891df6c 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -280,6 +280,7 @@ Clang-Tidy Checks
    :doc:`misc-override-with-different-visibility <misc/override-with-different-visibility>`,
    :doc:`misc-predictable-rand <misc/predictable-rand>`,
    :doc:`misc-redundant-expression <misc/redundant-expression>`, "Yes"
+   :doc:`misc-scope-reduction <misc/scope-reduction>`,
    :doc:`misc-static-assert <misc/static-assert>`, "Yes"
    :doc:`misc-throw-by-value-catch-by-reference <misc/throw-by-value-catch-by-reference>`,
    :doc:`misc-unconventional-assign-operator <misc/unconventional-assign-operator>`,
diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
new file mode 100644
index 0000000000000..11a85f406f5bc
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -0,0 +1,50 @@
+.. title:: clang-tidy - misc-scope-reduction
+
+misc-scope-reduction
+===========================
+
+Detects local variables in functions whose scopes can be minimized. This check
+covers guidelines described by SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and MISRA
+C:2012 Rule 8-9.
+
+Examples:
+
+.. code-block:: cpp
+
+    void test_deep_nesting() {
+      int deep = 1; // 'deep' can be declared in a smaller scope
+      if (true) {
+        if (true) {
+          if (true) {
+            if (true) {
+              int result = deep * 4;
+            }
+          }
+        }
+      }
+    }
+
+    void test_switch_multiple_cases(int value) {
+      int accumulator = 0; // 'accumulator' can be declared in a smaller scope
+      switch (value) {
+        case 1:
+          accumulator += 10;
+          break;
+        case 2:
+          accumulator += 20;
+          break;
+      }
+    }
+
+    void test_for_loop_expressions() {
+      int i; // 'i' can be declared in the for-loop initialization
+      for (i = 0; i < 5; i++) {
+        // loop body
+      }
+    }
+
+References
+----------
+This check corresponds to the CERT C Coding Standard rules
+`DCL19-C. Minimize the scope of variables and functions
+<https://wiki.sei.cmu.edu/confluence/spaces/c/pages/87152335/DCL19-C.+Minimize+the+scope+of+variables+and+functions>`_.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
new file mode 100644
index 0000000000000..ba9e0e9d3c466
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -0,0 +1,181 @@
+// RUN: %check_clang_tidy %s misc-scope-reduction %t -- --
+
+// Test case 1: Variable can be moved to smaller scope (if-block)
+void test_if_scope() {
+  int x = 42; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'x' can be declared in a smaller scope
+  if (true) {
+    int y = x + 1;
+  }
+}
+
+// Test case 2: Variable used across multiple scopes - should NOT warn
+int test_multiple_scopes(int v) {
+  int y = 0; // Should NOT warn - used in if-block and return
+  if (v) {
+    y = 10;
+  }
+  return y;
+}
+
+// Test case 3: Variable can be moved to nested if-block
+void test_nested_if() {
+  int a = 5; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'a' can be declared in a smaller scope
+  if (true) {
+    if (true) {
+      int b = a * 2;
+    }
+  }
+}
+
+// Test case 4: Variable used in same scope - should NOT warn
+void test_same_scope() {
+  int x = 10; // Should NOT warn - used in same scope
+  int y = x + 5;
+}
+
+// Test case 5: Variable can be moved to while loop body
+void test_while_loop() {
+  int counter = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'counter' can be declared in a smaller scope
+  while (true) {
+    counter++;
+    if (counter > 10) break;
+  }
+}
+
+// Test case 6: Variable used in multiple branches of same if-statement
+void test_if_branches(bool condition) {
+  int value = 100; // Should NOT warn - used in both branches
+  if (condition) {
+    value *= 2;
+  } else {
+    value /= 2;
+  }
+}
+
+// Test case 7: Variable can be moved to for-loop body
+void test_for_loop_body() {
+  int temp = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'temp' can be declared in a smaller scope
+  for (int i = 0; i < 10; i++) {
+    temp = i * i;
+  }
+}
+
+// Test case 8: Variable used in for-loop expressions - should NOT warn (current limitation)
+void test_for_loop_expressions() {
+  int i; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'i' can be declared in for-loop initialization
+  for (i = 0; i < 5; i++) {
+    // loop body
+  }
+}
+
+// Test case 9: Variable can be moved to switch case
+void test_switch_case(int value) {
+  int result = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'result' can be declared in a smaller scope
+  switch (value) {
+    case 1:
+      result = 10;
+      break;
+    default:
+      break;
+  }
+}
+
+// Test case 10: Variable used across multiple switch cases - should NOT warn
+void test_switch_multiple_cases(int value) {
+  int accumulator = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'accumulator' can be declared in a smaller scope
+  switch (value) {
+    case 1:
+      accumulator += 10;
+      break;
+    case 2:
+      accumulator += 20;
+      break;
+  }
+}
+
+// Test case 11: Variable with complex initialization can be moved
+void test_complex_init() {
+  int complex = (5 + 3) * 2; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'complex' can be declared in a smaller scope
+  if (true) {
+    int doubled = complex * 2;
+  }
+}
+
+// Test case 12: Multiple variables, some can be moved, some cannot
+int test_mixed_variables(bool flag) {
+  int movable = 10;   // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'movable' can be declared in a smaller scope
+  int unmovable = 20; // Should NOT warn - used across scopes
+
+  if (flag) {
+    int local = movable + 5;
+    unmovable += 1;
+  }
+
+  return unmovable;
+}
+
+// Test case 13: Variable in try-catch block
+void test_try_catch() {
+  int error_code = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'error_code' can be declared in a smaller scope
+  try {
+    error_code = 404;
+  } catch (...) {
+    // handle exception
+  }
+}
+
+// Test case 14: Variable used in catch block and try block - should NOT warn
+void test_try_catch_shared() {
+  int shared = 0; // Should NOT warn - used in both try and catch
+  try {
+    shared = 100;
+  } catch (...) {
+    shared = -1;
+  }
+}
+
+// Test case 15: Deeply nested scopes
+void test_deep_nesting() {
+  int deep = 1; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'deep' can be declared in a smaller scope
+  if (true) {
+    if (true) {
+      if (true) {
+        if (true) {
+          int result = deep * 4;
+        }
+      }
+    }
+  }
+}
+
+// Test case 16: Variable declared but never used - should NOT warn (different checker's job)
+void test_unused_variable() {
+  int unused = 42; // Should NOT warn - this checker only handles scope reduction
+}
+
+// Test case 17: Global variable - should NOT be processed
+int global_var = 100;
+
+// Test case 18: Static local variable - should NOT warn
+void test_static_variable() {
+  static int static_var = 0; // Should NOT warn - static variables have different semantics
+  if (true) {
+    static_var++;
+  }
+}
+
+// Test case 19: Function parameter - should NOT be processed
+void test_parameter(int param) {
+  if (true) {
+    int local = param + 1;
+  }
+}
+
+// Test case 20: Variable used in lambda - should NOT warn (complex case)
+void test_lambda() {
+  int captured = 10; // Should NOT warn - used in lambda
+  auto lambda = [&]() {
+    return captured * 2;
+  };
+  lambda();
+}

>From 73d6dbde8a002d018bc4951267a5f6c9fee732fa Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 15:20:50 +0100
Subject: [PATCH 02/38] First round of code review comments

* Address CI failures seen initial PR
* Move all CHECK-MESSAGES to line below findings
* Correct documentation format issues in rst files
* Add TODO for false positive in LIT
* Remove unnecessary headers, use more llvm idiom
  to iterate SmallVector
* Annotate code with TODOs from code review (helps me)
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   |  9 ++---
 .../clang-tidy/misc/ScopeReductionCheck.h     |  1 -
 .../checks/misc/scope-reduction.rst           |  3 +-
 .../checkers/misc/scope-reduction.cpp         | 35 +++++++++++++------
 4 files changed, 31 insertions(+), 17 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 51fb0598fa765..5034a426c43e3 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -39,14 +39,13 @@
 // declaration while still encompassing all it's uses.
 
 #include "ScopeReductionCheck.h"
-#include "../utils/ASTUtils.h"
-#include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::misc {
 
+// TODO: Try using utils::decl_ref_expr::allDeclRefExprs here.
 static void
 collectVariableUses(const Stmt *S, const VarDecl *Var,
                     llvm::SmallVector<const DeclRefExpr *, 8> &Uses) {
@@ -63,6 +62,8 @@ collectVariableUses(const Stmt *S, const VarDecl *Var,
 }
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
+  // TODO: Try adding unless(hasParent(declStmt(hasParent(forStmt( to matcher
+  //       to simplify check code.
   Finder->addMatcher(varDecl(hasLocalStorage()).bind("var"), this);
 }
 
@@ -153,12 +154,12 @@ void ScopeReductionCheck::check(
     InnermostScope = UseScopeChains[0][0];
 
     // For each subsequent use, find common ancestor scope
-    for (size_t i = 1; i < UseScopeChains.size(); ++i) {
+    for (const auto &ScopeChain : llvm::drop_begin(UseScopeChains)) {
       const CompoundStmt *CommonScope = nullptr;
 
       // Find first scope that appears in both chains (common ancestor)
       for (const auto *Scope1 : UseScopeChains[0]) {
-        for (const auto *Scope2 : UseScopeChains[i]) {
+        for (const auto *Scope2 : ScopeChain) {
           if (Scope1 == Scope2) {
             CommonScope = Scope1;
             break;
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index 690f58feb93f7..ef3c898f9c95f 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -11,7 +11,6 @@
 
 #include "../../clang-tidy/utils/DeclRefExprUtils.h"
 #include "../ClangTidyCheck.h"
-#include "clang/AST/ASTContext.h"
 
 namespace clang::tidy::misc {
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index 11a85f406f5bc..5c8090fe7c274 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -1,7 +1,7 @@
 .. title:: clang-tidy - misc-scope-reduction
 
 misc-scope-reduction
-===========================
+====================
 
 Detects local variables in functions whose scopes can be minimized. This check
 covers guidelines described by SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and MISRA
@@ -45,6 +45,7 @@ Examples:
 
 References
 ----------
+
 This check corresponds to the CERT C Coding Standard rules
 `DCL19-C. Minimize the scope of variables and functions
 <https://wiki.sei.cmu.edu/confluence/spaces/c/pages/87152335/DCL19-C.+Minimize+the+scope+of+variables+and+functions>`_.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index ba9e0e9d3c466..fcc6ea2613495 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -2,7 +2,8 @@
 
 // Test case 1: Variable can be moved to smaller scope (if-block)
 void test_if_scope() {
-  int x = 42; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'x' can be declared in a smaller scope
+  int x = 42;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
   if (true) {
     int y = x + 1;
   }
@@ -19,7 +20,8 @@ int test_multiple_scopes(int v) {
 
 // Test case 3: Variable can be moved to nested if-block
 void test_nested_if() {
-  int a = 5; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'a' can be declared in a smaller scope
+  int a = 5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'a' can be declared in a smaller scope
   if (true) {
     if (true) {
       int b = a * 2;
@@ -34,8 +36,11 @@ void test_same_scope() {
 }
 
 // Test case 5: Variable can be moved to while loop body
+// TODO: This is a false positive. Correcting this will require
+//       loop semantic comprehension and var lifetime analysis.
 void test_while_loop() {
-  int counter = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'counter' can be declared in a smaller scope
+  int counter = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'counter' can be declared in a smaller scope
   while (true) {
     counter++;
     if (counter > 10) break;
@@ -54,7 +59,8 @@ void test_if_branches(bool condition) {
 
 // Test case 7: Variable can be moved to for-loop body
 void test_for_loop_body() {
-  int temp = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'temp' can be declared in a smaller scope
+  int temp = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
   for (int i = 0; i < 10; i++) {
     temp = i * i;
   }
@@ -62,7 +68,8 @@ void test_for_loop_body() {
 
 // Test case 8: Variable used in for-loop expressions - should NOT warn (current limitation)
 void test_for_loop_expressions() {
-  int i; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'i' can be declared in for-loop initialization
+  int i;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in for-loop initialization
   for (i = 0; i < 5; i++) {
     // loop body
   }
@@ -70,7 +77,8 @@ void test_for_loop_expressions() {
 
 // Test case 9: Variable can be moved to switch case
 void test_switch_case(int value) {
-  int result = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'result' can be declared in a smaller scope
+  int result = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
   switch (value) {
     case 1:
       result = 10;
@@ -82,7 +90,8 @@ void test_switch_case(int value) {
 
 // Test case 10: Variable used across multiple switch cases - should NOT warn
 void test_switch_multiple_cases(int value) {
-  int accumulator = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'accumulator' can be declared in a smaller scope
+  int accumulator = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'accumulator' can be declared in a smaller scope
   switch (value) {
     case 1:
       accumulator += 10;
@@ -95,7 +104,8 @@ void test_switch_multiple_cases(int value) {
 
 // Test case 11: Variable with complex initialization can be moved
 void test_complex_init() {
-  int complex = (5 + 3) * 2; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'complex' can be declared in a smaller scope
+  int complex = (5 + 3) * 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'complex' can be declared in a smaller scope
   if (true) {
     int doubled = complex * 2;
   }
@@ -103,7 +113,8 @@ void test_complex_init() {
 
 // Test case 12: Multiple variables, some can be moved, some cannot
 int test_mixed_variables(bool flag) {
-  int movable = 10;   // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'movable' can be declared in a smaller scope
+  int movable = 10;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'movable' can be declared in a smaller scope
   int unmovable = 20; // Should NOT warn - used across scopes
 
   if (flag) {
@@ -116,7 +127,8 @@ int test_mixed_variables(bool flag) {
 
 // Test case 13: Variable in try-catch block
 void test_try_catch() {
-  int error_code = 0; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'error_code' can be declared in a smaller scope
+  int error_code = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'error_code' can be declared in a smaller scope
   try {
     error_code = 404;
   } catch (...) {
@@ -136,7 +148,8 @@ void test_try_catch_shared() {
 
 // Test case 15: Deeply nested scopes
 void test_deep_nesting() {
-  int deep = 1; // CHECK-MESSAGES: :[[@LINE]]:7: warning: variable 'deep' can be declared in a smaller scope
+  int deep = 1;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'deep' can be declared in a smaller scope
   if (true) {
     if (true) {
       if (true) {

>From fb2a5ae9f774fa0750c863aab2a6847cec037e72 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 16:04:07 +0100
Subject: [PATCH 03/38] Update to correct CI issues missed in last update

* Remove include of DeclRefExprUtils.h
* Correct spelling in header guard
---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index ef3c898f9c95f..15fb82cc02fb5 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -7,9 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDUCTIONCHECK_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDCUTIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDUCTIONCHECK_H
 
-#include "../../clang-tidy/utils/DeclRefExprUtils.h"
 #include "../ClangTidyCheck.h"
 
 namespace clang::tidy::misc {

>From eacd7141afab4e95c0d81c0127981f0c89b0599c Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 16:12:04 +0100
Subject: [PATCH 04/38] Update missing parts

* I guess I keep missing small things :/
* Addressed next header guard spelling. Maybe CI will pass now.
---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index 15fb82cc02fb5..ee5a94daaa855 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -23,4 +23,4 @@ class ScopeReductionCheck : public ClangTidyCheck {
 
 } // namespace clang::tidy::misc
 
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDCUTIONCHECK_H
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDUCTIONCHECK_H

>From 6146260773533bb22944b6c082dd71f10c8a4bc1 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 19:17:27 +0100
Subject: [PATCH 05/38] Update review from comments

* Use clang::tidy::utils::decl_ref_expr::allDeclRefExprs
* Remove some useless comments
* Narrow filter, find vars that have function body
* Assert on var from matcher in check instead of
  checking for NULL
* Add some TODOs
* Update rst hopefully one last time for CI to pass
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 37 +++++++++----------
 .../checks/misc/scope-reduction.rst           |  4 +-
 2 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 5034a426c43e3..a959b8367abac 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -40,38 +40,38 @@
 
 #include "ScopeReductionCheck.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "../utils/DeclRefExprUtils.h"
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::misc {
 
-// TODO: Try using utils::decl_ref_expr::allDeclRefExprs here.
-static void
-collectVariableUses(const Stmt *S, const VarDecl *Var,
-                    llvm::SmallVector<const DeclRefExpr *, 8> &Uses) {
-  if (!S)
+static void collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
+                                llvm::SmallVector<const clang::DeclRefExpr *, 8> &Uses) {
+  if (!S || !Var)
     return;
 
-  if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) {
-    if (DRE->getDecl() == Var)
-      Uses.push_back(DRE);
-  }
+  llvm::SmallPtrSet<const clang::DeclRefExpr *, 16> DREs =
+      clang::tidy::utils::decl_ref_expr::allDeclRefExprs(*Var, *S, Var->getASTContext());
 
-  for (const Stmt *Child : S->children())
-    collectVariableUses(Child, Var, Uses);
+  // Copy the results into the provided SmallVector
+  Uses.clear();
+  Uses.append(DREs.begin(), DREs.end());
 }
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
   // TODO: Try adding unless(hasParent(declStmt(hasParent(forStmt( to matcher
   //       to simplify check code.
-  Finder->addMatcher(varDecl(hasLocalStorage()).bind("var"), this);
+
+  // Match on varDecls that are part of a function
+  Finder->addMatcher(varDecl(hasLocalStorage(), hasAncestor(functionDecl())).bind("var"), this);
 }
 
 void ScopeReductionCheck::check(
     const ast_matchers::MatchFinder::MatchResult &Result) {
   const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var");
-  if (!Var)
-    return;
+
+  assert(Var);
 
   // Step 1: Filter out variables declared in for-loop initializations
   // These variables are already in their optimal scope and shouldn't be
@@ -94,10 +94,8 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // auto *Context = Result.Context;
-  auto *Function = dyn_cast<FunctionDecl>(Var->getDeclContext());
-  if (!Function || !Function->hasBody())
-    return;
+  const auto *Function = dyn_cast<FunctionDecl>(Var->getDeclContext());
+  assert(Function);
 
   // Step 2: Collect all uses of this variable within the function
   llvm::SmallVector<const DeclRefExpr *, 8> Uses;
@@ -217,7 +215,7 @@ void ScopeReductionCheck::check(
         diag(Var->getLocation(),
              "variable '%0' can be declared in a smaller scope")
             << Var->getName();
-        return; // early exit
+        return;
       }
     }
   }
@@ -225,6 +223,7 @@ void ScopeReductionCheck::check(
   // Step 7: Alternative analysis - check for for-loop initialization
   // opportunity This only runs if the compound statement analysis didn't find a
   // smaller scope Only check local variables, not parameters
+  // TODO: ParmVarDecls maybe excluded for all analysis.
   if (!isa<ParmVarDecl>(Var)) {
     const ForStmt *CommonForLoop = nullptr;
     bool AllUsesInSameForLoop = true;
diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index 5c8090fe7c274..46b3e163f83d8 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -4,8 +4,8 @@ misc-scope-reduction
 ====================
 
 Detects local variables in functions whose scopes can be minimized. This check
-covers guidelines described by SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and MISRA
-C:2012 Rule 8-9.
+covers guidelines described by SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and
+MISRA C:2012 Rule 8-9.
 
 Examples:
 

>From 45e474df4ae2b8fdaaee92b98f09609f5f4cc85f Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 19:46:09 +0100
Subject: [PATCH 06/38] More updates per review

* Remove test case numbers
* Pretty format last set of changes (maybe CI will pass!)
* Sync Release notes and documentation per review comment
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 14 ++++---
 clang-tools-extra/docs/ReleaseNotes.rst       |  2 +-
 .../checkers/misc/scope-reduction.cpp         | 40 +++++++++----------
 3 files changed, 30 insertions(+), 26 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index a959b8367abac..b1a462776fca0 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -39,20 +39,22 @@
 // declaration while still encompassing all it's uses.
 
 #include "ScopeReductionCheck.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "../utils/DeclRefExprUtils.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::misc {
 
-static void collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
-                                llvm::SmallVector<const clang::DeclRefExpr *, 8> &Uses) {
+static void
+collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
+                    llvm::SmallVector<const clang::DeclRefExpr *, 8> &Uses) {
   if (!S || !Var)
     return;
 
   llvm::SmallPtrSet<const clang::DeclRefExpr *, 16> DREs =
-      clang::tidy::utils::decl_ref_expr::allDeclRefExprs(*Var, *S, Var->getASTContext());
+      clang::tidy::utils::decl_ref_expr::allDeclRefExprs(*Var, *S,
+                                                         Var->getASTContext());
 
   // Copy the results into the provided SmallVector
   Uses.clear();
@@ -64,7 +66,9 @@ void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
   //       to simplify check code.
 
   // Match on varDecls that are part of a function
-  Finder->addMatcher(varDecl(hasLocalStorage(), hasAncestor(functionDecl())).bind("var"), this);
+  Finder->addMatcher(
+      varDecl(hasLocalStorage(), hasAncestor(functionDecl())).bind("var"),
+      this);
 }
 
 void ScopeReductionCheck::check(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b8c8279d3c907..6e83f03d4c709 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -100,7 +100,7 @@ New checks
 - New :doc:`misc-scope-reduction
   <clang-tidy/checks/misc/scope-reduction>` check.
 
-  Checks for opportunities to minimize scope of local variables.
+  Detects local variables in function whose scopes can be minimized.
 
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index fcc6ea2613495..dc5112e3dccb5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -1,6 +1,6 @@
 // RUN: %check_clang_tidy %s misc-scope-reduction %t -- --
 
-// Test case 1: Variable can be moved to smaller scope (if-block)
+// Variable can be moved to smaller scope (if-block)
 void test_if_scope() {
   int x = 42;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
@@ -9,7 +9,7 @@ void test_if_scope() {
   }
 }
 
-// Test case 2: Variable used across multiple scopes - should NOT warn
+// Variable used across multiple scopes - should NOT warn
 int test_multiple_scopes(int v) {
   int y = 0; // Should NOT warn - used in if-block and return
   if (v) {
@@ -18,7 +18,7 @@ int test_multiple_scopes(int v) {
   return y;
 }
 
-// Test case 3: Variable can be moved to nested if-block
+// Variable can be moved to nested if-block
 void test_nested_if() {
   int a = 5;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'a' can be declared in a smaller scope
@@ -29,13 +29,13 @@ void test_nested_if() {
   }
 }
 
-// Test case 4: Variable used in same scope - should NOT warn
+// Variable used in same scope - should NOT warn
 void test_same_scope() {
   int x = 10; // Should NOT warn - used in same scope
   int y = x + 5;
 }
 
-// Test case 5: Variable can be moved to while loop body
+// Variable can be moved to while loop body
 // TODO: This is a false positive. Correcting this will require
 //       loop semantic comprehension and var lifetime analysis.
 void test_while_loop() {
@@ -47,7 +47,7 @@ void test_while_loop() {
   }
 }
 
-// Test case 6: Variable used in multiple branches of same if-statement
+// Variable used in multiple branches of same if-statement
 void test_if_branches(bool condition) {
   int value = 100; // Should NOT warn - used in both branches
   if (condition) {
@@ -57,7 +57,7 @@ void test_if_branches(bool condition) {
   }
 }
 
-// Test case 7: Variable can be moved to for-loop body
+// Variable can be moved to for-loop body
 void test_for_loop_body() {
   int temp = 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
@@ -66,7 +66,7 @@ void test_for_loop_body() {
   }
 }
 
-// Test case 8: Variable used in for-loop expressions - should NOT warn (current limitation)
+// Variable used in for-loop expressions - should NOT warn (current limitation)
 void test_for_loop_expressions() {
   int i;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in for-loop initialization
@@ -75,7 +75,7 @@ void test_for_loop_expressions() {
   }
 }
 
-// Test case 9: Variable can be moved to switch case
+// Variable can be moved to switch case
 void test_switch_case(int value) {
   int result = 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
@@ -88,7 +88,7 @@ void test_switch_case(int value) {
   }
 }
 
-// Test case 10: Variable used across multiple switch cases - should NOT warn
+// Variable used across multiple switch cases - should NOT warn
 void test_switch_multiple_cases(int value) {
   int accumulator = 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'accumulator' can be declared in a smaller scope
@@ -102,7 +102,7 @@ void test_switch_multiple_cases(int value) {
   }
 }
 
-// Test case 11: Variable with complex initialization can be moved
+// Variable with complex initialization can be moved
 void test_complex_init() {
   int complex = (5 + 3) * 2;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'complex' can be declared in a smaller scope
@@ -111,7 +111,7 @@ void test_complex_init() {
   }
 }
 
-// Test case 12: Multiple variables, some can be moved, some cannot
+// Multiple variables, some can be moved, some cannot
 int test_mixed_variables(bool flag) {
   int movable = 10;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'movable' can be declared in a smaller scope
@@ -125,7 +125,7 @@ int test_mixed_variables(bool flag) {
   return unmovable;
 }
 
-// Test case 13: Variable in try-catch block
+// Variable in try-catch block
 void test_try_catch() {
   int error_code = 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'error_code' can be declared in a smaller scope
@@ -136,7 +136,7 @@ void test_try_catch() {
   }
 }
 
-// Test case 14: Variable used in catch block and try block - should NOT warn
+// Variable used in catch block and try block - should NOT warn
 void test_try_catch_shared() {
   int shared = 0; // Should NOT warn - used in both try and catch
   try {
@@ -146,7 +146,7 @@ void test_try_catch_shared() {
   }
 }
 
-// Test case 15: Deeply nested scopes
+// Deeply nested scopes
 void test_deep_nesting() {
   int deep = 1;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'deep' can be declared in a smaller scope
@@ -161,15 +161,15 @@ void test_deep_nesting() {
   }
 }
 
-// Test case 16: Variable declared but never used - should NOT warn (different checker's job)
+// Variable declared but never used - should NOT warn (different checker's job)
 void test_unused_variable() {
   int unused = 42; // Should NOT warn - this checker only handles scope reduction
 }
 
-// Test case 17: Global variable - should NOT be processed
+// Global variable - should NOT be processed
 int global_var = 100;
 
-// Test case 18: Static local variable - should NOT warn
+// Static local variable - should NOT warn
 void test_static_variable() {
   static int static_var = 0; // Should NOT warn - static variables have different semantics
   if (true) {
@@ -177,14 +177,14 @@ void test_static_variable() {
   }
 }
 
-// Test case 19: Function parameter - should NOT be processed
+// Function parameter - should NOT be processed
 void test_parameter(int param) {
   if (true) {
     int local = param + 1;
   }
 }
 
-// Test case 20: Variable used in lambda - should NOT warn (complex case)
+// Variable used in lambda - should NOT warn (complex case)
 void test_lambda() {
   int captured = 10; // Should NOT warn - used in lambda
   auto lambda = [&]() {

>From 0f1209ef9983bf7fae11df41c77460035a93b76a Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sun, 11 Jan 2026 22:39:19 +0100
Subject: [PATCH 07/38] Update - address more comments

* Add const qualifier to address tidy warning
* Add, update test cases per comments
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   |  2 +-
 .../checkers/misc/scope-reduction.cpp         | 22 ++++++++++++++-----
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index b1a462776fca0..f2eadc1cc3da8 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -52,7 +52,7 @@ collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
   if (!S || !Var)
     return;
 
-  llvm::SmallPtrSet<const clang::DeclRefExpr *, 16> DREs =
+  const llvm::SmallPtrSet<const clang::DeclRefExpr *, 16> DREs =
       clang::tidy::utils::decl_ref_expr::allDeclRefExprs(*Var, *S,
                                                          Var->getASTContext());
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index dc5112e3dccb5..195d1f8f70887 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -36,8 +36,8 @@ void test_same_scope() {
 }
 
 // Variable can be moved to while loop body
-// TODO: This is a false positive. Correcting this will require
-//       loop semantic comprehension and var lifetime analysis.
+// FIXME: This is a false positive. Correcting this will require
+//        loop semantic comprehension and var lifetime analysis.
 void test_while_loop() {
   int counter = 0;
   // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'counter' can be declared in a smaller scope
@@ -104,10 +104,10 @@ void test_switch_multiple_cases(int value) {
 
 // Variable with complex initialization can be moved
 void test_complex_init() {
-  int complex = (5 + 3) * 2;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'complex' can be declared in a smaller scope
+  int cmplx_expr = (5 + 3) * 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'cmplx_expr' can be declared in a smaller scope
   if (true) {
-    int doubled = complex * 2;
+    int doubled = cmplx_expr * 2;
   }
 }
 
@@ -192,3 +192,15 @@ void test_lambda() {
   };
   lambda();
 }
+
+// Variable set from function call, used in if clause
+// FIXME: This is a false positive because we cannot know if
+//        func() has side effects or not (since not visible).
+int func();
+void test_function_call() {
+  int i = func();
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in a smaller scope
+  if (true) {
+    i = 0;
+  }
+}

>From ca9a2d39cf3dc8196ec02cda7aa0b211b04b490d Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 12 Jan 2026 02:39:25 +0100
Subject: [PATCH 08/38] Update per review comments

* Narrow filter, remove code replaced by new filter
* Add test case for initializer from function
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 26 ++++---------------
 .../checkers/misc/scope-reduction.cpp         |  4 +--
 2 files changed, 6 insertions(+), 24 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index f2eadc1cc3da8..33800bb32b5b5 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -62,12 +62,12 @@ collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
 }
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
-  // TODO: Try adding unless(hasParent(declStmt(hasParent(forStmt( to matcher
-  //       to simplify check code.
-
-  // Match on varDecls that are part of a function
   Finder->addMatcher(
-      varDecl(hasLocalStorage(), hasAncestor(functionDecl())).bind("var"),
+      varDecl(hasLocalStorage(), hasAncestor(functionDecl()),
+              unless(hasParent(declStmt(hasParent(forStmt())))),
+              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
+                                          cxxOperatorCallExpr()))))
+          .bind("var"),
       this);
 }
 
@@ -81,22 +81,6 @@ void ScopeReductionCheck::check(
   // These variables are already in their optimal scope and shouldn't be
   // analyzed
   auto &Parents = Result.Context->getParentMapContext();
-  auto ParentNodes = Parents.getParents(DynTypedNode::create(*Var));
-
-  if (!ParentNodes.empty()) {
-    if (const auto *Parent = ParentNodes[0].get<Stmt>()) {
-      if (isa<DeclStmt>(Parent)) {
-        // Check if DeclStmt's parent is ForStmt
-        auto GrandParentNodes = Parents.getParents(*Parent);
-        if (!GrandParentNodes.empty()) {
-          if (const auto *GrandParent = GrandParentNodes[0].get<Stmt>()) {
-            if (isa<ForStmt>(GrandParent))
-              return; // Skip for-loop declared variables
-          }
-        }
-      }
-    }
-  }
 
   const auto *Function = dyn_cast<FunctionDecl>(Var->getDeclContext());
   assert(Function);
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 195d1f8f70887..f49094150eebd 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -194,12 +194,10 @@ void test_lambda() {
 }
 
 // Variable set from function call, used in if clause
-// FIXME: This is a false positive because we cannot know if
-//        func() has side effects or not (since not visible).
+// Should NOT warn. Don't know if func() has side effects
 int func();
 void test_function_call() {
   int i = func();
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in a smaller scope
   if (true) {
     i = 0;
   }

>From cee3061c116831cccb03b0dc3c91c85de2841785 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 12 Jan 2026 11:09:59 +0100
Subject: [PATCH 09/38] Update per review comments

* Narrow filter by adding parmVarDecl, simplifying check code
* Update code comments to match
* Add global variable use cases
* Narrow filter to exclude global variables in namespaces
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 88 ++++++++++---------
 .../checkers/misc/scope-reduction.cpp         | 54 ++++++++++++
 2 files changed, 100 insertions(+), 42 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 33800bb32b5b5..41610c315737a 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -16,8 +16,14 @@
 // be simpler.
 //
 // The 7-step algorithm used by this checker for scope reduction analysis is:
-// 1) Filter out variables declared in for-loop initializations
-//    - Those variables are already in optimal scope, and can be skipped
+// 1) AST Matcher Filtering
+//    - Only match variables within functions (hasAncestor(functionDecl())
+//    - Exclude for-loop declared variables
+//       (unless(hasParent(declStmt(hasParent(forStmt))))))
+//    - Exclude variables with function call initializors
+//       (unless(hasInitializer(...)))
+//    - Exclude parameters from analysis
+//       (unless(parmVarDecl())
 // 2) Collect variable uses
 //    - find all DeclRefExpr nodes that reference the variable
 // 3) Build scope chains
@@ -63,7 +69,8 @@ collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
-      varDecl(hasLocalStorage(), hasAncestor(functionDecl()),
+      varDecl(hasLocalStorage(), unless(hasGlobalStorage()),
+              hasAncestor(functionDecl()), unless(parmVarDecl()),
               unless(hasParent(declStmt(hasParent(forStmt())))),
               unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
                                           cxxOperatorCallExpr()))))
@@ -211,60 +218,57 @@ void ScopeReductionCheck::check(
   // Step 7: Alternative analysis - check for for-loop initialization
   // opportunity This only runs if the compound statement analysis didn't find a
   // smaller scope Only check local variables, not parameters
-  // TODO: ParmVarDecls maybe excluded for all analysis.
-  if (!isa<ParmVarDecl>(Var)) {
-    const ForStmt *CommonForLoop = nullptr;
-    bool AllUsesInSameForLoop = true;
-
-    for (const auto *Use : Uses) {
-      const ForStmt *ContainingForLoop = nullptr;
-      const Stmt *Current = Use;
-
-      // Walk up the AST to find a containing ForStmt
-      while (Current) {
-        auto ParentNodes = Parents.getParents(*Current);
-        if (ParentNodes.empty())
-          break;
+  const ForStmt *CommonForLoop = nullptr;
+  bool AllUsesInSameForLoop = true;
 
-        if (const auto *FS = ParentNodes[0].get<ForStmt>()) {
-          ContainingForLoop = FS;
-          break;
-        }
+  for (const auto *Use : Uses) {
+    const ForStmt *ContainingForLoop = nullptr;
+    const Stmt *Current = Use;
 
-        const Stmt *Parent = ParentNodes[0].get<Stmt>();
-        if (!Parent) {
-          // Handle Decl parents like we do in the existing logic
-          if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
-            auto DeclParentNodes = Parents.getParents(*DeclParent);
-            if (!DeclParentNodes.empty())
-              Parent = DeclParentNodes[0].get<Stmt>();
-          }
-          if (!Parent)
-            break;
-        }
-        Current = Parent;
-      }
+    // Walk up the AST to find a containing ForStmt
+    while (Current) {
+      auto ParentNodes = Parents.getParents(*Current);
+      if (ParentNodes.empty())
+        break;
 
-      if (!ContainingForLoop) {
-        AllUsesInSameForLoop = false;
+      if (const auto *FS = ParentNodes[0].get<ForStmt>()) {
+        ContainingForLoop = FS;
         break;
       }
 
-      if (!CommonForLoop) {
-        CommonForLoop = ContainingForLoop;
-      } else if (CommonForLoop != ContainingForLoop) {
-        AllUsesInSameForLoop = false;
-        break;
+      const Stmt *Parent = ParentNodes[0].get<Stmt>();
+      if (!Parent) {
+        // Handle Decl parents like we do in the existing logic
+        if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
+          auto DeclParentNodes = Parents.getParents(*DeclParent);
+          if (!DeclParentNodes.empty())
+            Parent = DeclParentNodes[0].get<Stmt>();
+        }
+        if (!Parent)
+          break;
       }
+      Current = Parent;
+    }
+
+    if (!ContainingForLoop) {
+      AllUsesInSameForLoop = false;
+      break;
     }
 
+    if (!CommonForLoop) {
+      CommonForLoop = ContainingForLoop;
+    } else if (CommonForLoop != ContainingForLoop) {
+      AllUsesInSameForLoop = false;
+      break;
+    }
+  }
+
     if (AllUsesInSameForLoop && CommonForLoop) {
       diag(Var->getLocation(),
            "variable '%0' can be declared in for-loop initialization")
           << Var->getName();
       return;
     }
-  }
 }
 
 } // namespace clang::tidy::misc
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index f49094150eebd..41b7b65358188 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -169,6 +169,28 @@ void test_unused_variable() {
 // Global variable - should NOT be processed
 int global_var = 100;
 
+namespace GlobalTestNamespace {
+  int namespaced_global = 200;
+
+  // Function using global variables - should NOT warn
+  void test_global_usage() {
+    int local = global_var + namespaced_global;
+    // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: variable 'local' can be declared in a smaller scope
+    if (true) {
+      local *= 2;
+    }
+  }
+
+  // Global vars used in smaller scopes. Should NOT be detected.
+  void test_globals_not_detected() {
+    if (true) {
+      global_var = 300;
+      namespaced_global = 400;
+      int result = global_var + namespaced_global;
+    }
+  }
+}
+
 // Static local variable - should NOT warn
 void test_static_variable() {
   static int static_var = 0; // Should NOT warn - static variables have different semantics
@@ -202,3 +224,35 @@ void test_function_call() {
     i = 0;
   }
 }
+
+// Variable used inside a loop.
+// Should NOT warn.
+// FIXME: temp needs to persist across loop iterations, therefore cannot move
+//        Requires more sophisticated analysis.
+void test_for_loop_reuse() {
+  int temp = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  for (int i = 0; i<10; i++) {
+    temp += i;
+  }
+}
+
+// Variable can be moved closer to lambda usage
+void test_lambda_movable() {
+  int local = 5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'local' can be declared in a smaller scope
+
+  if (true) {
+    auto lambda = [local]() {
+      return local *3;
+    };
+  }
+}
+
+// Variable declared but never used with empty scope after
+void test_unused_empty_scope() {
+  int unused = 42; // Should NOT warn - this checker only handles scope reduction
+  if (true) {
+    // empty scope, variable not used here
+  }
+}

>From 6fc91ce9055f53c09d2cd18495df3f32512ee87c Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 12 Jan 2026 12:27:13 +0100
Subject: [PATCH 10/38] update

* Manually format code that clang-format missed :/
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp          | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 41610c315737a..08f6a844d3be8 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -263,12 +263,12 @@ void ScopeReductionCheck::check(
     }
   }
 
-    if (AllUsesInSameForLoop && CommonForLoop) {
-      diag(Var->getLocation(),
-           "variable '%0' can be declared in for-loop initialization")
-          << Var->getName();
-      return;
-    }
+  if (AllUsesInSameForLoop && CommonForLoop) {
+    diag(Var->getLocation(),
+         "variable '%0' can be declared in for-loop initialization")
+        << Var->getName();
+    return;
+  }
 }
 
 } // namespace clang::tidy::misc

>From f387b0f19433c435c17b93a35346a3e7bd146eb7 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Tue, 13 Jan 2026 02:39:15 +0100
Subject: [PATCH 11/38] Update

* Debug, address problems diagnosing switch statements.
* I used Claude Sonnet 4.5 as a co-pilot to develop this checker
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 38 ++++++++++++
 .../checkers/misc/scope-reduction.cpp         | 58 ++++++++++++++++++-
 2 files changed, 95 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 08f6a844d3be8..875b9dda0392f 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -169,6 +169,44 @@ void ScopeReductionCheck::check(
 
   // Step 5: Check if current var declaration is broader than necessary
   if (InnermostScope) {
+    // Check if variable uses span multiple case labels in the same switch
+    // If so, the only common scope would be the switch body, which is invalid
+    // for declarations
+    std::set<const SwitchCase *> CaseLabels;
+    bool UsesInSwitch = false;
+
+    for (const auto *Use : Uses) {
+      const Stmt *Current = Use;
+      const SwitchCase *ContainingCase = nullptr;
+
+      // Walk up to find containing case label
+      while (Current) {
+        auto ParentNodes = Parents.getParents(*Current);
+        if (ParentNodes.empty())
+          break;
+
+        const Stmt *Parent = ParentNodes[0].get<Stmt>();
+        if (!Parent)
+          break;
+
+        if (const auto *CaseStmt = dyn_cast<SwitchCase>(Parent)) {
+          ContainingCase = CaseStmt;
+          UsesInSwitch = true;
+          break;
+        }
+        Current = Parent;
+      }
+
+      if (ContainingCase)
+        CaseLabels.insert(ContainingCase);
+    }
+
+    // If uses span multiple case labels, skip analysis
+    if (UsesInSwitch && CaseLabels.size() > 1) {
+      return; // Cannot declare variables in switch body when used across
+              // multiple cases
+    }
+
     // Find the compound statement containing the variable declaration
     const DynTypedNode Current = DynTypedNode::create(*Var);
     const CompoundStmt *VarScope = nullptr;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 41b7b65358188..f4233b9dd7387 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -91,7 +91,6 @@ void test_switch_case(int value) {
 // Variable used across multiple switch cases - should NOT warn
 void test_switch_multiple_cases(int value) {
   int accumulator = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'accumulator' can be declared in a smaller scope
   switch (value) {
     case 1:
       accumulator += 10;
@@ -256,3 +255,60 @@ void test_unused_empty_scope() {
     // empty scope, variable not used here
   }
 }
+
+// Variable used in switch and other scope - should warn if common scope allows
+void test_switch_mixed_usage(int value) {
+  int mixed = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'mixed' can be declared in a smaller scope
+  if (true) {
+    switch (value) {
+      case 1:
+        mixed = 10;
+        break;
+    }
+    mixed += 5; // Also used outside switch
+  }
+}
+
+// Variable in nested switch - should warn for single case
+void test_nested_switch(int outer, int inner) {
+  int nested = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'nested' can be declared in a smaller scope
+  switch (outer) {
+    case 1:
+      switch (inner) {
+        case 1:
+          nested = 42;
+          break;
+      }
+      break;
+  }
+}
+
+// Variable used in switch default only - should warn
+void test_switch_default_only(int value) {
+  int def = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'def' can be declared in a smaller scope
+  switch (value) {
+    case 1:
+      break;
+    default:
+      def = 100;
+      break;
+  }
+}
+
+// Variable used in multiple switches - should NOT warn
+void test_multiple_switches(int v1, int v2) {
+  int multi = 0; // Should NOT warn - used across different switches
+  switch (v1) {
+    case 1:
+      multi = 10;
+      break;
+  }
+  switch (v2) {
+    case 1:
+      multi = 20;
+      break;
+  }
+}

>From 189386bc2bf344bc8b2f8bf0ca2db4d39e26a366 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Tue, 13 Jan 2026 02:45:55 +0100
Subject: [PATCH 12/38] Update

* Remove MISRA from documentation.
---
 .../docs/clang-tidy/checks/misc/scope-reduction.rst            | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index 46b3e163f83d8..58fb14a48ff29 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -4,8 +4,7 @@ misc-scope-reduction
 ====================
 
 Detects local variables in functions whose scopes can be minimized. This check
-covers guidelines described by SEI DCL19-C, MISRA C++:2008 Rule 3-4-1, and
-MISRA C:2012 Rule 8-9.
+covers guidelines described by SEI DCL19-C.
 
 Examples:
 

>From bfa9307ca51d3aa38a8c573e40922b128250f16d Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Wed, 14 Jan 2026 19:53:09 +0100
Subject: [PATCH 13/38] Update

* Update references, remove mention of MISRA
* Add limitations
---
 .../checks/misc/scope-reduction.rst           | 41 +++++++++++++++----
 1 file changed, 33 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index 58fb14a48ff29..2cfa820b4a761 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -3,8 +3,7 @@
 misc-scope-reduction
 ====================
 
-Detects local variables in functions whose scopes can be minimized. This check
-covers guidelines described by SEI DCL19-C.
+Detects local variables in functions whose scopes can be minimized.
 
 Examples:
 
@@ -23,14 +22,13 @@ Examples:
       }
     }
 
-    void test_switch_multiple_cases(int value) {
-      int accumulator = 0; // 'accumulator' can be declared in a smaller scope
+    void test_switch_case(int value) {
+      int result = 0; // 'result' can be declared in a smaller scope
       switch (value) {
         case 1:
-          accumulator += 10;
+          result = 10;
           break;
-        case 2:
-          accumulator += 20;
+        default:
           break;
       }
     }
@@ -42,9 +40,36 @@ Examples:
       }
     }
 
+Limitations
+-----------
+
+This checker cannot currently detect when a variable's previous value affects
+subsequent iterations, resulting in false positives in some cases. This can
+be addressed by implementing a pattern matcher that recognizes this
+accumulator pattern across loop iterations or by using clang's builtin
+Lifetime analysis.
+
+.. code-block:: cpp
+
+    void test_while_loop() {
+      // falsely detects 'counter' can be moved to smaller scope
+      int counter = 0;
+      while (true) {
+        counter++;
+        if (counter > 10) break;
+      }
+    }
+
+    void test_for_loop_reuse() {
+      int temp = 0; // falsely detects 'temp' can be moved to smaller scope
+      for (int i = 0; i<10; i++) {
+        temp += i;
+      }
+    }
+
 References
 ----------
 
-This check corresponds to the CERT C Coding Standard rules
+This check corresponds to the CERT C Coding Standard rule.
 `DCL19-C. Minimize the scope of variables and functions
 <https://wiki.sei.cmu.edu/confluence/spaces/c/pages/87152335/DCL19-C.+Minimize+the+scope+of+variables+and+functions>`_.

>From 96ad7ecbea7226b6c06a531c32766799233a2892 Mon Sep 17 00:00:00 2001
From: vabridgers <58314289+vabridgers at users.noreply.github.com>
Date: Wed, 14 Jan 2026 15:02:55 -0600
Subject: [PATCH 14/38] Update
 clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst

Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
 .../docs/clang-tidy/checks/misc/scope-reduction.rst             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index 2cfa820b4a761..c43d63a157e98 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -43,7 +43,7 @@ Examples:
 Limitations
 -----------
 
-This checker cannot currently detect when a variable's previous value affects
+This check cannot currently detect when a variable's previous value affects
 subsequent iterations, resulting in false positives in some cases. This can
 be addressed by implementing a pattern matcher that recognizes this
 accumulator pattern across loop iterations or by using clang's builtin

>From 198891e32360364c19a25046dddfbce1fd982e42 Mon Sep 17 00:00:00 2001
From: vabridgers <58314289+vabridgers at users.noreply.github.com>
Date: Wed, 14 Jan 2026 15:03:09 -0600
Subject: [PATCH 15/38] Update
 clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst

Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
 .../docs/clang-tidy/checks/misc/scope-reduction.rst             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index c43d63a157e98..bf920bf26f820 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -46,7 +46,7 @@ Limitations
 This check cannot currently detect when a variable's previous value affects
 subsequent iterations, resulting in false positives in some cases. This can
 be addressed by implementing a pattern matcher that recognizes this
-accumulator pattern across loop iterations or by using clang's builtin
+accumulator pattern across loop iterations or by using Clang's built-in
 Lifetime analysis.
 
 .. code-block:: cpp

>From 6b87e9480e2b3523bafe436cc0052df5603c9839 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Fri, 16 Jan 2026 19:18:05 +0100
Subject: [PATCH 16/38] Update checker

* Add for-range loop detection, supporting test cases
* Correct RST documentation typos

Tested checker against full llvm/clang build. Checker initially
detected 50,750 misc-scope-reduction issues, many of which were
deemed to be false positives for for-range patterns. Implementing
detection reduced findings to 31,209.

Continuing to improve
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 10 ++++++----
 .../checkers/misc/scope-reduction.cpp         | 20 +++++++++++++++++++
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 875b9dda0392f..1abb4967c5ca6 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -69,11 +69,13 @@ collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
-      varDecl(hasLocalStorage(), unless(hasGlobalStorage()),
-              hasAncestor(functionDecl()), unless(parmVarDecl()),
+      varDecl(hasLocalStorage(),
+              unless(hasGlobalStorage()),
+              hasAncestor(functionDecl()),
+              unless(parmVarDecl()),
               unless(hasParent(declStmt(hasParent(forStmt())))),
-              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
-                                          cxxOperatorCallExpr()))))
+              unless(hasParent(declStmt(hasParent(cxxForRangeStmt())))),
+              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(), cxxOperatorCallExpr()))))
           .bind("var"),
       this);
 }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index f4233b9dd7387..2d414b1e68800 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -312,3 +312,23 @@ void test_multiple_switches(int v1, int v2) {
       break;
   }
 }
+
+// Range-based for loop declared variable - should NOT warn
+void test_range_for_declared() {
+  int vec[] = {1, 2, 3};
+  for (auto item : vec) {
+    // use item
+  }
+}
+
+// Variable used in range-based for loop - should warn
+void test_range_for_usage() {
+  int sum = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'sum' can be declared in a smaller scope
+  if (true) {
+    int vec[] = {1, 2, 3};
+    for (auto item : vec) {
+      sum += item;
+    }
+  }
+}

>From bc6a9116cb669b97c2f457a7e3e5cd5ce0b05efa Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 17 Jan 2026 14:08:33 +0100
Subject: [PATCH 17/38] Update diagnostics to include usage notes

* Update diagnostics to include usage notes
* Improve tests
* Diagnostic notes are limited to avoid overwhelming the user
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   |  28 +++-
 .../clang-tidy/misc/ScopeReductionCheck.h     |   3 +
 .../checkers/misc/scope-reduction.cpp         | 127 +++++++++++++++---
 3 files changed, 139 insertions(+), 19 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 1abb4967c5ca6..458d6268c518d 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -250,6 +250,11 @@ void ScopeReductionCheck::check(
         diag(Var->getLocation(),
              "variable '%0' can be declared in a smaller scope")
             << Var->getName();
+
+        emitUsageNotes(Uses);
+
+        diag(InnermostScope->getBeginLoc(), "can be declared in this scope",
+             DiagnosticIDs::Note);
         return;
       }
     }
@@ -307,7 +312,28 @@ void ScopeReductionCheck::check(
     diag(Var->getLocation(),
          "variable '%0' can be declared in for-loop initialization")
         << Var->getName();
-    return;
+
+    // Skip "used here" notes for for-loops, they're too noisy
+    //
+    diag(CommonForLoop->getBeginLoc(), "can be declared in this for-loop",
+         DiagnosticIDs::Note);
+  }
+}
+
+void ScopeReductionCheck::emitUsageNotes(
+    const llvm::SmallVector<const DeclRefExpr *, 8> &Uses) {
+  const size_t MaxUsageNotes = 3;
+  size_t NotesShown = 0;
+  for (const auto *Use : Uses) {
+    if (NotesShown >= MaxUsageNotes)
+      break;
+    diag(Use->getLocation(), "used here", DiagnosticIDs::Note);
+    NotesShown++;
+  }
+  if (Uses.size() > MaxUsageNotes) {
+    diag(Uses[MaxUsageNotes]->getLocation(), "and %0 more uses...",
+         DiagnosticIDs::Note)
+        << (Uses.size() - MaxUsageNotes);
   }
 }
 
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index ee5a94daaa855..c06acaa253129 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -19,6 +19,9 @@ class ScopeReductionCheck : public ClangTidyCheck {
       : ClangTidyCheck(Name, Context) {}
   void registerMatchers(ast_matchers::MatchFinder *Finder) override;
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);
 };
 
 } // namespace clang::tidy::misc
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 2d414b1e68800..bc92f1e3ffc5b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -3,7 +3,9 @@
 // Variable can be moved to smaller scope (if-block)
 void test_if_scope() {
   int x = 42;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
   if (true) {
     int y = x + 1;
   }
@@ -21,7 +23,9 @@ int test_multiple_scopes(int v) {
 // Variable can be moved to nested if-block
 void test_nested_if() {
   int a = 5;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'a' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'a' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:15: note: used here
+  // CHECK-NOTES: :[[@LINE+2]]:15: note: can be declared in this scope
   if (true) {
     if (true) {
       int b = a * 2;
@@ -40,7 +44,10 @@ void test_same_scope() {
 //        loop semantic comprehension and var lifetime analysis.
 void test_while_loop() {
   int counter = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'counter' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'counter' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:9: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:16: note: can be declared in this scope
   while (true) {
     counter++;
     if (counter > 10) break;
@@ -60,16 +67,19 @@ void test_if_branches(bool condition) {
 // Variable can be moved to for-loop body
 void test_for_loop_body() {
   int temp = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:32: note: can be declared in this scope
   for (int i = 0; i < 10; i++) {
     temp = i * i;
   }
 }
 
-// Variable used in for-loop expressions - should NOT warn (current limitation)
+// Variable used in for-loop expressions
 void test_for_loop_expressions() {
   int i;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in for-loop initialization
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in for-loop initialization
+  // CHECK-NOTES: :[[@LINE+1]]:3: note: can be declared in this for-loop
   for (i = 0; i < 5; i++) {
     // loop body
   }
@@ -78,7 +88,9 @@ void test_for_loop_expressions() {
 // Variable can be moved to switch case
 void test_switch_case(int value) {
   int result = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:7: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:18: note: can be declared in this scope
   switch (value) {
     case 1:
       result = 10;
@@ -104,7 +116,9 @@ void test_switch_multiple_cases(int value) {
 // Variable with complex initialization can be moved
 void test_complex_init() {
   int cmplx_expr = (5 + 3) * 2;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'cmplx_expr' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'cmplx_expr' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
   if (true) {
     int doubled = cmplx_expr * 2;
   }
@@ -113,7 +127,9 @@ void test_complex_init() {
 // Multiple variables, some can be moved, some cannot
 int test_mixed_variables(bool flag) {
   int movable = 10;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'movable' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'movable' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+5]]:17: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:13: note: can be declared in this scope
   int unmovable = 20; // Should NOT warn - used across scopes
 
   if (flag) {
@@ -127,7 +143,9 @@ int test_mixed_variables(bool flag) {
 // Variable in try-catch block
 void test_try_catch() {
   int error_code = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'error_code' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'error_code' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:7: note: can be declared in this scope
   try {
     error_code = 404;
   } catch (...) {
@@ -148,7 +166,9 @@ void test_try_catch_shared() {
 // Deeply nested scopes
 void test_deep_nesting() {
   int deep = 1;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'deep' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'deep' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:24: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:19: note: can be declared in this scope
   if (true) {
     if (true) {
       if (true) {
@@ -174,7 +194,9 @@ namespace GlobalTestNamespace {
   // Function using global variables - should NOT warn
   void test_global_usage() {
     int local = global_var + namespaced_global;
-    // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: variable 'local' can be declared in a smaller scope
+    // CHECK-NOTES: :[[@LINE-1]]:9: warning: variable 'local' can be declared in a smaller scope
+    // CHECK-NOTES: :[[@LINE+3]]:7: note: used here
+    // CHECK-NOTES: :[[@LINE+1]]:15: note: can be declared in this scope
     if (true) {
       local *= 2;
     }
@@ -230,7 +252,9 @@ void test_function_call() {
 //        Requires more sophisticated analysis.
 void test_for_loop_reuse() {
   int temp = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:30: note: can be declared in this scope
   for (int i = 0; i<10; i++) {
     temp += i;
   }
@@ -239,7 +263,10 @@ void test_for_loop_reuse() {
 // Variable can be moved closer to lambda usage
 void test_lambda_movable() {
   int local = 5;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'local' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'local' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+5]]:20: note: used here
+  // CHECK-NOTES: :[[@LINE+5]]:14: note: used here
+  // CHECK-NOTES: :[[@LINE+2]]:13: note: can be declared in this scope
 
   if (true) {
     auto lambda = [local]() {
@@ -259,7 +286,10 @@ void test_unused_empty_scope() {
 // Variable used in switch and other scope - should warn if common scope allows
 void test_switch_mixed_usage(int value) {
   int mixed = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'mixed' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'mixed' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:9: note: used here
+  // CHECK-NOTES: :[[@LINE+8]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
   if (true) {
     switch (value) {
       case 1:
@@ -273,7 +303,9 @@ void test_switch_mixed_usage(int value) {
 // Variable in nested switch - should warn for single case
 void test_nested_switch(int outer, int inner) {
   int nested = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'nested' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'nested' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:11: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:22: note: can be declared in this scope
   switch (outer) {
     case 1:
       switch (inner) {
@@ -288,7 +320,9 @@ void test_nested_switch(int outer, int inner) {
 // Variable used in switch default only - should warn
 void test_switch_default_only(int value) {
   int def = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'def' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'def' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:7: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:18: note: can be declared in this scope
   switch (value) {
     case 1:
       break;
@@ -324,7 +358,9 @@ void test_range_for_declared() {
 // Variable used in range-based for loop - should warn
 void test_range_for_usage() {
   int sum = 0;
-  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'sum' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'sum' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+5]]:7: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:27: note: can be declared in this scope
   if (true) {
     int vec[] = {1, 2, 3};
     for (auto item : vec) {
@@ -332,3 +368,58 @@ void test_range_for_usage() {
     }
   }
 }
+
+// Many variable uses - test diagnostic note limiting
+void test_diagnostic_limiting() {
+  int x = 42;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+6]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+6]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+6]]:13: note: and 3 more uses...
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int a = x + 1;  // First use
+    int b = x + 2;  // Second use
+    int c = x + 3;  // Third use
+    int d = x + 4;  // Fourth use - should show in overflow note
+    int e = x + 5;  // Fifth use
+    int f = x + 6;  // Sixth use
+  }
+}
+
+// Exactly 3 uses - no overflow message
+void test_exactly_three_uses() {
+  int x = 1;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+5]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+5]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+5]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int a = x + 1;  // First use
+    int b = x + 2;  // Second use
+    int c = x + 3;  // Third use
+  }
+}
+
+// Fewer than 3 uses - show all
+void test_few_uses() {
+  int x = 1;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:13: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int a = x + 1;  // First use
+  }
+}
+
+// For-loop case with many uses - test limiting for for-loop diagnostics
+void test_for_loop_limiting() {
+  int i;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'i' can be declared in for-loop initialization
+  // CHECK-NOTES: :[[@LINE+1]]:3: note: can be declared in this for-loop
+  for (i = 0; i < 5; i++) {
+    int temp = i; // Fourth use of i
+  }
+}

>From 8c134f8fd9a11da378b0f665db6e372af46e92f8 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 17 Jan 2026 21:11:57 +0100
Subject: [PATCH 18/38] Update - Introduce a private take method to simplify
 emitNotes method

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp | 7 +------
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h   | 6 ++++++
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 458d6268c518d..c1889ca12fc27 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -323,13 +323,8 @@ void ScopeReductionCheck::check(
 void ScopeReductionCheck::emitUsageNotes(
     const llvm::SmallVector<const DeclRefExpr *, 8> &Uses) {
   const size_t MaxUsageNotes = 3;
-  size_t NotesShown = 0;
-  for (const auto *Use : Uses) {
-    if (NotesShown >= MaxUsageNotes)
-      break;
+  for (const auto *Use : take(Uses, MaxUsageNotes))
     diag(Use->getLocation(), "used here", DiagnosticIDs::Note);
-    NotesShown++;
-  }
   if (Uses.size() > MaxUsageNotes) {
     diag(Uses[MaxUsageNotes]->getLocation(), "and %0 more uses...",
          DiagnosticIDs::Note)
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index c06acaa253129..8280e7b90c3f1 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -22,6 +22,12 @@ class ScopeReductionCheck : public ClangTidyCheck {
 
 private:
   void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);
+
+  template <typename Container>
+  auto take(const Container &container, size_t n) {
+    return llvm::make_range(container.begin(),
+                            container.begin() + std::min(n, container.size()));
+  }
 };
 
 } // namespace clang::tidy::misc

>From ce93a8448d93a4ea55efb072c6dfef251b2404b2 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 19 Jan 2026 13:36:23 +0100
Subject: [PATCH 19/38] Update

* Fix clang-formatting issue
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp              | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index c1889ca12fc27..33d939be30df2 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -69,13 +69,12 @@ collectVariableUses(const clang::Stmt *S, const clang::VarDecl *Var,
 
 void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
-      varDecl(hasLocalStorage(),
-              unless(hasGlobalStorage()),
-              hasAncestor(functionDecl()),
-              unless(parmVarDecl()),
+      varDecl(hasLocalStorage(), unless(hasGlobalStorage()),
+              hasAncestor(functionDecl()), unless(parmVarDecl()),
               unless(hasParent(declStmt(hasParent(forStmt())))),
               unless(hasParent(declStmt(hasParent(cxxForRangeStmt())))),
-              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(), cxxOperatorCallExpr()))))
+              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
+                                          cxxOperatorCallExpr()))))
           .bind("var"),
       this);
 }

>From db7a5276c11184ac47ddbedcdaf150fcd71e4969 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 19 Jan 2026 14:20:07 +0100
Subject: [PATCH 20/38] Update to fix another format issue

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index 8280e7b90c3f1..a448294572b62 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -24,9 +24,10 @@ class ScopeReductionCheck : public ClangTidyCheck {
   void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);
 
   template <typename Container>
-  auto take(const Container &container, size_t n) {
-    return llvm::make_range(container.begin(),
-                            container.begin() + std::min(n, container.size()));
+  auto take(const Container &ThisContainer, size_t n) {
+    return llvm::make_range(ThisContainer.begin(),
+                            ThisContainer.begin() +
+                                std::min(n, ThisContainer.size()));
   }
 };
 

>From 4837950f7fd433c427509bda1481df876e7cd4ba Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 19 Jan 2026 14:39:49 +0100
Subject: [PATCH 21/38] Update again, format issues

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index a448294572b62..11f77bdf74cb3 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -24,10 +24,10 @@ class ScopeReductionCheck : public ClangTidyCheck {
   void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);
 
   template <typename Container>
-  auto take(const Container &ThisContainer, size_t n) {
+  auto take(const Container &ThisContainer, size_t Count) {
     return llvm::make_range(ThisContainer.begin(),
                             ThisContainer.begin() +
-                                std::min(n, ThisContainer.size()));
+                                std::min(Count, ThisContainer.size()));
   }
 };
 

>From 1d996af3c22ca48576986ef9483c174a706ed5ab Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 19 Jan 2026 20:12:26 +0100
Subject: [PATCH 22/38] Update - reduce false positives from for-loop analysis

The original implementation detected false positives from
variables in inner scope of for-loops. This change addresses
that problem and adds a negative and positive test case.
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 51 ++++++++++++++++---
 .../checkers/misc/scope-reduction.cpp         | 22 ++++++++
 2 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 33d939be30df2..7f590818738a0 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -308,14 +308,49 @@ void ScopeReductionCheck::check(
   }
 
   if (AllUsesInSameForLoop && CommonForLoop) {
-    diag(Var->getLocation(),
-         "variable '%0' can be declared in for-loop initialization")
-        << Var->getName();
-
-    // Skip "used here" notes for for-loops, they're too noisy
-    //
-    diag(CommonForLoop->getBeginLoc(), "can be declared in this for-loop",
-         DiagnosticIDs::Note);
+    // Check if for-loop scope is broader than current declaration scope
+    const DynTypedNode Current = DynTypedNode::create(*Var);
+    const CompoundStmt *VarScope = nullptr;
+
+    auto ParentNodes = Parents.getParents(Current);
+    while (!ParentNodes.empty()) {
+      const Stmt *Parent = ParentNodes[0].get<Stmt>();
+      if (!Parent)
+        break;
+
+      if (const auto *CS = dyn_cast<CompoundStmt>(Parent)) {
+        VarScope = CS;
+        break;
+      }
+      ParentNodes = Parents.getParents(*Parent);
+    }
+
+    // Only report if for-loop is in a smaller scope than current declaration
+    if (VarScope) {
+      const Stmt *CheckScope = CommonForLoop;
+      bool IsSmaller = false;
+
+      while (CheckScope) {
+        if (CheckScope == VarScope) {
+          IsSmaller = true;
+          break;
+        }
+        auto CheckParents = Parents.getParents(*CheckScope);
+        if (CheckParents.empty())
+          break;
+        CheckScope = CheckParents[0].get<Stmt>();
+      }
+
+      if (IsSmaller) {
+        diag(Var->getLocation(),
+             "variable '%0' can be declared in for-loop initialization")
+            << Var->getName();
+
+        // Skip usage notes for for-loops - usage pattern is obvious
+        diag(CommonForLoop->getBeginLoc(), "can be declared in this for-loop",
+             DiagnosticIDs::Note);
+      }
+    }
   }
 }
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index bc92f1e3ffc5b..557938d3d5303 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -423,3 +423,25 @@ void test_for_loop_limiting() {
     int temp = i; // Fourth use of i
   }
 }
+
+// Test case for variables within the for-loop scope. (should NOT be reported)
+void testForLoopCase() {
+  for (int i = 0; i < 10; ++i) {
+    int byte = 0;  // Declared in for-loop scope, used in smaller loop body scope
+    byte = i * 2;  // Should NOT be reported - usage scope is smaller
+    byte += 1;
+  }
+}
+
+// Test case for variables used in broader scopes (SHOULD be reported)
+void testBroaderScope() {
+  int value = 0;  // Should be reported - used in broader if-statement scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    value = 42;
+    value += 1;
+  }
+}

>From c80f84a8c3dc9b75cf9c5b14bc1d50ea7c695b82 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Mon, 19 Jan 2026 23:20:55 +0100
Subject: [PATCH 23/38] Update comments

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 45 ++++++++++---------
 .../clang-tidy/misc/ScopeReductionCheck.h     | 17 +++++++
 2 files changed, 42 insertions(+), 20 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 7f590818738a0..784f0f6ea41d9 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -7,7 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 // This checker uses a 7-step algorithm to accomplish scope analysis of a
-// variable and determine if it's in too large a scope. Note that the
+// variable and determine if it can be declared in a smaller scope. Note that the
 // clang-tidy framework is aimed mainly at supporting text-manipulation,
 // diagnostics, or common AST patterns. Scope reduction analysis is
 // quite specialized, and there's not much support specifically for
@@ -20,29 +20,33 @@
 //    - Only match variables within functions (hasAncestor(functionDecl())
 //    - Exclude for-loop declared variables
 //       (unless(hasParent(declStmt(hasParent(forStmt))))))
-//    - Exclude variables with function call initializors
+//    - Exclude variables with function call initializers
 //       (unless(hasInitializer(...)))
 //    - Exclude parameters from analysis
 //       (unless(parmVarDecl())
 // 2) Collect variable uses
-//    - find all DeclRefExpr nodes that reference the variable
+//    - Find all DeclRefExpr nodes that reference the variable
 // 3) Build scope chains
-//    - for each use, find all compound statements that contain it (from
+//    - For each use, find all compound statements that contain it (from
 //      innermost to outermost)
 // 4) Find the innermost compound statement that contains all uses
 //    - This is the smallest scope where the variable could be declared
-// 5) Find declaration scope
-//    - Locate the compound statement containing the variable declaration
-// 6) Verify nesting
-//    - Ensure the usage scope is actually nested within the declaration scope
-// 7) Alternate analysis - check for for-loop initialization opportunity
-//    - This is only run if compound stmt analysis didn't find smaller scope
+// 5) Switch case analysis
+//    - Check if variable uses span multiple case labels in the same switch
+//    - Skip analysis if so, as variables cannot be declared in switch body
+// 6) Verify scope nesting and report
+//    - Find the compound statement containing the variable declaration
+//    - Only report if the usage scope is nested within the declaration scope
+//    - This ensures we only suggest moving variables to smaller scopes
+// 7) Alternative analysis - check for for-loop initialization opportunity
+//    - Only runs if compound statement analysis didn't find a smaller scope
 //    - Only check local variables, not parameters
 //    - Determine if all uses are within the same for-loop and suggest
-//      for-loop initialization
+//      for-loop initialization, but only if for-loop is in smaller scope
 //
-// The algo works by finding the smallest scope that could contain the variable
-// declaration while still encompassing all it's uses.
+// The algorithm works by finding the smallest scope that could contain the variable
+// declaration while still encompassing all its uses, but only reports when that
+// scope is smaller than the current declaration scope.
 
 #include "ScopeReductionCheck.h"
 #include "../utils/DeclRefExprUtils.h"
@@ -168,7 +172,7 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 5: Check if current var declaration is broader than necessary
+  // Step 5: Check if current variable declaration can be moved to a smaller scope
   if (InnermostScope) {
     // Check if variable uses span multiple case labels in the same switch
     // If so, the only common scope would be the switch body, which is invalid
@@ -225,9 +229,10 @@ void ScopeReductionCheck::check(
       ParentNodes = Parents.getParents(*Parent);
     }
 
-    // Step 6: Verify that usage scope is nested within decl scope
+    // Step 6: Verify that usage scope is nested within declaration scope
+    // Only report if we can move the variable to a smaller scope
     if (VarScope && VarScope != InnermostScope) {
-      // Walk up from innermost usage to see if the decl scope is reached
+      // Walk up from innermost usage scope to see if declaration scope is reached
       const Stmt *CheckScope = InnermostScope;
       bool IsNested = false;
 
@@ -244,7 +249,7 @@ void ScopeReductionCheck::check(
         CheckScope = CheckParent;
       }
 
-      // Only report if the usage scope is truly nested within the decl scope
+      // Only report if the usage scope is truly nested within the declaration scope
       if (IsNested) {
         diag(Var->getLocation(),
              "variable '%0' can be declared in a smaller scope")
@@ -259,9 +264,9 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 7: Alternative analysis - check for for-loop initialization
-  // opportunity This only runs if the compound statement analysis didn't find a
-  // smaller scope Only check local variables, not parameters
+  // Step 7: Alternative analysis - check for for-loop initialization opportunity
+  // This only runs if the compound statement analysis didn't find a smaller scope
+  // Only check local variables, not parameters
   const ForStmt *CommonForLoop = nullptr;
   bool AllUsesInSameForLoop = true;
 
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index 11f77bdf74cb3..3a4b3bb760a3c 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -6,6 +6,11 @@
 //
 //===----------------------------------------------------------------------===//
 
+/// \file
+/// This file defines ScopeReductionCheck, a clang-tidy checker that identifies
+/// variables that can be declared in smaller scopes to improve code locality
+/// and readability.
+
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDUCTIONCHECK_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SCOPEREDUCTIONCHECK_H
 
@@ -13,6 +18,14 @@
 
 namespace clang::tidy::misc {
 
+/// Detects variables that can be declared in smaller scopes.
+///
+/// This checker analyzes variable declarations and their usage patterns to
+/// determine if they can be moved to a more restrictive scope, improving
+/// code locality and reducing the variable's lifetime.
+///
+/// The checker uses a 7-step algorithm to perform scope analysis and only
+/// reports cases where variables can be moved to genuinely smaller scopes.
 class ScopeReductionCheck : public ClangTidyCheck {
 public:
   ScopeReductionCheck(StringRef Name, ClangTidyContext *Context)
@@ -21,8 +34,12 @@ class ScopeReductionCheck : public ClangTidyCheck {
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
 
 private:
+  /// Emit diagnostic notes showing where the variable is used.
+  /// Limits output to avoid excessive noise in diagnostics.
   void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);
 
+  /// Utility function to take the first N elements from a container.
+  /// Used to limit the number of usage notes displayed.
   template <typename Container>
   auto take(const Container &ThisContainer, size_t Count) {
     return llvm::make_range(ThisContainer.begin(),

>From 001cf1f182bfd16641675d3bdfad18d149d8609b Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Tue, 20 Jan 2026 00:21:56 +0100
Subject: [PATCH 24/38] Update -- add compound assignment and accumulator
 pattern detection

A significant number of false positives were found from accumulator
patterns and compound assignments. This change addresses that
and expands upon the test cases to cover those cases.
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 168 ++++++++++++++++--
 .../checkers/misc/scope-reduction.cpp         | 164 ++++++++++++++++-
 2 files changed, 308 insertions(+), 24 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 784f0f6ea41d9..7ed60e039c381 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -6,16 +6,16 @@
 //
 //===----------------------------------------------------------------------===//
 
-// This checker uses a 7-step algorithm to accomplish scope analysis of a
-// variable and determine if it can be declared in a smaller scope. Note that the
-// clang-tidy framework is aimed mainly at supporting text-manipulation,
+// This checker uses an 8-step algorithm to accomplish scope analysis of a
+// variable and determine if it can be declared in a smaller scope. Note that
+// the clang-tidy framework is aimed mainly at supporting text-manipulation,
 // diagnostics, or common AST patterns. Scope reduction analysis is
 // quite specialized, and there's not much support specifically for
 // those steps. Perhaps someone else knows better and can help simplify
 // this code in a more concrete way other than simply suggesting it can
 // be simpler.
 //
-// The 7-step algorithm used by this checker for scope reduction analysis is:
+// The 8-step algorithm used by this checker for scope reduction analysis is:
 // 1) AST Matcher Filtering
 //    - Only match variables within functions (hasAncestor(functionDecl())
 //    - Exclude for-loop declared variables
@@ -31,22 +31,26 @@
 //      innermost to outermost)
 // 4) Find the innermost compound statement that contains all uses
 //    - This is the smallest scope where the variable could be declared
-// 5) Switch case analysis
+// 5) Check for accumulator patterns
+//    - Detect compound assignments (+=, -=, etc.) and self-referencing
+//    assignments
+//    - Skip analysis for accumulator variables to avoid false positives
+// 6) Switch case analysis
 //    - Check if variable uses span multiple case labels in the same switch
 //    - Skip analysis if so, as variables cannot be declared in switch body
-// 6) Verify scope nesting and report
+// 7) Verify scope nesting and report
 //    - Find the compound statement containing the variable declaration
 //    - Only report if the usage scope is nested within the declaration scope
 //    - This ensures we only suggest moving variables to smaller scopes
-// 7) Alternative analysis - check for for-loop initialization opportunity
+// 8) Alternative analysis - check for for-loop initialization opportunity
 //    - Only runs if compound statement analysis didn't find a smaller scope
 //    - Only check local variables, not parameters
 //    - Determine if all uses are within the same for-loop and suggest
 //      for-loop initialization, but only if for-loop is in smaller scope
 //
-// The algorithm works by finding the smallest scope that could contain the variable
-// declaration while still encompassing all its uses, but only reports when that
-// scope is smaller than the current declaration scope.
+// The algorithm works by finding the smallest scope that could contain the
+// variable declaration while still encompassing all its uses, but only reports
+// when that scope is smaller than the current declaration scope.
 
 #include "ScopeReductionCheck.h"
 #include "../utils/DeclRefExprUtils.h"
@@ -172,7 +176,141 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 5: Check if current variable declaration can be moved to a smaller scope
+  // Step 5: Check for accumulator patterns - skip if variable is used in
+  // accumulator pattern
+  for (const auto *Use : Uses) {
+    auto Parents = Result.Context->getParentMapContext().getParents(*Use);
+    if (!Parents.empty()) {
+      if (const auto *BinOp = Parents[0].get<BinaryOperator>()) {
+        // Check for compound assignments (+=, -=, *=, etc.)
+        if (BinOp->isCompoundAssignmentOp() && BinOp->getLHS() == Use) {
+          // Only consider it an accumulator if it's inside a loop
+          const Stmt *Current = BinOp;
+          bool InLoop = false;
+          while (Current) {
+            auto CurrentParents =
+                Result.Context->getParentMapContext().getParents(*Current);
+            if (CurrentParents.empty())
+              break;
+
+            const Stmt *Parent = CurrentParents[0].get<Stmt>();
+            if (!Parent)
+              break;
+
+            if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
+                isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
+              InLoop = true;
+              break;
+            }
+            Current = Parent;
+          }
+
+          if (InLoop)
+            return; // Skip compound assignment patterns in loops
+        }
+        // Check for self-referencing assignments (var = var + something)
+        if (BinOp->isAssignmentOp() && BinOp->getLHS() == Use) {
+          if (const auto *RHS = BinOp->getRHS()) {
+            // Look for the variable on the right side
+            if (const auto *RHSRef =
+                    dyn_cast<DeclRefExpr>(RHS->IgnoreParenImpCasts())) {
+              if (RHSRef->getDecl() == Var) {
+                // Only consider it an accumulator if it's inside a loop
+                const Stmt *Current = BinOp;
+                bool InLoop = false;
+                while (Current) {
+                  auto CurrentParents =
+                      Result.Context->getParentMapContext().getParents(
+                          *Current);
+                  if (CurrentParents.empty())
+                    break;
+
+                  const Stmt *Parent = CurrentParents[0].get<Stmt>();
+                  if (!Parent)
+                    break;
+
+                  if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
+                      isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
+                    InLoop = true;
+                    break;
+                  }
+                  Current = Parent;
+                }
+
+                if (InLoop)
+                  return; // Skip self-referencing assignment in loops
+              }
+            }
+            // Check binary operations on RHS (var = var op something)
+            if (const auto *RHSBinOp =
+                    dyn_cast<BinaryOperator>(RHS->IgnoreParenImpCasts())) {
+              if (const auto *LHSRef = dyn_cast<DeclRefExpr>(
+                      RHSBinOp->getLHS()->IgnoreParenImpCasts())) {
+                if (LHSRef->getDecl() == Var) {
+                  // Only consider it an accumulator if it's inside a loop
+                  const Stmt *Current = BinOp;
+                  bool InLoop = false;
+                  while (Current) {
+                    auto CurrentParents =
+                        Result.Context->getParentMapContext().getParents(
+                            *Current);
+                    if (CurrentParents.empty())
+                      break;
+
+                    const Stmt *Parent = CurrentParents[0].get<Stmt>();
+                    if (!Parent)
+                      break;
+
+                    if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
+                        isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
+                      InLoop = true;
+                      break;
+                    }
+                    Current = Parent;
+                  }
+
+                  if (InLoop)
+                    return; // Skip accumulator pattern in loops
+                }
+              }
+              if (const auto *RHSRef = dyn_cast<DeclRefExpr>(
+                      RHSBinOp->getRHS()->IgnoreParenImpCasts())) {
+                if (RHSRef->getDecl() == Var) {
+                  // Only consider it an accumulator if it's inside a loop
+                  const Stmt *Current = BinOp;
+                  bool InLoop = false;
+                  while (Current) {
+                    auto CurrentParents =
+                        Result.Context->getParentMapContext().getParents(
+                            *Current);
+                    if (CurrentParents.empty())
+                      break;
+
+                    const Stmt *Parent = CurrentParents[0].get<Stmt>();
+                    if (!Parent)
+                      break;
+
+                    if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
+                        isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
+                      InLoop = true;
+                      break;
+                    }
+                    Current = Parent;
+                  }
+
+                  if (InLoop)
+                    return; // Skip accumulator pattern in loops
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // Step 6: Check if current variable declaration can be moved to a smaller
+  // scope
   if (InnermostScope) {
     // Check if variable uses span multiple case labels in the same switch
     // If so, the only common scope would be the switch body, which is invalid
@@ -229,7 +367,7 @@ void ScopeReductionCheck::check(
       ParentNodes = Parents.getParents(*Parent);
     }
 
-    // Step 6: Verify that usage scope is nested within declaration scope
+    // Step 7: Verify that usage scope is nested within declaration scope
     // Only report if we can move the variable to a smaller scope
     if (VarScope && VarScope != InnermostScope) {
       // Walk up from innermost usage scope to see if declaration scope is reached
@@ -264,9 +402,9 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 7: Alternative analysis - check for for-loop initialization opportunity
-  // This only runs if the compound statement analysis didn't find a smaller scope
-  // Only check local variables, not parameters
+  // Step 8: Alternative analysis - check for for-loop initialization
+  // opportunity This only runs if the compound statement analysis didn't find a
+  // smaller scope Only check local variables, not parameters
   const ForStmt *CommonForLoop = nullptr;
   bool AllUsesInSameForLoop = true;
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 557938d3d5303..0ccde4123dbee 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -248,13 +248,8 @@ void test_function_call() {
 
 // Variable used inside a loop.
 // Should NOT warn.
-// FIXME: temp needs to persist across loop iterations, therefore cannot move
-//        Requires more sophisticated analysis.
 void test_for_loop_reuse() {
   int temp = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:30: note: can be declared in this scope
   for (int i = 0; i<10; i++) {
     temp += i;
   }
@@ -355,12 +350,9 @@ void test_range_for_declared() {
   }
 }
 
-// Variable used in range-based for loop - should warn
+// Variable used in range-based for loop - should NOT warn
 void test_range_for_usage() {
   int sum = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'sum' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+5]]:7: note: used here
-  // CHECK-NOTES: :[[@LINE+3]]:27: note: can be declared in this scope
   if (true) {
     int vec[] = {1, 2, 3};
     for (auto item : vec) {
@@ -445,3 +437,157 @@ void testBroaderScope() {
     value += 1;
   }
 }
+// Test cases for accumulator pattern detection
+
+// Positive cases - should still warn (compound assignments outside loops)
+
+// Compound assignment outside loop - should warn
+void test_compound_assignment_no_loop() {
+  int value = 10;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    value *= 2;  // Not in loop, should still warn
+  }
+}
+
+// Self-referencing assignment outside loop - should warn
+void test_self_reference_no_loop() {
+  int x = 5;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:9: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    x = x + 10;  // Not in loop, should still warn
+  }
+}
+
+// Binary operation self-reference outside loop - should warn
+void test_binary_self_reference_no_loop() {
+  int result = 0;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:14: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    result = result + 42;  // Not in loop, should still warn
+  }
+}
+
+// Negative cases - should NOT warn (accumulator patterns in loops)
+
+// Compound assignment in while loop - should NOT warn
+void test_compound_assignment_while() {
+  int sum = 0;  // Should NOT warn - accumulator in while loop
+  while (sum < 100) {
+    sum += 10;
+  }
+}
+
+// Compound assignment in for loop - should NOT warn
+void test_compound_assignment_for() {
+  int total = 0;  // Should NOT warn - accumulator in for loop
+  for (int i = 0; i < 10; ++i) {
+    total += i;
+  }
+}
+
+// Self-referencing in for loop - should NOT warn
+void test_self_reference_for() {
+  bool found = false;  // Should NOT warn - accumulator pattern
+  for (int i = 0; i < 10; ++i) {
+    found = found || (i > 5);
+  }
+}
+
+// Binary operation self-reference in loop - should NOT warn
+void test_binary_self_reference_for() {
+  int product = 1;  // Should NOT warn - accumulator pattern
+  for (int i = 1; i <= 5; ++i) {
+    product = product * i;
+  }
+}
+
+// Multiple accumulator operations - should NOT warn
+void test_multiple_accumulator() {
+  int count = 0;  // Should NOT warn
+  for (int i = 0; i < 10; ++i) {
+    count += i;
+    count *= 2;  // Multiple compound assignments in same loop
+  }
+}
+
+// Range-based for loop accumulator - should NOT warn
+void test_range_for_accumulator() {
+  int sum = 0;  // Should NOT warn - accumulator in range-based for
+  int arr[] = {1, 2, 3, 4, 5};
+  for (auto item : arr) {
+    sum += item;
+  }
+}
+
+// Do-while loop accumulator - should NOT warn
+void test_do_while_accumulator() {
+  int counter = 0;  // Should NOT warn - accumulator in do-while
+  do {
+    counter++;
+  } while (counter < 5);
+}
+
+// Edge cases
+
+// Nested loops with accumulator - should NOT warn
+void test_nested_loop_accumulator() {
+  int total = 0;  // Should NOT warn
+  for (int i = 0; i < 5; ++i) {
+    for (int j = 0; j < 5; ++j) {
+      total += i * j;  // Accumulator in nested loop
+    }
+  }
+}
+
+// Accumulator in inner loop only - should NOT warn
+void test_inner_loop_accumulator() {
+  int sum = 0;  // Should NOT warn - used as accumulator in inner loop
+  if (true) {
+    for (int i = 0; i < 10; ++i) {
+      sum += i;
+    }
+  }
+}
+
+// Mixed usage - accumulator + other usage - complex case
+void test_mixed_accumulator_usage() {
+  int value = 0;  // Complex case - used as accumulator AND in other scope
+  for (int i = 0; i < 5; ++i) {
+    value += i;  // Accumulator usage
+  }
+  if (true) {
+    value = 100;  // Non-accumulator usage - this makes it complex
+  }
+}
+
+// Variable used in loop but not as accumulator - should warn
+void test_non_accumulator_in_loop() {
+  int temp = 42;  // Used in loop but not modified - should warn
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:18: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:32: note: can be declared in this scope
+  for (int i = 0; i < 10; ++i) {
+    int result = temp * 2;  // Just reading temp, not modifying it
+  }
+}
+
+// Compound assignment with different variable - should warn
+void test_compound_different_var() {
+  int x = 10;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'x' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:10: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int y = 5;
+    y += x;  // x is not the accumulator, y is
+  }
+}

>From e472b5472e7a8056b6593104242d983e2ff57fad Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Tue, 20 Jan 2026 11:08:33 +0100
Subject: [PATCH 25/38] update - simple format change

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 7ed60e039c381..59c9e47b877e0 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -387,7 +387,8 @@ void ScopeReductionCheck::check(
         CheckScope = CheckParent;
       }
 
-      // Only report if the usage scope is truly nested within the declaration scope
+      // Only report if the usage scope is truly nested within the declaration
+      // scope
       if (IsNested) {
         diag(Var->getLocation(),
              "variable '%0' can be declared in a smaller scope")

>From 7f1773b735e22b9a99af22c00c85b2b2b6dae0eb Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Tue, 20 Jan 2026 11:15:07 +0100
Subject: [PATCH 26/38] Update - simple format fixes

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 59c9e47b877e0..53fdad3833e1f 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -370,7 +370,8 @@ void ScopeReductionCheck::check(
     // Step 7: Verify that usage scope is nested within declaration scope
     // Only report if we can move the variable to a smaller scope
     if (VarScope && VarScope != InnermostScope) {
-      // Walk up from innermost usage scope to see if declaration scope is reached
+      // Walk up from innermost usage scope to see if declaration scope is
+      // reached
       const Stmt *CheckScope = InnermostScope;
       bool IsNested = false;
 
@@ -404,8 +405,8 @@ void ScopeReductionCheck::check(
   }
 
   // Step 8: Alternative analysis - check for for-loop initialization
-  // opportunity This only runs if the compound statement analysis didn't find a
-  // smaller scope Only check local variables, not parameters
+  // opportunity This only runs if the compound statement analysis didn't find
+  // a smaller scope Only check local variables, not parameters
   const ForStmt *CommonForLoop = nullptr;
   bool AllUsesInSameForLoop = true;
 

>From b69b49e16a9780c62d27df5a008fef619fbfec67 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Wed, 21 Jan 2026 15:23:26 +0100
Subject: [PATCH 27/38] Update - improve accumulator detection

* Improve accumulator detection to avoid reporting cases
  where read only variables are initialized outside of scope
  and used in an inner scope
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 181 +++++-------------
 .../checkers/misc/scope-reduction.cpp         |  16 +-
 2 files changed, 55 insertions(+), 142 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 53fdad3833e1f..eb46f1164b5e5 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -31,10 +31,11 @@
 //      innermost to outermost)
 // 4) Find the innermost compound statement that contains all uses
 //    - This is the smallest scope where the variable could be declared
-// 5) Check for accumulator patterns
-//    - Detect compound assignments (+=, -=, etc.) and self-referencing
-//    assignments
-//    - Skip analysis for accumulator variables to avoid false positives
+// 5) Check for loop usage
+//    - Skip analysis for any variable used within loops to avoid false
+//    positives
+//    - This prevents suggesting moving variables into loop bodies (inefficient)
+//    - Covers both accumulator patterns and read-only usage in loops
 // 6) Switch case analysis
 //    - Check if variable uses span multiple case labels in the same switch
 //    - Skip analysis if so, as variables cannot be declared in switch body
@@ -176,135 +177,59 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 5: Check for accumulator patterns - skip if variable is used in
-  // accumulator pattern
-  for (const auto *Use : Uses) {
-    auto Parents = Result.Context->getParentMapContext().getParents(*Use);
-    if (!Parents.empty()) {
-      if (const auto *BinOp = Parents[0].get<BinaryOperator>()) {
-        // Check for compound assignments (+=, -=, *=, etc.)
-        if (BinOp->isCompoundAssignmentOp() && BinOp->getLHS() == Use) {
-          // Only consider it an accumulator if it's inside a loop
-          const Stmt *Current = BinOp;
-          bool InLoop = false;
-          while (Current) {
-            auto CurrentParents =
-                Result.Context->getParentMapContext().getParents(*Current);
-            if (CurrentParents.empty())
-              break;
-
-            const Stmt *Parent = CurrentParents[0].get<Stmt>();
-            if (!Parent)
-              break;
-
-            if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
-                isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
-              InLoop = true;
-              break;
-            }
-            Current = Parent;
+  // Step 5: Check if suggested scope would place variable inside loop body
+  if (InnermostScope) {
+    for (const auto *Use : Uses) {
+      // Check if this use is inside a loop
+      const Stmt *Current = Use;
+      const Stmt *ContainingLoop = nullptr;
+
+      while (Current) {
+        auto CurrentParents =
+            Result.Context->getParentMapContext().getParents(*Current);
+        if (CurrentParents.empty())
+          break;
+
+        const Stmt *Parent = CurrentParents[0].get<Stmt>();
+        if (!Parent) {
+          // Try to get Decl parent and continue from there
+          if (const auto *DeclParent = CurrentParents[0].get<Decl>()) {
+            auto DeclParentNodes =
+                Result.Context->getParentMapContext().getParents(*DeclParent);
+            if (!DeclParentNodes.empty())
+              Parent = DeclParentNodes[0].get<Stmt>();
           }
+          if (!Parent)
+            break;
+        }
 
-          if (InLoop)
-            return; // Skip compound assignment patterns in loops
+        if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
+            isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
+          ContainingLoop = Parent;
+          break;
         }
-        // Check for self-referencing assignments (var = var + something)
-        if (BinOp->isAssignmentOp() && BinOp->getLHS() == Use) {
-          if (const auto *RHS = BinOp->getRHS()) {
-            // Look for the variable on the right side
-            if (const auto *RHSRef =
-                    dyn_cast<DeclRefExpr>(RHS->IgnoreParenImpCasts())) {
-              if (RHSRef->getDecl() == Var) {
-                // Only consider it an accumulator if it's inside a loop
-                const Stmt *Current = BinOp;
-                bool InLoop = false;
-                while (Current) {
-                  auto CurrentParents =
-                      Result.Context->getParentMapContext().getParents(
-                          *Current);
-                  if (CurrentParents.empty())
-                    break;
-
-                  const Stmt *Parent = CurrentParents[0].get<Stmt>();
-                  if (!Parent)
-                    break;
-
-                  if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
-                      isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
-                    InLoop = true;
-                    break;
-                  }
-                  Current = Parent;
-                }
-
-                if (InLoop)
-                  return; // Skip self-referencing assignment in loops
-              }
-            }
-            // Check binary operations on RHS (var = var op something)
-            if (const auto *RHSBinOp =
-                    dyn_cast<BinaryOperator>(RHS->IgnoreParenImpCasts())) {
-              if (const auto *LHSRef = dyn_cast<DeclRefExpr>(
-                      RHSBinOp->getLHS()->IgnoreParenImpCasts())) {
-                if (LHSRef->getDecl() == Var) {
-                  // Only consider it an accumulator if it's inside a loop
-                  const Stmt *Current = BinOp;
-                  bool InLoop = false;
-                  while (Current) {
-                    auto CurrentParents =
-                        Result.Context->getParentMapContext().getParents(
-                            *Current);
-                    if (CurrentParents.empty())
-                      break;
-
-                    const Stmt *Parent = CurrentParents[0].get<Stmt>();
-                    if (!Parent)
-                      break;
-
-                    if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
-                        isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
-                      InLoop = true;
-                      break;
-                    }
-                    Current = Parent;
-                  }
-
-                  if (InLoop)
-                    return; // Skip accumulator pattern in loops
-                }
-              }
-              if (const auto *RHSRef = dyn_cast<DeclRefExpr>(
-                      RHSBinOp->getRHS()->IgnoreParenImpCasts())) {
-                if (RHSRef->getDecl() == Var) {
-                  // Only consider it an accumulator if it's inside a loop
-                  const Stmt *Current = BinOp;
-                  bool InLoop = false;
-                  while (Current) {
-                    auto CurrentParents =
-                        Result.Context->getParentMapContext().getParents(
-                            *Current);
-                    if (CurrentParents.empty())
-                      break;
-
-                    const Stmt *Parent = CurrentParents[0].get<Stmt>();
-                    if (!Parent)
-                      break;
-
-                    if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
-                        isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
-                      InLoop = true;
-                      break;
-                    }
-                    Current = Parent;
-                  }
-
-                  if (InLoop)
-                    return; // Skip accumulator pattern in loops
-                }
-              }
-            }
+        Current = Parent;
+      }
+
+      // If use is in a loop, check if suggested scope is inside that loop
+      if (ContainingLoop) {
+        const Stmt *CheckScope = InnermostScope;
+        bool ScopeInsideLoop = false;
+
+        while (CheckScope) {
+          if (CheckScope == ContainingLoop) {
+            ScopeInsideLoop = true;
+            break;
           }
+          auto CheckParents =
+              Result.Context->getParentMapContext().getParents(*CheckScope);
+          if (CheckParents.empty())
+            break;
+          CheckScope = CheckParents[0].get<Stmt>();
         }
+
+        if (ScopeInsideLoop)
+          return; // Skip if suggested scope is inside loop body
       }
     }
   }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 0ccde4123dbee..0bfa8ff969d2b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -39,15 +39,9 @@ void test_same_scope() {
   int y = x + 5;
 }
 
-// Variable can be moved to while loop body
-// FIXME: This is a false positive. Correcting this will require
-//        loop semantic comprehension and var lifetime analysis.
+// Variable can be moved to while loop body. should NOT warn
 void test_while_loop() {
   int counter = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'counter' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
-  // CHECK-NOTES: :[[@LINE+4]]:9: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:16: note: can be declared in this scope
   while (true) {
     counter++;
     if (counter > 10) break;
@@ -64,12 +58,9 @@ void test_if_branches(bool condition) {
   }
 }
 
-// Variable can be moved to for-loop body
+// Variable can be moved to for-loop body. should NOT warn
 void test_for_loop_body() {
   int temp = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:32: note: can be declared in this scope
   for (int i = 0; i < 10; i++) {
     temp = i * i;
   }
@@ -572,9 +563,6 @@ void test_mixed_accumulator_usage() {
 // Variable used in loop but not as accumulator - should warn
 void test_non_accumulator_in_loop() {
   int temp = 42;  // Used in loop but not modified - should warn
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'temp' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+3]]:18: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:32: note: can be declared in this scope
   for (int i = 0; i < 10; ++i) {
     int result = temp * 2;  // Just reading temp, not modifying it
   }

>From 3105a4e70ec5b65ef2936bb5020ce165f23ed662 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Thu, 22 Jan 2026 00:48:16 +0100
Subject: [PATCH 28/38] Update - More accumulator patterns, fix false positives

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   |  13 +-
 .../checkers/misc/scope-reduction.cpp         | 202 ++++++++++++++++++
 2 files changed, 213 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index eb46f1164b5e5..c2f6602eedb6c 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -31,11 +31,14 @@
 //      innermost to outermost)
 // 4) Find the innermost compound statement that contains all uses
 //    - This is the smallest scope where the variable could be declared
-// 5) Check for loop usage
+// 5) Check for loop usage and variable modifications
 //    - Skip analysis for any variable used within loops to avoid false
 //    positives
+//    - Skip analysis for variables modified in smaller scopes (changes
+//    semantics)
 //    - This prevents suggesting moving variables into loop bodies (inefficient)
-//    - Covers both accumulator patterns and read-only usage in loops
+//    - This prevents moving variables that are modified, changing their
+//    lifetime
 // 6) Switch case analysis
 //    - Check if variable uses span multiple case labels in the same switch
 //    - Skip analysis if so, as variables cannot be declared in switch body
@@ -82,6 +85,7 @@ void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
               hasAncestor(functionDecl()), unless(parmVarDecl()),
               unless(hasParent(declStmt(hasParent(forStmt())))),
               unless(hasParent(declStmt(hasParent(cxxForRangeStmt())))),
+              unless(hasParent(cxxCatchStmt())),
               unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
                                           cxxOperatorCallExpr()))))
           .bind("var"),
@@ -178,8 +182,13 @@ void ScopeReductionCheck::check(
   }
 
   // Step 5: Check if suggested scope would place variable inside loop body
+  // or if variable is modified in suggested scope (changing semantics)
   if (InnermostScope) {
     for (const auto *Use : Uses) {
+      // TODO: Implement precise modification detection
+      // Current approach is too complex and breaks existing tests
+      // For now, accept the false positive in test_unary_outside_loop
+
       // Check if this use is inside a loop
       const Stmt *Current = Use;
       const Stmt *ContainingLoop = nullptr;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 0bfa8ff969d2b..318dc5400dd16 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -579,3 +579,205 @@ void test_compound_different_var() {
     y += x;  // x is not the accumulator, y is
   }
 }
+
+
+// Helper functions for test cases
+int calculateLimit() { return 10; }
+int getDefaultError() { return -1; }
+void riskyOperation() {}
+class Exception { public: int getCode() const { return 1; } };
+void handleError(int) {}
+int expensive() { return 42; }
+bool condition = true;
+void use(int) {}
+int getValue() { return 5; }
+void processResult(int) {}
+int transform(int x) { return x * 2; }
+void doSomething(int) {}
+int calculateValue(int x) { return x * x; }
+void process(int) {}
+
+// Unary operator accumulator patterns - currently might warn incorrectly
+
+// Post-increment in loop - should NOT warn (accumulator pattern)
+void test_post_increment_loop() {
+  int counter = 0;  // Should NOT warn - accumulator with post-increment
+  for (int i = 0; i < 10; ++i) {
+    counter++;
+  }
+}
+
+// Pre-increment in loop - should NOT warn (accumulator pattern)
+void test_pre_increment_loop() {
+  int counter = 0;  // Should NOT warn - accumulator with pre-increment
+  for (int i = 0; i < 10; ++i) {
+    ++counter;
+  }
+}
+
+// Post-decrement in loop - should NOT warn (accumulator pattern)
+void test_post_decrement_loop() {
+  int counter = 100;  // Should NOT warn - accumulator with post-decrement
+  while (counter > 0) {
+    counter--;
+  }
+}
+
+// Pre-decrement in loop - should NOT warn (accumulator pattern)
+void test_pre_decrement_loop() {
+  int counter = 100;  // Should NOT warn - accumulator with pre-decrement
+  while (counter > 0) {
+    --counter;
+  }
+}
+
+#if 0
+// Unary operators outside loops - currently warns (false positive)
+void test_unary_outside_loop() {
+  int value = 10;
+  // Currently warns but shouldn't - moving would change semantics (loses initialization)
+  if (true) {
+    value++;
+  }
+}
+#endif
+
+// Container accumulation patterns - should NOT warn
+
+// Array-like accumulation - should NOT warn
+void test_array_accumulation() {
+  int results[10];  // Should NOT warn - array accumulator
+  int index = 0;    // Should NOT warn - index accumulator
+  for (int i = 0; i < 10; ++i) {
+    results[index++] = i;
+  }
+}
+
+// Simple string accumulation - should NOT warn
+void test_simple_string_accumulation() {
+  char message[100] = "";  // Should NOT warn - string accumulator
+  int len = 0;             // Should NOT warn - length accumulator
+  for (int i = 0; i < 5; ++i) {
+    message[len++] = 'A' + i;
+  }
+}
+
+// Bitwise accumulation patterns - some already handled, some might not be
+
+// Bitwise OR with compound assignment - should NOT warn (already handled)
+void test_bitwise_compound() {
+  int flags = 0;  // Should NOT warn - compound assignment accumulator
+  for (int i = 0; i < 8; ++i) {
+    flags |= (1 << i);
+  }
+}
+
+// Bitwise OR with explicit assignment - should NOT warn
+void test_bitwise_explicit() {
+  int flags = 0;  // Should NOT warn - bitwise accumulator pattern
+  for (int i = 0; i < 8; ++i) {
+    flags = flags | (1 << i);
+  }
+}
+
+// Bitwise AND accumulation - should NOT warn
+void test_bitwise_and() {
+  int mask = 0xFF;  // Should NOT warn - bitwise accumulator pattern
+  for (int i = 0; i < 8; ++i) {
+    mask = mask & ~(1 << i);
+  }
+}
+
+// Scope reduction opportunities the checker might miss
+
+// Variable used only in loop condition - might be movable
+void test_loop_condition_only() {
+  int limit = calculateLimit();  // Might be movable to for-loop init
+  for (int i = 0; i < limit; ++i) {
+    // body doesn't use limit
+    doSomething(i);
+  }
+}
+
+// Variable in exception handling - should warn
+void test_exception_handling() {
+  int errorCode = getDefaultError();
+  // Should warn - errorCode could be moved to catch block
+  try {
+    riskyOperation();
+  } catch (const Exception& e) {
+    errorCode = e.getCode();
+    handleError(errorCode);
+  }
+}
+
+// Complex initialization dependencies
+void test_initialization_chain() {
+  int a = expensive();
+  int b = a * 2;  // b could potentially be moved if only used in smaller scope
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'b' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:9: note: used here
+  // CHECK-NOTES: :[[@LINE+2]]:18: note: can be declared in this scope
+  // Should warn for b - it could be moved to if-block
+  if (condition) {
+    use(b);
+  }
+}
+
+// Variable used in nested function calls
+void test_nested_calls() {
+  int temp = getValue();  // Should NOT warn - initialized with function call
+  if (condition) {
+    processResult(transform(temp));
+  }
+}
+
+// Multiple assignment patterns in same loop
+void test_multiple_assignments() {
+  int sum = 0;     // Should NOT warn - accumulator
+  int count = 0;   // Should NOT warn - accumulator
+  for (int i = 0; i < 10; ++i) {
+    sum += i;
+    count++;
+  }
+}
+
+// Mixed accumulator and non-accumulator usage
+void test_mixed_usage_complex() {
+  int value = 0;   // Complex case - accumulator in loop, then used elsewhere
+  for (int i = 0; i < 5; ++i) {
+    value += i;    // Accumulator usage
+  }
+  if (condition) {
+    value = 100;   // Non-accumulator usage
+    process(value);
+  }
+}
+
+// Variable modified in loop but not accumulator pattern
+void test_non_accumulator_modification() {
+  int temp = 0;
+  // Should warn - temp is modified but not in accumulator pattern
+  for (int i = 0; i < 10; ++i) {
+    temp = i * 2;  // Overwrites previous value, not accumulating
+    use(temp);
+  }
+}
+
+// Accumulator with function calls
+void test_accumulator_with_calls() {
+  int total = 0;   // Should NOT warn - accumulator pattern with function calls
+  for (int i = 0; i < 10; ++i) {
+    total += calculateValue(i);
+  }
+}
+
+// Conditional accumulation
+void test_conditional_accumulation() {
+  int sum = 0;     // Should NOT warn - conditional accumulator
+  for (int i = 0; i < 10; ++i) {
+    if (i % 2 == 0) {
+      sum += i;
+    }
+  }
+}

>From 539a0f219875fb5bdeab60aa28242150269dde41 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Thu, 22 Jan 2026 11:58:28 +0100
Subject: [PATCH 29/38] Update - fix a false positive accumulator case.

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 54 +++++++++++--------
 .../checkers/misc/scope-reduction.cpp         | 21 ++++----
 2 files changed, 43 insertions(+), 32 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index c2f6602eedb6c..f3191580a4751 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-// This checker uses an 8-step algorithm to accomplish scope analysis of a
+// This checker uses a 9-step algorithm to accomplish scope analysis of a
 // variable and determine if it can be declared in a smaller scope. Note that
 // the clang-tidy framework is aimed mainly at supporting text-manipulation,
 // diagnostics, or common AST patterns. Scope reduction analysis is
@@ -15,7 +15,7 @@
 // this code in a more concrete way other than simply suggesting it can
 // be simpler.
 //
-// The 8-step algorithm used by this checker for scope reduction analysis is:
+// The 9-step algorithm used by this checker for scope reduction analysis is:
 // 1) AST Matcher Filtering
 //    - Only match variables within functions (hasAncestor(functionDecl())
 //    - Exclude for-loop declared variables
@@ -24,6 +24,7 @@
 //       (unless(hasInitializer(...)))
 //    - Exclude parameters from analysis
 //       (unless(parmVarDecl())
+//    - Exclude try-catch variables (unless(hasParent(cxxCatchStmt())))
 // 2) Collect variable uses
 //    - Find all DeclRefExpr nodes that reference the variable
 // 3) Build scope chains
@@ -31,22 +32,21 @@
 //      innermost to outermost)
 // 4) Find the innermost compound statement that contains all uses
 //    - This is the smallest scope where the variable could be declared
-// 5) Check for loop usage and variable modifications
-//    - Skip analysis for any variable used within loops to avoid false
-//    positives
-//    - Skip analysis for variables modified in smaller scopes (changes
-//    semantics)
+// 5) Check for modification detection
+//    - Skip analysis for initialized variables modified with unary operators
+//    - This prevents moving variables where initialization would be lost
+//    - Skip analysis for variables modified with simple assignments
+// 6) Check for loop body placement
+//    - Skip analysis if suggested scope would place variable inside loop body
 //    - This prevents suggesting moving variables into loop bodies (inefficient)
-//    - This prevents moving variables that are modified, changing their
-//    lifetime
-// 6) Switch case analysis
+// 7) Switch case analysis
 //    - Check if variable uses span multiple case labels in the same switch
 //    - Skip analysis if so, as variables cannot be declared in switch body
-// 7) Verify scope nesting and report
+// 8) Verify scope nesting and report
 //    - Find the compound statement containing the variable declaration
 //    - Only report if the usage scope is nested within the declaration scope
 //    - This ensures we only suggest moving variables to smaller scopes
-// 8) Alternative analysis - check for for-loop initialization opportunity
+// 9) Alternative analysis - check for for-loop initialization opportunity
 //    - Only runs if compound statement analysis didn't find a smaller scope
 //    - Only check local variables, not parameters
 //    - Determine if all uses are within the same for-loop and suggest
@@ -181,15 +181,27 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 5: Check if suggested scope would place variable inside loop body
-  // or if variable is modified in suggested scope (changing semantics)
+  // Step 5: Check for modification detection
+  // Skip analysis for initialized variables that would lose initialization
+  // semantics
   if (InnermostScope) {
     for (const auto *Use : Uses) {
-      // TODO: Implement precise modification detection
-      // Current approach is too complex and breaks existing tests
-      // For now, accept the false positive in test_unary_outside_loop
+      // Check if initialized variable is modified with unary operators
+      // Moving would lose initialization value
+      if (Var->hasInit()) {
+        auto UseParents =
+            Result.Context->getParentMapContext().getParents(*Use);
+        if (!UseParents.empty()) {
+          if (const auto *UnaryOp = UseParents[0].get<UnaryOperator>()) {
+            if (UnaryOp->isIncrementDecrementOp()) {
+              return; // Skip - moving initialized variable that gets
+                      // incremented/decremented would lose initialization
+            }
+          }
+        }
+      }
 
-      // Check if this use is inside a loop
+      // Step 6: Check if this use is inside a loop
       const Stmt *Current = Use;
       const Stmt *ContainingLoop = nullptr;
 
@@ -243,7 +255,7 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 6: Check if current variable declaration can be moved to a smaller
+  // Step 7: Check if current variable declaration can be moved to a smaller
   // scope
   if (InnermostScope) {
     // Check if variable uses span multiple case labels in the same switch
@@ -301,7 +313,7 @@ void ScopeReductionCheck::check(
       ParentNodes = Parents.getParents(*Parent);
     }
 
-    // Step 7: Verify that usage scope is nested within declaration scope
+    // Step 8: Verify that usage scope is nested within declaration scope
     // Only report if we can move the variable to a smaller scope
     if (VarScope && VarScope != InnermostScope) {
       // Walk up from innermost usage scope to see if declaration scope is
@@ -338,7 +350,7 @@ void ScopeReductionCheck::check(
     }
   }
 
-  // Step 8: Alternative analysis - check for for-loop initialization
+  // Step 9: Alternative analysis - check for for-loop initialization
   // opportunity This only runs if the compound statement analysis didn't find
   // a smaller scope Only check local variables, not parameters
   const ForStmt *CommonForLoop = nullptr;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 318dc5400dd16..02e84787aedc5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -631,17 +631,6 @@ void test_pre_decrement_loop() {
   }
 }
 
-#if 0
-// Unary operators outside loops - currently warns (false positive)
-void test_unary_outside_loop() {
-  int value = 10;
-  // Currently warns but shouldn't - moving would change semantics (loses initialization)
-  if (true) {
-    value++;
-  }
-}
-#endif
-
 // Container accumulation patterns - should NOT warn
 
 // Array-like accumulation - should NOT warn
@@ -781,3 +770,13 @@ void test_conditional_accumulation() {
     }
   }
 }
+
+
+// Unary operators outside loops - should NOT warn
+void test_unary_outside_loop() {
+  int value = 10;
+  // Should NOT warn - moving would change semantics (loses initialization)
+  if (true) {
+    value++;
+  }
+}

>From 496548793937c582c00f95e5d3a4d991d83058be Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Thu, 22 Jan 2026 12:46:01 +0100
Subject: [PATCH 30/38] Update - address false positives in switch blocks

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 33 +++++++++++++++++--
 .../checkers/misc/scope-reduction.cpp         | 18 ++++------
 2 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index f3191580a4751..0c3487298c03a 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -34,8 +34,9 @@
 //    - This is the smallest scope where the variable could be declared
 // 5) Check for modification detection
 //    - Skip analysis for initialized variables modified with unary operators
-//    - This prevents moving variables where initialization would be lost
-//    - Skip analysis for variables modified with simple assignments
+//    - Skip analysis for initialized variables moved into switch bodies
+//    - This prevents moving variables where initialization would be lost or
+//    conditional
 // 6) Check for loop body placement
 //    - Skip analysis if suggested scope would place variable inside loop body
 //    - This prevents suggesting moving variables into loop bodies (inefficient)
@@ -183,7 +184,7 @@ void ScopeReductionCheck::check(
 
   // Step 5: Check for modification detection
   // Skip analysis for initialized variables that would lose initialization
-  // semantics
+  // semantics or make initialization conditional
   if (InnermostScope) {
     for (const auto *Use : Uses) {
       // Check if initialized variable is modified with unary operators
@@ -201,6 +202,32 @@ void ScopeReductionCheck::check(
         }
       }
 
+      // Check if initialized variable would be moved into a switch body
+      // Moving would make initialization conditional on case execution
+      if (Var->hasInit() && InnermostScope) {
+        auto ScopeParents =
+            Result.Context->getParentMapContext().getParents(*InnermostScope);
+        if (!ScopeParents.empty()) {
+          const Stmt *ScopeParent = ScopeParents[0].get<Stmt>();
+          if (!ScopeParent) {
+            if (const auto *DeclParent = ScopeParents[0].get<Decl>()) {
+              auto DeclParentNodes =
+                  Result.Context->getParentMapContext().getParents(*DeclParent);
+              if (!DeclParentNodes.empty())
+                ScopeParent = DeclParentNodes[0].get<Stmt>();
+            }
+          }
+
+          // Check if the suggested scope is the body of a switch statement
+          if (const auto *Switch = dyn_cast_or_null<SwitchStmt>(ScopeParent)) {
+            if (Switch->getBody() == InnermostScope) {
+              return; // Skip - moving initialized variable into switch body
+                      // would make initialization conditional
+            }
+          }
+        }
+      }
+
       // Step 6: Check if this use is inside a loop
       const Stmt *Current = Use;
       const Stmt *ContainingLoop = nullptr;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 02e84787aedc5..ad673a333c78d 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -76,12 +76,10 @@ void test_for_loop_expressions() {
   }
 }
 
-// Variable can be moved to switch case
+// should NOT warn.
+// moving would make initialization conditional
 void test_switch_case(int value) {
   int result = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'result' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+4]]:7: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:18: note: can be declared in this scope
   switch (value) {
     case 1:
       result = 10;
@@ -286,12 +284,10 @@ void test_switch_mixed_usage(int value) {
   }
 }
 
-// Variable in nested switch - should warn for single case
+// Variable in nested switch - should NOT warn
+// moving would make initialization conditional
 void test_nested_switch(int outer, int inner) {
   int nested = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'nested' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+6]]:11: note: used here
-  // CHECK-NOTES: :[[@LINE+3]]:22: note: can be declared in this scope
   switch (outer) {
     case 1:
       switch (inner) {
@@ -303,12 +299,10 @@ void test_nested_switch(int outer, int inner) {
   }
 }
 
-// Variable used in switch default only - should warn
+// Variable used in switch default only - should NOT warn
+// moving would make initialization conditional
 void test_switch_default_only(int value) {
   int def = 0;
-  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'def' can be declared in a smaller scope
-  // CHECK-NOTES: :[[@LINE+6]]:7: note: used here
-  // CHECK-NOTES: :[[@LINE+1]]:18: note: can be declared in this scope
   switch (value) {
     case 1:
       break;

>From 12fa14771c414c6b851943f3bf0da8884ea55230 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Thu, 22 Jan 2026 12:55:29 +0100
Subject: [PATCH 31/38] update - format changes

---
 .../checkers/misc/scope-reduction.cpp         | 151 ++++++++++++++++++
 1 file changed, 151 insertions(+)

diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index ad673a333c78d..2c69f04d9f197 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -774,3 +774,154 @@ void test_unary_outside_loop() {
     value++;
   }
 }
+
+// Pre-increment - should NOT warn
+void test_pre_increment_outside_loop() {
+  int value = 10;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    ++value;
+  }
+}
+
+// Post-decrement - should NOT warn  
+void test_post_decrement_outside_loop() {
+  int counter = 100;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    counter--;
+  }
+}
+
+// Pre-decrement - should NOT warn
+void test_pre_decrement_outside_loop() {
+  int counter = 100;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    --counter;
+  }
+}
+
+// Complex initialization with unary operator - should NOT warn
+void test_complex_init_with_unary() {
+  int calculated = 5 * 3 + 2;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    calculated++;
+  }
+}
+
+// Array initialization with unary operator - should NOT warn
+void test_array_init_with_unary() {
+  int arr[] = {1, 2, 3};
+  int size = 3;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    size--;
+  }
+}
+
+// Pointer initialization with unary operator - should NOT warn
+void test_pointer_init_with_unary() {
+  int value = 42;
+  int* ptr = &value;
+  // Should NOT warn - moving would lose initialization value  
+  if (true) {
+    ++ptr;
+  }
+}
+
+// Switch body edge cases
+
+// Switch with no default case - should NOT warn
+void test_switch_no_default(int value) {
+  int result = 0;
+  // Should NOT warn - moving to switch body would make initialization conditional
+  switch (value) {
+    case 1:
+      result = 10;
+      break;
+    case 2:
+      result = 20;
+      break;
+  }
+}
+
+// Switch with only default case - should NOT warn
+void test_switch_default_only_v2(int value) {
+  int result = 0;
+  // Should NOT warn - moving to switch body would make initialization conditional
+  switch (value) {
+    default:
+      result = 100;
+      break;
+  }
+}
+
+// Empty switch - should NOT warn (though variable unused)
+void test_switch_empty(int value) {
+  int result = 0;
+  // Should NOT warn - variable not used, but if it were used in switch body, would be conditional
+  switch (value) {
+  }
+}
+
+// Combination cases
+
+// Unary operator inside switch case - should NOT warn
+void test_unary_in_switch_case(int value) {
+  int counter = 0;
+  // Should NOT warn - moving to switch body would make initialization conditional
+  switch (value) {
+    case 1:
+      counter++;
+      break;
+    default:
+      break;
+  }
+}
+
+// Unary operator inside loop (accumulator pattern) - should NOT warn
+void test_unary_in_loop_accumulator() {
+  int counter = 0;
+  // Should NOT warn - accumulator pattern in loop
+  for (int i = 0; i < 10; ++i) {
+    counter++;
+  }
+}
+
+// Multiple unary operations on same variable - should NOT warn
+void test_multiple_unary_operations() {
+  int value = 10;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    value++;
+    ++value;
+    value--;
+  }
+}
+
+// Unary operator with other operations - should NOT warn
+void test_unary_with_other_ops() {
+  int value = 5;
+  // Should NOT warn - moving would lose initialization value
+  if (true) {
+    value++;
+    value *= 2;
+  }
+}
+
+// Nested switch with unary operator - should NOT warn
+void test_nested_switch_with_unary(int outer, int inner) {
+  int counter = 0;
+  // Should NOT warn - moving to outer switch would make initialization conditional
+  switch (outer) {
+    case 1:
+      switch (inner) {
+        case 1:
+          counter++;
+          break;
+      }
+      break;
+  }
+}

>From e9e734c1be488aebaf6fd1d8f32dc1835826f0fd Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Fri, 23 Jan 2026 11:41:34 +0100
Subject: [PATCH 32/38] Update - Add detection for complex class init for
 for-loop

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   |  6 +-
 .../checkers/misc/scope-reduction.cpp         | 58 +++++++++++++++++++
 2 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 0c3487298c03a..2fe97b3c1c6c0 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -87,8 +87,10 @@ void ScopeReductionCheck::registerMatchers(MatchFinder *Finder) {
               unless(hasParent(declStmt(hasParent(forStmt())))),
               unless(hasParent(declStmt(hasParent(cxxForRangeStmt())))),
               unless(hasParent(cxxCatchStmt())),
-              unless(hasInitializer(anyOf(callExpr(), cxxMemberCallExpr(),
-                                          cxxOperatorCallExpr()))))
+              unless(hasInitializer(anyOf(
+                  hasDescendant(callExpr()), hasDescendant(cxxMemberCallExpr()),
+                  hasDescendant(cxxOperatorCallExpr()), callExpr(),
+                  cxxMemberCallExpr(), cxxOperatorCallExpr()))))
           .bind("var"),
       this);
 }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 2c69f04d9f197..97b7f69a5818e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -925,3 +925,61 @@ void test_nested_switch_with_unary(int outer, int inner) {
       break;
   }
 }
+
+// Test cases for member function call initializers - should NOT suggest for-loop initialization
+// These test the fix for cases where B.buildUnmerge() was incorrectly flagged for for-loop init
+
+class Builder {
+public:
+  int buildUnmerge(int type, int reg);
+  int getNumOperands();
+};
+
+// Member function call in initializer - should NOT warn for for-loop initialization
+void test_member_function_call_initializer() {
+  Builder B;
+  int Reg = 42;
+  
+  // Should NOT suggest moving to for-loop initialization
+  // B.buildUnmerge() is a member function call and too complex for for-loop init
+  auto Unmerge = B.buildUnmerge(32, Reg);
+  for (int I = 0, E = Unmerge - 1; I != E; ++I) {
+    // use I in loop body
+    int temp = I * 2;
+  }
+}
+
+// Similar case with method chaining - should NOT warn
+void test_method_chaining() {
+  Builder B;
+  int Reg = 42;
+  
+  // Should NOT suggest moving to for-loop initialization  
+  // Method call is too complex for for-loop init
+  auto Result = B.buildUnmerge(32, Reg);
+  for (int I = 0; I < 10; ++I) {
+    int value = Result + I;
+  }
+}
+
+// Regular function call - should NOT warn (existing protection)
+int regularFunction(int x);
+void test_regular_function_call() {
+  int input = 5;
+  
+  // Should NOT suggest moving to for-loop initialization
+  auto result = regularFunction(input);
+  for (int I = 0; I < result; ++I) {
+    int temp = I;
+  }
+}
+
+// Simple initialization - should warn for for-loop initialization
+void test_simple_initialization_control() {
+  int limit = 10;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'limit' can be declared in for-loop initialization
+  // CHECK-NOTES: :[[@LINE+1]]:3: note: can be declared in this for-loop
+  for (int I = 0; I < limit; ++I) {
+    int temp = I;
+  }
+}

>From fc481dcd4243714b4ea8c6d165a8be62b08dfa9f Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Fri, 23 Jan 2026 22:25:00 +0100
Subject: [PATCH 33/38] Update - address false positives due to for-loop
 increment modifications

---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 49 ++++++++++++++
 .../checkers/misc/scope-reduction.cpp         | 65 +++++++++++++++++++
 2 files changed, 114 insertions(+)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 2fe97b3c1c6c0..fb9acdb1c2175 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -462,6 +462,55 @@ void ScopeReductionCheck::check(
       }
 
       if (IsSmaller) {
+        // Check if variable is modified in this for-loop's increment
+        // Moving to for-loop initialization would create complex, hard-to-read
+        // code
+        if (CommonForLoop && CommonForLoop->getInc()) {
+          for (const auto *Use : Uses) {
+            auto UseParents =
+                Result.Context->getParentMapContext().getParents(*Use);
+            if (!UseParents.empty()) {
+              if (const auto *BinOp = UseParents[0].get<BinaryOperator>()) {
+                if (BinOp->isAssignmentOp() && BinOp->getLHS() == Use) {
+                  if (BinOp == CommonForLoop->getInc())
+                    return; // Skip - variable modified in for-loop increment
+                }
+              }
+            }
+          }
+        }
+
+        // Check if variable's address is taken in the same for-loop
+        // Moving would break the dependency
+        if (CommonForLoop) {
+          for (const auto *Use : Uses) {
+            auto UseParents =
+                Result.Context->getParentMapContext().getParents(*Use);
+            if (!UseParents.empty()) {
+              if (const auto *UnaryOp = UseParents[0].get<UnaryOperator>()) {
+                if (UnaryOp->getOpcode() == UO_AddrOf) {
+                  // Check if this address-of is in the for-loop init
+                  if (CommonForLoop->getInit()) {
+                    const Stmt *Current = UnaryOp;
+                    while (Current) {
+                      if (Current == CommonForLoop->getInit()) {
+                        return; // Skip - variable's address taken in for-loop
+                                // init
+                      }
+                      auto CurrentParents =
+                          Result.Context->getParentMapContext().getParents(
+                              *Current);
+                      if (CurrentParents.empty())
+                        break;
+                      Current = CurrentParents[0].get<Stmt>();
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+
         diag(Var->getLocation(),
              "variable '%0' can be declared in for-loop initialization")
             << Var->getName();
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 97b7f69a5818e..20509c629c444 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -983,3 +983,68 @@ void test_simple_initialization_control() {
     int temp = I;
   }
 }
+
+// Test cases for for-loop increment modification - should NOT suggest for-loop initialization
+// These test the fix for variables modified in for-loop increment expressions
+
+struct Node {
+  Node* getNext();
+};
+
+// Variable initialized and modified in for-loop increment - should NOT warn
+void test_for_loop_increment_modification() {
+  Node root;
+
+  // Should NOT suggest moving to for-loop initialization
+  // M is initialized to &root, then modified in for-loop increment
+  // Moving would lose the initialization value
+  Node* M = &root;
+  for (; M; M = M->getNext()) {
+    // use M in loop body
+    if (M) {
+      // process node
+    }
+  }
+}
+
+// Similar case with different initialization - should NOT warn
+void test_for_loop_increment_modification_v2() {
+  Node nodes[10];
+
+  // Should NOT suggest moving to for-loop initialization
+  // ptr is initialized to nodes, then modified in increment
+  Node* ptr = nodes;
+  for (; ptr; ptr = ptr->getNext()) {
+    // process ptr
+  }
+}
+
+// Variable modified in for-loop increment but dependencies prevent moving - should NOT warn
+void test_for_loop_increment_uninitialized() {
+  Node root;
+
+  // Should NOT suggest moving root or current to for-loop initialization
+  // root: address taken in for-loop init (&root)
+  // current: modified in for-loop increment
+  Node* current;
+  for (current = &root; current; current = current->getNext()) {
+    // use current
+  }
+}
+
+// Variable used in for-loop but not modified in increment - should warn
+void test_for_loop_not_modified_in_increment() {
+  Node root;
+
+  // Should suggest moving to for-loop initialization
+  // node is used in condition but not modified in increment
+  Node* node = &root;
+  // CHECK-NOTES: :[[@LINE-1]]:9: warning: variable 'node' can be declared in for-loop initialization
+  // CHECK-NOTES: :[[@LINE+1]]:3: note: can be declared in this for-loop
+  for (int i = 0; node && i < 10; ++i) {
+    // use node but don't modify it in increment
+    if (node) {
+      // process
+    }
+  }
+}

>From cff58bd036a33d37575a114de8a93a2d2bbd8ca8 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 24 Jan 2026 11:01:57 +0100
Subject: [PATCH 34/38] Update - Helper function refactoring

Improves maintainability, reduces code
---
 .../clang-tidy/misc/ScopeReductionCheck.cpp   | 139 ++++++++----------
 .../clang-tidy/misc/ScopeReductionCheck.h     |  13 ++
 2 files changed, 77 insertions(+), 75 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index fb9acdb1c2175..4922d5c2efa05 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -231,34 +231,21 @@ void ScopeReductionCheck::check(
       }
 
       // Step 6: Check if this use is inside a loop
-      const Stmt *Current = Use;
-      const Stmt *ContainingLoop = nullptr;
-
-      while (Current) {
-        auto CurrentParents =
-            Result.Context->getParentMapContext().getParents(*Current);
-        if (CurrentParents.empty())
-          break;
-
-        const Stmt *Parent = CurrentParents[0].get<Stmt>();
-        if (!Parent) {
-          // Try to get Decl parent and continue from there
-          if (const auto *DeclParent = CurrentParents[0].get<Decl>()) {
-            auto DeclParentNodes =
-                Result.Context->getParentMapContext().getParents(*DeclParent);
-            if (!DeclParentNodes.empty())
-              Parent = DeclParentNodes[0].get<Stmt>();
-          }
-          if (!Parent)
-            break;
-        }
+      auto &Parents = Result.Context->getParentMapContext();
 
-        if (isa<ForStmt>(Parent) || isa<WhileStmt>(Parent) ||
-            isa<DoStmt>(Parent) || isa<CXXForRangeStmt>(Parent)) {
-          ContainingLoop = Parent;
-          break;
-        }
-        Current = Parent;
+      // Check if use is in any type of loop and get the specific loop
+      const Stmt *ContainingLoop = nullptr;
+      if (const auto *ForLoop = findAncestorOfType<ForStmt>(Use, Parents)) {
+        ContainingLoop = ForLoop;
+      } else if (const auto *WhileLoop =
+                     findAncestorOfType<WhileStmt>(Use, Parents)) {
+        ContainingLoop = WhileLoop;
+      } else if (const auto *DoLoop =
+                     findAncestorOfType<DoStmt>(Use, Parents)) {
+        ContainingLoop = DoLoop;
+      } else if (const auto *RangeLoop =
+                     findAncestorOfType<CXXForRangeStmt>(Use, Parents)) {
+        ContainingLoop = RangeLoop;
       }
 
       // If use is in a loop, check if suggested scope is inside that loop
@@ -294,29 +281,13 @@ void ScopeReductionCheck::check(
     bool UsesInSwitch = false;
 
     for (const auto *Use : Uses) {
-      const Stmt *Current = Use;
-      const SwitchCase *ContainingCase = nullptr;
-
-      // Walk up to find containing case label
-      while (Current) {
-        auto ParentNodes = Parents.getParents(*Current);
-        if (ParentNodes.empty())
-          break;
-
-        const Stmt *Parent = ParentNodes[0].get<Stmt>();
-        if (!Parent)
-          break;
+      // Find containing switch case using helper
+      const SwitchCase *ContainingCase = findContainingSwitchCase(Use, Parents);
 
-        if (const auto *CaseStmt = dyn_cast<SwitchCase>(Parent)) {
-          ContainingCase = CaseStmt;
-          UsesInSwitch = true;
-          break;
-        }
-        Current = Parent;
-      }
-
-      if (ContainingCase)
+      if (ContainingCase) {
         CaseLabels.insert(ContainingCase);
+        UsesInSwitch = true;
+      }
     }
 
     // If uses span multiple case labels, skip analysis
@@ -386,33 +357,9 @@ void ScopeReductionCheck::check(
   bool AllUsesInSameForLoop = true;
 
   for (const auto *Use : Uses) {
-    const ForStmt *ContainingForLoop = nullptr;
-    const Stmt *Current = Use;
-
-    // Walk up the AST to find a containing ForStmt
-    while (Current) {
-      auto ParentNodes = Parents.getParents(*Current);
-      if (ParentNodes.empty())
-        break;
-
-      if (const auto *FS = ParentNodes[0].get<ForStmt>()) {
-        ContainingForLoop = FS;
-        break;
-      }
-
-      const Stmt *Parent = ParentNodes[0].get<Stmt>();
-      if (!Parent) {
-        // Handle Decl parents like we do in the existing logic
-        if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
-          auto DeclParentNodes = Parents.getParents(*DeclParent);
-          if (!DeclParentNodes.empty())
-            Parent = DeclParentNodes[0].get<Stmt>();
-        }
-        if (!Parent)
-          break;
-      }
-      Current = Parent;
-    }
+    // Find containing for-loop using helper
+    const ForStmt *ContainingForLoop =
+        findAncestorOfType<ForStmt>(Use, Parents);
 
     if (!ContainingForLoop) {
       AllUsesInSameForLoop = false;
@@ -536,3 +483,45 @@ void ScopeReductionCheck::emitUsageNotes(
 }
 
 } // namespace clang::tidy::misc
+
+template <typename T>
+const T *clang::tidy::misc::ScopeReductionCheck::findAncestorOfType(
+    const Stmt *Start, ParentMapContext &Parents) {
+  const Stmt *Current = Start;
+  while (Current) {
+    auto ParentNodes = Parents.getParents(*Current);
+    if (ParentNodes.empty())
+      break;
+
+    const Stmt *Parent = ParentNodes[0].get<Stmt>();
+    if (!Parent) {
+      // Try to get Decl parent and continue from there
+      if (const auto *DeclParent = ParentNodes[0].get<Decl>()) {
+        auto DeclParentNodes = Parents.getParents(*DeclParent);
+        if (!DeclParentNodes.empty())
+          Parent = DeclParentNodes[0].get<Stmt>();
+      }
+      if (!Parent)
+        break;
+    }
+
+    if (const auto *Result = dyn_cast<T>(Parent))
+      return Result;
+    Current = Parent;
+  }
+  return nullptr;
+}
+
+bool clang::tidy::misc::ScopeReductionCheck::isInLoop(
+    const Stmt *Use, ParentMapContext &Parents) {
+  return findAncestorOfType<ForStmt>(Use, Parents) ||
+         findAncestorOfType<WhileStmt>(Use, Parents) ||
+         findAncestorOfType<DoStmt>(Use, Parents) ||
+         findAncestorOfType<CXXForRangeStmt>(Use, Parents);
+}
+
+const clang::SwitchCase *
+clang::tidy::misc::ScopeReductionCheck::findContainingSwitchCase(
+    const Stmt *Use, ParentMapContext &Parents) {
+  return findAncestorOfType<clang::SwitchCase>(Use, Parents);
+}
diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
index 3a4b3bb760a3c..6d26f1d3aecdb 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.h
@@ -34,6 +34,19 @@ class ScopeReductionCheck : public ClangTidyCheck {
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
 
 private:
+  /// Find the first ancestor of the specified type by walking up the AST.
+  /// Returns nullptr if no ancestor of the specified type is found.
+  template <typename T>
+  const T *findAncestorOfType(const Stmt *Start, ParentMapContext &Parents);
+
+  /// Check if a statement is inside any type of loop.
+  bool isInLoop(const Stmt *Use, ParentMapContext &Parents);
+
+  /// Find the containing switch case for a statement.
+  /// Returns nullptr if not inside a switch case.
+  const clang::SwitchCase *findContainingSwitchCase(const Stmt *Use,
+                                                    ParentMapContext &Parents);
+
   /// Emit diagnostic notes showing where the variable is used.
   /// Limits output to avoid excessive noise in diagnostics.
   void emitUsageNotes(const llvm::SmallVector<const DeclRefExpr *, 8> &Uses);

>From a0a7e78b416996628116a4acae7a5498b0af7ad7 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 24 Jan 2026 16:37:26 +0100
Subject: [PATCH 35/38] update - expand test coverage

---
 .../checkers/misc/scope-reduction.cpp         | 361 ++++++++++++++++++
 1 file changed, 361 insertions(+)

diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
index 20509c629c444..ebff11f7ed8d5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/scope-reduction.cpp
@@ -1048,3 +1048,364 @@ void test_for_loop_not_modified_in_increment() {
     }
   }
 }
+
+// =============================================================================
+// C-SPECIFIC CONSTRUCTS
+// =============================================================================
+
+// C-style array with scope reduction opportunity
+void test_c_style_array() {
+  int arr[10];
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'arr' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:10: note: can be declared in this scope
+  if (1) {
+    arr[0] = 42;
+    arr[1] = 43;
+  }
+}
+
+// Function pointer usage
+int add(int a, int b) { return a + b; }
+void test_function_pointer() {
+  int (*func_ptr)(int, int) = add;
+  // CHECK-NOTES: :[[@LINE-1]]:9: warning: variable 'func_ptr' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:18: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:10: note: can be declared in this scope
+  if (1) {
+    int result = func_ptr(1, 2);
+  }
+}
+
+// C-style struct initialization
+struct Point {
+  int x, y;
+};
+
+void test_c_struct_init() {
+  struct Point p = {10, 20};
+  // CHECK-NOTES: :[[@LINE-1]]:16: warning: variable 'p' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:15: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:21: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:10: note: can be declared in this scope
+  if (1) {
+    int sum = p.x + p.y;
+  }
+}
+
+// goto statement with variable scope
+void test_goto_statement() {
+  int value = 42;
+  // Should NOT warn - used across goto boundary
+  if (value > 0) {
+    goto end;
+  }
+  value = 0;
+end:
+  return;
+}
+
+// =============================================================================
+// MODERN C++ FEATURES
+// =============================================================================
+
+// auto type deduction edge cases
+void test_auto_deduction() {
+  auto value = 42;
+  // CHECK-NOTES: :[[@LINE-1]]:8: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int doubled = value * 2;
+  }
+}
+
+// auto with complex type deduction - simplified without std::vector
+void test_auto_complex() {
+  auto value = 42;
+  // CHECK-NOTES: :[[@LINE-1]]:8: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int doubled = value * 2;
+  }
+}
+
+// Structured bindings - simplified without std::pair
+struct SimplePair {
+  int first, second;
+};
+
+void test_structured_bindings() {
+  SimplePair pair_val = {1, 2};
+  // CHECK-NOTES: :[[@LINE-1]]:14: warning: variable 'pair_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:15: note: used here
+  // CHECK-NOTES: :[[@LINE+3]]:32: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int sum = pair_val.first + pair_val.second;
+  }
+}
+
+// constexpr variables
+void test_constexpr_variable() {
+  constexpr int compile_time_val = 42;
+  // CHECK-NOTES: :[[@LINE-1]]:17: warning: variable 'compile_time_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:18: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int result = compile_time_val * 2;
+  }
+}
+
+// if constexpr (C++17)
+template<bool B>
+void test_if_constexpr() {
+  int value = 10;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:20: note: can be declared in this scope
+  if constexpr (B) {
+    int doubled = value * 2;
+  }
+}
+
+// =============================================================================
+// COMPLEX CONTROL FLOW
+// =============================================================================
+
+// Switch with fallthrough cases
+void test_switch_fallthrough(int value) {
+  int result = 0;
+  // Should NOT warn - used across multiple cases with fallthrough
+  switch (value) {
+    case 1:
+      result = 10;
+      // fallthrough
+    case 2:
+      result += 5;
+      break;
+    default:
+      break;
+  }
+}
+
+// Nested try-catch - simplified without std::runtime_error
+void test_nested_try_catch() {
+  int error_code = 0;
+  // Should NOT warn - used in multiple exception contexts
+  try {
+    try {
+      error_code = 100;
+      throw 42; // throw int instead of std::runtime_error
+    } catch (int) {
+      error_code = 200;
+      throw;
+    }
+  } catch (...) {
+    error_code = 300;
+  }
+}
+
+// Loop with break/continue affecting scope
+void test_loop_break_continue() {
+  int counter = 0;
+  // Should NOT warn - counter used across break/continue boundaries
+  for (int i = 0; i < 10; ++i) {
+    if (i % 2 == 0) {
+      counter++;
+      continue;
+    }
+    if (counter > 5) {
+      break;
+    }
+    counter += 2;
+  }
+}
+
+// Nested loops with different variable usage
+void test_nested_loop_patterns() {
+  int outer_var = 0;
+  int inner_var = 0;
+  // outer_var: should NOT warn
+  // inner_var: should NOT warn
+  for (int i = 0; i < 5; ++i) {
+    outer_var += i;
+    for (int j = 0; j < 3; ++j) {
+      inner_var = j * 2;
+      int temp = inner_var + 1;
+    }
+  }
+}
+
+// =============================================================================
+// VARIABLE LIFETIME EDGE CASES
+// =============================================================================
+
+// RAII pattern with destructor
+class Resource {
+public:
+  Resource() {}
+  ~Resource() {}
+  void use() {}
+};
+
+void test_raii_pattern() {
+  Resource res;
+  // CHECK-NOTES: :[[@LINE-1]]:12: warning: variable 'res' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:5: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    res.use();
+  }
+  // Destructor called here
+}
+
+// Static local variable in different contexts
+void test_static_local_contexts() {
+  static int call_count = 0;
+  // Should NOT warn - static variables have different lifetime semantics
+  if (true) {
+    call_count++;
+  }
+}
+
+// Thread-local variable (C++11)
+thread_local int tls_var = 0;
+void test_thread_local() {
+  int local_copy = tls_var;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'local_copy' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int doubled = local_copy * 2;
+  }
+}
+
+// =============================================================================
+// PREPROCESSOR INTERACTIONS
+// =============================================================================
+
+#define USE_VAR(x) ((x) * 2)
+
+// Variable used through macro
+void test_macro_usage() {
+  int value = 10;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'value' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:26: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int result = USE_VAR(value);
+  }
+}
+
+// Conditional compilation
+// Should NOT warn - variable only used in conditional block
+void test_conditional_compilation_undefined() {
+  int debug_var = 42;
+#ifdef DEBUG
+  if (true) {
+    int temp = debug_var;
+  }
+#endif
+}
+
+#define DEBUG_DEFINED
+// Should warn - variable used in conditional block
+void test_conditional_compilation_defined() {
+  int debug_var = 42;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'debug_var' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+4]]:16: note: used here
+  // CHECK-NOTES: :[[@LINE+2]]:13: note: can be declared in this scope
+#ifdef DEBUG_DEFINED
+  if (true) {
+    int temp = debug_var;
+  }
+#endif
+}
+
+// =============================================================================
+// ADDITIONAL EDGE CASES
+// =============================================================================
+
+// Variable declared in one scope, used in sibling scope
+void test_sibling_scopes() {
+  int shared = 0;
+  // Should NOT warn - used across sibling scopes
+  if (true) {
+    shared = 10;
+  } else {
+    shared = 20;
+  }
+}
+
+// Variable with comma operator
+void test_comma_operator() {
+  int a = 1, b = 2;
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'a' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:15: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:13: note: can be declared in this scope
+  // CHECK-NOTES: :[[@LINE-4]]:14: warning: variable 'b' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:19: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int sum = a + b;
+  }
+}
+
+// Variable in ternary operator
+void test_ternary_operator() {
+  // Should warn for true_val and false_val - only used in ternary
+  int condition_var = 1;
+  int true_val = 10;
+  int false_val = 20;
+  // CHECK-NOTES: :[[@LINE-3]]:7: warning: variable 'condition_var' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+9]]:18: note: used here
+  // CHECK-NOTES: :[[@LINE+7]]:13: note: can be declared in this scope
+  // CHECK-NOTES: :[[@LINE-5]]:7: warning: variable 'true_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:34: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:13: note: can be declared in this scope
+  // CHECK-NOTES: :[[@LINE-7]]:7: warning: variable 'false_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:45: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int result = condition_var ? true_val : false_val;
+  }
+}
+
+void test_ternary_operator_cond(int cond) {
+  // Should warn for true_val and false_val - only used in ternary
+  int true_val = 10;
+  int false_val = 20;
+  // CHECK-NOTES: :[[@LINE-2]]:7: warning: variable 'true_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+6]]:25: note: used here
+  // CHECK-NOTES: :[[@LINE+4]]:13: note: can be declared in this scope
+  // CHECK-NOTES: :[[@LINE-4]]:7: warning: variable 'false_val' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:36: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int result = cond ? true_val : false_val;
+  }
+}
+
+// Variable used in sizeof expression
+void test_sizeof_usage() {
+  // Should warn
+  int array[100];
+  // CHECK-NOTES: :[[@LINE-1]]:7: warning: variable 'array' can be declared in a smaller scope
+  // CHECK-NOTES: :[[@LINE+3]]:23: note: used here
+  // CHECK-NOTES: :[[@LINE+1]]:13: note: can be declared in this scope
+  if (true) {
+    int size = sizeof(array);
+  }
+}
+
+// Variable used in decltype (C++11)
+void test_decltype_usage() {
+  int value = 42;
+  // Should NOT warn - decltype doesn't evaluate the expression
+  if (true) {
+    decltype(value) another = 10;
+  }
+}

>From 1d40516b552c0f88327c4f8e7620581a7337bdfa Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 24 Jan 2026 17:32:42 +0100
Subject: [PATCH 36/38] Update - fix lint error

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index 4922d5c2efa05..dfbe0a032136f 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -358,7 +358,7 @@ void ScopeReductionCheck::check(
 
   for (const auto *Use : Uses) {
     // Find containing for-loop using helper
-    const ForStmt *ContainingForLoop =
+    const auto *ContainingForLoop =
         findAncestorOfType<ForStmt>(Use, Parents);
 
     if (!ContainingForLoop) {

>From 5e095fa89332bdcac2d50a138f547391c923f74c Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 24 Jan 2026 17:38:06 +0100
Subject: [PATCH 37/38] Update - fix next lint error

---
 clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
index dfbe0a032136f..a86c96de008e8 100644
--- a/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ScopeReductionCheck.cpp
@@ -358,8 +358,7 @@ void ScopeReductionCheck::check(
 
   for (const auto *Use : Uses) {
     // Find containing for-loop using helper
-    const auto *ContainingForLoop =
-        findAncestorOfType<ForStmt>(Use, Parents);
+    const auto *ContainingForLoop = findAncestorOfType<ForStmt>(Use, Parents);
 
     if (!ContainingForLoop) {
       AllUsesInSameForLoop = false;

>From fb9c927623466c0900c22bb9c2a53752a69744a1 Mon Sep 17 00:00:00 2001
From: Vince Bridgers <vince.a.bridgers at ericsson.com>
Date: Sat, 24 Jan 2026 19:36:21 +0100
Subject: [PATCH 38/38] Update - Remove Limitations section, false positive was
 addressed

---
 .../checks/misc/scope-reduction.rst           | 27 -------------------
 1 file changed, 27 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
index bf920bf26f820..94b5867219165 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/misc/scope-reduction.rst
@@ -40,33 +40,6 @@ Examples:
       }
     }
 
-Limitations
------------
-
-This check cannot currently detect when a variable's previous value affects
-subsequent iterations, resulting in false positives in some cases. This can
-be addressed by implementing a pattern matcher that recognizes this
-accumulator pattern across loop iterations or by using Clang's built-in
-Lifetime analysis.
-
-.. code-block:: cpp
-
-    void test_while_loop() {
-      // falsely detects 'counter' can be moved to smaller scope
-      int counter = 0;
-      while (true) {
-        counter++;
-        if (counter > 10) break;
-      }
-    }
-
-    void test_for_loop_reuse() {
-      int temp = 0; // falsely detects 'temp' can be moved to smaller scope
-      for (int i = 0; i<10; i++) {
-        temp += i;
-      }
-    }
-
 References
 ----------
 



More information about the cfe-commits mailing list