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: 93.6 % 173 162
Test Date: 2026-07-11 15:47:05 Functions: 95.0 % 20 19
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-rib.h"
      28              : 
      29              : namespace Rust {
      30              : namespace Analysis {
      31           13 : UnusedChecker::UnusedChecker ()
      32           13 :   : nr_context (Resolver2_0::FinalizedNameResolutionContext::get ()),
      33           13 :     mappings (Analysis::Mappings::get ()), unused_context (UnusedContext ())
      34           13 : {}
      35              : void
      36           13 : UnusedChecker::go (HIR::Crate &crate)
      37              : {
      38           13 :   UnusedCollector collector (unused_context);
      39           13 :   collector.go (crate);
      40           36 :   for (auto &item : crate.get_items ())
      41           23 :     item->accept_vis (*this);
      42           13 : }
      43              : 
      44              : bool
      45           29 : is_snake_case (Identifier identifier)
      46              : {
      47           29 :   auto s = identifier.as_string ();
      48           29 :   return std::all_of (s.begin (), s.end (), [] (unsigned char c) {
      49           59 :     return ISLOWER (c) || ISDIGIT (c) || c == '_';
      50           29 :   });
      51           29 : }
      52              : 
      53              : void
      54            1 : UnusedChecker::visit (HIR::ConstantItem &item)
      55              : {
      56            2 :   std::string var_name = item.get_identifier ().as_string ();
      57            1 :   auto id = item.get_mappings ().get_hirid ();
      58            1 :   if (!unused_context.is_variable_used (id) && var_name[0] != '_')
      59            0 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      60              :                      "unused variable %qs",
      61            0 :                      item.get_identifier ().as_string ().c_str ());
      62              : 
      63              :   // The unused_visibilities lint: a visibility qualifier on a `const _` item
      64              :   // has no effect.
      65            1 :   if (var_name == "_" && item.get_visibility ().is_public ())
      66            1 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      67              :                      "visibility qualifier on a %<const _%> item is unused");
      68            1 : }
      69              : 
      70              : void
      71            2 : UnusedChecker::visit (HIR::StaticItem &item)
      72              : {
      73            4 :   std::string var_name = item.get_identifier ().as_string ();
      74            2 :   auto id = item.get_mappings ().get_hirid ();
      75            2 :   if (!unused_context.is_variable_used (id) && var_name[0] != '_')
      76            1 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      77              :                      "unused variable %qs",
      78            2 :                      item.get_identifier ().as_string ().c_str ());
      79              : 
      80            2 :   if (!std::all_of (var_name.begin (), var_name.end (), [] (unsigned char c) {
      81            6 :         return ISUPPER (c) || ISDIGIT (c) || c == '_';
      82              :       }))
      83            1 :     rust_warning_at (item.get_locus (), OPT_Wunused_variable,
      84              :                      "static variable %qs should have an upper case name",
      85              :                      var_name.c_str ());
      86            2 : }
      87              : 
      88              : void
      89            0 : UnusedChecker::visit (HIR::TraitItemFunc &item)
      90              : {
      91              :   // TODO: check trait item functions if they are not derived.
      92            0 : }
      93              : void
      94           13 : UnusedChecker::visit (HIR::IdentifierPattern &pattern)
      95              : {
      96           26 :   std::string var_name = pattern.get_identifier ().as_string ();
      97           13 :   auto id = pattern.get_mappings ().get_hirid ();
      98           13 :   if (!unused_context.is_variable_used (id)
      99           13 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     100            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     101              :                      "unused variable %qs",
     102            2 :                      pattern.get_identifier ().as_string ().c_str ());
     103              : 
     104           13 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
     105           14 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     106            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     107              :                      "unused mut %qs",
     108            2 :                      pattern.get_identifier ().as_string ().c_str ());
     109              : 
     110           13 :   if (!is_snake_case (pattern.get_identifier ()))
     111            1 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     112              :                      "variable %qs should have a snake case name",
     113              :                      var_name.c_str ());
     114           13 : }
     115              : 
     116              : void
     117            9 : UnusedChecker::visit (HIR::AssignmentExpr &expr)
     118              : {
     119            9 :   const auto &lhs = expr.get_lhs ();
     120            9 :   auto var_name = lhs.to_string ();
     121            9 :   NodeId ast_node_id = lhs.get_mappings ().get_nodeid ();
     122            9 :   if (auto def_id
     123            9 :       = nr_context.lookup (ast_node_id, Resolver2_0::Namespace::Values))
     124              :     {
     125            9 :       if (auto id = mappings.lookup_node_to_hir (*def_id))
     126              :         {
     127            9 :           if (unused_context.is_variable_assigned (
     128            9 :                 *id, lhs.get_mappings ().get_hirid ())
     129            9 :               && var_name[0] != '_')
     130            7 :             rust_warning_at (lhs.get_locus (), OPT_Wunused_variable,
     131              :                              "unused assignment %qs", var_name.c_str ());
     132              :         }
     133              :     }
     134            9 : }
     135              : 
     136              : void
     137            2 : UnusedChecker::visit (HIR::StructPatternFieldIdent &pattern)
     138              : {
     139            4 :   std::string var_name = pattern.get_identifier ().as_string ();
     140            2 :   auto id = pattern.get_mappings ().get_hirid ();
     141            2 :   if (!unused_context.is_variable_used (id)
     142            2 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     143            0 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     144              :                      "unused variable %qs",
     145            0 :                      pattern.get_identifier ().as_string ().c_str ());
     146              : 
     147            2 :   if (pattern.is_mut () && !unused_context.is_mut_used (id)
     148            4 :       && var_name != Values::Keywords::SELF && var_name[0] != '_')
     149            2 :     rust_warning_at (pattern.get_locus (), OPT_Wunused_variable,
     150              :                      "unused mut %qs",
     151            4 :                      pattern.get_identifier ().as_string ().c_str ());
     152            2 : }
     153              : 
     154              : void
     155            1 : UnusedChecker::visit (HIR::EmptyStmt &stmt)
     156              : {
     157            1 :   rust_warning_at (stmt.get_locus (), OPT_Wunused_variable,
     158              :                    "unnecessary trailing semicolons");
     159            1 : }
     160              : 
     161              : void
     162           14 : UnusedChecker::visit (HIR::Function &fct)
     163              : {
     164           14 :   if (!is_snake_case (fct.get_function_name ()))
     165            1 :     rust_warning_at (fct.get_locus (), OPT_Wunused_variable,
     166              :                      "function %qs should have a snake case name",
     167            2 :                      fct.get_function_name ().as_string ().c_str ());
     168           14 :   walk (fct);
     169           14 : }
     170              : 
     171              : void
     172            1 : UnusedChecker::visit (HIR::Module &mod)
     173              : {
     174            1 :   if (!is_snake_case (mod.get_module_name ()))
     175            1 :     rust_warning_at (mod.get_locus (), OPT_Wunused_variable,
     176              :                      "module %qs should have a snake case name",
     177            2 :                      mod.get_module_name ().as_string ().c_str ());
     178            1 :   walk (mod);
     179            1 : }
     180              : 
     181              : void
     182            1 : UnusedChecker::visit (HIR::LifetimeParam &lft)
     183              : {
     184            3 :   if (!is_snake_case (lft.get_lifetime ().get_name ()))
     185            1 :     rust_warning_at (lft.get_locus (), OPT_Wunused_variable,
     186              :                      "lifetime %qs should have a snake case name",
     187            2 :                      lft.get_lifetime ().get_name ().c_str ());
     188            1 :   walk (lft);
     189            1 : }
     190              : 
     191              : void
     192            2 : UnusedChecker::visit (HIR::ExternBlock &block)
     193              : {
     194            2 :   if (!block.has_abi ())
     195            1 :     rust_warning_at (block.get_locus (), OPT_Wunused_variable,
     196              :                      "extern declarations without an explicit ABI are "
     197              :                      "deprecated");
     198            2 :   walk (block);
     199            2 : }
     200              : 
     201              : void
     202            4 : UnusedChecker::visit_loop_label (HIR::LoopLabel &label)
     203              : {
     204            4 :   auto lifetime = label.get_lifetime ();
     205            4 :   std::string var_name = lifetime.to_string ();
     206            4 :   auto id = lifetime.get_mappings ().get_hirid ();
     207            4 :   if (!unused_context.is_label_used (id) && var_name[0] != '_')
     208            2 :     rust_warning_at (lifetime.get_locus (), OPT_Wunused_variable,
     209            4 :                      "unused label %qs", lifetime.to_string ().c_str ());
     210            4 : }
     211              : 
     212              : void
     213            2 : UnusedChecker::visit (HIR::StructPatternFieldIdentPat &field)
     214              : {
     215            2 :   auto &pattern = field.get_pattern ();
     216            2 :   if (pattern.get_pattern_type () == HIR::Pattern::PatternType::IDENTIFIER)
     217              :     {
     218            2 :       auto &ident = static_cast<HIR::IdentifierPattern &> (pattern);
     219            4 :       if (!ident.has_subpattern ()
     220            4 :           && ident.get_identifier ().as_string ()
     221            6 :                == field.get_identifier ().as_string ())
     222            2 :         rust_warning_at (field.get_locus (), OPT_Wunused_variable,
     223              :                          "the %qs in this pattern is redundant",
     224            2 :                          (field.get_identifier ().as_string () + ":").c_str ());
     225              :     }
     226            2 :   walk (field);
     227            2 : }
     228              : 
     229              : namespace {
     230              : 
     231              : bool
     232            8 : literal_int_value (const HIR::Literal &lit, bool minus, int64_t &out)
     233              : {
     234            8 :   if (lit.get_lit_type () != HIR::Literal::LitType::INT)
     235              :     return false;
     236              : 
     237            8 :   std::string digits = lit.as_string ();
     238            8 :   digits.erase (std::remove (digits.begin (), digits.end (), '_'),
     239            8 :                 digits.end ());
     240              : 
     241            8 :   char *end = nullptr;
     242            8 :   long long value = std::strtoll (digits.c_str (), &end, 10);
     243            8 :   if (end == digits.c_str () || *end != '\0')
     244              :     return false;
     245              : 
     246            8 :   out = minus ? -value : value;
     247            8 :   return true;
     248            8 : }
     249              : 
     250              : bool
     251            8 : range_bound_int (HIR::RangePatternBound &bound, int64_t &out)
     252              : {
     253            8 :   if (bound.get_bound_type ()
     254              :       != HIR::RangePatternBound::RangePatternBoundType::LITERAL)
     255              :     return false;
     256              : 
     257            8 :   auto &lit = static_cast<HIR::RangePatternBoundLiteral &> (bound);
     258            8 :   return literal_int_value (lit.get_literal (), lit.get_has_minus (), out);
     259              : }
     260              : 
     261              : } // namespace
     262              : 
     263              : void
     264            4 : UnusedChecker::visit (HIR::MatchExpr &expr)
     265              : {
     266            4 :   struct Range
     267              :   {
     268              :     int64_t lo;
     269              :     int64_t hi;
     270              :     bool inclusive;
     271              :     location_t locus;
     272              :   };
     273            4 :   std::vector<Range> ranges;
     274            4 :   std::vector<int64_t> starts;
     275              : 
     276           12 :   for (auto &match_case : expr.get_match_cases ())
     277              :     {
     278            8 :       auto &pattern = match_case.get_arm ().get_pattern ();
     279            8 :       if (!pattern)
     280            0 :         continue;
     281              : 
     282            8 :       if (pattern->get_pattern_type () == HIR::Pattern::PatternType::RANGE)
     283              :         {
     284            4 :           auto &range = static_cast<HIR::RangePattern &> (*pattern);
     285            4 :           int64_t lo, hi;
     286            4 :           if (range_bound_int (range.get_lower_bound (), lo)
     287            4 :               && range_bound_int (range.get_upper_bound (), hi))
     288              :             {
     289            4 :               ranges.push_back (
     290            4 :                 {lo, hi, range.is_inclusive_range (), range.get_locus ()});
     291            4 :               starts.push_back (lo);
     292              :             }
     293              :         }
     294            4 :       else if (pattern->get_pattern_type ()
     295              :                == HIR::Pattern::PatternType::LITERAL)
     296              :         {
     297            0 :           auto &lit = static_cast<HIR::LiteralPattern &> (*pattern);
     298            0 :           int64_t value;
     299            0 :           if (literal_int_value (lit.get_literal (), lit.get_has_minus (),
     300              :                                  value))
     301            0 :             starts.push_back (value);
     302              :         }
     303              :     }
     304              : 
     305              :   // A value is covered if a range matches it or some arm starts exactly on it.
     306            6 :   auto covered = [&] (int64_t value) {
     307            4 :     if (std::find (starts.begin (), starts.end (), value) != starts.end ())
     308              :       return true;
     309            3 :     for (auto &range : ranges)
     310              :       {
     311            2 :         int64_t top = range.inclusive ? range.hi : range.hi - 1;
     312            2 :         if (value >= range.lo && value <= top)
     313            1 :           return true;
     314              :       }
     315              :     return false;
     316            4 :   };
     317              : 
     318              :   // An exclusive range `lo..hi` leaves `hi` unmatched. If another arm picks up
     319              :   // at `hi + 1`, that single value was almost certainly meant to be included.
     320            8 :   for (auto &range : ranges)
     321              :     {
     322            4 :       if (range.inclusive)
     323            2 :         continue;
     324              : 
     325            2 :       int64_t missed = range.hi;
     326            2 :       if (!covered (missed)
     327            2 :           && std::find (starts.begin (), starts.end (), missed + 1)
     328            2 :                != starts.end ())
     329            1 :         rust_warning_at (range.locus, OPT_Wunused_variable,
     330              :                          "multiple ranges are one apart");
     331              :     }
     332              : 
     333            4 :   walk (expr);
     334            4 : }
     335              : 
     336              : } // namespace Analysis
     337              : } // 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.