]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/fill_match_arms.rs
Merge #8524 #8527
[rust.git] / crates / ide_assists / src / handlers / fill_match_arms.rs
1 use std::iter;
2
3 use either::Either;
4 use hir::{Adt, HasSource, ModuleDef, Semantics};
5 use ide_db::helpers::{mod_path_to_ast, FamousDefs};
6 use ide_db::RootDatabase;
7 use itertools::Itertools;
8 use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
9
10 use crate::{
11     utils::{self, render_snippet, Cursor},
12     AssistContext, AssistId, AssistKind, Assists,
13 };
14
15 // Assist: fill_match_arms
16 //
17 // Adds missing clauses to a `match` expression.
18 //
19 // ```
20 // enum Action { Move { distance: u32 }, Stop }
21 //
22 // fn handle(action: Action) {
23 //     match action {
24 //         $0
25 //     }
26 // }
27 // ```
28 // ->
29 // ```
30 // enum Action { Move { distance: u32 }, Stop }
31 //
32 // fn handle(action: Action) {
33 //     match action {
34 //         $0Action::Move { distance } => {}
35 //         Action::Stop => {}
36 //     }
37 // }
38 // ```
39 pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40     let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?;
41     let match_arm_list = match_expr.match_arm_list()?;
42
43     let expr = match_expr.expr()?;
44
45     let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
46     if arms.len() == 1 {
47         if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
48             arms.clear();
49         }
50     }
51
52     let top_lvl_pats: Vec<_> = arms
53         .iter()
54         .filter_map(ast::MatchArm::pat)
55         .flat_map(|pat| match pat {
56             // Special casee OrPat as separate top-level pats
57             Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
58             _ => Either::Right(iter::once(pat)),
59         })
60         // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
61         .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
62         .collect();
63
64     let module = ctx.sema.scope(expr.syntax()).module()?;
65
66     let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
67         let variants = enum_def.variants(ctx.db());
68
69         let mut variants = variants
70             .into_iter()
71             .filter_map(|variant| build_pat(ctx.db(), module, variant))
72             .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
73             .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
74             .collect::<Vec<_>>();
75         if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
76             // Match `Some` variant first.
77             cov_mark::hit!(option_order);
78             variants.reverse()
79         }
80         variants
81     } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
82         // When calculating the match arms for a tuple of enums, we want
83         // to create a match arm for each possible combination of enum
84         // values. The `multi_cartesian_product` method transforms
85         // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
86         // where each tuple represents a proposed match arm.
87         enum_defs
88             .into_iter()
89             .map(|enum_def| enum_def.variants(ctx.db()))
90             .multi_cartesian_product()
91             .map(|variants| {
92                 let patterns =
93                     variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
94                 ast::Pat::from(make::tuple_pat(patterns))
95             })
96             .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
97             .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
98             .collect()
99     } else {
100         return None;
101     };
102
103     if missing_arms.is_empty() {
104         return None;
105     }
106
107     let target = ctx.sema.original_range(match_expr.syntax()).range;
108     acc.add(
109         AssistId("fill_match_arms", AssistKind::QuickFix),
110         "Fill match arms",
111         target,
112         |builder| {
113             let new_arm_list = match_arm_list.remove_placeholder();
114             let n_old_arms = new_arm_list.arms().count();
115             let new_arm_list = new_arm_list.append_arms(missing_arms);
116             let first_new_arm = new_arm_list.arms().nth(n_old_arms);
117             let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
118             match (first_new_arm, ctx.config.snippet_cap) {
119                 (Some(first_new_arm), Some(cap)) => {
120                     let extend_lifetime;
121                     let cursor =
122                         match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
123                         {
124                             Some(it) => {
125                                 extend_lifetime = it.syntax().clone();
126                                 Cursor::Replace(&extend_lifetime)
127                             }
128                             None => Cursor::Before(first_new_arm.syntax()),
129                         };
130                     let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
131                     builder.replace_snippet(cap, old_range, snippet);
132                 }
133                 _ => builder.replace(old_range, new_arm_list.to_string()),
134             }
135         },
136     )
137 }
138
139 fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
140     !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
141 }
142
143 // Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
144 fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
145     match (pat, var) {
146         (Pat::WildcardPat(_), _) => true,
147         (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
148             tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
149         }
150         _ => utils::does_pat_match_variant(pat, var),
151     }
152 }
153
154 fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
155     sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
156         Some(Adt::Enum(e)) => Some(e),
157         _ => None,
158     })
159 }
160
161 fn resolve_tuple_of_enum_def(
162     sema: &Semantics<RootDatabase>,
163     expr: &ast::Expr,
164 ) -> Option<Vec<hir::Enum>> {
165     sema.type_of_expr(&expr)?
166         .tuple_fields(sema.db)
167         .iter()
168         .map(|ty| {
169             ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
170                 Some(Adt::Enum(e)) => Some(e),
171                 // For now we only handle expansion for a tuple of enums. Here
172                 // we map non-enum items to None and rely on `collect` to
173                 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
174                 _ => None,
175             })
176         })
177         .collect()
178 }
179
180 fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> {
181     let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
182
183     // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
184     let pat: ast::Pat = match var.source(db)?.value.kind() {
185         ast::StructKind::Tuple(field_list) => {
186             let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
187             make::tuple_struct_pat(path, pats).into()
188         }
189         ast::StructKind::Record(field_list) => {
190             let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
191             make::record_pat(path, pats).into()
192         }
193         ast::StructKind::Unit => make::path_pat(path),
194     };
195
196     Some(pat)
197 }
198
199 #[cfg(test)]
200 mod tests {
201     use ide_db::helpers::FamousDefs;
202
203     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
204
205     use super::fill_match_arms;
206
207     #[test]
208     fn all_match_arms_provided() {
209         check_assist_not_applicable(
210             fill_match_arms,
211             r#"
212             enum A {
213                 As,
214                 Bs{x:i32, y:Option<i32>},
215                 Cs(i32, Option<i32>),
216             }
217             fn main() {
218                 match A::As$0 {
219                     A::As,
220                     A::Bs{x,y:Some(_)} => {}
221                     A::Cs(_, Some(_)) => {}
222                 }
223             }
224             "#,
225         );
226     }
227
228     #[test]
229     fn tuple_of_non_enum() {
230         // for now this case is not handled, although it potentially could be
231         // in the future
232         check_assist_not_applicable(
233             fill_match_arms,
234             r#"
235             fn main() {
236                 match (0, false)$0 {
237                 }
238             }
239             "#,
240         );
241     }
242
243     #[test]
244     fn partial_fill_record_tuple() {
245         check_assist(
246             fill_match_arms,
247             r#"
248             enum A {
249                 As,
250                 Bs { x: i32, y: Option<i32> },
251                 Cs(i32, Option<i32>),
252             }
253             fn main() {
254                 match A::As$0 {
255                     A::Bs { x, y: Some(_) } => {}
256                     A::Cs(_, Some(_)) => {}
257                 }
258             }
259             "#,
260             r#"
261             enum A {
262                 As,
263                 Bs { x: i32, y: Option<i32> },
264                 Cs(i32, Option<i32>),
265             }
266             fn main() {
267                 match A::As {
268                     A::Bs { x, y: Some(_) } => {}
269                     A::Cs(_, Some(_)) => {}
270                     $0A::As => {}
271                 }
272             }
273             "#,
274         );
275     }
276
277     #[test]
278     fn partial_fill_option() {
279         check_assist(
280             fill_match_arms,
281             r#"
282 enum Option<T> { Some(T), None }
283 use Option::*;
284
285 fn main() {
286     match None$0 {
287         None => {}
288     }
289 }
290             "#,
291             r#"
292 enum Option<T> { Some(T), None }
293 use Option::*;
294
295 fn main() {
296     match None {
297         None => {}
298         Some(${0:_}) => {}
299     }
300 }
301             "#,
302         );
303     }
304
305     #[test]
306     fn partial_fill_or_pat() {
307         check_assist(
308             fill_match_arms,
309             r#"
310 enum A { As, Bs, Cs(Option<i32>) }
311 fn main() {
312     match A::As$0 {
313         A::Cs(_) | A::Bs => {}
314     }
315 }
316 "#,
317             r#"
318 enum A { As, Bs, Cs(Option<i32>) }
319 fn main() {
320     match A::As {
321         A::Cs(_) | A::Bs => {}
322         $0A::As => {}
323     }
324 }
325 "#,
326         );
327     }
328
329     #[test]
330     fn partial_fill() {
331         check_assist(
332             fill_match_arms,
333             r#"
334 enum A { As, Bs, Cs, Ds(String), Es(B) }
335 enum B { Xs, Ys }
336 fn main() {
337     match A::As$0 {
338         A::Bs if 0 < 1 => {}
339         A::Ds(_value) => { let x = 1; }
340         A::Es(B::Xs) => (),
341     }
342 }
343 "#,
344             r#"
345 enum A { As, Bs, Cs, Ds(String), Es(B) }
346 enum B { Xs, Ys }
347 fn main() {
348     match A::As {
349         A::Bs if 0 < 1 => {}
350         A::Ds(_value) => { let x = 1; }
351         A::Es(B::Xs) => (),
352         $0A::As => {}
353         A::Cs => {}
354     }
355 }
356 "#,
357         );
358     }
359
360     #[test]
361     fn partial_fill_bind_pat() {
362         check_assist(
363             fill_match_arms,
364             r#"
365 enum A { As, Bs, Cs(Option<i32>) }
366 fn main() {
367     match A::As$0 {
368         A::As(_) => {}
369         a @ A::Bs(_) => {}
370     }
371 }
372 "#,
373             r#"
374 enum A { As, Bs, Cs(Option<i32>) }
375 fn main() {
376     match A::As {
377         A::As(_) => {}
378         a @ A::Bs(_) => {}
379         A::Cs(${0:_}) => {}
380     }
381 }
382 "#,
383         );
384     }
385
386     #[test]
387     fn fill_match_arms_empty_body() {
388         check_assist(
389             fill_match_arms,
390             r#"
391 enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
392
393 fn main() {
394     let a = A::As;
395     match a$0 {}
396 }
397 "#,
398             r#"
399 enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
400
401 fn main() {
402     let a = A::As;
403     match a {
404         $0A::As => {}
405         A::Bs => {}
406         A::Cs(_) => {}
407         A::Ds(_, _) => {}
408         A::Es { x, y } => {}
409     }
410 }
411 "#,
412         );
413     }
414
415     #[test]
416     fn fill_match_arms_tuple_of_enum() {
417         check_assist(
418             fill_match_arms,
419             r#"
420             enum A { One, Two }
421             enum B { One, Two }
422
423             fn main() {
424                 let a = A::One;
425                 let b = B::One;
426                 match (a$0, b) {}
427             }
428             "#,
429             r#"
430             enum A { One, Two }
431             enum B { One, Two }
432
433             fn main() {
434                 let a = A::One;
435                 let b = B::One;
436                 match (a, b) {
437                     $0(A::One, B::One) => {}
438                     (A::One, B::Two) => {}
439                     (A::Two, B::One) => {}
440                     (A::Two, B::Two) => {}
441                 }
442             }
443             "#,
444         );
445     }
446
447     #[test]
448     fn fill_match_arms_tuple_of_enum_ref() {
449         check_assist(
450             fill_match_arms,
451             r#"
452             enum A { One, Two }
453             enum B { One, Two }
454
455             fn main() {
456                 let a = A::One;
457                 let b = B::One;
458                 match (&a$0, &b) {}
459             }
460             "#,
461             r#"
462             enum A { One, Two }
463             enum B { One, Two }
464
465             fn main() {
466                 let a = A::One;
467                 let b = B::One;
468                 match (&a, &b) {
469                     $0(A::One, B::One) => {}
470                     (A::One, B::Two) => {}
471                     (A::Two, B::One) => {}
472                     (A::Two, B::Two) => {}
473                 }
474             }
475             "#,
476         );
477     }
478
479     #[test]
480     fn fill_match_arms_tuple_of_enum_partial() {
481         check_assist(
482             fill_match_arms,
483             r#"
484 enum A { One, Two }
485 enum B { One, Two }
486
487 fn main() {
488     let a = A::One;
489     let b = B::One;
490     match (a$0, b) {
491         (A::Two, B::One) => {}
492     }
493 }
494 "#,
495             r#"
496 enum A { One, Two }
497 enum B { One, Two }
498
499 fn main() {
500     let a = A::One;
501     let b = B::One;
502     match (a, b) {
503         (A::Two, B::One) => {}
504         $0(A::One, B::One) => {}
505         (A::One, B::Two) => {}
506         (A::Two, B::Two) => {}
507     }
508 }
509 "#,
510         );
511     }
512
513     #[test]
514     fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
515         let ra_fixture = r#"
516 fn main() {
517     let a = Some(1);
518     let b = Some(());
519     match (a$0, b) {
520         (Some(_), _) => {}
521         (None, Some(_)) => {}
522     }
523 }
524 "#;
525         check_assist(
526             fill_match_arms,
527             &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
528             r#"
529 fn main() {
530     let a = Some(1);
531     let b = Some(());
532     match (a, b) {
533         (Some(_), _) => {}
534         (None, Some(_)) => {}
535         $0(None, None) => {}
536     }
537 }
538 "#,
539         );
540     }
541
542     #[test]
543     fn fill_match_arms_partial_with_deep_pattern() {
544         // Fixme: cannot handle deep patterns
545         let ra_fixture = r#"
546 fn main() {
547     match $0Some(true) {
548         Some(true) => {}
549         None => {}
550     }
551 }
552 "#;
553         check_assist_not_applicable(
554             fill_match_arms,
555             &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
556         );
557     }
558
559     #[test]
560     fn fill_match_arms_tuple_of_enum_not_applicable() {
561         check_assist_not_applicable(
562             fill_match_arms,
563             r#"
564             enum A { One, Two }
565             enum B { One, Two }
566
567             fn main() {
568                 let a = A::One;
569                 let b = B::One;
570                 match (a$0, b) {
571                     (A::Two, B::One) => {}
572                     (A::One, B::One) => {}
573                     (A::One, B::Two) => {}
574                     (A::Two, B::Two) => {}
575                 }
576             }
577             "#,
578         );
579     }
580
581     #[test]
582     fn fill_match_arms_single_element_tuple_of_enum() {
583         check_assist(
584             fill_match_arms,
585             r#"
586             enum A { One, Two }
587
588             fn main() {
589                 let a = A::One;
590                 match (a$0, ) {
591                 }
592             }
593             "#,
594             r#"
595             enum A { One, Two }
596
597             fn main() {
598                 let a = A::One;
599                 match (a, ) {
600                     $0(A::One,) => {}
601                     (A::Two,) => {}
602                 }
603             }
604             "#,
605         );
606     }
607
608     #[test]
609     fn test_fill_match_arm_refs() {
610         check_assist(
611             fill_match_arms,
612             r#"
613             enum A { As }
614
615             fn foo(a: &A) {
616                 match a$0 {
617                 }
618             }
619             "#,
620             r#"
621             enum A { As }
622
623             fn foo(a: &A) {
624                 match a {
625                     $0A::As => {}
626                 }
627             }
628             "#,
629         );
630
631         check_assist(
632             fill_match_arms,
633             r#"
634             enum A {
635                 Es { x: usize, y: usize }
636             }
637
638             fn foo(a: &mut A) {
639                 match a$0 {
640                 }
641             }
642             "#,
643             r#"
644             enum A {
645                 Es { x: usize, y: usize }
646             }
647
648             fn foo(a: &mut A) {
649                 match a {
650                     $0A::Es { x, y } => {}
651                 }
652             }
653             "#,
654         );
655     }
656
657     #[test]
658     fn fill_match_arms_target() {
659         check_assist_target(
660             fill_match_arms,
661             r#"
662             enum E { X, Y }
663
664             fn main() {
665                 match E::X$0 {}
666             }
667             "#,
668             "match E::X {}",
669         );
670     }
671
672     #[test]
673     fn fill_match_arms_trivial_arm() {
674         check_assist(
675             fill_match_arms,
676             r#"
677             enum E { X, Y }
678
679             fn main() {
680                 match E::X {
681                     $0_ => {}
682                 }
683             }
684             "#,
685             r#"
686             enum E { X, Y }
687
688             fn main() {
689                 match E::X {
690                     $0E::X => {}
691                     E::Y => {}
692                 }
693             }
694             "#,
695         );
696     }
697
698     #[test]
699     fn fill_match_arms_qualifies_path() {
700         check_assist(
701             fill_match_arms,
702             r#"
703             mod foo { pub enum E { X, Y } }
704             use foo::E::X;
705
706             fn main() {
707                 match X {
708                     $0
709                 }
710             }
711             "#,
712             r#"
713             mod foo { pub enum E { X, Y } }
714             use foo::E::X;
715
716             fn main() {
717                 match X {
718                     $0X => {}
719                     foo::E::Y => {}
720                 }
721             }
722             "#,
723         );
724     }
725
726     #[test]
727     fn fill_match_arms_preserves_comments() {
728         check_assist(
729             fill_match_arms,
730             r#"
731             enum A { One, Two }
732             fn foo(a: A) {
733                 match a {
734                     // foo bar baz$0
735                     A::One => {}
736                     // This is where the rest should be
737                 }
738             }
739             "#,
740             r#"
741             enum A { One, Two }
742             fn foo(a: A) {
743                 match a {
744                     // foo bar baz
745                     A::One => {}
746                     // This is where the rest should be
747                     $0A::Two => {}
748                 }
749             }
750             "#,
751         );
752     }
753
754     #[test]
755     fn fill_match_arms_preserves_comments_empty() {
756         check_assist(
757             fill_match_arms,
758             r#"
759             enum A { One, Two }
760             fn foo(a: A) {
761                 match a {
762                     // foo bar baz$0
763                 }
764             }
765             "#,
766             r#"
767             enum A { One, Two }
768             fn foo(a: A) {
769                 match a {
770                     // foo bar baz
771                     $0A::One => {}
772                     A::Two => {}
773                 }
774             }
775             "#,
776         );
777     }
778
779     #[test]
780     fn fill_match_arms_placeholder() {
781         check_assist(
782             fill_match_arms,
783             r#"
784             enum A { One, Two, }
785             fn foo(a: A) {
786                 match a$0 {
787                     _ => (),
788                 }
789             }
790             "#,
791             r#"
792             enum A { One, Two, }
793             fn foo(a: A) {
794                 match a {
795                     $0A::One => {}
796                     A::Two => {}
797                 }
798             }
799             "#,
800         );
801     }
802
803     #[test]
804     fn option_order() {
805         cov_mark::check!(option_order);
806         let before = r#"
807 fn foo(opt: Option<i32>) {
808     match opt$0 {
809     }
810 }
811 "#;
812         let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
813
814         check_assist(
815             fill_match_arms,
816             before,
817             r#"
818 fn foo(opt: Option<i32>) {
819     match opt {
820         Some(${0:_}) => {}
821         None => {}
822     }
823 }
824 "#,
825         );
826     }
827
828     #[test]
829     fn works_inside_macro_call() {
830         check_assist(
831             fill_match_arms,
832             r#"
833 macro_rules! m { ($expr:expr) => {$expr}}
834 enum Test {
835     A,
836     B,
837     C,
838 }
839
840 fn foo(t: Test) {
841     m!(match t$0 {});
842 }"#,
843             r#"macro_rules! m { ($expr:expr) => {$expr}}
844 enum Test {
845     A,
846     B,
847     C,
848 }
849
850 fn foo(t: Test) {
851     m!(match t {
852     $0Test::A => {}
853     Test::B => {}
854     Test::C => {}
855 });
856 }"#,
857         );
858     }
859 }