about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs128
1 files changed, 100 insertions, 28 deletions
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index 80bd1b7e8a4..a30c4d04ee5 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -1,5 +1,6 @@
 use std::iter;
 
+use either::Either;
 use hir::{Adt, HasSource, ModuleDef, Semantics};
 use ide_db::helpers::{mod_path_to_ast, FamousDefs};
 use ide_db::RootDatabase;
@@ -7,7 +8,7 @@ use itertools::Itertools;
 use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
 
 use crate::{
-    utils::{does_pat_match_variant, render_snippet, Cursor},
+    utils::{self, render_snippet, Cursor},
     AssistContext, AssistId, AssistKind, Assists,
 };
 
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
         }
     }
 
+    let top_lvl_pats: Vec<_> = arms
+        .iter()
+        .filter_map(ast::MatchArm::pat)
+        .flat_map(|pat| match pat {
+            // Special casee OrPat as separate top-level pats
+            Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
+            _ => Either::Right(iter::once(pat)),
+        })
+        // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
+        .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
+        .collect();
+
     let module = ctx.sema.scope(expr.syntax()).module()?;
 
     let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
@@ -56,7 +69,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
         let mut variants = variants
             .into_iter()
             .filter_map(|variant| build_pat(ctx.db(), module, variant))
-            .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
+            .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
             .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
             .collect::<Vec<_>>();
         if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
@@ -66,11 +79,6 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
         }
         variants
     } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
-        // Partial fill not currently supported for tuple of enums.
-        if !arms.is_empty() {
-            return None;
-        }
-
         // When calculating the match arms for a tuple of enums, we want
         // to create a match arm for each possible combination of enum
         // values. The `multi_cartesian_product` method transforms
@@ -85,7 +93,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
                     variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
                 ast::Pat::from(make::tuple_pat(patterns))
             })
-            .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
+            .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
             .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
             .collect()
     } else {
@@ -128,16 +136,19 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
     )
 }
 
-fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
-    existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
-        // Special casee OrPat as separate top-level pats
-        let top_level_pats: Vec<Pat> = match pat {
-            Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
-            _ => vec![pat],
-        };
+fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
+    !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
+}
 
-        !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
-    })
+// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
+fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
+    match (pat, var) {
+        (Pat::WildcardPat(_), _) => true,
+        (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
+            tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
+        }
+        _ => utils::does_pat_match_variant(pat, var),
+    }
 }
 
 fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
@@ -467,20 +478,81 @@ fn main() {
 
     #[test]
     fn fill_match_arms_tuple_of_enum_partial() {
-        check_assist_not_applicable(
+        check_assist(
             fill_match_arms,
             r#"
-            enum A { One, Two }
-            enum B { One, Two }
+enum A { One, Two }
+enum B { One, Two }
 
-            fn main() {
-                let a = A::One;
-                let b = B::One;
-                match (a$0, b) {
-                    (A::Two, B::One) => {}
-                }
-            }
-            "#,
+fn main() {
+    let a = A::One;
+    let b = B::One;
+    match (a$0, b) {
+        (A::Two, B::One) => {}
+    }
+}
+"#,
+            r#"
+enum A { One, Two }
+enum B { One, Two }
+
+fn main() {
+    let a = A::One;
+    let b = B::One;
+    match (a, b) {
+        (A::Two, B::One) => {}
+        $0(A::One, B::One) => {}
+        (A::One, B::Two) => {}
+        (A::Two, B::Two) => {}
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
+        let ra_fixture = r#"
+fn main() {
+    let a = Some(1);
+    let b = Some(());
+    match (a$0, b) {
+        (Some(_), _) => {}
+        (None, Some(_)) => {}
+    }
+}
+"#;
+        check_assist(
+            fill_match_arms,
+            &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
+            r#"
+fn main() {
+    let a = Some(1);
+    let b = Some(());
+    match (a, b) {
+        (Some(_), _) => {}
+        (None, Some(_)) => {}
+        $0(None, None) => {}
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn fill_match_arms_partial_with_deep_pattern() {
+        // Fixme: cannot handle deep patterns
+        let ra_fixture = r#"
+fn main() {
+    match $0Some(true) {
+        Some(true) => {}
+        None => {}
+    }
+}
+"#;
+        check_assist_not_applicable(
+            fill_match_arms,
+            &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
         );
     }