[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