ide_diagnostics/handlers/
unused_variables.rs1use hir::Name;
2use ide_db::text_edit::TextEdit;
3use ide_db::{
4 FileRange, RootDatabase,
5 assists::{Assist, AssistId},
6 label::Label,
7 source_change::SourceChange,
8};
9use syntax::{AstNode, Edition, TextRange, ToSmolStr};
10
11use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
12
13pub(crate) fn unused_variables(
17 ctx: &DiagnosticsContext<'_>,
18 d: &hir::UnusedVariable,
19) -> Option<Diagnostic> {
20 let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
21 if ast.file_id.macro_file().is_some() {
22 return None;
24 }
25 let diagnostic_range = ctx.sema.diagnostics_display_range(ast);
26 let primary_source = d.local.primary_source(ctx.sema.db);
28 let name_range = primary_source
29 .name()
30 .map(|v| v.syntax().original_file_range_rooted(ctx.sema.db))
31 .filter(|it| {
32 Some(it.file_id) == ast.file_id.file_id()
33 && diagnostic_range.range.contains_range(it.range)
34 });
35 let is_shorthand_field = primary_source
36 .source
37 .value
38 .left()
39 .and_then(|name| name.syntax().parent())
40 .and_then(syntax::ast::RecordPatField::cast)
41 .is_some_and(|field| field.colon_token().is_none());
42 let var_name = d.local.name(ctx.sema.db);
43 Some(
44 Diagnostic::new_with_syntax_node_ptr(
45 ctx,
46 DiagnosticCode::RustcLint("unused_variables"),
47 "unused variable",
48 ast,
49 )
50 .with_fixes(name_range.and_then(|it| {
51 fixes(
52 ctx.sema.db,
53 var_name,
54 it.range,
55 diagnostic_range,
56 ast.file_id.is_macro(),
57 is_shorthand_field,
58 ctx.edition,
59 )
60 })),
61 )
62}
63
64fn fixes(
65 db: &RootDatabase,
66 var_name: Name,
67 name_range: TextRange,
68 diagnostic_range: FileRange,
69 is_in_marco: bool,
70 is_shorthand_field: bool,
71 edition: Edition,
72) -> Option<Vec<Assist>> {
73 if is_in_marco {
74 return None;
75 }
76 let name = var_name.display(db, edition).to_smolstr();
77 let name = name.strip_prefix("r#").unwrap_or(&name);
78 let new_name = if is_shorthand_field { format!("{name}: _{name}") } else { format!("_{name}") };
79
80 Some(vec![Assist {
81 id: AssistId::quick_fix("unscore_unused_variable_name"),
82 label: Label::new(format!("Rename unused {name} to {new_name}")),
83 group: None,
84 target: diagnostic_range.range,
85 source_change: Some(SourceChange::from_text_edit(
86 diagnostic_range.file_id,
87 TextEdit::replace(name_range, new_name),
88 )),
89 command: None,
90 }])
91}
92
93#[cfg(test)]
94mod tests {
95 use crate::tests::{check_diagnostics, check_fix};
96
97 #[test]
98 fn unused_variables_simple() {
99 check_diagnostics(
100 r#"
101//- minicore: fn
102struct Foo { f1: i32, f2: i64 }
103
104fn f(kkk: i32) {}
105 //^^^ 💡 warn: unused variable
106fn main() {
107 let a = 2;
108 //^ 💡 warn: unused variable
109 let b = 5;
110 // note: `unused variable` implies `unused mut`, so we should not emit both at the same time.
111 let mut c = f(b);
112 //^^^^^ 💡 warn: unused variable
113 let (d, e) = (3, 5);
114 //^ 💡 warn: unused variable
115 let _ = e;
116 let f1 = 2;
117 let f2 = 5;
118 let f = Foo { f1, f2 };
119 match f {
120 Foo { f1, f2 } => {
121 //^^ 💡 warn: unused variable
122 _ = f2;
123 }
124 }
125 let g = false;
126 if g {}
127 let h: fn() -> i32 = || 2;
128 let i = h();
129 //^ 💡 warn: unused variable
130}
131"#,
132 );
133 }
134
135 #[test]
136 fn unused_self() {
137 check_diagnostics(
138 r#"
139struct S {
140}
141impl S {
142 fn owned_self(self, u: i32) {}
143 //^ 💡 warn: unused variable
144 fn ref_self(&self, u: i32) {}
145 //^ 💡 warn: unused variable
146 fn ref_mut_self(&mut self, u: i32) {}
147 //^ 💡 warn: unused variable
148 fn owned_mut_self(mut self) {}
149 //^^^^^^^^ 💡 warn: variable does not need to be mutable
150
151}
152"#,
153 );
154 }
155
156 #[test]
157 fn allow_unused_variables_for_identifiers_starting_with_underline() {
158 check_diagnostics(
159 r#"
160fn main() {
161 let _x = 2;
162}
163"#,
164 );
165 }
166
167 #[test]
168 fn respect_lint_attributes_for_unused_variables() {
169 check_diagnostics(
170 r#"
171fn main() {
172 #[allow(unused_variables)]
173 let x = 2;
174}
175
176#[deny(unused)]
177fn main2() {
178 let x = 2;
179 //^ 💡 error: unused variable
180}
181"#,
182 );
183 }
184
185 #[test]
186 fn apply_last_lint_attribute_when_multiple_are_present() {
187 check_diagnostics(
188 r#"
189#![allow(unused_variables)]
190#![warn(unused_variables)]
191#![deny(unused_variables)]
192
193fn main() {
194 let x = 2;
195 //^ 💡 error: unused variable
196
197 #[deny(unused_variables)]
198 #[warn(unused_variables)]
199 #[allow(unused_variables)]
200 let y = 0;
201}
202"#,
203 );
204 }
205
206 #[test]
207 fn prefer_closest_ancestor_lint_attribute() {
208 check_diagnostics(
209 r#"
210#![allow(unused_variables)]
211
212fn main() {
213 #![warn(unused_variables)]
214
215 #[deny(unused_variables)]
216 let x = 2;
217 //^ 💡 error: unused variable
218}
219
220#[warn(unused_variables)]
221fn main2() {
222 #[deny(unused_variables)]
223 let x = 2;
224 //^ 💡 error: unused variable
225}
226
227#[warn(unused_variables)]
228fn main3() {
229 let x = 2;
230 //^ 💡 warn: unused variable
231}
232
233fn main4() {
234 let x = 2;
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn fix_unused_variable() {
242 check_fix(
243 r#"
244fn main() {
245 let x$0 = 2;
246}
247"#,
248 r#"
249fn main() {
250 let _x = 2;
251}
252"#,
253 );
254
255 check_fix(
256 r#"
257fn main() {
258 let ($0d, _e) = (3, 5);
259}
260"#,
261 r#"
262fn main() {
263 let (_d, _e) = (3, 5);
264}
265"#,
266 );
267
268 check_fix(
269 r#"
270struct Foo { f1: i32, f2: i64 }
271fn main() {
272 let f = Foo { f1: 0, f2: 0 };
273 match f {
274 Foo { f1$0, f2 } => {
275 _ = f2;
276 }
277 }
278}
279"#,
280 r#"
281struct Foo { f1: i32, f2: i64 }
282fn main() {
283 let f = Foo { f1: 0, f2: 0 };
284 match f {
285 Foo { f1: _f1, f2 } => {
286 _ = f2;
287 }
288 }
289}
290"#,
291 );
292
293 check_fix(
294 r#"
295fn main() {
296 let $0r#type = 2;
297}
298"#,
299 r#"
300fn main() {
301 let _type = 2;
302}
303"#,
304 );
305 }
306
307 #[test]
308 fn no_fix_for_marco() {
309 check_diagnostics(
310 r#"
311macro_rules! my_macro {
312 () => {
313 let x = 3;
314 };
315}
316
317fn main() {
318 my_macro!();
319}
320"#,
321 );
322 }
323 #[test]
324 fn unused_variable_in_array_destructure() {
325 check_fix(
326 r#"
327fn main() {
328 let arr = [1, 2, 3, 4, 5];
329 let [_x, y$0 @ ..] = arr;
330}
331"#,
332 r#"
333fn main() {
334 let arr = [1, 2, 3, 4, 5];
335 let [_x, _y @ ..] = arr;
336}
337"#,
338 );
339 }
340
341 #[test]
342 fn unused_variable_in_record_field() {
343 check_fix(
344 r#"
345struct S { field : u32 }
346fn main() {
347 let s = S { field : 2 };
348 let S { field: $0x } = s
349}
350"#,
351 r#"
352struct S { field : u32 }
353fn main() {
354 let s = S { field : 2 };
355 let S { field: _x } = s
356}
357"#,
358 );
359 }
360
361 #[test]
362 fn unused_variable_in_shorthand_record_field() {
363 check_fix(
364 r#"
365struct S { field : u32 }
366fn main() {
367 let s = S { field : 2 };
368 let S { $0field } = s
369}
370"#,
371 r#"
372struct S { field : u32 }
373fn main() {
374 let s = S { field : 2 };
375 let S { field: _field } = s
376}
377"#,
378 );
379 }
380
381 #[test]
383 fn unknown_struct_pattern_param_type() {
384 check_diagnostics(
385 r#"
386struct S { field : u32 }
387fn f(S { field }: error) {
388 // ^^^^^ 💡 warn: unused variable
389}
390"#,
391 );
392 }
393}