1
+ use std:: iter;
2
+
1
3
use clippy_utils:: diagnostics:: span_lint_and_then;
2
- use clippy_utils:: is_from_proc_macro;
3
- use clippy_utils:: source:: { SourceText , SpanRangeExt , indent_of, reindent_multiline} ;
4
+ use clippy_utils:: source:: { SpanRangeExt , indent_of, reindent_multiline} ;
5
+ use clippy_utils:: sugg:: Sugg ;
6
+ use clippy_utils:: ty:: expr_type_is_certain;
7
+ use clippy_utils:: { is_expr_default, is_from_proc_macro} ;
4
8
use rustc_errors:: Applicability ;
5
9
use rustc_hir:: { Block , Expr , ExprKind , MatchSource , Node , StmtKind } ;
6
10
use rustc_lint:: LateContext ;
11
+ use rustc_span:: SyntaxContext ;
7
12
8
13
use super :: { UNIT_ARG , utils} ;
9
14
@@ -59,7 +64,7 @@ fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
59
64
}
60
65
}
61
66
62
- fn lint_unit_args ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , args_to_recover : & [ & Expr < ' _ > ] ) {
67
+ fn lint_unit_args < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > , args_to_recover : & [ & ' tcx Expr < ' tcx > ] ) {
63
68
let mut applicability = Applicability :: MachineApplicable ;
64
69
let ( singular, plural) = if args_to_recover. len ( ) > 1 {
65
70
( "" , "s" )
@@ -100,34 +105,41 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
100
105
101
106
let arg_snippets: Vec < _ > = args_to_recover
102
107
. iter ( )
103
- . filter_map ( |arg| arg. span . get_source_text ( cx) )
108
+ // If the argument is from an expansion and is a `Default::default()`, we skip it
109
+ . filter ( |arg| !arg. span . from_expansion ( ) || !is_expr_default_nested ( cx, arg) )
110
+ . filter_map ( |arg| get_expr_snippet ( cx, arg) )
104
111
. collect ( ) ;
105
- let arg_snippets_without_empty_blocks: Vec < _ > = args_to_recover
112
+
113
+ // If the argument is an empty block or `Default::default()`, we can replace it with `()`.
114
+ let arg_snippets_without_redundant_exprs: Vec < _ > = args_to_recover
106
115
. iter ( )
107
- . filter ( |arg| !is_empty_block ( arg) )
108
- . filter_map ( |arg| arg . span . get_source_text ( cx) )
116
+ . filter ( |arg| !is_expr_default_nested ( cx , arg ) && ( arg . span . from_expansion ( ) || ! is_empty_block ( arg) ) )
117
+ . filter_map ( |arg| get_expr_snippet_with_type_certainty ( cx, arg ) )
109
118
. collect ( ) ;
110
119
111
120
if let Some ( call_snippet) = expr. span . get_source_text ( cx) {
112
- let sugg = fmt_stmts_and_call (
113
- cx,
114
- expr,
115
- & call_snippet,
116
- & arg_snippets,
117
- & arg_snippets_without_empty_blocks,
118
- ) ;
119
-
120
- if arg_snippets_without_empty_blocks. is_empty ( ) {
121
+ if arg_snippets_without_redundant_exprs. is_empty ( )
122
+ && let suggestions = args_to_recover
123
+ . iter ( )
124
+ . filter ( |arg| !arg. span . from_expansion ( ) || !is_expr_default_nested ( cx, arg) )
125
+ . map ( |arg| ( arg. span . parent_callsite ( ) . unwrap_or ( arg. span ) , "()" . to_string ( ) ) )
126
+ . collect :: < Vec < _ > > ( )
127
+ && !suggestions. is_empty ( )
128
+ {
121
129
db. multipart_suggestion (
122
130
format ! ( "use {singular}unit literal{plural} instead" ) ,
123
- args_to_recover
124
- . iter ( )
125
- . map ( |arg| ( arg. span , "()" . to_string ( ) ) )
126
- . collect :: < Vec < _ > > ( ) ,
131
+ suggestions,
127
132
applicability,
128
133
) ;
129
134
} else {
130
- let plural = arg_snippets_without_empty_blocks. len ( ) > 1 ;
135
+ let plural = arg_snippets_without_redundant_exprs. len ( ) > 1 ;
136
+ let sugg = fmt_stmts_and_call (
137
+ cx,
138
+ expr,
139
+ & call_snippet,
140
+ arg_snippets,
141
+ arg_snippets_without_redundant_exprs,
142
+ ) ;
131
143
let empty_or_s = if plural { "s" } else { "" } ;
132
144
let it_or_them = if plural { "them" } else { "it" } ;
133
145
db. span_suggestion (
@@ -144,6 +156,55 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
144
156
) ;
145
157
}
146
158
159
+ fn is_expr_default_nested < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> bool {
160
+ is_expr_default ( cx, expr)
161
+ || matches ! ( expr. kind, ExprKind :: Block ( block, _)
162
+ if block. expr. is_some( ) && is_expr_default_nested( cx, block. expr. unwrap( ) ) )
163
+ }
164
+
165
+ enum MaybeTypeUncertain < ' tcx > {
166
+ Certain ( Sugg < ' tcx > ) ,
167
+ Uncertain ( Sugg < ' tcx > ) ,
168
+ }
169
+
170
+ impl From < MaybeTypeUncertain < ' _ > > for String {
171
+ fn from ( value : MaybeTypeUncertain < ' _ > ) -> Self {
172
+ match value {
173
+ MaybeTypeUncertain :: Certain ( sugg) => sugg. to_string ( ) ,
174
+ MaybeTypeUncertain :: Uncertain ( sugg) => format ! ( "let _: () = {sugg}" ) ,
175
+ }
176
+ }
177
+ }
178
+
179
+ fn get_expr_snippet < ' tcx > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) -> Option < Sugg < ' tcx > > {
180
+ let mut app = Applicability :: MachineApplicable ;
181
+ let snip = Sugg :: hir_with_context ( cx, expr, SyntaxContext :: root ( ) , ".." , & mut app) ;
182
+ if app != Applicability :: MachineApplicable {
183
+ return None ;
184
+ }
185
+
186
+ Some ( snip)
187
+ }
188
+
189
+ fn get_expr_snippet_with_type_certainty < ' tcx > (
190
+ cx : & LateContext < ' tcx > ,
191
+ expr : & ' tcx Expr < ' tcx > ,
192
+ ) -> Option < MaybeTypeUncertain < ' tcx > > {
193
+ get_expr_snippet ( cx, expr) . map ( |snip| {
194
+ // If the type of the expression is certain, we can use it directly.
195
+ // Otherwise, we wrap it in a `let _: () = ...` to ensure the type is correct.
196
+ if !expr_type_is_certain ( cx, expr) && !is_block_with_no_expr ( expr) {
197
+ MaybeTypeUncertain :: Uncertain ( snip)
198
+ } else {
199
+ MaybeTypeUncertain :: Certain ( snip)
200
+ }
201
+ } )
202
+ }
203
+
204
+ fn is_block_with_no_expr ( expr : & Expr < ' _ > ) -> bool {
205
+ matches ! ( expr. kind, ExprKind :: Block ( Block { expr: None , .. } , _) )
206
+ }
207
+
147
208
fn is_empty_block ( expr : & Expr < ' _ > ) -> bool {
148
209
matches ! (
149
210
expr. kind,
@@ -162,23 +223,20 @@ fn fmt_stmts_and_call(
162
223
cx : & LateContext < ' _ > ,
163
224
call_expr : & Expr < ' _ > ,
164
225
call_snippet : & str ,
165
- args_snippets : & [ SourceText ] ,
166
- non_empty_block_args_snippets : & [ SourceText ] ,
226
+ args_snippets : Vec < Sugg < ' _ > > ,
227
+ non_empty_block_args_snippets : Vec < MaybeTypeUncertain < ' _ > > ,
167
228
) -> String {
168
229
let call_expr_indent = indent_of ( cx, call_expr. span ) . unwrap_or ( 0 ) ;
169
- let call_snippet_with_replacements = args_snippets
170
- . iter ( )
171
- . fold ( call_snippet . to_owned ( ) , |acc , arg| acc . replacen ( arg . as_ref ( ) , "()" , 1 ) ) ;
230
+ let call_snippet_with_replacements = args_snippets. into_iter ( ) . fold ( call_snippet . to_owned ( ) , |acc , arg| {
231
+ acc . replacen ( & arg . to_string ( ) , "()" , 1 )
232
+ } ) ;
172
233
173
- let mut stmts_and_call = non_empty_block_args_snippets
174
- . iter ( )
175
- . map ( |it| it. as_ref ( ) . to_owned ( ) )
176
- . collect :: < Vec < _ > > ( ) ;
177
- stmts_and_call. push ( call_snippet_with_replacements) ;
178
- stmts_and_call = stmts_and_call
234
+ let stmts_and_call = non_empty_block_args_snippets
179
235
. into_iter ( )
236
+ . map ( Into :: into)
237
+ . chain ( iter:: once ( call_snippet_with_replacements) )
180
238
. map ( |v| reindent_multiline ( & v, true , Some ( call_expr_indent) ) )
181
- . collect ( ) ;
239
+ . collect :: < Vec < _ > > ( ) ;
182
240
183
241
let mut stmts_and_call_snippet = stmts_and_call. join ( & format ! ( "{}{}" , ";\n " , " " . repeat( call_expr_indent) ) ) ;
184
242
// expr is not in a block statement or result expression position, wrap in a block
0 commit comments