diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in
index e5a8a5eb462..bb063f1f102 100644
--- a/gcc/rust/Make-lang.in
+++ b/gcc/rust/Make-lang.in
@@ -200,6 +200,9 @@ GRS_OBJS = \
rust/rust-const-checker.o \
rust/rust-lint-marklive.o \
rust/rust-lint-unused-var.o \
+ rust/rust-unused-checker.o \
+ rust/rust-unused-collector.o \
+ rust/rust-unused-context.o \
rust/rust-readonly-check.o \
rust/rust-hir-type-check-path.o \
rust/rust-unsafe-checker.o \
@@ -432,6 +435,7 @@ RUST_INCLUDES = -I $(srcdir)/rust \
-I $(srcdir)/rust/typecheck \
-I $(srcdir)/rust/checks/lints \
-I $(srcdir)/rust/checks/errors \
+ -I $(srcdir)/rust/checks/lints/unused \
-I $(srcdir)/rust/checks/errors/privacy \
-I $(srcdir)/rust/checks/errors/borrowck \
-I $(srcdir)/rust/checks/errors/feature \
@@ -502,6 +506,11 @@ rust/%.o: rust/checks/lints/%.cc
$(COMPILE) $(RUST_CXXFLAGS) $(RUST_INCLUDES) $<
$(POSTCOMPILE)
+# build unused checking pass files in rust folder
+rust/%.o: rust/checks/lints/unused/%.cc
+ $(COMPILE) $(RUST_CXXFLAGS) $(RUST_INCLUDES) $<
+ $(POSTCOMPILE)
+
# build rust/checks/errors files in rust folder
rust/%.o: rust/checks/errors/%.cc
$(COMPILE) $(RUST_CXXFLAGS) $(RUST_INCLUDES) $<
diff --git a/gcc/rust/checks/lints/unused/rust-unused-checker.cc b/gcc/rust/checks/lints/unused/rust-unused-checker.cc
new file mode 100644
index 00000000000..3585c0537cb
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-checker.cc
@@ -0,0 +1,99 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-unused-checker.h"
+#include "rust-hir-expr.h"
+#include "rust-hir-item.h"
+
+#include "options.h"
+
+namespace Rust {
+namespace Analysis {
+UnusedChecker::UnusedChecker ()
+ : nr_context (
+ Resolver2_0::ImmutableNameResolutionContext::get ().resolver ()),
+ mappings (Analysis::Mappings::get ()), unused_context (UnusedContext ())
+{}
+void
+UnusedChecker::go (HIR::Crate &crate)
+{
+ UnusedCollector collector (unused_context);
+ collector.go (crate);
+ for (auto &item : crate.get_items ())
+ item->accept_vis (*this);
+}
+void
+UnusedChecker::visit (HIR::ConstantItem &item)
+{
+ std::string var_name = item.get_identifier ().as_string ();
+ bool starts_with_under_score = var_name.compare (0, 1, "_") == 0;
+ auto id = item.get_mappings ().get_hirid ();
+ if (!unused_context.is_variable_used (id) && !starts_with_under_score)
+ rust_warning_at (item.get_locus (), OPT_Wunused_variable,
+ "unused variable %qs",
+ item.get_identifier ().as_string ().c_str ());
+}
+
+void
+UnusedChecker::visit (HIR::StaticItem &item)
+{
+ std::string var_name = item.get_identifier ().as_string ();
+ bool starts_with_under_score = var_name.compare (0, 1, "_") == 0;
+ auto id = item.get_mappings ().get_hirid ();
+ if (!unused_context.is_variable_used (id) && !starts_with_under_score)
+ rust_warning_at (item.get_locus (), OPT_Wunused_variable,
+ "unused variable %qs",
+ item.get_identifier ().as_string ().c_str ());
+}
+
+void
+UnusedChecker::visit (HIR::TraitItemFunc &item)
+{
+ // TODO: check trait item functions if they are not derived.
+}
+void
+UnusedChecker::visit (HIR::IdentifierPattern &pattern)
+{
+ std::string var_name = pattern.get_identifier ().as_string ();
+ bool starts_with_under_score = var_name.compare (0, 1, "_") == 0;
+ auto id = pattern.get_mappings ().get_hirid ();
+ if (!unused_context.is_variable_used (id) && var_name != "self"
+ && !starts_with_under_score)
+ rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
+ "unused variable %qs",
+ pattern.get_identifier ().as_string ().c_str ());
+}
+void
+
+UnusedChecker::visit (HIR::AssignmentExpr &expr)
+
+{
+ const auto &lhs = expr.get_lhs ();
+ auto s = lhs.as_string ();
+ std::string var_name = s.substr (0, s.find (':'));
+ bool starts_with_under_score = var_name.compare (0, 1, "_") == 0;
+ NodeId ast_node_id = lhs.get_mappings ().get_nodeid ();
+ NodeId def_id = nr_context.lookup (ast_node_id).value ();
+ HirId id = mappings.lookup_node_to_hir (def_id).value ();
+ if (unused_context.is_variable_assigned (id, lhs.get_mappings ().get_hirid ())
+ && !starts_with_under_score)
+ rust_warning_at (lhs.get_locus (), OPT_Wunused_variable,
+ "unused assignment %qs", var_name.c_str ());
+}
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-checker.h b/gcc/rust/checks/lints/unused/rust-unused-checker.h
new file mode 100644
index 00000000000..6f5f8badefe
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-checker.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-hir-expr.h"
+#include "rust-hir-item.h"
+#include "rust-hir-pattern.h"
+#include "rust-hir-visitor.h"
+#include "rust-unused-collector.h"
+#include "rust-immutable-name-resolution-context.h"
+
+namespace Rust {
+namespace Analysis {
+class UnusedChecker : public HIR::DefaultHIRVisitor
+{
+public:
+ UnusedChecker ();
+ void go (HIR::Crate &crate);
+
+private:
+ const Resolver2_0::NameResolutionContext &nr_context;
+ Analysis::Mappings &mappings;
+ UnusedContext unused_context;
+
+ using HIR::DefaultHIRVisitor::visit;
+ virtual void visit (HIR::TraitItemFunc &decl) override;
+ virtual void visit (HIR::ConstantItem &item) override;
+ virtual void visit (HIR::StaticItem &item) override;
+ virtual void visit (HIR::IdentifierPattern &identifier) override;
+ virtual void visit (HIR::AssignmentExpr &identifier) override;
+};
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-collector.cc b/gcc/rust/checks/lints/unused/rust-unused-collector.cc
new file mode 100644
index 00000000000..530c6b0ce9c
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-collector.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-unused-collector.h"
+#include "rust-hir-expr.h"
+#include "rust-hir-full-decls.h"
+#include "rust-hir-item.h"
+#include "rust-hir-path.h"
+#include "rust-hir-pattern.h"
+#include "rust-immutable-name-resolution-context.h"
+
+namespace Rust {
+namespace Analysis {
+UnusedCollector::UnusedCollector (UnusedContext &context)
+ : nr_context (
+ Resolver2_0::ImmutableNameResolutionContext::get ().resolver ()),
+ mappings (Analysis::Mappings::get ()), unused_context (context)
+{}
+void
+UnusedCollector::go (HIR::Crate &crate)
+{
+ for (auto &item : crate.get_items ())
+ item->accept_vis (*this);
+}
+
+void
+UnusedCollector::visit (HIR::PathInExpression &expr)
+{
+ mark_path_used (expr);
+ walk (expr);
+}
+
+void
+UnusedCollector::visit (HIR::QualifiedPathInExpression &expr)
+{
+ mark_path_used (expr);
+ walk (expr);
+}
+
+void
+UnusedCollector::visit (HIR::StructExprFieldIdentifier &ident)
+{
+ mark_path_used (ident);
+ walk (ident);
+}
+void
+UnusedCollector::visit (HIR::AssignmentExpr &expr)
+{
+ auto def_id = get_def_id (expr.get_lhs ());
+ HirId id = expr.get_lhs ().get_mappings ().get_hirid ();
+ unused_context.add_assign (def_id, id);
+ visit_outer_attrs (expr);
+ expr.get_rhs ().accept_vis (*this);
+}
+
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-collector.h b/gcc/rust/checks/lints/unused/rust-unused-collector.h
new file mode 100644
index 00000000000..7f3ad88fdca
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-collector.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-hir-expr.h"
+#include "rust-hir-path.h"
+#include "rust-hir-pattern.h"
+#include "rust-hir-visitor.h"
+#include "rust-mapping-common.h"
+#include "rust-name-resolution-context.h"
+#include "rust-unused-context.h"
+
+namespace Rust {
+namespace Analysis {
+class UnusedCollector : public HIR::DefaultHIRVisitor
+{
+public:
+ UnusedCollector (UnusedContext &context);
+ void go (HIR::Crate &crate);
+
+private:
+ const Resolver2_0::NameResolutionContext &nr_context;
+ Analysis::Mappings &mappings;
+ UnusedContext &unused_context;
+
+ using HIR::DefaultHIRVisitor::visit;
+ virtual void visit (HIR::PathInExpression &expr) override;
+ virtual void visit (HIR::StructExprFieldIdentifier &ident) override;
+ virtual void visit (HIR::QualifiedPathInExpression &expr) override;
+ virtual void visit (HIR::AssignmentExpr &expr) override;
+
+ template HirId get_def_id (T &path_expr)
+ {
+ NodeId ast_node_id = path_expr.get_mappings ().get_nodeid ();
+ NodeId id = nr_context.lookup (ast_node_id).value ();
+ HirId def_id = mappings.lookup_node_to_hir (id).value ();
+ return def_id;
+ }
+
+ template void mark_path_used (T &path_expr)
+ {
+ auto def_id = get_def_id (path_expr);
+ unused_context.add_variable (def_id);
+ unused_context.remove_assign (def_id);
+ }
+};
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-context.cc b/gcc/rust/checks/lints/unused/rust-unused-context.cc
new file mode 100644
index 00000000000..d975865ed58
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-context.cc
@@ -0,0 +1,70 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-unused-context.h"
+
+namespace Rust {
+namespace Analysis {
+
+void
+UnusedContext::add_variable (HirId id)
+
+{
+ used_vars.emplace (id);
+}
+
+bool
+UnusedContext::is_variable_used (HirId id) const
+{
+ return used_vars.find (id) != used_vars.end ();
+}
+
+void
+UnusedContext::add_assign (HirId id_def, HirId id)
+{
+ assigned_vars[id_def].push_back (id);
+}
+
+void
+UnusedContext::remove_assign (HirId id_def)
+{
+ if (assigned_vars.find (id_def) != assigned_vars.end ())
+ assigned_vars[id_def].pop_back ();
+}
+bool
+UnusedContext::is_variable_assigned (HirId id_def, HirId id)
+{
+ auto assigned_vec = assigned_vars[id_def];
+ return std::find (assigned_vec.begin (), assigned_vec.end (), id)
+ != assigned_vec.end ();
+}
+
+std::string
+UnusedContext::as_string () const
+{
+ std::stringstream ss;
+ ss << "UnusedContext: ";
+ for (const auto &v : used_vars)
+ {
+ ss << "HirId: " << v << "\n";
+ }
+ return ss.str ();
+}
+
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/checks/lints/unused/rust-unused-context.h b/gcc/rust/checks/lints/unused/rust-unused-context.h
new file mode 100644
index 00000000000..0a9faf53c8c
--- /dev/null
+++ b/gcc/rust/checks/lints/unused/rust-unused-context.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2025 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3. If not see
+// .
+
+#include "rust-mapping-common.h"
+
+namespace Rust {
+namespace Analysis {
+
+class UnusedContext
+{
+public:
+ void add_variable (HirId id);
+ bool is_variable_used (HirId id) const;
+ void add_assign (HirId id_def, HirId id);
+ void remove_assign (HirId id_def);
+ bool is_variable_assigned (HirId id_def, HirId id);
+
+ std::string as_string () const;
+
+private:
+ std::unordered_set used_vars;
+ std::map> assigned_vars;
+};
+
+} // namespace Analysis
+} // namespace Rust
diff --git a/gcc/rust/lang.opt b/gcc/rust/lang.opt
index d9824f1a5ac..3c708bafe9f 100644
--- a/gcc/rust/lang.opt
+++ b/gcc/rust/lang.opt
@@ -233,4 +233,8 @@ frust-assume-builtin-offset-of
Rust Var(flag_assume_builtin_offset_of)
Define a built-in offset_of macro in the compiler and assume it is present
+frust-unused-check-2.0
+Rust Var(flag_unused_check_2_0)
+Use the new unused variable check implementation.
+
; This comment is to ensure we retain the blank line above.
diff --git a/gcc/rust/rust-session-manager.cc b/gcc/rust/rust-session-manager.cc
index 64181184683..293e4bc65f2 100644
--- a/gcc/rust/rust-session-manager.cc
+++ b/gcc/rust/rust-session-manager.cc
@@ -38,6 +38,7 @@
#include "rust-cfg-parser.h"
#include "rust-lint-scan-deadcode.h"
#include "rust-lint-unused-var.h"
+#include "rust-unused-checker.h"
#include "rust-readonly-check.h"
#include "rust-hir-dump.h"
#include "rust-ast-dump.h"
@@ -733,7 +734,12 @@ Session::compile_crate (const char *filename)
{
// lints
Analysis::ScanDeadcode::Scan (hir);
- Analysis::UnusedVariables::Lint (*ctx);
+
+ if (flag_unused_check_2_0)
+ Analysis::UnusedChecker ().go (hir);
+ else
+ Analysis::UnusedVariables::Lint (*ctx);
+
HIR::ReadonlyChecker ().go (hir);
// metadata
diff --git a/gcc/testsuite/rust/compile/issue-4260_0.rs b/gcc/testsuite/rust/compile/issue-4260_0.rs
new file mode 100644
index 00000000000..b6f1fba3b31
--- /dev/null
+++ b/gcc/testsuite/rust/compile/issue-4260_0.rs
@@ -0,0 +1,22 @@
+// { dg-additional-options "-frust-unused-check-2.0" }
+pub fn a()->i32 {
+ let mut a = 1;
+ a = 2;
+// { dg-warning "unused assignment .a." "" { target *-*-* } .-1 }
+ a = 3;
+// { dg-warning "unused assignment .a." "" { target *-*-* } .-1 }
+ a = 4;
+// { dg-warning "unused assignment .a." "" { target *-*-* } .-1 }
+ a = 5;
+ let mut b = a;
+ b = 1;
+// { dg-warning "unused assignment .b." "" { target *-*-* } .-1 }
+ b = 2;
+// { dg-warning "unused assignment .b." "" { target *-*-* } .-1 }
+ b = 3;
+// { dg-warning "unused assignment .b." "" { target *-*-* } .-1 }
+ b = 4;
+// { dg-warning "unused assignment .b." "" { target *-*-* } .-1 }
+ b = 5;
+ return b
+}
diff --git a/gcc/testsuite/rust/compile/static_item_0.rs b/gcc/testsuite/rust/compile/static_item_0.rs
new file mode 100644
index 00000000000..3fc74715c6d
--- /dev/null
+++ b/gcc/testsuite/rust/compile/static_item_0.rs
@@ -0,0 +1,3 @@
+// { dg-additional-options "-frust-unused-check-2.0" }
+static TEST: usize = 1;
+// { dg-warning "unused variable .TEST." "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/rust/compile/template_function_0.rs b/gcc/testsuite/rust/compile/template_function_0.rs
new file mode 100644
index 00000000000..4d12d948e60
--- /dev/null
+++ b/gcc/testsuite/rust/compile/template_function_0.rs
@@ -0,0 +1,7 @@
+// { dg-additional-options "-frust-unused-check-2.0" }
+#[lang = "sized"]
+pub trait Sized {}
+
+pub fn test (a: usize) -> () {
+ // { dg-warning "unused variable .a." "" { target *-*-* } .-1 }
+}