1+ use std:: ops:: ControlFlow ;
2+
13use clippy_utils:: diagnostics:: span_lint_and_sugg;
24use clippy_utils:: macros:: HirNode ;
3- use clippy_utils:: source:: { indent_of, snippet, snippet_block_with_context, snippet_with_context} ;
5+ use clippy_utils:: source:: {
6+ RelativeIndent , indent_of, reindent_multiline_relative, snippet, snippet_block_with_context, snippet_with_context,
7+ } ;
48use clippy_utils:: { is_refutable, peel_blocks} ;
9+ use rustc_data_structures:: fx:: FxHashSet ;
510use rustc_errors:: Applicability ;
6- use rustc_hir:: { Arm , Expr , ExprKind , Node , PatKind , StmtKind } ;
11+ use rustc_hir:: def:: Res ;
12+ use rustc_hir:: intravisit:: { Visitor , walk_block, walk_expr, walk_path, walk_stmt} ;
13+ use rustc_hir:: { Arm , Block , Expr , ExprKind , HirId , Node , PatKind , Path , Stmt , StmtKind } ;
714use rustc_lint:: LateContext ;
8- use rustc_span:: Span ;
15+ use rustc_span:: { Span , Symbol } ;
916
1017use super :: MATCH_SINGLE_BINDING ;
1118
@@ -50,10 +57,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
5057 cx,
5158 ( ex, expr) ,
5259 ( bind_names, matched_vars) ,
53- & snippet_body,
60+ snippet_body,
5461 & mut app,
5562 Some ( span) ,
5663 true ,
64+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
5765 ) ;
5866
5967 span_lint_and_sugg (
@@ -83,10 +91,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
8391 cx,
8492 ( ex, expr) ,
8593 ( bind_names, matched_vars) ,
86- & snippet_body,
94+ snippet_body,
8795 & mut app,
8896 None ,
8997 true ,
98+ is_var_binding_used_later ( cx, expr, & arms[ 0 ] ) ,
9099 ) ;
91100 ( expr. span , sugg)
92101 } ,
@@ -108,10 +117,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
108117 cx,
109118 ( ex, expr) ,
110119 ( bind_names, matched_vars) ,
111- & snippet_body,
120+ snippet_body,
112121 & mut app,
113122 None ,
114123 false ,
124+ true ,
115125 ) ;
116126
117127 span_lint_and_sugg (
@@ -139,6 +149,125 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
139149 }
140150}
141151
152+ struct VarBindingVisitor < ' a , ' tcx > {
153+ cx : & ' a LateContext < ' tcx > ,
154+ identifiers : FxHashSet < Symbol > ,
155+ }
156+
157+ impl < ' tcx > Visitor < ' tcx > for VarBindingVisitor < ' _ , ' tcx > {
158+ type Result = ControlFlow < ( ) > ;
159+
160+ fn visit_path ( & mut self , path : & Path < ' tcx > , _: HirId ) -> Self :: Result {
161+ if let Res :: Local ( _) = path. res
162+ && let [ segment] = path. segments
163+ && self . identifiers . contains ( & segment. ident . name )
164+ {
165+ return ControlFlow :: Break ( ( ) ) ;
166+ }
167+
168+ walk_path ( self , path)
169+ }
170+
171+ fn visit_block ( & mut self , block : & ' tcx Block < ' tcx > ) -> Self :: Result {
172+ let before = self . identifiers . clone ( ) ;
173+ walk_block ( self , block) ?;
174+ self . identifiers = before;
175+ ControlFlow :: Continue ( ( ) )
176+ }
177+
178+ fn visit_stmt ( & mut self , stmt : & ' tcx Stmt < ' tcx > ) -> Self :: Result {
179+ if let StmtKind :: Let ( let_stmt) = stmt. kind {
180+ if let Some ( init) = let_stmt. init {
181+ self . visit_expr ( init) ?;
182+ }
183+
184+ let_stmt. pat . each_binding ( |_, _, _, ident| {
185+ self . identifiers . remove ( & ident. name ) ;
186+ } ) ;
187+ }
188+ walk_stmt ( self , stmt)
189+ }
190+
191+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> Self :: Result {
192+ match expr. kind {
193+ ExprKind :: If (
194+ Expr {
195+ kind : ExprKind :: Let ( let_expr) ,
196+ ..
197+ } ,
198+ then,
199+ else_,
200+ ) => {
201+ self . visit_expr ( let_expr. init ) ?;
202+ let before = self . identifiers . clone ( ) ;
203+ let_expr. pat . each_binding ( |_, _, _, ident| {
204+ self . identifiers . remove ( & ident. name ) ;
205+ } ) ;
206+
207+ self . visit_expr ( then) ?;
208+ self . identifiers = before;
209+ if let Some ( else_) = else_ {
210+ self . visit_expr ( else_) ?;
211+ }
212+ ControlFlow :: Continue ( ( ) )
213+ } ,
214+ ExprKind :: Closure ( closure) => {
215+ let body = self . cx . tcx . hir_body ( closure. body ) ;
216+ let before = self . identifiers . clone ( ) ;
217+ for param in body. params {
218+ param. pat . each_binding ( |_, _, _, ident| {
219+ self . identifiers . remove ( & ident. name ) ;
220+ } ) ;
221+ }
222+ self . visit_expr ( body. value ) ?;
223+ self . identifiers = before;
224+ ControlFlow :: Continue ( ( ) )
225+ } ,
226+ ExprKind :: Match ( expr, arms, _) => {
227+ self . visit_expr ( expr) ?;
228+ for arm in arms {
229+ let before = self . identifiers . clone ( ) ;
230+ arm. pat . each_binding ( |_, _, _, ident| {
231+ self . identifiers . remove ( & ident. name ) ;
232+ } ) ;
233+ if let Some ( guard) = arm. guard {
234+ self . visit_expr ( guard) ?;
235+ }
236+ self . visit_expr ( arm. body ) ?;
237+ self . identifiers = before;
238+ }
239+ ControlFlow :: Continue ( ( ) )
240+ } ,
241+ _ => walk_expr ( self , expr) ,
242+ }
243+ }
244+ }
245+
246+ fn is_var_binding_used_later ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arm : & Arm < ' _ > ) -> bool {
247+ let Node :: Stmt ( stmt) = cx. tcx . parent_hir_node ( expr. hir_id ) else {
248+ return false ;
249+ } ;
250+ let Node :: Block ( block) = cx. tcx . parent_hir_node ( stmt. hir_id ) else {
251+ return false ;
252+ } ;
253+
254+ let mut identifiers = FxHashSet :: default ( ) ;
255+ arm. pat . each_binding ( |_, _, _, ident| {
256+ identifiers. insert ( ident. name ) ;
257+ } ) ;
258+
259+ let mut visitor = VarBindingVisitor { cx, identifiers } ;
260+ block
261+ . stmts
262+ . iter ( )
263+ . skip_while ( |s| s. hir_id != stmt. hir_id )
264+ . skip ( 1 )
265+ . any ( |stmt| matches ! ( visitor. visit_stmt( stmt) , ControlFlow :: Break ( ( ) ) ) )
266+ || block
267+ . expr
268+ . is_some_and ( |expr| matches ! ( visitor. visit_expr( expr) , ControlFlow :: Break ( ( ) ) ) )
269+ }
270+
142271/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
143272fn opt_parent_assign_span < ' a > ( cx : & LateContext < ' a > , ex : & Expr < ' a > ) -> Option < AssignmentExpr > {
144273 if let Node :: Expr ( parent_arm_expr) = cx. tcx . parent_hir_node ( ex. hir_id ) {
@@ -161,47 +290,58 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass
161290 None
162291}
163292
164- fn expr_parent_requires_curlies < ' a > ( cx : & LateContext < ' a > , match_expr : & Expr < ' a > ) -> bool {
293+ fn expr_in_nested_block ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
294+ if let Node :: Block ( block) = cx. tcx . parent_hir_node ( match_expr. hir_id ) {
295+ return block
296+ . expr
297+ . map_or_else ( || matches ! ( block. stmts, [ _] ) , |_| block. stmts . is_empty ( ) ) ;
298+ }
299+ false
300+ }
301+
302+ fn expr_must_have_curlies ( cx : & LateContext < ' _ > , match_expr : & Expr < ' _ > ) -> bool {
165303 let parent = cx. tcx . parent_hir_node ( match_expr. hir_id ) ;
166- matches ! (
167- parent,
168- Node :: Expr ( Expr {
169- kind: ExprKind :: Closure { .. } ,
170- ..
171- } ) | Node :: AnonConst ( ..)
304+ if let Node :: Expr ( Expr {
305+ kind : ExprKind :: Closure { .. } ,
306+ ..
307+ } )
308+ | Node :: AnonConst ( ..) = parent
309+ {
310+ return true ;
311+ }
312+
313+ if let Node :: Arm ( arm) = & cx. tcx . parent_hir_node ( match_expr. hir_id )
314+ && let ExprKind :: Match ( ..) = arm. body . kind
315+ {
316+ return true ;
317+ }
318+
319+ false
320+ }
321+
322+ fn reindent_snippet_if_in_block ( snippet_body : & str , has_assignment : bool ) -> String {
323+ if has_assignment || !snippet_body. starts_with ( '{' ) {
324+ return reindent_multiline_relative ( snippet_body, true , RelativeIndent :: Add ( 0 ) ) ;
325+ }
326+
327+ reindent_multiline_relative (
328+ snippet_body. trim_start_matches ( '{' ) . trim_end_matches ( '}' ) . trim ( ) ,
329+ false ,
330+ RelativeIndent :: Sub ( 4 ) ,
172331 )
173332}
174333
334+ #[ expect( clippy:: too_many_arguments) ]
175335fn sugg_with_curlies < ' a > (
176336 cx : & LateContext < ' a > ,
177337 ( ex, match_expr) : ( & Expr < ' a > , & Expr < ' a > ) ,
178338 ( bind_names, matched_vars) : ( Span , Span ) ,
179- snippet_body : & str ,
339+ mut snippet_body : String ,
180340 applicability : & mut Applicability ,
181341 assignment : Option < Span > ,
182342 needs_var_binding : bool ,
343+ is_var_binding_used_later : bool ,
183344) -> String {
184- let mut indent = " " . repeat ( indent_of ( cx, ex. span ) . unwrap_or ( 0 ) ) ;
185-
186- let ( mut cbrace_start, mut cbrace_end) = ( String :: new ( ) , String :: new ( ) ) ;
187- if expr_parent_requires_curlies ( cx, match_expr) {
188- cbrace_end = format ! ( "\n {indent}}}" ) ;
189- // Fix body indent due to the closure
190- indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
191- cbrace_start = format ! ( "{{\n {indent}" ) ;
192- }
193-
194- // If the parent is already an arm, and the body is another match statement,
195- // we need curly braces around suggestion
196- if let Node :: Arm ( arm) = & cx. tcx . parent_hir_node ( match_expr. hir_id )
197- && let ExprKind :: Match ( ..) = arm. body . kind
198- {
199- cbrace_end = format ! ( "\n {indent}}}" ) ;
200- // Fix body indent due to the match
201- indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
202- cbrace_start = format ! ( "{{\n {indent}" ) ;
203- }
204-
205345 let assignment_str = assignment. map_or_else ( String :: new, |span| {
206346 let mut s = snippet ( cx, span, ".." ) . to_string ( ) ;
207347 s. push_str ( " = " ) ;
@@ -221,5 +361,17 @@ fn sugg_with_curlies<'a>(
221361 . to_string ( )
222362 } ;
223363
364+ let mut indent = " " . repeat ( indent_of ( cx, ex. span ) . unwrap_or ( 0 ) ) ;
365+ let ( mut cbrace_start, mut cbrace_end) = ( String :: new ( ) , String :: new ( ) ) ;
366+ if !expr_in_nested_block ( cx, match_expr)
367+ && ( ( needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies ( cx, match_expr) )
368+ {
369+ cbrace_end = format ! ( "\n {indent}}}" ) ;
370+ // Fix body indent due to the closure
371+ indent = " " . repeat ( indent_of ( cx, bind_names) . unwrap_or ( 0 ) ) ;
372+ cbrace_start = format ! ( "{{\n {indent}" ) ;
373+ snippet_body = reindent_snippet_if_in_block ( & snippet_body, !assignment_str. is_empty ( ) ) ;
374+ }
375+
224376 format ! ( "{cbrace_start}{scrutinee};\n {indent}{assignment_str}{snippet_body}{cbrace_end}" )
225377}
0 commit comments