Skip to content

Commit 988042e

Browse files
authored
chore: multipart suggestions for let_unit_value lint (#13754)
This should address #13099 for the let_unit test. changelog: [let_unit]: Updated let_unit to use multipart_suggestions where appropriate
2 parents 592fd34 + ec24388 commit 988042e

File tree

4 files changed

+228
-27
lines changed

4 files changed

+228
-27
lines changed

clippy_lints/src/unit_types/let_unit_value.rs

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::source::snippet_with_context;
3-
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source, is_local_used};
3+
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
44
use core::ops::ControlFlow;
55
use rustc_errors::Applicability;
66
use rustc_hir::def::{DefKind, Res};
@@ -71,25 +71,38 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
7171
local.span,
7272
"this let-binding has unit value",
7373
|diag| {
74+
let mut suggestions = Vec::new();
75+
76+
// Suggest omitting the `let` binding
7477
if let Some(expr) = &local.init {
7578
let mut app = Applicability::MachineApplicable;
7679
let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0;
77-
diag.span_suggestion(local.span, "omit the `let` binding", format!("{snip};"), app);
80+
suggestions.push((local.span, format!("{snip};")));
7881
}
7982

80-
if let PatKind::Binding(_, binding_hir_id, ident, ..) = local.pat.kind
83+
// If this is a binding pattern, we need to add suggestions to remove any usages
84+
// of the variable
85+
if let PatKind::Binding(_, binding_hir_id, ..) = local.pat.kind
8186
&& let Some(body_id) = cx.enclosing_body.as_ref()
82-
&& let body = cx.tcx.hir().body(*body_id)
83-
&& is_local_used(cx, body, binding_hir_id)
8487
{
85-
let identifier = ident.as_str();
88+
let body = cx.tcx.hir().body(*body_id);
89+
90+
// Collect variable usages
8691
let mut visitor = UnitVariableCollector::new(binding_hir_id);
8792
walk_body(&mut visitor, body);
88-
visitor.spans.into_iter().for_each(|span| {
89-
let msg =
90-
format!("variable `{identifier}` of type `()` can be replaced with explicit `()`");
91-
diag.span_suggestion(span, msg, "()", Applicability::MachineApplicable);
92-
});
93+
94+
// Add suggestions for replacing variable usages
95+
suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string())));
96+
}
97+
98+
// Emit appropriate diagnostic based on whether there are usages of the let binding
99+
if !suggestions.is_empty() {
100+
let message = if suggestions.len() == 1 {
101+
"omit the `let` binding"
102+
} else {
103+
"omit the `let` binding and replace variable usages with `()`"
104+
};
105+
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
93106
}
94107
},
95108
);

tests/ui/let_unit.fixed

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
#![warn(clippy::let_unit_value)]
2+
#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)]
3+
4+
macro_rules! let_and_return {
5+
($n:expr) => {{
6+
let ret = $n;
7+
}};
8+
}
9+
10+
fn main() {
11+
println!("x");
12+
let _y = 1; // this is fine
13+
let _z = ((), 1); // this as well
14+
if true {
15+
// do not lint this, since () is explicit
16+
let _a = ();
17+
let () = dummy();
18+
let () = ();
19+
() = dummy();
20+
() = ();
21+
let _a: () = ();
22+
let _a: () = dummy();
23+
}
24+
25+
consume_units_with_for_loop(); // should be fine as well
26+
27+
multiline_sugg();
28+
29+
let_and_return!(()) // should be fine
30+
}
31+
32+
fn dummy() {}
33+
34+
// Related to issue #1964
35+
fn consume_units_with_for_loop() {
36+
// `for_let_unit` lint should not be triggered by consuming them using for loop.
37+
let v = vec![(), (), ()];
38+
let mut count = 0;
39+
for _ in v {
40+
count += 1;
41+
}
42+
assert_eq!(count, 3);
43+
44+
// Same for consuming from some other Iterator<Item = ()>.
45+
let (tx, rx) = ::std::sync::mpsc::channel();
46+
tx.send(()).unwrap();
47+
drop(tx);
48+
49+
count = 0;
50+
for _ in rx.iter() {
51+
count += 1;
52+
}
53+
assert_eq!(count, 1);
54+
}
55+
56+
fn multiline_sugg() {
57+
let v: Vec<u8> = vec![2];
58+
59+
v
60+
.into_iter()
61+
.map(|i| i * 2)
62+
.filter(|i| i % 2 == 0)
63+
.map(|_| ())
64+
.next()
65+
.unwrap();
66+
}
67+
68+
#[derive(Copy, Clone)]
69+
pub struct ContainsUnit(()); // should be fine
70+
71+
fn _returns_generic() {
72+
fn f<T>() -> T {
73+
unimplemented!()
74+
}
75+
fn f2<T, U>(_: T) -> U {
76+
unimplemented!()
77+
}
78+
fn f3<T>(x: T) -> T {
79+
x
80+
}
81+
fn f5<T: Default>(x: bool) -> Option<T> {
82+
x.then(|| T::default())
83+
}
84+
85+
let _: () = f();
86+
let x: () = f();
87+
88+
let _: () = f2(0i32);
89+
let x: () = f2(0i32);
90+
91+
let _: () = f3(());
92+
let x: () = f3(());
93+
94+
fn f4<T>(mut x: Vec<T>) -> T {
95+
x.pop().unwrap()
96+
}
97+
let _: () = f4(vec![()]);
98+
let x: () = f4(vec![()]);
99+
100+
let _: () = {
101+
let x = 5;
102+
f2(x)
103+
};
104+
105+
let _: () = if true { f() } else { f2(0) };
106+
let x: () = if true { f() } else { f2(0) };
107+
108+
match Some(0) {
109+
None => f2(1),
110+
Some(0) => f(),
111+
Some(1) => f2(3),
112+
Some(_) => (),
113+
};
114+
115+
let _: () = f5(true).unwrap();
116+
117+
#[allow(clippy::let_unit_value)]
118+
{
119+
let x = f();
120+
let y;
121+
let z;
122+
match 0 {
123+
0 => {
124+
y = f();
125+
z = f();
126+
},
127+
1 => {
128+
println!("test");
129+
y = f();
130+
z = f3(());
131+
},
132+
_ => panic!(),
133+
}
134+
135+
let x1;
136+
let x2;
137+
if true {
138+
x1 = f();
139+
x2 = x1;
140+
} else {
141+
x2 = f();
142+
x1 = x2;
143+
}
144+
145+
let opt;
146+
match f5(true) {
147+
Some(x) => opt = x,
148+
None => panic!(),
149+
};
150+
151+
#[warn(clippy::let_unit_value)]
152+
{
153+
let _: () = x;
154+
let _: () = y;
155+
let _: () = z;
156+
let _: () = x1;
157+
let _: () = x2;
158+
let _: () = opt;
159+
}
160+
}
161+
162+
let () = f();
163+
}
164+
165+
fn attributes() {
166+
fn f() {}
167+
168+
#[allow(clippy::let_unit_value)]
169+
let _ = f();
170+
#[expect(clippy::let_unit_value)]
171+
let _ = f();
172+
}
173+
174+
async fn issue10433() {
175+
let _pending: () = std::future::pending().await;
176+
}
177+
178+
pub async fn issue11502(a: ()) {}
179+
180+
pub fn issue12594() {
181+
fn returns_unit() {}
182+
183+
fn returns_result<T>(res: T) -> Result<T, ()> {
184+
Ok(res)
185+
}
186+
187+
fn actual_test() {
188+
// create first a unit value'd value
189+
returns_unit();
190+
returns_result(()).unwrap();
191+
returns_result(()).unwrap();
192+
// make sure we replace only the first variable
193+
let res = 1;
194+
returns_result(res).unwrap();
195+
}
196+
}

tests/ui/let_unit.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#![warn(clippy::let_unit_value)]
22
#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)]
33

4-
//@no-rustfix: need to change the suggestion to a multipart suggestion
5-
64
macro_rules! let_and_return {
75
($n:expr) => {{
86
let ret = $n;

tests/ui/let_unit.stderr

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this let-binding has unit value
2-
--> tests/ui/let_unit.rs:13:5
2+
--> tests/ui/let_unit.rs:11:5
33
|
44
LL | let _x = println!("x");
55
| ^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `println!("x");`
@@ -8,7 +8,7 @@ LL | let _x = println!("x");
88
= help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]`
99

1010
error: this let-binding has unit value
11-
--> tests/ui/let_unit.rs:61:5
11+
--> tests/ui/let_unit.rs:59:5
1212
|
1313
LL | / let _ = v
1414
LL | | .into_iter()
@@ -31,7 +31,7 @@ LL + .unwrap();
3131
|
3232

3333
error: this let-binding has unit value
34-
--> tests/ui/let_unit.rs:110:5
34+
--> tests/ui/let_unit.rs:108:5
3535
|
3636
LL | / let x = match Some(0) {
3737
LL | | None => f2(1),
@@ -52,23 +52,17 @@ LL + };
5252
|
5353

5454
error: this let-binding has unit value
55-
--> tests/ui/let_unit.rs:191:9
55+
--> tests/ui/let_unit.rs:189:9
5656
|
5757
LL | let res = returns_unit();
5858
| ^^^^^^^^^^^^^^^^^^^^^^^^^
5959
|
60-
help: omit the `let` binding
61-
|
62-
LL | returns_unit();
63-
|
64-
help: variable `res` of type `()` can be replaced with explicit `()`
60+
help: omit the `let` binding and replace variable usages with `()`
6561
|
66-
LL | returns_result(()).unwrap();
67-
| ~~
68-
help: variable `res` of type `()` can be replaced with explicit `()`
62+
LL ~ returns_unit();
63+
LL ~ returns_result(()).unwrap();
64+
LL ~ returns_result(()).unwrap();
6965
|
70-
LL | returns_result(()).unwrap();
71-
| ~~
7266

7367
error: aborting due to 4 previous errors
7468

0 commit comments

Comments
 (0)