From 09147c3303f0ffe607c0decb2979980f9a296a5c Mon Sep 17 00:00:00 2001 From: Comonad Date: Wed, 21 Apr 2021 19:33:45 +0800 Subject: Add support for fill match arms of boolean values - Add support for boolean inside tuple --- crates/ide_assists/src/handlers/fill_match_arms.rs | 222 ++++++++++++++++++--- crates/syntax/src/ast/make.rs | 8 + 2 files changed, 207 insertions(+), 23 deletions(-) diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index a30c4d04e..be927cc1c 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs @@ -53,7 +53,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< .iter() .filter_map(ast::MatchArm::pat) .flat_map(|pat| match pat { - // Special casee OrPat as separate top-level pats + // Special case OrPat as separate top-level pats Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), _ => Either::Right(iter::once(pat)), }) @@ -72,7 +72,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) .collect::>(); - if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { + if Some(enum_def) + == FamousDefs(&ctx.sema, Some(module.krate())) + .core_option_Option() + .map(|x| lift_enum(x)) + { // Match `Some` variant first. cov_mark::hit!(option_order); variants.reverse() @@ -151,49 +155,99 @@ fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { } } -fn resolve_enum_def(sema: &Semantics, expr: &ast::Expr) -> Option { +#[derive(Eq, PartialEq, Clone)] +enum ExtendedEnum { + Bool, + Enum(hir::Enum), +} + +#[derive(Eq, PartialEq, Clone)] +enum ExtendedVariant { + True, + False, + Variant(hir::Variant), +} + +fn lift_enum(e: hir::Enum) -> ExtendedEnum { + ExtendedEnum::Enum(e) +} + +impl ExtendedEnum { + fn variants(&self, db: &RootDatabase) -> Vec { + match self { + ExtendedEnum::Enum(e) => { + e.variants(db).into_iter().map(|x| ExtendedVariant::Variant(x)).collect::>() + } + ExtendedEnum::Bool => { + Vec::::from([ExtendedVariant::True, ExtendedVariant::False]) + } + } + } +} + +fn resolve_enum_def(sema: &Semantics, expr: &ast::Expr) -> Option { sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { - Some(Adt::Enum(e)) => Some(e), - _ => None, + Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), + _ => { + if ty.is_bool() { + Some(ExtendedEnum::Bool) + } else { + None + } + } }) } fn resolve_tuple_of_enum_def( sema: &Semantics, expr: &ast::Expr, -) -> Option> { +) -> Option> { sema.type_of_expr(&expr)? .tuple_fields(sema.db) .iter() .map(|ty| { ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { - Some(Adt::Enum(e)) => Some(e), + Some(Adt::Enum(e)) => Some(lift_enum(e)), // For now we only handle expansion for a tuple of enums. Here // we map non-enum items to None and rely on `collect` to // convert Vec> into Option>. - _ => None, + _ => { + if ty.is_bool() { + Some(ExtendedEnum::Bool) + } else { + None + } + } }) }) .collect() } -fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option { - let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); +fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option { + match var { + ExtendedVariant::Variant(var) => { + let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); + + // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though + let pat: ast::Pat = match var.source(db)?.value.kind() { + ast::StructKind::Tuple(field_list) => { + let pats = + iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); + make::tuple_struct_pat(path, pats).into() + } + ast::StructKind::Record(field_list) => { + let pats = + field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); + make::record_pat(path, pats).into() + } + ast::StructKind::Unit => make::path_pat(path), + }; - // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though - let pat: ast::Pat = match var.source(db)?.value.kind() { - ast::StructKind::Tuple(field_list) => { - let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); - make::tuple_struct_pat(path, pats).into() - } - ast::StructKind::Record(field_list) => { - let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); - make::record_pat(path, pats).into() + Some(pat) } - ast::StructKind::Unit => make::path_pat(path), - }; - - Some(pat) + ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), + ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))), + } } #[cfg(test)] @@ -225,6 +279,21 @@ mod tests { ); } + #[test] + fn all_boolean_match_arms_provided() { + check_assist_not_applicable( + fill_match_arms, + r#" + fn foo(a: bool) { + match a$0 { + true => {} + false => {} + } + } + "#, + ) + } + #[test] fn tuple_of_non_enum() { // for now this case is not handled, although it potentially could be @@ -240,6 +309,113 @@ mod tests { ); } + #[test] + fn fill_match_arms_boolean() { + check_assist( + fill_match_arms, + r#" + fn foo(a: bool) { + match a$0 { + } + } + "#, + r#" + fn foo(a: bool) { + match a { + $0true => {} + false => {} + } + } + "#, + ) + } + + #[test] + fn partial_fill_boolean() { + check_assist( + fill_match_arms, + r#" + fn foo(a: bool) { + match a$0 { + true => {} + } + } + "#, + r#" + fn foo(a: bool) { + match a { + true => {} + $0false => {} + } + } + "#, + ) + } + + #[test] + fn all_boolean_tuple_arms_provided() { + check_assist_not_applicable( + fill_match_arms, + r#" + fn foo(a: bool) { + match (a, a)$0 { + (true, true) => {} + (true, false) => {} + (false, true) => {} + (false, false) => {} + } + } + "#, + ) + } + + #[test] + fn fill_boolean_tuple() { + check_assist( + fill_match_arms, + r#" + fn foo(a: bool) { + match (a, a)$0 { + } + } + "#, + r#" + fn foo(a: bool) { + match (a, a) { + $0(true, true) => {} + (true, false) => {} + (false, true) => {} + (false, false) => {} + } + } + "#, + ) + } + + #[test] + fn partial_fill_boolean_tuple() { + check_assist( + fill_match_arms, + r#" + fn foo(a: bool) { + match (a, a)$0 { + (false, true) => {} + } + } + "#, + r#" + fn foo(a: bool) { + match (a, a) { + (false, true) => {} + $0(true, true) => {} + (true, false) => {} + (false, false) => {} + } + } + "#, + ) + } + #[test] fn partial_fill_record_tuple() { check_assist( diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 94d4f2cf0..4cf6f871e 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -294,6 +294,14 @@ pub fn wildcard_pat() -> ast::WildcardPat { } } +pub fn literal_pat(lit: &str) -> ast::LiteralPat { + return from_text(lit); + + fn from_text(text: &str) -> ast::LiteralPat { + ast_from_text(&format!("fn f() {{ match x {{ {} => {{}} }} }}", text)) + } +} + /// Creates a tuple of patterns from an iterator of patterns. /// /// Invariant: `pats` must be length > 0 -- cgit v1.2.3