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 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
76// Assist: expand_tuple_struct_rest_pattern
77//
78// Fills fields by replacing rest pattern in tuple struct patterns.
79//
80// ```
81// struct Bar(Y, Z);
82//
83// fn foo(bar: Bar) {
84//     let Bar(..$0) = bar;
85// }
86// ```
87// ->
88// ```
89// struct Bar(Y, Z);
90//
91// fn foo(bar: Bar) {
92//     let Bar(_0, _1) = bar;
93// }
94// ```
95fn 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
161// Assist: expand_tuple_rest_pattern
162//
163// Fills fields by replacing rest pattern in tuple patterns.
164//
165// ```
166// fn foo(bar: (char, i32, i32)) {
167//     let (ch, ..$0) = bar;
168// }
169// ```
170// ->
171// ```
172// fn foo(bar: (char, i32, i32)) {
173//     let (ch, _1, _2) = bar;
174// }
175// ```
176fn 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
226// Assist: expand_slice_rest_pattern
227//
228// Fills fields by replacing rest pattern in slice patterns.
229//
230// ```
231// fn foo(bar: [i32; 3]) {
232//     let [first, ..$0] = bar;
233// }
234// ```
235// ->
236// ```
237// fn foo(bar: [i32; 3]) {
238//     let [first, _1, _2] = bar;
239// }
240// ```
241fn 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        // This is still possible even though it's meaningless
705        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}