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
|