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