@@ -8,10 +8,7 @@ use itertools::Itertools;
8
8
use syntax:: ast:: edit_in_place:: Removable ;
9
9
use syntax:: ast:: { self , make, AstNode , HasName , MatchArmList , MatchExpr , Pat } ;
10
10
11
- use crate :: {
12
- utils:: { self , render_snippet, Cursor } ,
13
- AssistContext , AssistId , AssistKind , Assists ,
14
- } ;
11
+ use crate :: { utils, AssistContext , AssistId , AssistKind , Assists } ;
15
12
16
13
// Assist: add_missing_match_arms
17
14
//
@@ -75,14 +72,18 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
75
72
. collect ( ) ;
76
73
77
74
let module = ctx. sema . scope ( expr. syntax ( ) ) ?. module ( ) ;
78
- let ( mut missing_pats, is_non_exhaustive) : (
75
+ let ( mut missing_pats, is_non_exhaustive, has_hidden_variants ) : (
79
76
Peekable < Box < dyn Iterator < Item = ( ast:: Pat , bool ) > > > ,
80
77
bool ,
78
+ bool ,
81
79
) = if let Some ( enum_def) = resolve_enum_def ( & ctx. sema , & expr) {
82
80
let is_non_exhaustive = enum_def. is_non_exhaustive ( ctx. db ( ) , module. krate ( ) ) ;
83
81
84
82
let variants = enum_def. variants ( ctx. db ( ) ) ;
85
83
84
+ let has_hidden_variants =
85
+ variants. iter ( ) . any ( |variant| variant. should_be_hidden ( ctx. db ( ) , module. krate ( ) ) ) ;
86
+
86
87
let missing_pats = variants
87
88
. into_iter ( )
88
89
. filter_map ( |variant| {
@@ -101,7 +102,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
101
102
} else {
102
103
Box :: new ( missing_pats)
103
104
} ;
104
- ( missing_pats. peekable ( ) , is_non_exhaustive)
105
+ ( missing_pats. peekable ( ) , is_non_exhaustive, has_hidden_variants )
105
106
} else if let Some ( enum_defs) = resolve_tuple_of_enum_def ( & ctx. sema , & expr) {
106
107
let is_non_exhaustive =
107
108
enum_defs. iter ( ) . any ( |enum_def| enum_def. is_non_exhaustive ( ctx. db ( ) , module. krate ( ) ) ) ;
@@ -124,6 +125,12 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
124
125
if n_arms > 256 {
125
126
return None ;
126
127
}
128
+
129
+ let has_hidden_variants = variants_of_enums
130
+ . iter ( )
131
+ . flatten ( )
132
+ . any ( |variant| variant. should_be_hidden ( ctx. db ( ) , module. krate ( ) ) ) ;
133
+
127
134
let missing_pats = variants_of_enums
128
135
. into_iter ( )
129
136
. multi_cartesian_product ( )
@@ -139,7 +146,11 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
139
146
( ast:: Pat :: from ( make:: tuple_pat ( patterns) ) , is_hidden)
140
147
} )
141
148
. filter ( |( variant_pat, _) | is_variant_missing ( & top_lvl_pats, variant_pat) ) ;
142
- ( ( Box :: new ( missing_pats) as Box < dyn Iterator < Item = _ > > ) . peekable ( ) , is_non_exhaustive)
149
+ (
150
+ ( Box :: new ( missing_pats) as Box < dyn Iterator < Item = _ > > ) . peekable ( ) ,
151
+ is_non_exhaustive,
152
+ has_hidden_variants,
153
+ )
143
154
} else if let Some ( ( enum_def, len) ) = resolve_array_of_enum_def ( & ctx. sema , & expr) {
144
155
let is_non_exhaustive = enum_def. is_non_exhaustive ( ctx. db ( ) , module. krate ( ) ) ;
145
156
let variants = enum_def. variants ( ctx. db ( ) ) ;
@@ -148,6 +159,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
148
159
return None ;
149
160
}
150
161
162
+ let has_hidden_variants =
163
+ variants. iter ( ) . any ( |variant| variant. should_be_hidden ( ctx. db ( ) , module. krate ( ) ) ) ;
164
+
151
165
let variants_of_enums = vec ! [ variants; len] ;
152
166
153
167
let missing_pats = variants_of_enums
@@ -164,28 +178,42 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
164
178
( ast:: Pat :: from ( make:: slice_pat ( patterns) ) , is_hidden)
165
179
} )
166
180
. filter ( |( variant_pat, _) | is_variant_missing ( & top_lvl_pats, variant_pat) ) ;
167
- ( ( Box :: new ( missing_pats) as Box < dyn Iterator < Item = _ > > ) . peekable ( ) , is_non_exhaustive)
181
+ (
182
+ ( Box :: new ( missing_pats) as Box < dyn Iterator < Item = _ > > ) . peekable ( ) ,
183
+ is_non_exhaustive,
184
+ has_hidden_variants,
185
+ )
168
186
} else {
169
187
return None ;
170
188
} ;
171
189
172
190
let mut needs_catch_all_arm = is_non_exhaustive && !has_catch_all_arm;
173
191
174
- if !needs_catch_all_arm && missing_pats. peek ( ) . is_none ( ) {
192
+ if !needs_catch_all_arm
193
+ && ( ( has_hidden_variants && has_catch_all_arm) || missing_pats. peek ( ) . is_none ( ) )
194
+ {
175
195
return None ;
176
196
}
177
197
178
198
acc. add (
179
199
AssistId ( "add_missing_match_arms" , AssistKind :: QuickFix ) ,
180
200
"Fill match arms" ,
181
201
target_range,
182
- |builder | {
202
+ |edit | {
183
203
let new_match_arm_list = match_arm_list. clone_for_update ( ) ;
204
+
205
+ // having any hidden variants means that we need a catch-all arm
206
+ needs_catch_all_arm |= has_hidden_variants;
207
+
184
208
let missing_arms = missing_pats
185
- . map ( |( pat, hidden) | {
186
- ( make:: match_arm ( iter:: once ( pat) , None , make:: ext:: expr_todo ( ) ) , hidden)
209
+ . filter ( |( _, hidden) | {
210
+ // filter out hidden patterns because they're handled by the catch-all arm
211
+ !hidden
187
212
} )
188
- . map ( |( it, hidden) | ( it. clone_for_update ( ) , hidden) ) ;
213
+ . map ( |( pat, _) | {
214
+ make:: match_arm ( iter:: once ( pat) , None , make:: ext:: expr_todo ( ) )
215
+ . clone_for_update ( )
216
+ } ) ;
189
217
190
218
let catch_all_arm = new_match_arm_list
191
219
. arms ( )
@@ -204,15 +232,13 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
204
232
cov_mark:: hit!( add_missing_match_arms_empty_expr) ;
205
233
}
206
234
}
235
+
207
236
let mut first_new_arm = None ;
208
- for ( arm, hidden) in missing_arms {
209
- if hidden {
210
- needs_catch_all_arm = !has_catch_all_arm;
211
- } else {
212
- first_new_arm. get_or_insert_with ( || arm. clone ( ) ) ;
213
- new_match_arm_list. add_arm ( arm) ;
214
- }
237
+ for arm in missing_arms {
238
+ first_new_arm. get_or_insert_with ( || arm. clone ( ) ) ;
239
+ new_match_arm_list. add_arm ( arm) ;
215
240
}
241
+
216
242
if needs_catch_all_arm && !has_catch_all_arm {
217
243
cov_mark:: hit!( added_wildcard_pattern) ;
218
244
let arm = make:: match_arm (
@@ -225,24 +251,39 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
225
251
new_match_arm_list. add_arm ( arm) ;
226
252
}
227
253
228
- let old_range = ctx. sema . original_range ( match_arm_list. syntax ( ) ) . range ;
229
- match ( first_new_arm, ctx. config . snippet_cap ) {
230
- ( Some ( first_new_arm) , Some ( cap) ) => {
231
- let extend_lifetime;
232
- let cursor =
233
- match first_new_arm. syntax ( ) . descendants ( ) . find_map ( ast:: WildcardPat :: cast)
234
- {
235
- Some ( it) => {
236
- extend_lifetime = it. syntax ( ) . clone ( ) ;
237
- Cursor :: Replace ( & extend_lifetime)
238
- }
239
- None => Cursor :: Before ( first_new_arm. syntax ( ) ) ,
240
- } ;
241
- let snippet = render_snippet ( cap, new_match_arm_list. syntax ( ) , cursor) ;
242
- builder. replace_snippet ( cap, old_range, snippet) ;
254
+ if let ( Some ( first_new_arm) , Some ( cap) ) = ( first_new_arm, ctx. config . snippet_cap ) {
255
+ match first_new_arm. syntax ( ) . descendants ( ) . find_map ( ast:: WildcardPat :: cast) {
256
+ Some ( it) => edit. add_placeholder_snippet ( cap, it) ,
257
+ None => edit. add_tabstop_before ( cap, first_new_arm) ,
243
258
}
244
- _ => builder. replace ( old_range, new_match_arm_list. to_string ( ) ) ,
245
259
}
260
+
261
+ // FIXME: Hack for mutable syntax trees not having great support for macros
262
+ // Just replace the element that the original range came from
263
+ let old_place = {
264
+ // Find the original element
265
+ let old_file_range = ctx. sema . original_range ( match_arm_list. syntax ( ) ) ;
266
+ let file = ctx. sema . parse ( old_file_range. file_id ) ;
267
+ let old_place = file. syntax ( ) . covering_element ( old_file_range. range ) ;
268
+
269
+ // Make `old_place` mut
270
+ match old_place {
271
+ syntax:: SyntaxElement :: Node ( it) => {
272
+ syntax:: SyntaxElement :: from ( edit. make_syntax_mut ( it) )
273
+ }
274
+ syntax:: SyntaxElement :: Token ( it) => {
275
+ // Don't have a way to make tokens mut, so instead make the parent mut
276
+ // and find the token again
277
+ let parent = edit. make_syntax_mut ( it. parent ( ) . unwrap ( ) ) ;
278
+ let mut_token =
279
+ parent. covering_element ( it. text_range ( ) ) . into_token ( ) . unwrap ( ) ;
280
+
281
+ syntax:: SyntaxElement :: from ( mut_token)
282
+ }
283
+ }
284
+ } ;
285
+
286
+ syntax:: ted:: replace ( old_place, new_match_arm_list. syntax ( ) ) ;
246
287
} ,
247
288
)
248
289
}
@@ -1621,10 +1662,9 @@ pub enum E { #[doc(hidden)] A, }
1621
1662
) ;
1622
1663
}
1623
1664
1624
- // FIXME: I don't think the assist should be applicable in this case
1625
1665
#[ test]
1626
1666
fn does_not_fill_wildcard_with_wildcard ( ) {
1627
- check_assist (
1667
+ check_assist_not_applicable (
1628
1668
add_missing_match_arms,
1629
1669
r#"
1630
1670
//- /main.rs crate:main deps:e
@@ -1635,13 +1675,6 @@ fn foo(t: ::e::E) {
1635
1675
}
1636
1676
//- /e.rs crate:e
1637
1677
pub enum E { #[doc(hidden)] A, }
1638
- "# ,
1639
- r#"
1640
- fn foo(t: ::e::E) {
1641
- match t {
1642
- _ => todo!(),
1643
- }
1644
- }
1645
1678
"# ,
1646
1679
) ;
1647
1680
}
@@ -1777,7 +1810,7 @@ fn foo(t: ::e::E, b: bool) {
1777
1810
1778
1811
#[ test]
1779
1812
fn does_not_fill_wildcard_with_partial_wildcard_and_wildcard ( ) {
1780
- check_assist (
1813
+ check_assist_not_applicable (
1781
1814
add_missing_match_arms,
1782
1815
r#"
1783
1816
//- /main.rs crate:main deps:e
@@ -1789,14 +1822,6 @@ fn foo(t: ::e::E, b: bool) {
1789
1822
}
1790
1823
//- /e.rs crate:e
1791
1824
pub enum E { #[doc(hidden)] A, }"# ,
1792
- r#"
1793
- fn foo(t: ::e::E, b: bool) {
1794
- match t {
1795
- _ if b => todo!(),
1796
- _ => todo!(),
1797
- }
1798
- }
1799
- "# ,
1800
1825
) ;
1801
1826
}
1802
1827
0 commit comments