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
|