LCOV - code coverage report
Current view: top level - gcc/rust/checks/lints/unused - rust-unused-checker.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 96.7 % 210 203
Test Date: 2026-09-12 16:25:28 Functions: 100.0 % 25 25
Legend: Lines:     hit not hit

            Line data    Source code
       1              : // Copyright (C) 2025-2026 Free Software Foundation, Inc.
       2              : 
       3              : // This file is part of GCC.
       4              : 
       5              : // GCC is free software; you can redistribute it and/or modify it under
       6              : // the terms of the GNU General Public License as published by the Free
       7              : // Software Foundation; either version 3, or (at your option) any later
       8              : // version.
       9              : 
      10              : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
      11              : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
      12              : // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      13              : // for more details.
      14              : 
      15              : // You should have received a copy of the GNU General Public License
      16              : // along with GCC; see the file COPYING3.  If not see
      17              : // <http://www.gnu.org/licenses/>.
      18              : 
      19              : #include "rust-unused-checker.h"
      20              : #include "rust-hir-expr.h"
      21              : #include "rust-hir-generic-param.h"
      22              : #include "rust-hir-item.h"
      23              : #include "rust-hir-pattern.h"
      24              : 
      25              : #include "options.h"
      26              : #include "rust-keyword-values.h"
      27              : #include "rust-attribute-values.h"
      28              : #include "rust-rib.h"
      29              : 
      30              : namespace Rust {
      31              : namespace Analysis {
      32           26 : UnusedChecker::UnusedChecker ()
      33           26 :   : nr_context (Resolver2_0::FinalizedNameResolutionContext::get ()),
      34           26 :     mappings (Analysis::Mappings::get ()), unused_context (UnusedContext ())
      35           26 : {}
      36              : void
      37           26 : UnusedChecker::go (HIR::Crate &crate)
      38              : {
      39           26 :   UnusedCollector collector (unused_context);
      40           26 :   collector.go (crate);
      41           75 :   for (auto &item : crate.get_items ())
      42           49 :     item->accept_vis (*this);
      43           26 : }
      44              : 
      45              : bool
      46           54 : is_snake_case (Identifier identifier)
      47              : {
      48           54 :   auto s = identifier.as_string ();
      49           54 :   return std::all_of (s.begin (), s.end (), [] (unsigned char c) {
      50          109 :     return ISLOWER (c) || ISDIGIT (c) || c == '_';
      51           54 :   });
      52           54 : }
      53              : 
      54              : void
      55            2 : UnusedChecker::visit (HIR::ConstantItem &item)
      56              : {
      57            4 :   std::string var_name = item.get_identifier ().as_string ();
      58            2 :   if (var_name == "_" && item.get_visibility ().is_public ())
      59            1 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      60              :                      "visibility qualifier on a %<const _%> item is unused");
      61            2 : }
      62              : 
      63              : void
      64            3 : UnusedChecker::visit (HIR::StaticItem &item)
      65              : {
      66            6 :   std::string var_name = item.get_identifier ().as_string ();
      67            3 :   if (!std::all_of (var_name.begin (), var_name.end (), [] (unsigned char c) {
      68            7 :         return ISUPPER (c) || ISDIGIT (c) || c == '_';
      69              :       }))
      70            1 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      71              :                      "static variable %qs should have an upper case name",
      72              :                      var_name.c_str ());
      73            3 : }
      74              : 
      75              : void
      76            3 : UnusedChecker::visit (HIR::TraitItemFunc &item)
      77              : {
      78              :   // TODO: check trait item functions if they are not derived.
      79            3 : }
      80              : void
      81           25 : UnusedChecker::visit (HIR::IdentifierPattern &pattern)
      82              : {
      83           50 :   std::string var_name = pattern.get_identifier ().as_string ();
      84           25 :   auto id = pattern.get_mappings ().get_hirid ();
      85           25 :   if (!unused_context.is_variable_used (id)
      86           25 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
      87            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
      88              :                      "unused variable %qs",
      89            2 :                      pattern.get_identifier ().as_string ().c_str ());
      90              : 
      91           25 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
      92           26 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
      93            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
      94              :                      "unused mut %qs",
      95            2 :                      pattern.get_identifier ().as_string ().c_str ());
      96              : 
      97           25 :   if (!is_snake_case (pattern.get_identifier ()))
      98            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
      99              :                      "variable %qs should have a snake case name",
     100              :                      var_name.c_str ());
     101           25 : }
     102              : 
     103              : void
     104           15 : UnusedChecker::visit (HIR::AssignmentExpr &expr)
     105              : {
     106           15 :   const auto &lhs = expr.get_lhs ();
     107           15 :   auto var_name = lhs.to_string ();
     108           15 :   if (var_name[0] != '_'
     109           15 :       && unused_context.is_assign_unused (lhs.get_mappings ().get_hirid ()))
     110           10 :     rust_warning_at (lhs.get_locus (), OPT_Wunused_variable,
     111              :                      "unused assignment %qs", var_name.c_str ());
     112           15 : }
     113              : 
     114              : void
     115            2 : UnusedChecker::visit (HIR::StructPatternFieldIdent &pattern)
     116              : {
     117            4 :   std::string var_name = pattern.get_identifier ().as_string ();
     118            2 :   auto id = pattern.get_mappings ().get_hirid ();
     119            2 :   if (!unused_context.is_variable_used (id)
     120            2 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     121            0 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     122              :                      "unused variable %qs",
     123            0 :                      pattern.get_identifier ().as_string ().c_str ());
     124              : 
     125            2 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
     126            4 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     127            2 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     128              :                      "unused mut %qs",
     129            4 :                      pattern.get_identifier ().as_string ().c_str ());
     130            2 : }
     131              : 
     132              : void
     133            1 : UnusedChecker::visit (HIR::EmptyStmt &stmt)
     134              : {
     135            1 :   rust_warning_at (stmt.get_locus (), OPT_Wunused_variable,
     136              :                    "unnecessary trailing semicolons");
     137            1 : }
     138              : 
     139              : void
     140           27 : UnusedChecker::visit (HIR::Function &fct)
     141              : {
     142           27 :   if (!is_snake_case (fct.get_function_name ()))
     143            1 :     rust_warning_at (fct.get_locus (), OPT_Wunused_variable,
     144              :                      "function %qs should have a snake case name",
     145            2 :                      fct.get_function_name ().as_string ().c_str ());
     146              : 
     147              :   // The no_mangle_generic_items lint: a generic function cannot be exported
     148              :   // with a fixed symbol, so `#[no_mangle]`/`#[export_name]` has no effect.
     149           27 :   if (fct.has_generics ())
     150            4 :     for (auto &attr : fct.get_outer_attrs ())
     151              :       {
     152            1 :         auto name = attr.get_path ().as_string ();
     153            1 :         if (name == "no_mangle" || name == "export_name")
     154              :           {
     155            1 :             rust_warning_at (fct.get_locus (), OPT_Wattributes,
     156              :                              "generic functions must be mangled, %qs has no "
     157              :                              "effect",
     158              :                              name.c_str ());
     159            1 :             break;
     160              :           }
     161            1 :       }
     162           27 :   walk (fct);
     163           27 : }
     164              : 
     165              : void
     166            1 : UnusedChecker::visit (HIR::Module &mod)
     167              : {
     168            1 :   if (!is_snake_case (mod.get_module_name ()))
     169            1 :     rust_warning_at (mod.get_locus (), OPT_Wunused_variable,
     170              :                      "module %qs should have a snake case name",
     171            2 :                      mod.get_module_name ().as_string ().c_str ());
     172            1 :   walk (mod);
     173            1 : }
     174              : 
     175              : void
     176            1 : UnusedChecker::visit (HIR::LifetimeParam &lft)
     177              : {
     178            3 :   if (!is_snake_case (lft.get_lifetime ().get_name ()))
     179            1 :     rust_warning_at (lft.get_locus (), OPT_Wunused_variable,
     180              :                      "lifetime %qs should have a snake case name",
     181            2 :                      lft.get_lifetime ().get_name ().c_str ());
     182            1 :   walk (lft);
     183            1 : }
     184              : 
     185              : void
     186            2 : UnusedChecker::visit (HIR::ExternBlock &block)
     187              : {
     188            2 :   if (!block.has_abi ())
     189            1 :     rust_warning_at (block.get_locus (), OPT_Wunused_variable,
     190              :                      "extern declarations without an explicit ABI are "
     191              :                      "deprecated");
     192            2 :   walk (block);
     193            2 : }
     194              : 
     195              : void
     196            5 : UnusedChecker::visit_loop_label (HIR::LoopLabel &label)
     197              : {
     198            5 :   auto lifetime = label.get_lifetime ();
     199            5 :   std::string var_name = lifetime.to_string ();
     200            5 :   auto id = lifetime.get_mappings ().get_hirid ();
     201            5 :   if (!unused_context.is_label_used (id) && var_name[0] != '_')
     202            2 :     rust_warning_at (lifetime.get_locus (), OPT_Wunused_variable,
     203            4 :                      "unused label %qs", lifetime.to_string ().c_str ());
     204            5 : }
     205              : 
     206              : void
     207            2 : UnusedChecker::visit (HIR::StructPatternFieldIdentPat &field)
     208              : {
     209            2 :   auto &pattern = field.get_pattern ();
     210            2 :   if (pattern.get_pattern_type () == HIR::Pattern::PatternType::IDENTIFIER)
     211              :     {
     212            2 :       auto &ident = static_cast<HIR::IdentifierPattern &> (pattern);
     213            4 :       if (!ident.has_subpattern ()
     214            4 :           && ident.get_identifier ().as_string ()
     215            6 :                == field.get_identifier ().as_string ())
     216            2 :         rust_warning_at (field.get_locus (), OPT_Wunused_variable,
     217              :                          "the %qs in this pattern is redundant",
     218            2 :                          (field.get_identifier ().as_string () + ":").c_str ());
     219              :     }
     220            2 :   walk (field);
     221            2 : }
     222              : 
     223              : namespace {
     224              : 
     225              : bool
     226            8 : literal_int_value (const HIR::Literal &lit, bool minus, int64_t &out)
     227              : {
     228            8 :   if (lit.get_lit_type () != HIR::Literal::LitType::INT)
     229              :     return false;
     230              : 
     231            8 :   std::string digits = lit.as_string ();
     232            8 :   digits.erase (std::remove (digits.begin (), digits.end (), '_'),
     233            8 :                 digits.end ());
     234              : 
     235            8 :   char *end = nullptr;
     236            8 :   long long value = std::strtoll (digits.c_str (), &end, 10);
     237            8 :   if (end == digits.c_str () || *end != '\0')
     238              :     return false;
     239              : 
     240            8 :   out = minus ? -value : value;
     241            8 :   return true;
     242            8 : }
     243              : 
     244              : bool
     245            8 : range_bound_int (HIR::RangePatternBound &bound, int64_t &out)
     246              : {
     247            8 :   if (bound.get_bound_type ()
     248              :       != HIR::RangePatternBound::RangePatternBoundType::LITERAL)
     249              :     return false;
     250              : 
     251            8 :   auto &lit = static_cast<HIR::RangePatternBoundLiteral &> (bound);
     252            8 :   return literal_int_value (lit.get_literal (), lit.get_has_minus (), out);
     253              : }
     254              : 
     255              : } // namespace
     256              : 
     257              : void
     258            4 : UnusedChecker::visit (HIR::MatchExpr &expr)
     259              : {
     260            4 :   struct Range
     261              :   {
     262              :     int64_t lo;
     263              :     int64_t hi;
     264              :     bool inclusive;
     265              :     location_t locus;
     266              :   };
     267            4 :   std::vector<Range> ranges;
     268            4 :   std::vector<int64_t> starts;
     269              : 
     270           12 :   for (auto &match_case : expr.get_match_cases ())
     271              :     {
     272            8 :       auto &pattern = match_case.get_arm ().get_pattern ();
     273            8 :       if (!pattern)
     274            0 :         continue;
     275              : 
     276            8 :       if (pattern->get_pattern_type () == HIR::Pattern::PatternType::RANGE)
     277              :         {
     278            4 :           auto &range = static_cast<HIR::RangePattern &> (*pattern);
     279            4 :           int64_t lo, hi;
     280            4 :           if (range_bound_int (range.get_lower_bound (), lo)
     281            4 :               && range_bound_int (range.get_upper_bound (), hi))
     282              :             {
     283            4 :               ranges.push_back (
     284            4 :                 {lo, hi, range.is_inclusive_range (), range.get_locus ()});
     285            4 :               starts.push_back (lo);
     286              :             }
     287              :         }
     288            4 :       else if (pattern->get_pattern_type ()
     289              :                == HIR::Pattern::PatternType::LITERAL)
     290              :         {
     291            0 :           auto &lit = static_cast<HIR::LiteralPattern &> (*pattern);
     292            0 :           int64_t value;
     293            0 :           if (literal_int_value (lit.get_literal (), lit.get_has_minus (),
     294              :                                  value))
     295            0 :             starts.push_back (value);
     296              :         }
     297              :     }
     298              : 
     299              :   // A value is covered if a range matches it or some arm starts exactly on it.
     300            6 :   auto covered = [&] (int64_t value) {
     301            4 :     if (std::find (starts.begin (), starts.end (), value) != starts.end ())
     302              :       return true;
     303            3 :     for (auto &range : ranges)
     304              :       {
     305            2 :         int64_t top = range.inclusive ? range.hi : range.hi - 1;
     306            2 :         if (value >= range.lo && value <= top)
     307            2 :           return true;
     308              :       }
     309              :     return false;
     310            4 :   };
     311              : 
     312              :   // An exclusive range `lo..hi` leaves `hi` unmatched. If another arm picks up
     313              :   // at `hi + 1`, that single value was almost certainly meant to be included.
     314            8 :   for (auto &range : ranges)
     315              :     {
     316            4 :       if (range.inclusive)
     317            2 :         continue;
     318              : 
     319            2 :       int64_t missed = range.hi;
     320            2 :       if (!covered (missed)
     321            2 :           && std::find (starts.begin (), starts.end (), missed + 1)
     322            2 :                != starts.end ())
     323            1 :         rust_warning_at (range.locus, OPT_Wunused_variable,
     324              :                          "multiple ranges are one apart");
     325              :     }
     326              : 
     327            4 :   walk (expr);
     328            4 : }
     329              : 
     330              : void
     331           15 : UnusedChecker::visit (HIR::LetStmt &stmt)
     332              : {
     333           15 :   for (auto &attr : stmt.get_outer_attrs ())
     334            1 :     if (attr.get_path ().as_string () == Values::Attributes::DOC)
     335              :       {
     336            1 :         rust_warning_at (stmt.get_locus (), OPT_Wunused_variable,
     337              :                          "unused doc comment");
     338            1 :         break;
     339              :       }
     340           15 :   if (stmt.has_init_expr ()
     341           15 :       && stmt.get_init_expr ().get_expression_type ()
     342              :            == HIR::Expr::ExprType::Block)
     343              :     {
     344            1 :       auto &block = static_cast<HIR::BlockExpr &> (stmt.get_init_expr ());
     345            1 :       if (block.get_statements ().empty () && block.has_expr ())
     346            1 :         rust_warning_at (block.get_locus (), OPT_Wunused,
     347              :                          "unnecessary braces around assigned value");
     348              :     }
     349           15 :   walk (stmt);
     350           15 : }
     351              : 
     352              : void
     353            1 : UnusedChecker::visit (HIR::BorrowExpr &expr)
     354              : {
     355              :   // The static_mut_refs lint: taking a reference to a mutable static is
     356              :   // discouraged as it can easily lead to undefined behaviour.
     357            1 :   NodeId ast_node_id = expr.get_expr ().get_mappings ().get_nodeid ();
     358            1 :   if (auto def
     359            1 :       = nr_context.lookup (ast_node_id, Resolver2_0::Namespace::Values))
     360            1 :     if (auto id = mappings.lookup_node_to_hir (*def))
     361            1 :       if (auto item = mappings.lookup_hir_item (*id))
     362            1 :         if (item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
     363              :           {
     364            1 :             auto &static_item = static_cast<HIR::StaticItem &> (*item.value ());
     365            1 :             if (static_item.is_mut ())
     366            1 :               rust_warning_at (expr.get_locus (), OPT_Wunused,
     367              :                                "creating a reference to a mutable static");
     368              :           }
     369            1 :   walk (expr);
     370            1 : }
     371              : 
     372              : namespace {
     373              : // Probe whether an expression is itself an arithmetic negation, without
     374              : // recursing (so it only inspects the node it is dispatched on). Used to detect
     375              : // `- -x` for the double_negations lint, since gccrs builds with -fno-rtti and
     376              : // HIR has no down-cast helper.
     377            2 : class NegationProbe : public HIR::HIRFullVisitorBase
     378              : {
     379              : public:
     380              :   bool is_negation = false;
     381              :   using HIR::HIRFullVisitorBase::visit;
     382            1 :   void visit (HIR::NegationExpr &expr) override
     383              :   {
     384            1 :     is_negation = expr.get_expr_type () == HIR::NegationExpr::ExprType::NEGATE;
     385            1 :   }
     386              : };
     387              : } // namespace
     388              : 
     389              : void
     390            2 : UnusedChecker::visit (HIR::NegationExpr &expr)
     391              : {
     392            2 :   if (expr.get_expr_type () == HIR::NegationExpr::ExprType::NEGATE)
     393              :     {
     394            2 :       NegationProbe probe;
     395            2 :       expr.get_expr ().accept_vis (probe);
     396            2 :       if (probe.is_negation)
     397            1 :         rust_warning_at (expr.get_locus (), OPT_Wunused,
     398              :                          "use of a double negation");
     399            2 :     }
     400            2 :   walk (expr);
     401            2 : }
     402              : 
     403              : void
     404            3 : UnusedChecker::visit (HIR::BreakExpr &expr)
     405              : {
     406            3 :   if (expr.has_label () && expr.has_break_expr ()
     407            4 :       && expr.get_expr ().get_expression_type ()
     408              :            == HIR::Expr::ExprType::BaseLoop)
     409            1 :     rust_warning_at (
     410              :       expr.get_locus (), OPT_Wunused,
     411              :       "this labeled %<break%> expression is easy to confuse with "
     412              :       "an unlabeled %<break%> with a labeled value expression");
     413            3 :   walk (expr);
     414            3 : }
     415              : 
     416              : } // namespace Analysis
     417              : } // namespace Rust
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.