1
- use hir:: Semantics ;
2
- use ide_db:: RootDatabase ;
3
1
use syntax:: T ;
4
2
use 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 } ;
6
7
7
8
use crate :: { AssistContext , AssistId , Assists } ;
8
9
@@ -25,155 +26,205 @@ use crate::{AssistContext, AssistId, Assists};
25
26
// }
26
27
// ```
27
28
pub ( 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
29
30
let let_stmt = ctx
30
31
. find_token_syntax_at_offset ( T ! [ else] )
31
32
. and_then ( |it| it. parent ( ) ?. parent ( ) )
32
33
. or_else ( || ctx. find_token_syntax_at_offset ( T ! [ let ] ) ?. parent ( ) ) ?;
33
34
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
36
43
if let_stmt. ty ( ) . is_some ( ) {
37
- // don't support let with type annotation
38
44
return None ;
39
45
}
40
46
let pat = let_stmt. pat ( ) ?;
41
- let mut binders = Vec :: new ( ) ;
42
- binders_in_pat ( & mut binders, & pat, & ctx. sema ) ?;
43
47
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
+
45
63
acc. add (
46
64
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 ( ) ) ;
53
73
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 < _ > > ( ) ;
56
78
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 ( ) ,
65
89
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 ( ) ) ;
78
100
} 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. file_id ( ) , editor) ;
87
119
} ,
88
120
)
89
121
}
90
122
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
+ ) ;
106
136
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) ) ;
116
138
}
117
- Some ( ( ) )
139
+ non_mut_pat . into ( )
118
140
}
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 ( )
135
143
}
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 ( )
148
154
}
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 ( )
152
196
}
153
- Some ( ( ) )
154
197
}
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 ( ) ,
155
225
// 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
+ } )
177
228
}
178
229
179
230
#[ cfg( test) ]
0 commit comments