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
|