1- use hir:: Semantics ;
2- use ide_db:: RootDatabase ;
31use syntax:: T ;
42use syntax:: ast:: RangeItem ;
5- use syntax:: ast:: { AstNode , HasName , LetStmt , Name , Pat , edit:: AstNodeEdit } ;
3+ use syntax:: ast:: edit:: IndentLevel ;
4+ use syntax:: ast:: edit_in_place:: Indent ;
5+ use syntax:: ast:: syntax_factory:: SyntaxFactory ;
6+ use syntax:: ast:: { self , AstNode , HasName , LetStmt , Pat } ;
67
78use crate :: { AssistContext , AssistId , Assists } ;
89
@@ -25,155 +26,205 @@ use crate::{AssistContext, AssistId, Assists};
2526// }
2627// ```
2728pub ( crate ) fn convert_let_else_to_match ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
28- // should focus on else token to trigger
29+ // Should focus on the ` else` token to trigger
2930 let let_stmt = ctx
3031 . find_token_syntax_at_offset ( T ! [ else] )
3132 . and_then ( |it| it. parent ( ) ?. parent ( ) )
3233 . or_else ( || ctx. find_token_syntax_at_offset ( T ! [ let ] ) ?. parent ( ) ) ?;
3334 let let_stmt = LetStmt :: cast ( let_stmt) ?;
34- let let_else_block = let_stmt. let_else ( ) ?. block_expr ( ) ?;
35- let let_init = let_stmt. initializer ( ) ?;
35+ let else_block = let_stmt. let_else ( ) ?. block_expr ( ) ?;
36+ let else_expr = if else_block. statements ( ) . next ( ) . is_none ( ) {
37+ else_block. tail_expr ( ) ?
38+ } else {
39+ else_block. into ( )
40+ } ;
41+ let init = let_stmt. initializer ( ) ?;
42+ // Ignore let stmt with type annotation
3643 if let_stmt. ty ( ) . is_some ( ) {
37- // don't support let with type annotation
3844 return None ;
3945 }
4046 let pat = let_stmt. pat ( ) ?;
41- let mut binders = Vec :: new ( ) ;
42- binders_in_pat ( & mut binders, & pat, & ctx. sema ) ?;
4347
44- let target = let_stmt. syntax ( ) . text_range ( ) ;
48+ let make = SyntaxFactory :: with_mappings ( ) ;
49+ let mut idents = Vec :: default ( ) ;
50+ let pat_without_mut = remove_mut_and_collect_idents ( & make, & pat, & mut idents) ?;
51+ let bindings = idents
52+ . into_iter ( )
53+ . filter_map ( |ref pat| {
54+ // Identifiers which resolve to constants are not bindings
55+ if ctx. sema . resolve_bind_pat_to_const ( pat) . is_none ( ) {
56+ Some ( ( pat. name ( ) ?, pat. ref_token ( ) . is_none ( ) && pat. mut_token ( ) . is_some ( ) ) )
57+ } else {
58+ None
59+ }
60+ } )
61+ . collect :: < Vec < _ > > ( ) ;
62+
4563 acc. add (
4664 AssistId :: refactor_rewrite ( "convert_let_else_to_match" ) ,
47- "Convert let-else to let and match" ,
48- target,
49- |edit| {
50- let indent_level = let_stmt. indent_level ( ) . 0 as usize ;
51- let indent = " " . repeat ( indent_level) ;
52- let indent1 = " " . repeat ( indent_level + 1 ) ;
65+ if bindings. is_empty ( ) {
66+ "Convert let-else to match"
67+ } else {
68+ "Convert let-else to let and match"
69+ } ,
70+ let_stmt. syntax ( ) . text_range ( ) ,
71+ |builder| {
72+ let mut editor = builder. make_editor ( let_stmt. syntax ( ) ) ;
5373
54- let binders_str = binders_to_str ( & binders, false ) ;
55- let binders_str_mut = binders_to_str ( & binders, true ) ;
74+ let binding_paths = bindings
75+ . iter ( )
76+ . map ( |( name, _) | make. expr_path ( make. ident_path ( & name. to_string ( ) ) ) )
77+ . collect :: < Vec < _ > > ( ) ;
5678
57- let init_expr = let_init. syntax ( ) . text ( ) ;
58- let mut pat_no_mut = pat. syntax ( ) . text ( ) . to_string ( ) ;
59- // remove the mut from the pattern
60- for ( b, ismut) in binders. iter ( ) {
61- if * ismut {
62- pat_no_mut = pat_no_mut. replace ( & format ! ( "mut {b}" ) , & b. to_string ( ) ) ;
63- }
64- }
79+ let binding_arm = make. match_arm (
80+ pat_without_mut,
81+ None ,
82+ // There are three possible cases:
83+ //
84+ // - No bindings: `None => {}`
85+ // - Single binding: `Some(it) => it`
86+ // - Multiple bindings: `Foo::Bar { a, b, .. } => (a, b)`
87+ match binding_paths. len ( ) {
88+ 0 => make. expr_empty_block ( ) . into ( ) ,
6589
66- let only_expr = let_else_block. statements ( ) . next ( ) . is_none ( ) ;
67- let branch2 = match & let_else_block. tail_expr ( ) {
68- Some ( tail) if only_expr => format ! ( "{tail}," ) ,
69- _ => let_else_block. syntax ( ) . text ( ) . to_string ( ) ,
70- } ;
71- let replace = if binders. is_empty ( ) {
72- format ! (
73- "match {init_expr} {{
74- {indent1}{pat_no_mut} => {binders_str}
75- {indent1}_ => {branch2}
76- {indent}}}"
77- )
90+ 1 => binding_paths[ 0 ] . clone ( ) ,
91+ _ => make. expr_tuple ( binding_paths) . into ( ) ,
92+ } ,
93+ ) ;
94+ let else_arm = make. match_arm ( make. wildcard_pat ( ) . into ( ) , None , else_expr) ;
95+ let match_ = make. expr_match ( init, make. match_arm_list ( [ binding_arm, else_arm] ) ) ;
96+ match_. reindent_to ( IndentLevel :: from_node ( let_stmt. syntax ( ) ) ) ;
97+
98+ if bindings. is_empty ( ) {
99+ editor. replace ( let_stmt. syntax ( ) , match_. syntax ( ) ) ;
78100 } else {
79- format ! (
80- "let {binders_str_mut} = match {init_expr} {{
81- {indent1}{pat_no_mut} => {binders_str},
82- {indent1}_ => {branch2}
83- {indent}}};"
84- )
85- } ;
86- edit. replace ( target, replace) ;
101+ let ident_pats = bindings
102+ . into_iter ( )
103+ . map ( |( name, is_mut) | make. ident_pat ( false , is_mut, name) . into ( ) )
104+ . collect :: < Vec < Pat > > ( ) ;
105+ let new_let_stmt = make. let_stmt (
106+ if ident_pats. len ( ) == 1 {
107+ ident_pats[ 0 ] . clone ( )
108+ } else {
109+ make. tuple_pat ( ident_pats) . into ( )
110+ } ,
111+ None ,
112+ Some ( match_. into ( ) ) ,
113+ ) ;
114+ editor. replace ( let_stmt. syntax ( ) , new_let_stmt. syntax ( ) ) ;
115+ }
116+
117+ editor. add_mappings ( make. finish_with_mappings ( ) ) ;
118+ builder. add_file_edits ( ctx. vfs_file_id ( ) , editor) ;
87119 } ,
88120 )
89121}
90122
91- /// Gets a list of binders in a pattern, and whether they are mut.
92- fn binders_in_pat (
93- acc : & mut Vec < ( Name , bool ) > ,
94- pat : & Pat ,
95- sem : & Semantics < ' _ , RootDatabase > ,
96- ) -> Option < ( ) > {
97- use Pat :: * ;
98- match pat {
99- IdentPat ( p) => {
100- let ident = p. name ( ) ?;
101- let ismut = p. ref_token ( ) . is_none ( ) && p. mut_token ( ) . is_some ( ) ;
102- // check for const reference
103- if sem. resolve_bind_pat_to_const ( p) . is_none ( ) {
104- acc. push ( ( ident, ismut) ) ;
105- }
123+ fn remove_mut_and_collect_idents (
124+ make : & SyntaxFactory ,
125+ pat : & ast:: Pat ,
126+ acc : & mut Vec < ast:: IdentPat > ,
127+ ) -> Option < ast:: Pat > {
128+ Some ( match pat {
129+ ast:: Pat :: IdentPat ( p) => {
130+ acc. push ( p. clone ( ) ) ;
131+ let non_mut_pat = make. ident_pat (
132+ p. ref_token ( ) . is_some ( ) ,
133+ p. ref_token ( ) . is_some ( ) && p. mut_token ( ) . is_some ( ) ,
134+ p. name ( ) ?,
135+ ) ;
106136 if let Some ( inner) = p. pat ( ) {
107- binders_in_pat ( acc, & inner, sem) ?;
108- }
109- Some ( ( ) )
110- }
111- BoxPat ( p) => p. pat ( ) . and_then ( |p| binders_in_pat ( acc, & p, sem) ) ,
112- RestPat ( _) | LiteralPat ( _) | PathPat ( _) | WildcardPat ( _) | ConstBlockPat ( _) => Some ( ( ) ) ,
113- OrPat ( p) => {
114- for p in p. pats ( ) {
115- binders_in_pat ( acc, & p, sem) ?;
137+ non_mut_pat. set_pat ( remove_mut_and_collect_idents ( make, & inner, acc) ) ;
116138 }
117- Some ( ( ) )
139+ non_mut_pat . into ( )
118140 }
119- ParenPat ( p) => p. pat ( ) . and_then ( |p| binders_in_pat ( acc, & p, sem) ) ,
120- RangePat ( p) => {
121- if let Some ( st) = p. start ( ) {
122- binders_in_pat ( acc, & st, sem) ?
123- }
124- if let Some ( ed) = p. end ( ) {
125- binders_in_pat ( acc, & ed, sem) ?
126- }
127- Some ( ( ) )
128- }
129- RecordPat ( p) => {
130- for f in p. record_pat_field_list ( ) ?. fields ( ) {
131- let pat = f. pat ( ) ?;
132- binders_in_pat ( acc, & pat, sem) ?;
133- }
134- Some ( ( ) )
141+ ast:: Pat :: BoxPat ( p) => {
142+ make. box_pat ( remove_mut_and_collect_idents ( make, & p. pat ( ) ?, acc) ?) . into ( )
135143 }
136- RefPat ( p) => p. pat ( ) . and_then ( |p| binders_in_pat ( acc, & p, sem) ) ,
137- SlicePat ( p) => {
138- for p in p. pats ( ) {
139- binders_in_pat ( acc, & p, sem) ?;
140- }
141- Some ( ( ) )
142- }
143- TuplePat ( p) => {
144- for p in p. fields ( ) {
145- binders_in_pat ( acc, & p, sem) ?;
146- }
147- Some ( ( ) )
144+ ast:: Pat :: OrPat ( p) => make
145+ . or_pat (
146+ p. pats ( )
147+ . map ( |pat| remove_mut_and_collect_idents ( make, & pat, acc) )
148+ . collect :: < Option < Vec < _ > > > ( ) ?,
149+ p. leading_pipe ( ) . is_some ( ) ,
150+ )
151+ . into ( ) ,
152+ ast:: Pat :: ParenPat ( p) => {
153+ make. paren_pat ( remove_mut_and_collect_idents ( make, & p. pat ( ) ?, acc) ?) . into ( )
148154 }
149- TupleStructPat ( p) => {
150- for p in p. fields ( ) {
151- binders_in_pat ( acc, & p, sem) ?;
155+ ast:: Pat :: RangePat ( p) => make
156+ . range_pat (
157+ if let Some ( start) = p. start ( ) {
158+ Some ( remove_mut_and_collect_idents ( make, & start, acc) ?)
159+ } else {
160+ None
161+ } ,
162+ if let Some ( end) = p. end ( ) {
163+ Some ( remove_mut_and_collect_idents ( make, & end, acc) ?)
164+ } else {
165+ None
166+ } ,
167+ )
168+ . into ( ) ,
169+ ast:: Pat :: RecordPat ( p) => make
170+ . record_pat_with_fields (
171+ p. path ( ) ?,
172+ make. record_pat_field_list (
173+ p. record_pat_field_list ( ) ?
174+ . fields ( )
175+ . map ( |field| {
176+ remove_mut_and_collect_idents ( make, & field. pat ( ) ?, acc) . map ( |pat| {
177+ if let Some ( name_ref) = field. name_ref ( ) {
178+ make. record_pat_field ( name_ref, pat)
179+ } else {
180+ make. record_pat_field_shorthand ( pat)
181+ }
182+ } )
183+ } )
184+ . collect :: < Option < Vec < _ > > > ( ) ?,
185+ p. record_pat_field_list ( ) ?. rest_pat ( ) ,
186+ ) ,
187+ )
188+ . into ( ) ,
189+ ast:: Pat :: RefPat ( p) => {
190+ let inner = p. pat ( ) ?;
191+ if let ast:: Pat :: IdentPat ( ident) = inner {
192+ acc. push ( ident) ;
193+ p. clone_for_update ( ) . into ( )
194+ } else {
195+ make. ref_pat ( remove_mut_and_collect_idents ( make, & inner, acc) ?) . into ( )
152196 }
153- Some ( ( ) )
154197 }
198+ ast:: Pat :: SlicePat ( p) => make
199+ . slice_pat (
200+ p. pats ( )
201+ . map ( |pat| remove_mut_and_collect_idents ( make, & pat, acc) )
202+ . collect :: < Option < Vec < _ > > > ( ) ?,
203+ )
204+ . into ( ) ,
205+ ast:: Pat :: TuplePat ( p) => make
206+ . tuple_pat (
207+ p. fields ( )
208+ . map ( |field| remove_mut_and_collect_idents ( make, & field, acc) )
209+ . collect :: < Option < Vec < _ > > > ( ) ?,
210+ )
211+ . into ( ) ,
212+ ast:: Pat :: TupleStructPat ( p) => make
213+ . tuple_struct_pat (
214+ p. path ( ) ?,
215+ p. fields ( )
216+ . map ( |field| remove_mut_and_collect_idents ( make, & field, acc) )
217+ . collect :: < Option < Vec < _ > > > ( ) ?,
218+ )
219+ . into ( ) ,
220+ ast:: Pat :: RestPat ( _)
221+ | ast:: Pat :: LiteralPat ( _)
222+ | ast:: Pat :: PathPat ( _)
223+ | ast:: Pat :: WildcardPat ( _)
224+ | ast:: Pat :: ConstBlockPat ( _) => pat. clone ( ) ,
155225 // don't support macro pat yet
156- MacroPat ( _) => None ,
157- }
158- }
159-
160- fn binders_to_str ( binders : & [ ( Name , bool ) ] , addmut : bool ) -> String {
161- let vars = binders
162- . iter ( )
163- . map (
164- |( ident, ismut) | {
165- if * ismut && addmut { format ! ( "mut {ident}" ) } else { ident. to_string ( ) }
166- } ,
167- )
168- . collect :: < Vec < _ > > ( )
169- . join ( ", " ) ;
170- if binders. is_empty ( ) {
171- String :: from ( "{}" )
172- } else if binders. len ( ) == 1 {
173- vars
174- } else {
175- format ! ( "({vars})" )
176- }
226+ ast:: Pat :: MacroPat ( _) => return None ,
227+ } )
177228}
178229
179230#[ cfg( test) ]
0 commit comments