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