Skip to main content

ide_assists/handlers/
expand_rest_pattern.rs

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
11// Assist: expand_record_rest_pattern
12//
13// Fills fields by replacing rest pattern in record patterns.
14//
15// ```
16// struct Bar { y: Y, z: Z }
17//
18// fn foo(bar: Bar) {
19//     let Bar { ..$0 } = bar;
20// }
21// ```
22// ->
23// ```
24// struct Bar { y: Y, z: Z }
25//
26// fn foo(bar: Bar) {
27//     let Bar { y, z } = bar;
28// }
29// ```
30fn 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
74// Assist: expand_tuple_struct_rest_pattern
75//
76// Fills fields by replacing rest pattern in tuple struct patterns.
77//
78// ```
79// struct Bar(Y, Z);
80//
81// fn foo(bar: Bar) {
82//     let Bar(..$0) = bar;
83// }
84// ```
85// ->
86// ```
87// struct Bar(Y, Z);
88//
89// fn foo(bar: Bar) {
90//     let Bar(_0, _1) = bar;
91// }
92// ```
93fn 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
157// Assist: expand_tuple_rest_pattern
158//
159// Fills fields by replacing rest pattern in tuple patterns.
160//
161// ```
162// fn foo(bar: (char, i32, i32)) {
163//     let (ch, ..$0) = bar;
164// }
165// ```
166// ->
167// ```
168// fn foo(bar: (char, i32, i32)) {
169//     let (ch, _1, _2) = bar;
170// }
171// ```
172fn 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
219// Assist: expand_slice_rest_pattern
220//
221// Fills fields by replacing rest pattern in slice patterns.
222//
223// ```
224// fn foo(bar: [i32; 3]) {
225//     let [first, ..$0] = bar;
226// }
227// ```
228// ->
229// ```
230// fn foo(bar: [i32; 3]) {
231//     let [first, _1, _2] = bar;
232// }
233// ```
234fn 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        // This is still possible even though it's meaningless
696        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}