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.4 % 196 189
Test Date: 2026-08-08 15:37:49 Functions: 100.0 % 22 22
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           21 : UnusedChecker::UnusedChecker ()
      33           21 :   : nr_context (Resolver2_0::FinalizedNameResolutionContext::get ()),
      34           21 :     mappings (Analysis::Mappings::get ()), unused_context (UnusedContext ())
      35           21 : {}
      36              : void
      37           21 : UnusedChecker::go (HIR::Crate &crate)
      38              : {
      39           21 :   UnusedCollector collector (unused_context);
      40           21 :   collector.go (crate);
      41           59 :   for (auto &item : crate.get_items ())
      42           38 :     item->accept_vis (*this);
      43           21 : }
      44              : 
      45              : bool
      46           44 : is_snake_case (Identifier identifier)
      47              : {
      48           44 :   auto s = identifier.as_string ();
      49           44 :   return std::all_of (s.begin (), s.end (), [] (unsigned char c) {
      50           87 :     return ISLOWER (c) || ISDIGIT (c) || c == '_';
      51           44 :   });
      52           44 : }
      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            2 : UnusedChecker::visit (HIR::TraitItemFunc &item)
      77              : {
      78              :   // TODO: check trait item functions if they are not derived.
      79            2 : }
      80              : void
      81           21 : UnusedChecker::visit (HIR::IdentifierPattern &pattern)
      82              : {
      83           42 :   std::string var_name = pattern.get_identifier ().as_string ();
      84           21 :   auto id = pattern.get_mappings ().get_hirid ();
      85           21 :   if (!unused_context.is_variable_used (id)
      86           21 :       && 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           21 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
      92           22 :       && 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           21 :   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           21 : }
     102              : 
     103              : void
     104           10 : UnusedChecker::visit (HIR::AssignmentExpr &expr)
     105              : {
     106           10 :   const auto &lhs = expr.get_lhs ();
     107           10 :   auto var_name = lhs.to_string ();
     108           10 :   NodeId ast_node_id = lhs.get_mappings ().get_nodeid ();
     109           10 :   if (auto def_id
     110           10 :       = nr_context.lookup (ast_node_id, Resolver2_0::Namespace::Values))
     111              :     {
     112           10 :       if (auto id = mappings.lookup_node_to_hir (*def_id))
     113              :         {
     114           10 :           if (unused_context.is_variable_assigned (
     115           10 :                 *id, lhs.get_mappings ().get_hirid ())
     116           10 :               && var_name[0] != '_')
     117            7 :             rust_warning_at (lhs.get_locus (), OPT_Wunused_variable,
     118              :                              "unused assignment %qs", var_name.c_str ());
     119              :         }
     120              :     }
     121           10 : }
     122              : 
     123              : void
     124            2 : UnusedChecker::visit (HIR::StructPatternFieldIdent &pattern)
     125              : {
     126            4 :   std::string var_name = pattern.get_identifier ().as_string ();
     127            2 :   auto id = pattern.get_mappings ().get_hirid ();
     128            2 :   if (!unused_context.is_variable_used (id)
     129            2 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     130            0 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     131              :                      "unused variable %qs",
     132            0 :                      pattern.get_identifier ().as_string ().c_str ());
     133              : 
     134            2 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
     135            4 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     136            2 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     137              :                      "unused mut %qs",
     138            4 :                      pattern.get_identifier ().as_string ().c_str ());
     139            2 : }
     140              : 
     141              : void
     142            1 : UnusedChecker::visit (HIR::EmptyStmt &stmt)
     143              : {
     144            1 :   rust_warning_at (stmt.get_locus (), OPT_Wunused_variable,
     145              :                    "unnecessary trailing semicolons");
     146            1 : }
     147              : 
     148              : void
     149           21 : UnusedChecker::visit (HIR::Function &fct)
     150              : {
     151           21 :   if (!is_snake_case (fct.get_function_name ()))
     152            1 :     rust_warning_at (fct.get_locus (), OPT_Wunused_variable,
     153              :                      "function %qs should have a snake case name",
     154            2 :                      fct.get_function_name ().as_string ().c_str ());
     155              : 
     156              :   // The no_mangle_generic_items lint: a generic function cannot be exported
     157              :   // with a fixed symbol, so `#[no_mangle]`/`#[export_name]` has no effect.
     158           21 :   if (fct.has_generics ())
     159            4 :     for (auto &attr : fct.get_outer_attrs ())
     160              :       {
     161            1 :         auto name = attr.get_path ().as_string ();
     162            1 :         if (name == "no_mangle" || name == "export_name")
     163              :           {
     164            1 :             rust_warning_at (fct.get_locus (), OPT_Wattributes,
     165              :                              "generic functions must be mangled, %qs has no "
     166              :                              "effect",
     167              :                              name.c_str ());
     168            1 :             break;
     169              :           }
     170            1 :       }
     171           21 :   walk (fct);
     172           21 : }
     173              : 
     174              : void
     175            1 : UnusedChecker::visit (HIR::Module &mod)
     176              : {
     177            1 :   if (!is_snake_case (mod.get_module_name ()))
     178            1 :     rust_warning_at (mod.get_locus (), OPT_Wunused_variable,
     179              :                      "module %qs should have a snake case name",
     180            2 :                      mod.get_module_name ().as_string ().c_str ());
     181            1 :   walk (mod);
     182            1 : }
     183              : 
     184              : void
     185            1 : UnusedChecker::visit (HIR::LifetimeParam &lft)
     186              : {
     187            3 :   if (!is_snake_case (lft.get_lifetime ().get_name ()))
     188            1 :     rust_warning_at (lft.get_locus (), OPT_Wunused_variable,
     189              :                      "lifetime %qs should have a snake case name",
     190            2 :                      lft.get_lifetime ().get_name ().c_str ());
     191            1 :   walk (lft);
     192            1 : }
     193              : 
     194              : void
     195            2 : UnusedChecker::visit (HIR::ExternBlock &block)
     196              : {
     197            2 :   if (!block.has_abi ())
     198            1 :     rust_warning_at (block.get_locus (), OPT_Wunused_variable,
     199              :                      "extern declarations without an explicit ABI are "
     200              :                      "deprecated");
     201            2 :   walk (block);
     202            2 : }
     203              : 
     204              : void
     205            4 : UnusedChecker::visit_loop_label (HIR::LoopLabel &label)
     206              : {
     207            4 :   auto lifetime = label.get_lifetime ();
     208            4 :   std::string var_name = lifetime.to_string ();
     209            4 :   auto id = lifetime.get_mappings ().get_hirid ();
     210            4 :   if (!unused_context.is_label_used (id) && var_name[0] != '_')
     211            2 :     rust_warning_at (lifetime.get_locus (), OPT_Wunused_variable,
     212            4 :                      "unused label %qs", lifetime.to_string ().c_str ());
     213            4 : }
     214              : 
     215              : void
     216            2 : UnusedChecker::visit (HIR::StructPatternFieldIdentPat &field)
     217              : {
     218            2 :   auto &pattern = field.get_pattern ();
     219            2 :   if (pattern.get_pattern_type () == HIR::Pattern::PatternType::IDENTIFIER)
     220              :     {
     221            2 :       auto &ident = static_cast<HIR::IdentifierPattern &> (pattern);
     222            4 :       if (!ident.has_subpattern ()
     223            4 :           && ident.get_identifier ().as_string ()
     224            6 :                == field.get_identifier ().as_string ())
     225            2 :         rust_warning_at (field.get_locus (), OPT_Wunused_variable,
     226              :                          "the %qs in this pattern is redundant",
     227            2 :                          (field.get_identifier ().as_string () + ":").c_str ());
     228              :     }
     229            2 :   walk (field);
     230            2 : }
     231              : 
     232              : namespace {
     233              : 
     234              : bool
     235            8 : literal_int_value (const HIR::Literal &lit, bool minus, int64_t &out)
     236              : {
     237            8 :   if (lit.get_lit_type () != HIR::Literal::LitType::INT)
     238              :     return false;
     239              : 
     240            8 :   std::string digits = lit.as_string ();
     241            8 :   digits.erase (std::remove (digits.begin (), digits.end (), '_'),
     242            8 :                 digits.end ());
     243              : 
     244            8 :   char *end = nullptr;
     245            8 :   long long value = std::strtoll (digits.c_str (), &end, 10);
     246            8 :   if (end == digits.c_str () || *end != '\0')
     247              :     return false;
     248              : 
     249            8 :   out = minus ? -value : value;
     250            8 :   return true;
     251            8 : }
     252              : 
     253              : bool
     254            8 : range_bound_int (HIR::RangePatternBound &bound, int64_t &out)
     255              : {
     256            8 :   if (bound.get_bound_type ()
     257              :       != HIR::RangePatternBound::RangePatternBoundType::LITERAL)
     258              :     return false;
     259              : 
     260            8 :   auto &lit = static_cast<HIR::RangePatternBoundLiteral &> (bound);
     261            8 :   return literal_int_value (lit.get_literal (), lit.get_has_minus (), out);
     262              : }
     263              : 
     264              : } // namespace
     265              : 
     266              : void
     267            4 : UnusedChecker::visit (HIR::MatchExpr &expr)
     268              : {
     269            4 :   struct Range
     270              :   {
     271              :     int64_t lo;
     272              :     int64_t hi;
     273              :     bool inclusive;
     274              :     location_t locus;
     275              :   };
     276            4 :   std::vector<Range> ranges;
     277            4 :   std::vector<int64_t> starts;
     278              : 
     279           12 :   for (auto &match_case : expr.get_match_cases ())
     280              :     {
     281            8 :       auto &pattern = match_case.get_arm ().get_pattern ();
     282            8 :       if (!pattern)
     283            0 :         continue;
     284              : 
     285            8 :       if (pattern->get_pattern_type () == HIR::Pattern::PatternType::RANGE)
     286              :         {
     287            4 :           auto &range = static_cast<HIR::RangePattern &> (*pattern);
     288            4 :           int64_t lo, hi;
     289            4 :           if (range_bound_int (range.get_lower_bound (), lo)
     290            4 :               && range_bound_int (range.get_upper_bound (), hi))
     291              :             {
     292            4 :               ranges.push_back (
     293            4 :                 {lo, hi, range.is_inclusive_range (), range.get_locus ()});
     294            4 :               starts.push_back (lo);
     295              :             }
     296              :         }
     297            4 :       else if (pattern->get_pattern_type ()
     298              :                == HIR::Pattern::PatternType::LITERAL)
     299              :         {
     300            0 :           auto &lit = static_cast<HIR::LiteralPattern &> (*pattern);
     301            0 :           int64_t value;
     302            0 :           if (literal_int_value (lit.get_literal (), lit.get_has_minus (),
     303              :                                  value))
     304            0 :             starts.push_back (value);
     305              :         }
     306              :     }
     307              : 
     308              :   // A value is covered if a range matches it or some arm starts exactly on it.
     309            6 :   auto covered = [&] (int64_t value) {
     310            4 :     if (std::find (starts.begin (), starts.end (), value) != starts.end ())
     311              :       return true;
     312            3 :     for (auto &range : ranges)
     313              :       {
     314            2 :         int64_t top = range.inclusive ? range.hi : range.hi - 1;
     315            2 :         if (value >= range.lo && value <= top)
     316            1 :           return true;
     317              :       }
     318              :     return false;
     319            4 :   };
     320              : 
     321              :   // An exclusive range `lo..hi` leaves `hi` unmatched. If another arm picks up
     322              :   // at `hi + 1`, that single value was almost certainly meant to be included.
     323            8 :   for (auto &range : ranges)
     324              :     {
     325            4 :       if (range.inclusive)
     326            2 :         continue;
     327              : 
     328            2 :       int64_t missed = range.hi;
     329            2 :       if (!covered (missed)
     330            2 :           && std::find (starts.begin (), starts.end (), missed + 1)
     331            2 :                != starts.end ())
     332            1 :         rust_warning_at (range.locus, OPT_Wunused_variable,
     333              :                          "multiple ranges are one apart");
     334              :     }
     335              : 
     336            4 :   walk (expr);
     337            4 : }
     338              : 
     339              : void
     340           11 : UnusedChecker::visit (HIR::LetStmt &stmt)
     341              : {
     342           11 :   for (auto &attr : stmt.get_outer_attrs ())
     343            1 :     if (attr.get_path ().as_string () == Values::Attributes::DOC)
     344              :       {
     345            1 :         rust_warning_at (stmt.get_locus (), OPT_Wunused_variable,
     346              :                          "unused doc comment");
     347            1 :         break;
     348              :       }
     349           11 :   if (stmt.has_init_expr ()
     350           11 :       && stmt.get_init_expr ().get_expression_type ()
     351              :            == HIR::Expr::ExprType::Block)
     352              :     {
     353            1 :       auto &block = static_cast<HIR::BlockExpr &> (stmt.get_init_expr ());
     354            1 :       if (block.get_statements ().empty () && block.has_expr ())
     355            1 :         rust_warning_at (block.get_locus (), OPT_Wunused,
     356              :                          "unnecessary braces around assigned value");
     357              :     }
     358           11 :   walk (stmt);
     359           11 : }
     360              : 
     361              : void
     362            1 : UnusedChecker::visit (HIR::BorrowExpr &expr)
     363              : {
     364              :   // The static_mut_refs lint: taking a reference to a mutable static is
     365              :   // discouraged as it can easily lead to undefined behaviour.
     366            1 :   NodeId ast_node_id = expr.get_expr ().get_mappings ().get_nodeid ();
     367            1 :   if (auto def
     368            1 :       = nr_context.lookup (ast_node_id, Resolver2_0::Namespace::Values))
     369            1 :     if (auto id = mappings.lookup_node_to_hir (*def))
     370            1 :       if (auto item = mappings.lookup_hir_item (*id))
     371            1 :         if (item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
     372              :           {
     373            1 :             auto &static_item = static_cast<HIR::StaticItem &> (*item.value ());
     374            1 :             if (static_item.is_mut ())
     375            1 :               rust_warning_at (expr.get_locus (), OPT_Wunused,
     376              :                                "creating a reference to a mutable static");
     377              :           }
     378            1 :   walk (expr);
     379            1 : }
     380              : 
     381              : } // namespace Analysis
     382              : } // 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.