From 1d612a6ec48390c38d577cd35369e0891fe6cb6e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 16 Oct 2020 15:56:26 +0200 Subject: De-duplicate `add_group` callsite in qualify_path --- crates/assists/src/handlers/qualify_path.rs | 229 +++++++++++++--------------- 1 file changed, 108 insertions(+), 121 deletions(-) diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs index 479ff498c..f436bdbbf 100644 --- a/crates/assists/src/handlers/qualify_path.rs +++ b/crates/assists/src/handlers/qualify_path.rs @@ -5,7 +5,7 @@ use ide_db::RootDatabase; use syntax::{ ast, ast::{make, ArgListOwner}, - AstNode, TextRange, + AstNode, }; use test_utils::mark; @@ -16,8 +16,6 @@ use crate::{ AssistId, AssistKind, GroupLabel, }; -const ASSIST_ID: AssistId = AssistId("qualify_path", AssistKind::QuickFix); - // Assist: qualify_path // // If the name is unresolved, provides all possible qualified paths for it. @@ -51,162 +49,151 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> return None; } + let candidate = import_assets.import_candidate(); let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; - match import_assets.import_candidate() { - ImportCandidate::QualifierStart(candidate) => { + + let qualify_candidate = match candidate { + ImportCandidate::QualifierStart(_) => { + mark::hit!(qualify_path_qualifier_start); let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; let segment = path.segment()?; - qualify_path_qualifier_start(acc, proposed_imports, range, segment, &candidate.name) + QualifyCandidate::QualifierStart(segment) } - ImportCandidate::UnqualifiedName(candidate) => { - qualify_path_unqualified_name(acc, proposed_imports, range, &candidate.name) + ImportCandidate::UnqualifiedName(_) => { + mark::hit!(qualify_path_unqualified_name); + QualifyCandidate::UnqualifiedName } ImportCandidate::TraitAssocItem(_) => { + mark::hit!(qualify_path_trait_assoc_item); let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; let (qualifier, segment) = (path.qualifier()?, path.segment()?); - qualify_path_trait_assoc_item(acc, proposed_imports, range, qualifier, segment) + QualifyCandidate::TraitAssocItem(qualifier, segment) } ImportCandidate::TraitMethod(_) => { + mark::hit!(qualify_path_trait_method); let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; - qualify_path_trait_method(acc, ctx.sema.db, proposed_imports, range, mcall_expr)?; + QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr) } }; - Some(()) -} -// a test that covers this -> `associated_struct_const` -fn qualify_path_qualifier_start( - acc: &mut Assists, - proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, - range: TextRange, - segment: ast::PathSegment, - qualifier_start: &ast::NameRef, -) { - mark::hit!(qualify_path_qualifier_start); - let group_label = GroupLabel(format!("Qualify {}", qualifier_start)); - for (import, _) in proposed_imports { + let group_label = group_label(candidate); + for (import, item) in proposed_imports { acc.add_group( &group_label, - ASSIST_ID, - format!("Qualify with `{}`", &import), + AssistId("qualify_path", AssistKind::QuickFix), + label(candidate, &import), range, |builder| { - let import = mod_path_to_ast(&import); - builder.replace(range, format!("{}::{}", import, segment)); + qualify_candidate.qualify( + |replace_with: String| builder.replace(range, replace_with), + import, + item, + ) }, ); } + Some(()) } -// a test that covers this -> `applicable_when_found_an_import_partial` -fn qualify_path_unqualified_name( - acc: &mut Assists, - proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, - range: TextRange, - name: &ast::NameRef, -) { - mark::hit!(qualify_path_unqualified_name); - let group_label = GroupLabel(format!("Qualify {}", name)); - for (import, _) in proposed_imports { - acc.add_group( - &group_label, - ASSIST_ID, - format!("Qualify as `{}`", &import), - range, - |builder| builder.replace(range, mod_path_to_ast(&import).to_string()), - ); - } +enum QualifyCandidate<'db> { + QualifierStart(ast::PathSegment), + UnqualifiedName, + TraitAssocItem(ast::Path, ast::PathSegment), + TraitMethod(&'db RootDatabase, ast::MethodCallExpr), } -// a test that covers this -> `associated_trait_const` -fn qualify_path_trait_assoc_item( - acc: &mut Assists, - proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, - range: TextRange, - qualifier: ast::Path, - segment: ast::PathSegment, -) { - mark::hit!(qualify_path_trait_assoc_item); - let group_label = GroupLabel(format!("Qualify {}", &segment)); - for (import, _) in proposed_imports { - acc.add_group( - &group_label, - ASSIST_ID, - format!("Qualify with cast as `{}`", &import), - range, - |builder| { +impl QualifyCandidate<'_> { + fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { + match self { + QualifyCandidate::QualifierStart(segment) => { let import = mod_path_to_ast(&import); - builder.replace(range, format!("<{} as {}>::{}", qualifier, import, segment)); - }, - ); + replacer(format!("{}::{}", import, segment)); + } + QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()), + QualifyCandidate::TraitAssocItem(qualifier, segment) => { + let import = mod_path_to_ast(&import); + replacer(format!("<{} as {}>::{}", qualifier, import, segment)); + } + &QualifyCandidate::TraitMethod(db, ref mcall_expr) => { + Self::qualify_trait_method(db, mcall_expr, replacer, import, item); + } + } + } + + fn qualify_trait_method( + db: &RootDatabase, + mcall_expr: &ast::MethodCallExpr, + mut replacer: impl FnMut(String), + import: hir::ModPath, + item: hir::ItemInNs, + ) -> Option<()> { + let receiver = mcall_expr.receiver()?; + let trait_method_name = mcall_expr.name_ref()?; + let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); + let trait_ = item_as_trait(item)?; + let method = find_trait_method(db, trait_, &trait_method_name)?; + if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { + let import = mod_path_to_ast(&import); + let receiver = match self_access { + hir::Access::Shared => make::expr_ref(receiver, false), + hir::Access::Exclusive => make::expr_ref(receiver, true), + hir::Access::Owned => receiver, + }; + replacer(format!( + "{}::{}{}", + import, + trait_method_name, + match arg_list.clone() { + Some(args) => make::arg_list(iter::once(receiver).chain(args)), + None => make::arg_list(iter::once(receiver)), + } + )); + } + Some(()) } } -// a test that covers this -> `trait_method` -fn qualify_path_trait_method( - acc: &mut Assists, +fn find_trait_method( db: &RootDatabase, - proposed_imports: Vec<(hir::ModPath, hir::ItemInNs)>, - range: TextRange, - mcall_expr: ast::MethodCallExpr, -) -> Option<()> { - mark::hit!(qualify_path_trait_method); - - let receiver = mcall_expr.receiver()?; - let trait_method_name = mcall_expr.name_ref()?; - let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); - let group_label = GroupLabel(format!("Qualify {}", trait_method_name)); - let find_method = |item: &hir::AssocItem| { - item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false) - }; - for (import, trait_) in proposed_imports.into_iter().filter_map(filter_trait) { - acc.add_group( - &group_label, - ASSIST_ID, - format!("Qualify `{}`", &import), - range, - |builder| { - let import = mod_path_to_ast(&import); - if let Some(hir::AssocItem::Function(method)) = - trait_.items(db).into_iter().find(find_method) - { - if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { - let receiver = receiver.clone(); - let receiver = match self_access { - hir::Access::Shared => make::expr_ref(receiver, false), - hir::Access::Exclusive => make::expr_ref(receiver, true), - hir::Access::Owned => receiver, - }; - builder.replace( - range, - format!( - "{}::{}{}", - import, - trait_method_name, - match arg_list.clone() { - Some(args) => make::arg_list(iter::once(receiver).chain(args)), - None => make::arg_list(iter::once(receiver)), - } - ), - ); - } - } - }, - ); + trait_: hir::Trait, + trait_method_name: &ast::NameRef, +) -> Option { + if let Some(hir::AssocItem::Function(method)) = + trait_.items(db).into_iter().find(|item: &hir::AssocItem| { + item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false) + }) + { + Some(method) + } else { + None } - Some(()) } -fn filter_trait( - (import, trait_): (hir::ModPath, hir::ItemInNs), -) -> Option<(hir::ModPath, hir::Trait)> { - if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(trait_.as_module_def_id()?) { - Some((import, trait_)) +fn item_as_trait(item: hir::ItemInNs) -> Option { + if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) { + Some(trait_) } else { None } } +fn group_label(candidate: &ImportCandidate) -> GroupLabel { + let name = match candidate { + ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, + ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, + }; + GroupLabel(format!("Qualify {}", name)) +} + +fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { + match candidate { + ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), + ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), + ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), + ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; -- cgit v1.2.3