1use hir::{PathResolution, StructKind};
2use ide_db::syntax_helpers::suggest_name::NameGenerator;
3use syntax::{
4 AstNode, ToSmolStr,
5 ast::{self, syntax_factory::SyntaxFactory},
6 match_ast,
7};
8
9use crate::{AssistContext, AssistId, Assists};
10
11fn expand_record_rest_pattern(
31 acc: &mut Assists,
32 ctx: &AssistContext<'_>,
33 record_pat: ast::RecordPat,
34 rest_pat: ast::RestPat,
35) -> Option<()> {
36 let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat);
37 if missing_fields.is_empty() {
38 cov_mark::hit!(no_missing_fields);
39 return None;
40 }
41
42 let old_field_list = record_pat.record_pat_field_list()?;
43 let old_range = ctx.sema.original_range_opt(old_field_list.syntax())?;
44 if old_range.file_id != ctx.file_id() {
45 return None;
46 }
47
48 let edition = ctx.sema.scope(record_pat.syntax())?.krate().edition(ctx.db());
49 acc.add(
50 AssistId::refactor_rewrite("expand_record_rest_pattern"),
51 "Fill struct fields",
52 rest_pat.syntax().text_range(),
53 |builder| {
54 let make = SyntaxFactory::with_mappings();
55 let mut editor = builder.make_editor(rest_pat.syntax());
56 let new_fields = old_field_list.fields().chain(missing_fields.iter().map(|(f, _)| {
57 make.record_pat_field_shorthand(
58 make.ident_pat(
59 false,
60 false,
61 make.name(&f.name(ctx.sema.db).display_no_db(edition).to_smolstr()),
62 )
63 .into(),
64 )
65 }));
66 let new_field_list = make.record_pat_field_list(new_fields, None);
67
68 editor.replace(old_field_list.syntax(), new_field_list.syntax());
69
70 editor.add_mappings(make.finish_with_mappings());
71 builder.add_file_edits(ctx.vfs_file_id(), editor);
72 },
73 )
74}
75
76fn expand_tuple_struct_rest_pattern(
96 acc: &mut Assists,
97 ctx: &AssistContext<'_>,
98 pat: ast::TupleStructPat,
99 rest_pat: ast::RestPat,
100) -> Option<()> {
101 let path = pat.path()?;
102 let fields = match ctx.sema.type_of_pat(&pat.clone().into())?.original.as_adt()? {
103 hir::Adt::Struct(s) if s.kind(ctx.sema.db) == StructKind::Tuple => s.fields(ctx.sema.db),
104 hir::Adt::Enum(_) => match ctx.sema.resolve_path(&path)? {
105 PathResolution::Def(hir::ModuleDef::Variant(v))
106 if v.kind(ctx.sema.db) == StructKind::Tuple =>
107 {
108 v.fields(ctx.sema.db)
109 }
110 _ => return None,
111 },
112 _ => return None,
113 };
114
115 let rest_pat = rest_pat.into();
116 let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.fields())?;
117
118 if fields.len().saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
119 cov_mark::hit!(no_missing_fields_tuple_struct);
120 return None;
121 }
122
123 let old_range = ctx.sema.original_range_opt(pat.syntax())?;
124 if old_range.file_id != ctx.file_id() {
125 return None;
126 }
127
128 acc.add(
129 AssistId::refactor_rewrite("expand_tuple_struct_rest_pattern"),
130 "Fill tuple struct fields",
131 rest_pat.syntax().text_range(),
132 |builder| {
133 let make = SyntaxFactory::with_mappings();
134 let mut editor = builder.make_editor(rest_pat.syntax());
135
136 let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax()));
137 let new_pat = make.tuple_struct_pat(
138 path,
139 pat.fields()
140 .take(prefix_count)
141 .chain(fields[prefix_count..fields.len() - suffix_count].iter().map(|f| {
142 gen_unnamed_pat(
143 ctx,
144 &make,
145 &mut name_gen,
146 &f.ty(ctx.db()).to_type(ctx.sema.db),
147 f.index(),
148 )
149 }))
150 .chain(pat.fields().skip(prefix_count + 1)),
151 );
152
153 editor.replace(pat.syntax(), new_pat.syntax());
154
155 editor.add_mappings(make.finish_with_mappings());
156 builder.add_file_edits(ctx.vfs_file_id(), editor);
157 },
158 )
159}
160
161fn expand_tuple_rest_pattern(
177 acc: &mut Assists,
178 ctx: &AssistContext<'_>,
179 pat: ast::TuplePat,
180 rest_pat: ast::RestPat,
181) -> Option<()> {
182 let fields = ctx.sema.type_of_pat(&pat.clone().into())?.original.tuple_fields(ctx.db());
183 let len = fields.len();
184
185 let rest_pat = rest_pat.into();
186 let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.fields())?;
187
188 if len.saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
189 cov_mark::hit!(no_missing_fields_tuple);
190 return None;
191 }
192
193 let old_range = ctx.sema.original_range_opt(pat.syntax())?;
194 if old_range.file_id != ctx.file_id() {
195 return None;
196 }
197
198 acc.add(
199 AssistId::refactor_rewrite("expand_tuple_rest_pattern"),
200 "Fill tuple fields",
201 rest_pat.syntax().text_range(),
202 |builder| {
203 let make = SyntaxFactory::with_mappings();
204 let mut editor = builder.make_editor(rest_pat.syntax());
205
206 let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax()));
207 let new_pat = make.tuple_pat(
208 pat.fields()
209 .take(prefix_count)
210 .chain(fields[prefix_count..len - suffix_count].iter().enumerate().map(
211 |(index, ty)| {
212 gen_unnamed_pat(ctx, &make, &mut name_gen, ty, prefix_count + index)
213 },
214 ))
215 .chain(pat.fields().skip(prefix_count + 1)),
216 );
217
218 editor.replace(pat.syntax(), new_pat.syntax());
219
220 editor.add_mappings(make.finish_with_mappings());
221 builder.add_file_edits(ctx.vfs_file_id(), editor);
222 },
223 )
224}
225
226fn expand_slice_rest_pattern(
242 acc: &mut Assists,
243 ctx: &AssistContext<'_>,
244 pat: ast::SlicePat,
245 rest_pat: ast::RestPat,
246) -> Option<()> {
247 let (ty, len) = ctx.sema.type_of_pat(&pat.clone().into())?.original.as_array(ctx.db())?;
248
249 let rest_pat = rest_pat.into();
250 let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.pats())?;
251
252 if len.saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
253 cov_mark::hit!(no_missing_fields_slice);
254 return None;
255 }
256
257 let old_range = ctx.sema.original_range_opt(pat.syntax())?;
258 if old_range.file_id != ctx.file_id() {
259 return None;
260 }
261
262 acc.add(
263 AssistId::refactor_rewrite("expand_slice_rest_pattern"),
264 "Fill slice fields",
265 rest_pat.syntax().text_range(),
266 |builder| {
267 let make = SyntaxFactory::with_mappings();
268 let mut editor = builder.make_editor(rest_pat.syntax());
269
270 let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax()));
271 let new_pat = make.slice_pat(
272 pat.pats()
273 .take(prefix_count)
274 .chain(
275 (prefix_count..len - suffix_count)
276 .map(|index| gen_unnamed_pat(ctx, &make, &mut name_gen, &ty, index)),
277 )
278 .chain(pat.pats().skip(prefix_count + 1)),
279 );
280
281 editor.replace(pat.syntax(), new_pat.syntax());
282
283 editor.add_mappings(make.finish_with_mappings());
284 builder.add_file_edits(ctx.vfs_file_id(), editor);
285 },
286 )
287}
288
289pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
290 let rest_pat = ctx.find_node_at_offset::<ast::RestPat>()?;
291 let parent = rest_pat.syntax().parent()?;
292 match_ast! {
293 match parent {
294 ast::RecordPatFieldList(it) => expand_record_rest_pattern(acc, ctx, it.syntax().parent().and_then(ast::RecordPat::cast)?, rest_pat),
295 ast::TupleStructPat(it) => expand_tuple_struct_rest_pattern(acc, ctx, it, rest_pat),
296 ast::TuplePat(it) => expand_tuple_rest_pattern(acc, ctx, it, rest_pat),
297 ast::SlicePat(it) => expand_slice_rest_pattern(acc, ctx, it, rest_pat),
298 _ => None,
299 }
300 }
301}
302
303fn gen_unnamed_pat(
304 ctx: &AssistContext<'_>,
305 make: &SyntaxFactory,
306 name_gen: &mut NameGenerator,
307 ty: &hir::Type<'_>,
308 index: usize,
309) -> ast::Pat {
310 make.ident_pat(
311 false,
312 false,
313 match name_gen.for_type(ty, ctx.sema.db, ctx.edition()) {
314 Some(name) => make.name(&name),
315 None => make.name(&format!("_{index}")),
316 },
317 )
318 .into()
319}
320
321fn calculate_counts(
322 rest_pat: &ast::Pat,
323 mut pats: ast::AstChildren<ast::Pat>,
324) -> Option<(usize, usize)> {
325 let prefix_count = pats.by_ref().position(|p| p == *rest_pat)?;
326 let suffix_count = pats.count();
327 Some((prefix_count, suffix_count))
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use crate::tests::{check_assist, check_assist_not_applicable};
334
335 #[test]
336 fn fill_fields_enum_with_only_ellipsis() {
337 check_assist(
338 expand_rest_pattern,
339 r#"
340enum Foo {
341 A(X),
342 B{y: Y, z: Z}
343}
344
345fn bar(foo: Foo) {
346 match foo {
347 Foo::A(_) => false,
348 Foo::B{ ..$0 } => true,
349 };
350}
351"#,
352 r#"
353enum Foo {
354 A(X),
355 B{y: Y, z: Z}
356}
357
358fn bar(foo: Foo) {
359 match foo {
360 Foo::A(_) => false,
361 Foo::B{ y, z } => true,
362 };
363}
364"#,
365 )
366 }
367
368 #[test]
369 fn fill_fields_enum_with_fields() {
370 check_assist(
371 expand_rest_pattern,
372 r#"
373enum Foo {
374 A(X),
375 B{y: Y, z: Z}
376}
377
378fn bar(foo: Foo) {
379 match foo {
380 Foo::A(_) => false,
381 Foo::B{ y, ..$0 } => true,
382 };
383}
384"#,
385 r#"
386enum Foo {
387 A(X),
388 B{y: Y, z: Z}
389}
390
391fn bar(foo: Foo) {
392 match foo {
393 Foo::A(_) => false,
394 Foo::B{ y, z } => true,
395 };
396}
397"#,
398 )
399 }
400
401 #[test]
402 fn fill_fields_struct_with_only_ellipsis() {
403 check_assist(
404 expand_rest_pattern,
405 r#"
406struct Bar {
407 y: Y,
408 z: Z,
409}
410
411fn foo(bar: Bar) {
412 let Bar { ..$0 } = bar;
413}
414"#,
415 r#"
416struct Bar {
417 y: Y,
418 z: Z,
419}
420
421fn foo(bar: Bar) {
422 let Bar { y, z } = bar;
423}
424"#,
425 );
426 check_assist(
427 expand_rest_pattern,
428 r#"
429struct Y;
430struct Z;
431struct Bar(Y, Z)
432
433fn foo(bar: Bar) {
434 let Bar(..$0) = bar;
435}
436"#,
437 r#"
438struct Y;
439struct Z;
440struct Bar(Y, Z)
441
442fn foo(bar: Bar) {
443 let Bar(y, z) = bar;
444}
445"#,
446 )
447 }
448
449 #[test]
450 fn fill_fields_struct_with_fields() {
451 check_assist(
452 expand_rest_pattern,
453 r#"
454struct Bar {
455 y: Y,
456 z: Z,
457}
458
459fn foo(bar: Bar) {
460 let Bar { y, ..$0 } = bar;
461}
462"#,
463 r#"
464struct Bar {
465 y: Y,
466 z: Z,
467}
468
469fn foo(bar: Bar) {
470 let Bar { y, z } = bar;
471}
472"#,
473 );
474 check_assist(
475 expand_rest_pattern,
476 r#"
477struct X;
478struct Y;
479struct Z;
480struct Bar(X, Y, Z)
481
482fn foo(bar: Bar) {
483 let Bar(x, ..$0, z) = bar;
484}
485"#,
486 r#"
487struct X;
488struct Y;
489struct Z;
490struct Bar(X, Y, Z)
491
492fn foo(bar: Bar) {
493 let Bar(x, y, z) = bar;
494}
495"#,
496 )
497 }
498
499 #[test]
500 fn fill_tuple_with_fields() {
501 check_assist(
502 expand_rest_pattern,
503 r#"
504fn foo(bar: (char, i32, i32)) {
505 let (ch, ..$0) = bar;
506}
507"#,
508 r#"
509fn foo(bar: (char, i32, i32)) {
510 let (ch, _1, _2) = bar;
511}
512"#,
513 );
514 check_assist(
515 expand_rest_pattern,
516 r#"
517fn foo(bar: (char, i32, i32)) {
518 let (ch, ..$0, end) = bar;
519}
520"#,
521 r#"
522fn foo(bar: (char, i32, i32)) {
523 let (ch, _1, end) = bar;
524}
525"#,
526 );
527 }
528
529 #[test]
530 fn fill_array_with_fields() {
531 check_assist(
532 expand_rest_pattern,
533 r#"
534fn foo(bar: [i32; 4]) {
535 let [first, ..$0] = bar;
536}
537"#,
538 r#"
539fn foo(bar: [i32; 4]) {
540 let [first, _1, _2, _3] = bar;
541}
542"#,
543 );
544 check_assist(
545 expand_rest_pattern,
546 r#"
547fn foo(bar: [i32; 4]) {
548 let [first, second, ..$0] = bar;
549}
550"#,
551 r#"
552fn foo(bar: [i32; 4]) {
553 let [first, second, _2, _3] = bar;
554}
555"#,
556 );
557 check_assist(
558 expand_rest_pattern,
559 r#"
560fn foo(bar: [i32; 4]) {
561 let [first, second, ..$0, end] = bar;
562}
563"#,
564 r#"
565fn foo(bar: [i32; 4]) {
566 let [first, second, _2, end] = bar;
567}
568"#,
569 );
570 }
571
572 #[test]
573 fn fill_fields_struct_generated_by_macro() {
574 check_assist(
575 expand_rest_pattern,
576 r#"
577macro_rules! position {
578 ($t: ty) => {
579 struct Pos {x: $t, y: $t}
580 };
581}
582
583position!(usize);
584
585fn macro_call(pos: Pos) {
586 let Pos { ..$0 } = pos;
587}
588"#,
589 r#"
590macro_rules! position {
591 ($t: ty) => {
592 struct Pos {x: $t, y: $t}
593 };
594}
595
596position!(usize);
597
598fn macro_call(pos: Pos) {
599 let Pos { x, y } = pos;
600}
601"#,
602 );
603 }
604
605 #[test]
606 fn fill_fields_enum_generated_by_macro() {
607 check_assist(
608 expand_rest_pattern,
609 r#"
610macro_rules! enum_gen {
611 ($t: ty) => {
612 enum Foo {
613 A($t),
614 B{x: $t, y: $t},
615 }
616 };
617}
618
619enum_gen!(usize);
620
621fn macro_call(foo: Foo) {
622 match foo {
623 Foo::A(_) => false,
624 Foo::B{ ..$0 } => true,
625 }
626}
627"#,
628 r#"
629macro_rules! enum_gen {
630 ($t: ty) => {
631 enum Foo {
632 A($t),
633 B{x: $t, y: $t},
634 }
635 };
636}
637
638enum_gen!(usize);
639
640fn macro_call(foo: Foo) {
641 match foo {
642 Foo::A(_) => false,
643 Foo::B{ x, y } => true,
644 }
645}
646"#,
647 );
648 }
649
650 #[test]
651 fn not_applicable_when_not_in_ellipsis() {
652 check_assist_not_applicable(
653 expand_rest_pattern,
654 r#"
655enum Foo {
656 A(X),
657 B{y: Y, z: Z}
658}
659
660fn bar(foo: Foo) {
661 match foo {
662 Foo::A(_) => false,
663 Foo::B{..}$0 => true,
664 };
665}
666"#,
667 );
668 check_assist_not_applicable(
669 expand_rest_pattern,
670 r#"
671enum Foo {
672 A(X),
673 B{y: Y, z: Z}
674}
675
676fn bar(foo: Foo) {
677 match foo {
678 Foo::A(_) => false,
679 Foo::B$0{..} => true,
680 };
681}
682"#,
683 );
684 check_assist_not_applicable(
685 expand_rest_pattern,
686 r#"
687enum Foo {
688 A(X),
689 B{y: Y, z: Z}
690}
691
692fn bar(foo: Foo) {
693 match foo {
694 Foo::A(_) => false,
695 Foo::$0B{..} => true,
696 };
697}
698"#,
699 );
700 }
701
702 #[test]
703 fn not_applicable_when_no_missing_fields() {
704 cov_mark::check!(no_missing_fields);
706 cov_mark::check!(no_missing_fields_tuple_struct);
707 cov_mark::check!(no_missing_fields_tuple);
708 cov_mark::check!(no_missing_fields_slice);
709 check_assist_not_applicable(
710 expand_rest_pattern,
711 r#"
712enum Foo {
713 A(X),
714 B{y: Y, z: Z}
715}
716
717fn bar(foo: Foo) {
718 match foo {
719 Foo::A(_) => false,
720 Foo::B{y, z, ..$0} => true,
721 };
722}
723"#,
724 );
725 check_assist_not_applicable(
726 expand_rest_pattern,
727 r#"
728struct Bar {
729 y: Y,
730 z: Z,
731}
732
733fn foo(bar: Bar) {
734 let Bar { y, z, ..$0 } = bar;
735}
736"#,
737 );
738 check_assist_not_applicable(
739 expand_rest_pattern,
740 r#"
741struct Bar(Y, Z)
742
743fn foo(bar: Bar) {
744 let Bar(y, ..$0, z) = bar;
745}
746"#,
747 );
748 check_assist_not_applicable(
749 expand_rest_pattern,
750 r#"
751fn foo(bar: (i32, i32)) {
752 let (y, ..$0, z) = bar;
753}
754"#,
755 );
756 check_assist_not_applicable(
757 expand_rest_pattern,
758 r#"
759fn foo(bar: [i32; 2]) {
760 let [y, ..$0, z] = bar;
761}
762"#,
763 );
764 }
765}