Skip to content

Commit 536c63b

Browse files
committed
fix: unit_arg wrongly handled macros
1 parent 0524773 commit 536c63b

File tree

7 files changed

+188
-19
lines changed

7 files changed

+188
-19
lines changed

clippy_lints/src/unit_types/unit_arg.rs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::source::{SourceText, SpanRangeExt, indent_of, reindent_multiline};
2+
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
3+
use clippy_utils::sugg::Sugg;
34
use clippy_utils::{is_expr_default, is_from_proc_macro};
45
use rustc_errors::Applicability;
56
use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind};
67
use rustc_lint::LateContext;
8+
use rustc_span::SyntaxContext;
79

810
use super::{UNIT_ARG, utils};
911

@@ -100,14 +102,16 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_
100102

101103
let arg_snippets: Vec<_> = args_to_recover
102104
.iter()
103-
.filter_map(|arg| arg.span.get_source_text(cx))
105+
// If the argument is from an expansion and is a `Default::default()`, we skip it
106+
.filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg))
107+
.filter_map(|arg| get_expr_snippet(cx, arg))
104108
.collect();
105109

106110
// If the argument is an empty block or `Default::default()`, we can replace it with `()`.
107111
let arg_snippets_without_redundant_exprs: Vec<_> = args_to_recover
108112
.iter()
109-
.filter(|arg| !is_empty_block(arg) && !is_expr_default(cx, arg))
110-
.filter_map(|arg| arg.span.get_source_text(cx))
113+
.filter(|arg| !is_expr_default_nested(cx, arg) && (arg.span.from_expansion() || !is_empty_block(arg)))
114+
.filter_map(|arg| get_expr_snippet(cx, arg))
111115
.collect();
112116

113117
if let Some(call_snippet) = expr.span.get_source_text(cx) {
@@ -119,13 +123,17 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_
119123
&arg_snippets_without_redundant_exprs,
120124
);
121125

122-
if arg_snippets_without_redundant_exprs.is_empty() {
126+
if arg_snippets_without_redundant_exprs.is_empty()
127+
&& let suggestions = args_to_recover
128+
.iter()
129+
.filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg))
130+
.map(|arg| (arg.span.parent_callsite().unwrap_or(arg.span), "()".to_string()))
131+
.collect::<Vec<_>>()
132+
&& !suggestions.is_empty()
133+
{
123134
db.multipart_suggestion(
124135
format!("use {singular}unit literal{plural} instead"),
125-
args_to_recover
126-
.iter()
127-
.map(|arg| (arg.span, "()".to_string()))
128-
.collect::<Vec<_>>(),
136+
suggestions,
129137
applicability,
130138
);
131139
} else {
@@ -146,6 +154,22 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_
146154
);
147155
}
148156

157+
fn is_expr_default_nested<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
158+
is_expr_default(cx, expr)
159+
|| matches!(expr.kind, ExprKind::Block(block, _)
160+
if block.expr.is_some() && is_expr_default_nested(cx, block.expr.unwrap()))
161+
}
162+
163+
fn get_expr_snippet<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<Sugg<'tcx>> {
164+
let mut app = Applicability::MachineApplicable;
165+
let snip = Sugg::hir_with_context(cx, expr, SyntaxContext::root(), "..", &mut app);
166+
if app != Applicability::MachineApplicable {
167+
return None;
168+
}
169+
170+
Some(snip)
171+
}
172+
149173
fn is_empty_block(expr: &Expr<'_>) -> bool {
150174
matches!(
151175
expr.kind,
@@ -164,17 +188,17 @@ fn fmt_stmts_and_call(
164188
cx: &LateContext<'_>,
165189
call_expr: &Expr<'_>,
166190
call_snippet: &str,
167-
args_snippets: &[SourceText],
168-
non_empty_block_args_snippets: &[SourceText],
191+
args_snippets: &[Sugg<'_>],
192+
non_empty_block_args_snippets: &[Sugg<'_>],
169193
) -> String {
170194
let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0);
171-
let call_snippet_with_replacements = args_snippets
172-
.iter()
173-
.fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1));
195+
let call_snippet_with_replacements = args_snippets.iter().fold(call_snippet.to_owned(), |acc, arg| {
196+
acc.replacen(&arg.to_string(), "()", 1)
197+
});
174198

175199
let mut stmts_and_call = non_empty_block_args_snippets
176200
.iter()
177-
.map(|it| it.as_ref().to_owned())
201+
.map(|it| it.to_string())
178202
.collect::<Vec<_>>();
179203
stmts_and_call.push(call_snippet_with_replacements);
180204
stmts_and_call = stmts_and_call

clippy_utils/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,13 +3472,12 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
34723472
}
34733473
}
34743474

3475-
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
3475+
/// Checks if the given expression is a call to `Default::default()`.
34763476
pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
34773477
if let ExprKind::Call(fn_expr, []) = &expr.kind
34783478
&& let ExprKind::Path(qpath) = &fn_expr.kind
34793479
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
34803480
{
3481-
// right hand side of assignment is `Default::default`
34823481
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
34833482
} else {
34843483
false

tests/ui/unit_arg.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,27 @@ fn main() {
151151
bad();
152152
ok();
153153
}
154+
155+
fn issue14857() {
156+
let fn_take_unit = |_: ()| {};
157+
fn some_other_fn(_: &i32) {}
158+
159+
macro_rules! mac {
160+
(def) => {
161+
Default::default()
162+
};
163+
(func $f:expr) => {
164+
$f()
165+
};
166+
(nonempty_block $e:expr) => {{
167+
some_other_fn(&$e);
168+
$e
169+
}};
170+
}
171+
fn_take_unit(mac!(def));
172+
//~^ unit_arg
173+
fn_take_unit(mac!(func Default::default));
174+
//~^ unit_arg
175+
fn_take_unit(mac!(nonempty_block Default::default()));
176+
//~^ unit_arg
177+
}

tests/ui/unit_arg.stderr

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,26 @@ LL ~ foo(1);
199199
LL + Some(())
200200
|
201201

202-
error: aborting due to 10 previous errors
202+
error: passing a unit value to a function
203+
--> tests/ui/unit_arg.rs:171:5
204+
|
205+
LL | fn_take_unit(mac!(def));
206+
| ^^^^^^^^^^^^^^^^^^^^^^^
207+
|
208+
209+
error: passing a unit value to a function
210+
--> tests/ui/unit_arg.rs:173:5
211+
|
212+
LL | fn_take_unit(mac!(func Default::default));
213+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
214+
|
215+
216+
error: passing a unit value to a function
217+
--> tests/ui/unit_arg.rs:175:5
218+
|
219+
LL | fn_take_unit(mac!(nonempty_block Default::default()));
220+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
221+
|
222+
223+
error: aborting due to 13 previous errors
203224

tests/ui/unit_arg_fixable.fixed

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,34 @@ fn issue14857() {
3737
let fn_take_unit = |_: ()| {};
3838
fn_take_unit(());
3939
//~^ unit_arg
40+
41+
fn some_other_fn(_: &i32) {}
42+
43+
macro_rules! another_mac {
44+
() => {
45+
some_other_fn(&Default::default())
46+
};
47+
($e:expr) => {
48+
some_other_fn(&$e)
49+
};
50+
}
51+
52+
another_mac!();
53+
fn_take_unit(());
54+
//~^ unit_arg
55+
another_mac!(1);
56+
fn_take_unit(());
57+
//~^ unit_arg
58+
59+
macro_rules! mac {
60+
(nondef $e:expr) => {
61+
$e
62+
};
63+
(empty_block) => {{}};
64+
}
65+
fn_take_unit(mac!(nondef ()));
66+
//~^ unit_arg
67+
mac!(empty_block);
68+
fn_take_unit(());
69+
//~^ unit_arg
4070
}

tests/ui/unit_arg_fixable.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,31 @@ fn issue14857() {
3434
let fn_take_unit = |_: ()| {};
3535
fn_take_unit(Default::default());
3636
//~^ unit_arg
37+
38+
fn some_other_fn(_: &i32) {}
39+
40+
macro_rules! another_mac {
41+
() => {
42+
some_other_fn(&Default::default())
43+
};
44+
($e:expr) => {
45+
some_other_fn(&$e)
46+
};
47+
}
48+
49+
fn_take_unit(another_mac!());
50+
//~^ unit_arg
51+
fn_take_unit(another_mac!(1));
52+
//~^ unit_arg
53+
54+
macro_rules! mac {
55+
(nondef $e:expr) => {
56+
$e
57+
};
58+
(empty_block) => {{}};
59+
}
60+
fn_take_unit(mac!(nondef Default::default()));
61+
//~^ unit_arg
62+
fn_take_unit(mac!(empty_block));
63+
//~^ unit_arg
3764
}

tests/ui/unit_arg_fixable.stderr

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,49 @@ LL | fn_take_unit(Default::default());
5050
| |
5151
| help: use a unit literal instead: `()`
5252

53-
error: aborting due to 5 previous errors
53+
error: passing a unit value to a function
54+
--> tests/ui/unit_arg_fixable.rs:49:5
55+
|
56+
LL | fn_take_unit(another_mac!());
57+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58+
|
59+
help: move the expression in front of the call and replace it with the unit literal `()`
60+
|
61+
LL ~ another_mac!();
62+
LL ~ fn_take_unit(());
63+
|
64+
65+
error: passing a unit value to a function
66+
--> tests/ui/unit_arg_fixable.rs:51:5
67+
|
68+
LL | fn_take_unit(another_mac!(1));
69+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70+
|
71+
help: move the expression in front of the call and replace it with the unit literal `()`
72+
|
73+
LL ~ another_mac!(1);
74+
LL ~ fn_take_unit(());
75+
|
76+
77+
error: passing a unit value to a function
78+
--> tests/ui/unit_arg_fixable.rs:60:5
79+
|
80+
LL | fn_take_unit(mac!(nondef Default::default()));
81+
| ^^^^^^^^^^^^^^^^^^^^^^^^^------------------^^
82+
| |
83+
| help: use a unit literal instead: `()`
84+
85+
error: passing a unit value to a function
86+
--> tests/ui/unit_arg_fixable.rs:62:5
87+
|
88+
LL | fn_take_unit(mac!(empty_block));
89+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90+
|
91+
help: move the expression in front of the call and replace it with the unit literal `()`
92+
|
93+
LL ~ mac!(empty_block);
94+
LL ~ fn_take_unit(());
95+
|
96+
97+
error: aborting due to 9 previous errors
5498

0 commit comments

Comments
 (0)