diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in index e5a8a5eb462b..bb063f1f102f 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 000000000000..fc3fb1348a97 --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-checker.cc @@ -0,0 +1,125 @@ +// 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 ()); + + if (pattern.is_mut () && !unused_context.is_mut_used (id) + && var_name != "self" && !starts_with_under_score) + rust_warning_at (pattern.get_locus (), OPT_Wunused_variable, + "unused mut %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 ()); +} + +void +UnusedChecker::visit (HIR::StructPatternFieldIdent &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 ()); + + if (pattern.is_mut () && !unused_context.is_mut_used (id) + && var_name != "self" && !starts_with_under_score) + rust_warning_at (pattern.get_locus (), OPT_Wunused_variable, + "unused mut %qs", + pattern.get_identifier ().as_string ().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 000000000000..690435ce455e --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-checker.h @@ -0,0 +1,48 @@ +// 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; + virtual void visit (HIR::StructPatternFieldIdent &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 000000000000..b07b09e4a362 --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-collector.cc @@ -0,0 +1,92 @@ +// 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.remove_mut (def_id); + unused_context.add_assign (def_id, id); + visit_outer_attrs (expr); + expr.get_rhs ().accept_vis (*this); +} + +void +UnusedCollector::visit (HIR::IdentifierPattern &pattern) +{ + if (pattern.is_mut ()) + unused_context.add_mut (pattern.get_mappings ().get_hirid ()); + + walk (pattern); +} + +void +UnusedCollector::visit (HIR::StructPatternFieldIdent &pattern) +{ + if (pattern.is_mut ()) + unused_context.add_mut (pattern.get_mappings ().get_hirid ()); + + walk (pattern); +} + +} // 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 000000000000..ad10677cf879 --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-collector.h @@ -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-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; + + // Unused var + virtual void visit (HIR::PathInExpression &expr) override; + virtual void visit (HIR::StructExprFieldIdentifier &ident) override; + virtual void visit (HIR::QualifiedPathInExpression &expr) override; + + // Unused assignments + virtual void visit (HIR::AssignmentExpr &expr) override; + + // Unused mut + virtual void visit (HIR::IdentifierPattern &pattern) override; + virtual void visit (HIR::StructPatternFieldIdent &pattern) 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 000000000000..29142f4838ed --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-context.cc @@ -0,0 +1,89 @@ +// 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 (); +} + +void +UnusedContext::add_mut (HirId id) +{ + mutable_vars.emplace (id); +} + +void +UnusedContext::remove_mut (HirId id) +{ + mutable_vars.erase (id); +} + +bool +UnusedContext::is_mut_used (HirId id) const +{ + return mutable_vars.find (id) == mutable_vars.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 000000000000..832779d7dbf8 --- /dev/null +++ b/gcc/rust/checks/lints/unused/rust-unused-context.h @@ -0,0 +1,49 @@ +// 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: + // Unused var + void add_variable (HirId id); + bool is_variable_used (HirId id) const; + + // Assigned var + void add_assign (HirId id_def, HirId id); + void remove_assign (HirId id_def); + bool is_variable_assigned (HirId id_def, HirId id); + + // Mutable var + void add_mut (HirId id); + void remove_mut (HirId id); + bool is_mut_used (HirId id) const; + std::string as_string () const; + +private: + std::unordered_set used_vars; + std::unordered_set mutable_vars; + std::map> assigned_vars; +}; + +} // namespace Analysis +} // namespace Rust diff --git a/gcc/rust/lang.opt b/gcc/rust/lang.opt index d9824f1a5ac2..3c708bafe9f7 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 641811846832..293e4bc65f2d 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 000000000000..b6f1fba3b310 --- /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 000000000000..3fc74715c6d9 --- /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 000000000000..4d12d948e60a --- /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 } +} diff --git a/gcc/testsuite/rust/compile/unused-mut-identifier_0.rs b/gcc/testsuite/rust/compile/unused-mut-identifier_0.rs new file mode 100644 index 000000000000..58d1b09e2267 --- /dev/null +++ b/gcc/testsuite/rust/compile/unused-mut-identifier_0.rs @@ -0,0 +1,6 @@ +// { dg-additional-options "-frust-unused-check-2.0" } +pub fn a() ->i32 { + let mut x = 2; +// { dg-warning "unused mut .x." "" { target *-*-* } .-1 } + return x +} diff --git a/gcc/testsuite/rust/compile/unused-mut-struct-field_0.rs b/gcc/testsuite/rust/compile/unused-mut-struct-field_0.rs new file mode 100644 index 000000000000..1662dd3ba1c4 --- /dev/null +++ b/gcc/testsuite/rust/compile/unused-mut-struct-field_0.rs @@ -0,0 +1,17 @@ +// { dg-additional-options "-frust-unused-check-2.0" } +struct Point { x: i32, y: i32 } +// { dg-warning "field is never read: .x." "" { target *-*-* } .-1 } +// { dg-warning "field is never read: .y." "" { target *-*-* } .-2 } + +pub fn main() -> (i32, i32){ + let p = Point { x: 1, y: 2 }; + + match p { + Point { mut x, mut y } => { +// { dg-warning "unused mut .x." "" { target *-*-* } .-1 } +// { dg-warning "unused mut .y." "" { target *-*-* } .-2 } + return (x,y) + } + } +} +