Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/add_missing_match_arms.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/add_missing_match_arms.rs | 134 |
1 files changed, 94 insertions, 40 deletions
diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index b063e5ffce..3c33ddec31 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -1,4 +1,4 @@ -use std::iter::{self, Peekable}; +use std::iter; use either::Either; use hir::{Adt, AsAssocItem, Crate, FindPathConfig, HasAttrs, ModuleDef, Semantics}; @@ -8,7 +8,7 @@ use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast}; use itertools::Itertools; use syntax::ast::edit::IndentLevel; use syntax::ast::syntax_factory::SyntaxFactory; -use syntax::ast::{self, AstNode, MatchArmList, MatchExpr, Pat, make}; +use syntax::ast::{self, AstNode, MatchArmList, MatchExpr, Pat}; use syntax::syntax_editor::{Position, SyntaxEditor}; use syntax::{SyntaxKind, SyntaxNode, ToSmolStr}; @@ -74,7 +74,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .filter(|pat| !matches!(pat, Pat::WildcardPat(_))) .collect(); - let make = SyntaxFactory::with_mappings(); + let make = SyntaxFactory::without_mappings(); let scope = ctx.sema.scope(expr.syntax())?; let module = scope.module(); @@ -93,8 +93,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) } else { None }; - let (mut missing_pats, is_non_exhaustive, has_hidden_variants): ( - Peekable<Box<dyn Iterator<Item = (ast::Pat, bool)>>>, + let (missing_pats, is_non_exhaustive, has_hidden_variants): ( + Vec<(ast::Pat, bool)>, bool, bool, ) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { @@ -117,15 +117,15 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); let option_enum = FamousDefs(&ctx.sema, module.krate(ctx.db())).core_option_Option(); - let missing_pats: Box<dyn Iterator<Item = _>> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum) + let missing_pats: Vec<_> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum) { // Match `Some` variant first. cov_mark::hit!(option_order); - Box::new(missing_pats.rev()) + missing_pats.rev().collect() } else { - Box::new(missing_pats) + missing_pats.collect() }; - (missing_pats.peekable(), is_non_exhaustive, has_hidden_variants) + (missing_pats, is_non_exhaustive, has_hidden_variants) } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { let is_non_exhaustive = enum_defs .iter() @@ -169,12 +169,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) (ast::Pat::from(make.tuple_pat(patterns)), is_hidden) }) - .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); - ( - (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), - is_non_exhaustive, - has_hidden_variants, - ) + .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)) + .collect(); + (missing_pats, is_non_exhaustive, has_hidden_variants) } else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { @@ -205,12 +202,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) (ast::Pat::from(make.slice_pat(patterns)), is_hidden) }) - .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); - ( - (Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), - is_non_exhaustive, - has_hidden_variants, - ) + .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)) + .collect(); + (missing_pats, is_non_exhaustive, has_hidden_variants) } else { return None; }; @@ -218,20 +212,31 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let mut needs_catch_all_arm = is_non_exhaustive && !has_catch_all_arm; if !needs_catch_all_arm - && ((has_hidden_variants && has_catch_all_arm) || missing_pats.peek().is_none()) + && ((has_hidden_variants && has_catch_all_arm) || missing_pats.is_empty()) { return None; } + let visible_count = missing_pats.iter().filter(|(_, hidden)| !hidden).count(); + let label = if visible_count == 0 { + "Add missing catch-all match arm `_`".to_owned() + } else if visible_count == 1 { + let pat = &missing_pats.iter().find(|(_, hidden)| !hidden).unwrap().0; + format!("Add missing match arm `{pat}`") + } else { + format!("Add {visible_count} missing match arms") + }; + acc.add( AssistId::quick_fix("add_missing_match_arms"), - "Fill match arms", + label, ctx.sema.original_range(match_expr.syntax()).range, |builder| { // having any hidden variants means that we need a catch-all arm needs_catch_all_arm |= has_hidden_variants; let mut missing_arms = missing_pats + .into_iter() .filter(|(_, hidden)| { // filter out hidden patterns because they're handled by the catch-all arm !hidden @@ -266,12 +271,12 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) } }; - let mut editor = builder.make_editor(&old_place); + let editor = builder.make_editor(&old_place); let mut arms_edit = ArmsEdit { match_arm_list, place: old_place, last_arm: None }; - arms_edit.remove_wildcard_arms(ctx, &mut editor); - arms_edit.add_comma_after_last_arm(ctx, &make, &mut editor); - arms_edit.append_arms(&missing_arms, &make, &mut editor); + arms_edit.remove_wildcard_arms(ctx, &editor); + arms_edit.add_comma_after_last_arm(ctx, &make, &editor); + arms_edit.append_arms(&missing_arms, &make, &editor); if let Some(cap) = ctx.config.snippet_cap { if let Some(it) = missing_arms @@ -292,7 +297,6 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) } } - editor.add_mappings(make.take()); builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) @@ -353,7 +357,7 @@ struct ArmsEdit { } impl ArmsEdit { - fn remove_wildcard_arms(&mut self, ctx: &AssistContext<'_>, editor: &mut SyntaxEditor) { + fn remove_wildcard_arms(&mut self, ctx: &AssistContext<'_>, editor: &SyntaxEditor) { for arm in self.match_arm_list.arms() { if !matches!(arm.pat(), Some(Pat::WildcardPat(_))) { self.last_arm = Some(arm); @@ -382,7 +386,7 @@ impl ArmsEdit { } } - fn append_arms(&self, arms: &[ast::MatchArm], make: &SyntaxFactory, editor: &mut SyntaxEditor) { + fn append_arms(&self, arms: &[ast::MatchArm], make: &SyntaxFactory, editor: &SyntaxEditor) { let Some(mut before) = self.place.last_token() else { stdx::never!("match arm list not contain any token"); return; @@ -415,7 +419,7 @@ impl ArmsEdit { &self, ctx: &AssistContext<'_>, make: &SyntaxFactory, - editor: &mut SyntaxEditor, + editor: &SyntaxEditor, ) { if let Some(last_arm) = &self.last_arm && last_arm.comma_token().is_none() @@ -588,12 +592,12 @@ fn build_pat( ExtendedVariant::Variant { variant: var, use_self } => { let edition = module.krate(db).edition(db); let path = if use_self { - make::path_from_segments( + make.path_from_segments( [ - make::path_segment(make::name_ref_self_ty()), - make::path_segment(make::name_ref( - &var.name(db).display(db, edition).to_smolstr(), - )), + make.path_segment(make.name_ref_self_ty()), + make.path_segment( + make.name_ref(&var.name(db).display(db, edition).to_smolstr()), + ), ], false, ) @@ -607,7 +611,7 @@ fn build_pat( let pats = fields.into_iter().map(|f| { let name = name_generator.for_type(&f.ty(db).to_type(db), db, edition); match name { - Some(name) => make::ext::simple_ident_pat(make.name(&name)).into(), + Some(name) => make.ident_pat(false, false, make.name(&name)).into(), None => make.wildcard_pat().into(), } }); @@ -635,7 +639,7 @@ mod tests { use crate::AssistConfig; use crate::tests::{ TEST_CONFIG, check_assist, check_assist_not_applicable, check_assist_target, - check_assist_unresolved, check_assist_with_config, + check_assist_unresolved, check_assist_with_config, check_assist_with_label, }; use super::add_missing_match_arms; @@ -1828,8 +1832,10 @@ fn foo(t: Test) { #[test] fn lazy_computation() { - // Computing a single missing arm is enough to determine applicability of the assist. - cov_mark::check_count!(add_missing_match_arms_lazy_computation, 1); + // We now collect all missing arms eagerly, so we can show the count + // of missing arms. + cov_mark::check_count!(add_missing_match_arms_lazy_computation, 4); + check_assist_unresolved( add_missing_match_arms, r#" @@ -1842,6 +1848,54 @@ fn foo(tuple: (A, A)) { } #[test] + fn label_single_missing_arm() { + check_assist_with_label( + add_missing_match_arms, + r#" +enum A { One, Two } +fn foo(a: A) { + match $0a { + A::One => {} + } +} +"#, + "Add missing match arm `A::Two`", + ); + } + + #[test] + fn label_multiple_missing_arms() { + check_assist_with_label( + add_missing_match_arms, + r#" +enum A { One, Two, Three } +fn foo(a: A) { + match $0a {} +} +"#, + "Add 3 missing match arms", + ); + } + + #[test] + fn label_catch_all_only() { + check_assist_with_label( + add_missing_match_arms, + r#" +//- /main.rs crate:main deps:e +fn foo(t: ::e::E) { + match $0t { + e::E::A => {} + } +} +//- /e.rs crate:e +pub enum E { A, #[doc(hidden)] B, } +"#, + "Add missing catch-all match arm `_`", + ); + } + + #[test] fn adds_comma_before_new_arms() { check_assist( add_missing_match_arms, |