1+ use std:: iter;
2+
13use 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} ;
48use rustc_errors:: Applicability ;
59use rustc_hir:: { Block , Expr , ExprKind , MatchSource , Node , StmtKind } ;
610use rustc_lint:: LateContext ;
11+ use rustc_span:: SyntaxContext ;
712
813use super :: { UNIT_ARG , utils} ;
914
@@ -59,7 +64,7 @@ fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
5964 }
6065}
6166
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 > ] ) {
6368 let mut applicability = Applicability :: MachineApplicable ;
6469 let ( singular, plural) = if args_to_recover. len ( ) > 1 {
6570 ( "" , "s" )
@@ -100,34 +105,41 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
100105
101106 let arg_snippets: Vec < _ > = args_to_recover
102107 . 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) )
104111 . 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
106115 . 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 ) )
109118 . collect ( ) ;
110119
111120 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+ {
121129 db. multipart_suggestion (
122130 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,
127132 applicability,
128133 ) ;
129134 } 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+ ) ;
131143 let empty_or_s = if plural { "s" } else { "" } ;
132144 let it_or_them = if plural { "them" } else { "it" } ;
133145 db. span_suggestion (
@@ -144,6 +156,55 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
144156 ) ;
145157}
146158
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+
147208fn is_empty_block ( expr : & Expr < ' _ > ) -> bool {
148209 matches ! (
149210 expr. kind,
@@ -162,23 +223,20 @@ fn fmt_stmts_and_call(
162223 cx : & LateContext < ' _ > ,
163224 call_expr : & Expr < ' _ > ,
164225 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 < ' _ > > ,
167228) -> String {
168229 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+ } ) ;
172233
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
179235 . into_iter ( )
236+ . map ( Into :: into)
237+ . chain ( iter:: once ( call_snippet_with_replacements) )
180238 . map ( |v| reindent_multiline ( & v, true , Some ( call_expr_indent) ) )
181- . collect ( ) ;
239+ . collect :: < Vec < _ > > ( ) ;
182240
183241 let mut stmts_and_call_snippet = stmts_and_call. join ( & format ! ( "{}{}" , ";\n " , " " . repeat( call_expr_indent) ) ) ;
184242 // expr is not in a block statement or result expression position, wrap in a block
0 commit comments