Skip to content

Commit 1a415e6

Browse files
authored
double_parens: add structured suggestions, fix bug (#15420)
The issue that I saw made me rethink the lint design by quite a lot, so I decided to include that change in this PR fixes #9000 changelog: [`double_parens`]: add structured suggestions changelog: [`double_parens`]: fix FP when macros are involved
2 parents b05d55e + b613be2 commit 1a415e6

File tree

4 files changed

+234
-49
lines changed

4 files changed

+234
-49
lines changed

clippy_lints/src/double_parens.rs

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
use clippy_utils::diagnostics::span_lint;
2-
use rustc_ast::ast::{Expr, ExprKind};
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::{HasSession, snippet_with_applicability, snippet_with_context};
3+
use rustc_ast::ast::{Expr, ExprKind, MethodCall};
4+
use rustc_errors::Applicability;
35
use rustc_lint::{EarlyContext, EarlyLintPass};
46
use rustc_session::declare_lint_pass;
57

@@ -24,7 +26,7 @@ declare_clippy_lint! {
2426
/// Use instead:
2527
/// ```no_run
2628
/// fn simple_no_parens() -> i32 {
27-
/// 0
29+
/// (0)
2830
/// }
2931
///
3032
/// # fn foo(bar: usize) {}
@@ -40,29 +42,54 @@ declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]);
4042

4143
impl EarlyLintPass for DoubleParens {
4244
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
43-
let span = match &expr.kind {
44-
ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span,
45-
ExprKind::Call(_, params)
46-
if let [param] = &**params
47-
&& let ExprKind::Paren(_) = param.kind =>
48-
{
49-
param.span
45+
match &expr.kind {
46+
// ((..))
47+
// ^^^^^^ expr
48+
// ^^^^ inner
49+
ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => {
50+
// suggest removing the outer parens
51+
if expr.span.eq_ctxt(inner.span) {
52+
let mut applicability = Applicability::MachineApplicable;
53+
// We don't need to use `snippet_with_context` here, because:
54+
// - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above)
55+
// - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine
56+
let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability);
57+
span_lint_and_sugg(
58+
cx,
59+
DOUBLE_PARENS,
60+
expr.span,
61+
"unnecessary parentheses",
62+
"remove them",
63+
sugg.to_string(),
64+
applicability,
65+
);
66+
}
5067
},
51-
ExprKind::MethodCall(call)
52-
if let [arg] = &*call.args
53-
&& let ExprKind::Paren(_) = arg.kind =>
68+
69+
// func((n))
70+
// ^^^^^^^^^ expr
71+
// ^^^ arg
72+
// ^ inner
73+
ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. })
74+
if let [arg] = &**args
75+
&& let ExprKind::Paren(inner) = &arg.kind =>
5476
{
55-
arg.span
77+
// suggest removing the inner parens
78+
if expr.span.eq_ctxt(arg.span) {
79+
let mut applicability = Applicability::MachineApplicable;
80+
let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0;
81+
span_lint_and_sugg(
82+
cx,
83+
DOUBLE_PARENS,
84+
arg.span,
85+
"unnecessary parentheses",
86+
"remove them",
87+
sugg.to_string(),
88+
applicability,
89+
);
90+
}
5691
},
57-
_ => return,
58-
};
59-
if !expr.span.from_expansion() {
60-
span_lint(
61-
cx,
62-
DOUBLE_PARENS,
63-
span,
64-
"consider removing unnecessary double parentheses",
65-
);
92+
_ => {},
6693
}
6794
}
6895
}

tests/ui/double_parens.fixed

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#![warn(clippy::double_parens)]
2+
#![expect(clippy::eq_op, clippy::no_effect)]
3+
#![feature(custom_inner_attributes)]
4+
#![rustfmt::skip]
5+
6+
fn dummy_fn<T>(_: T) {}
7+
8+
struct DummyStruct;
9+
10+
impl DummyStruct {
11+
fn dummy_method<T>(&self, _: T) {}
12+
}
13+
14+
fn simple_double_parens() -> i32 {
15+
(0)
16+
//~^ double_parens
17+
}
18+
19+
fn fn_double_parens() {
20+
dummy_fn(0);
21+
//~^ double_parens
22+
}
23+
24+
fn method_double_parens(x: DummyStruct) {
25+
x.dummy_method(0);
26+
//~^ double_parens
27+
}
28+
29+
fn tuple_double_parens() -> (i32, i32) {
30+
(1, 2)
31+
//~^ double_parens
32+
}
33+
34+
#[allow(clippy::unused_unit)]
35+
fn unit_double_parens() {
36+
()
37+
//~^ double_parens
38+
}
39+
40+
fn fn_tuple_ok() {
41+
dummy_fn((1, 2));
42+
}
43+
44+
fn method_tuple_ok(x: DummyStruct) {
45+
x.dummy_method((1, 2));
46+
}
47+
48+
fn fn_unit_ok() {
49+
dummy_fn(());
50+
}
51+
52+
fn method_unit_ok(x: DummyStruct) {
53+
x.dummy_method(());
54+
}
55+
56+
// Issue #3206
57+
fn inside_macro() {
58+
assert_eq!((1, 2), (1, 2), "Error");
59+
assert_eq!((1, 2), (1, 2), "Error");
60+
//~^ double_parens
61+
}
62+
63+
fn issue9000(x: DummyStruct) {
64+
macro_rules! foo {
65+
() => {(100)}
66+
}
67+
// don't lint: the inner paren comes from the macro expansion
68+
(foo!());
69+
dummy_fn(foo!());
70+
x.dummy_method(foo!());
71+
72+
macro_rules! baz {
73+
($n:literal) => {($n)}
74+
}
75+
// don't lint: don't get confused by the expression inside the inner paren
76+
// having the same `ctxt` as the overall expression
77+
// (this is a bug that happened during the development of the fix)
78+
(baz!(100));
79+
dummy_fn(baz!(100));
80+
x.dummy_method(baz!(100));
81+
82+
// should lint: both parens are from inside the macro
83+
macro_rules! bar {
84+
() => {(100)}
85+
//~^ double_parens
86+
}
87+
bar!();
88+
89+
// should lint: both parens are from outside the macro;
90+
// make sure to suggest the macro unexpanded
91+
(vec![1, 2]);
92+
//~^ double_parens
93+
dummy_fn(vec![1, 2]);
94+
//~^ double_parens
95+
x.dummy_method(vec![1, 2]);
96+
//~^ double_parens
97+
}
98+
99+
fn main() {}

tests/ui/double_parens.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::double_parens)]
2-
#![allow(dead_code, clippy::eq_op)]
2+
#![expect(clippy::eq_op, clippy::no_effect)]
33
#![feature(custom_inner_attributes)]
44
#![rustfmt::skip]
55

@@ -8,38 +8,33 @@ fn dummy_fn<T>(_: T) {}
88
struct DummyStruct;
99

1010
impl DummyStruct {
11-
fn dummy_method<T>(self, _: T) {}
11+
fn dummy_method<T>(&self, _: T) {}
1212
}
1313

1414
fn simple_double_parens() -> i32 {
1515
((0))
1616
//~^ double_parens
17-
18-
1917
}
2018

2119
fn fn_double_parens() {
2220
dummy_fn((0));
2321
//~^ double_parens
24-
2522
}
2623

2724
fn method_double_parens(x: DummyStruct) {
2825
x.dummy_method((0));
2926
//~^ double_parens
30-
3127
}
3228

3329
fn tuple_double_parens() -> (i32, i32) {
3430
((1, 2))
3531
//~^ double_parens
36-
3732
}
3833

34+
#[allow(clippy::unused_unit)]
3935
fn unit_double_parens() {
4036
(())
4137
//~^ double_parens
42-
4338
}
4439

4540
fn fn_tuple_ok() {
@@ -63,7 +58,42 @@ fn inside_macro() {
6358
assert_eq!((1, 2), (1, 2), "Error");
6459
assert_eq!(((1, 2)), (1, 2), "Error");
6560
//~^ double_parens
61+
}
6662

63+
fn issue9000(x: DummyStruct) {
64+
macro_rules! foo {
65+
() => {(100)}
66+
}
67+
// don't lint: the inner paren comes from the macro expansion
68+
(foo!());
69+
dummy_fn(foo!());
70+
x.dummy_method(foo!());
71+
72+
macro_rules! baz {
73+
($n:literal) => {($n)}
74+
}
75+
// don't lint: don't get confused by the expression inside the inner paren
76+
// having the same `ctxt` as the overall expression
77+
// (this is a bug that happened during the development of the fix)
78+
(baz!(100));
79+
dummy_fn(baz!(100));
80+
x.dummy_method(baz!(100));
81+
82+
// should lint: both parens are from inside the macro
83+
macro_rules! bar {
84+
() => {((100))}
85+
//~^ double_parens
86+
}
87+
bar!();
88+
89+
// should lint: both parens are from outside the macro;
90+
// make sure to suggest the macro unexpanded
91+
((vec![1, 2]));
92+
//~^ double_parens
93+
dummy_fn((vec![1, 2]));
94+
//~^ double_parens
95+
x.dummy_method((vec![1, 2]));
96+
//~^ double_parens
6797
}
6898

6999
fn main() {}

tests/ui/double_parens.stderr

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,70 @@
1-
error: consider removing unnecessary double parentheses
1+
error: unnecessary parentheses
22
--> tests/ui/double_parens.rs:15:5
33
|
44
LL | ((0))
5-
| ^^^^^
5+
| ^^^^^ help: remove them: `(0)`
66
|
77
= note: `-D clippy::double-parens` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::double_parens)]`
99

10-
error: consider removing unnecessary double parentheses
11-
--> tests/ui/double_parens.rs:22:14
10+
error: unnecessary parentheses
11+
--> tests/ui/double_parens.rs:20:14
1212
|
1313
LL | dummy_fn((0));
14-
| ^^^
14+
| ^^^ help: remove them: `0`
1515

16-
error: consider removing unnecessary double parentheses
17-
--> tests/ui/double_parens.rs:28:20
16+
error: unnecessary parentheses
17+
--> tests/ui/double_parens.rs:25:20
1818
|
1919
LL | x.dummy_method((0));
20-
| ^^^
20+
| ^^^ help: remove them: `0`
2121

22-
error: consider removing unnecessary double parentheses
23-
--> tests/ui/double_parens.rs:34:5
22+
error: unnecessary parentheses
23+
--> tests/ui/double_parens.rs:30:5
2424
|
2525
LL | ((1, 2))
26-
| ^^^^^^^^
26+
| ^^^^^^^^ help: remove them: `(1, 2)`
2727

28-
error: consider removing unnecessary double parentheses
29-
--> tests/ui/double_parens.rs:40:5
28+
error: unnecessary parentheses
29+
--> tests/ui/double_parens.rs:36:5
3030
|
3131
LL | (())
32-
| ^^^^
32+
| ^^^^ help: remove them: `()`
3333

34-
error: consider removing unnecessary double parentheses
35-
--> tests/ui/double_parens.rs:64:16
34+
error: unnecessary parentheses
35+
--> tests/ui/double_parens.rs:59:16
3636
|
3737
LL | assert_eq!(((1, 2)), (1, 2), "Error");
38-
| ^^^^^^^^
38+
| ^^^^^^^^ help: remove them: `(1, 2)`
3939

40-
error: aborting due to 6 previous errors
40+
error: unnecessary parentheses
41+
--> tests/ui/double_parens.rs:84:16
42+
|
43+
LL | () => {((100))}
44+
| ^^^^^^^ help: remove them: `(100)`
45+
...
46+
LL | bar!();
47+
| ------ in this macro invocation
48+
|
49+
= note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info)
50+
51+
error: unnecessary parentheses
52+
--> tests/ui/double_parens.rs:91:5
53+
|
54+
LL | ((vec![1, 2]));
55+
| ^^^^^^^^^^^^^^ help: remove them: `(vec![1, 2])`
56+
57+
error: unnecessary parentheses
58+
--> tests/ui/double_parens.rs:93:14
59+
|
60+
LL | dummy_fn((vec![1, 2]));
61+
| ^^^^^^^^^^^^ help: remove them: `vec![1, 2]`
62+
63+
error: unnecessary parentheses
64+
--> tests/ui/double_parens.rs:95:20
65+
|
66+
LL | x.dummy_method((vec![1, 2]));
67+
| ^^^^^^^^^^^^ help: remove them: `vec![1, 2]`
68+
69+
error: aborting due to 10 previous errors
4170

0 commit comments

Comments
 (0)