Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/auto_import.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/auto_import.rs | 209 |
1 files changed, 182 insertions, 27 deletions
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index a92a000c3f..d310e11011 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -1,16 +1,18 @@ use std::cmp::Reverse; -use hir::{db::HirDatabase, Module}; +use either::Either; +use hir::{Module, Type, db::HirDatabase}; use ide_db::{ + active_parameter::ActiveParameter, helpers::mod_path_to_ast, imports::{ import_assets::{ImportAssets, ImportCandidate, LocatedImport}, - insert_use::{insert_use, insert_use_as_alias, ImportScope}, + insert_use::{ImportScope, insert_use, insert_use_as_alias}, }, }; -use syntax::{ast, AstNode, Edition, NodeOrToken, SyntaxElement}; +use syntax::{AstNode, Edition, SyntaxNode, ast, match_ast}; -use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; +use crate::{AssistContext, AssistId, Assists, GroupLabel}; // Feature: Auto Import // @@ -92,7 +94,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let cfg = ctx.config.import_path_config(); - let (import_assets, syntax_under_caret) = find_importable_node(ctx)?; + let (import_assets, syntax_under_caret, expected) = find_importable_node(ctx)?; let mut proposed_imports: Vec<_> = import_assets .search_for_imports(&ctx.sema, cfg, ctx.config.insert_use.prefix_kind) .collect(); @@ -100,17 +102,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< return None; } - let range = match &syntax_under_caret { - NodeOrToken::Node(node) => ctx.sema.original_range(node).range, - NodeOrToken::Token(token) => token.text_range(), - }; - let scope = ImportScope::find_insert_use_container( - &match syntax_under_caret { - NodeOrToken::Node(it) => it, - NodeOrToken::Token(it) => it.parent()?, - }, - &ctx.sema, - )?; + let range = ctx.sema.original_range(&syntax_under_caret).range; + let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; // we aren't interested in different namespaces proposed_imports.sort_by(|a, b| a.import_path.cmp(&b.import_path)); @@ -118,8 +111,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let current_module = ctx.sema.scope(scope.as_syntax_node()).map(|scope| scope.module()); // prioritize more relevant imports - proposed_imports - .sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref()))); + proposed_imports.sort_by_key(|import| { + Reverse(relevance_score(ctx, import, expected.as_ref(), current_module.as_ref())) + }); let edition = current_module.map(|it| it.krate().edition(ctx.db())).unwrap_or(Edition::CURRENT); let group_label = group_label(import_assets.import_candidate()); @@ -127,7 +121,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let import_path = import.import_path; let (assist_id, import_name) = - (AssistId("auto_import", AssistKind::QuickFix), import_path.display(ctx.db(), edition)); + (AssistId::quick_fix("auto_import"), import_path.display(ctx.db(), edition)); acc.add_group( &group_label, assist_id, @@ -180,22 +174,61 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< pub(super) fn find_importable_node( ctx: &AssistContext<'_>, -) -> Option<(ImportAssets, SyntaxElement)> { +) -> Option<(ImportAssets, SyntaxNode, Option<Type>)> { + // Deduplicate this with the `expected_type_and_name` logic for completions + let expected = |expr_or_pat: Either<ast::Expr, ast::Pat>| match expr_or_pat { + Either::Left(expr) => { + let parent = expr.syntax().parent()?; + // FIXME: Expand this + match_ast! { + match parent { + ast::ArgList(list) => { + ActiveParameter::at_arg( + &ctx.sema, + list, + expr.syntax().text_range().start(), + ).map(|ap| ap.ty) + }, + ast::LetStmt(stmt) => { + ctx.sema.type_of_pat(&stmt.pat()?).map(|t| t.original) + }, + _ => None, + } + } + } + Either::Right(pat) => { + let parent = pat.syntax().parent()?; + // FIXME: Expand this + match_ast! { + match parent { + ast::LetStmt(stmt) => { + ctx.sema.type_of_expr(&stmt.initializer()?).map(|t| t.original) + }, + _ => None, + } + } + } + }; + if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { + let expected = + path_under_caret.top_path().syntax().parent().and_then(Either::cast).and_then(expected); ImportAssets::for_exact_path(&path_under_caret, &ctx.sema) - .zip(Some(path_under_caret.syntax().clone().into())) + .map(|it| (it, path_under_caret.syntax().clone(), expected)) } else if let Some(method_under_caret) = ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>() { + let expected = expected(Either::Left(method_under_caret.clone().into())); ImportAssets::for_method_call(&method_under_caret, &ctx.sema) - .zip(Some(method_under_caret.syntax().clone().into())) + .map(|it| (it, method_under_caret.syntax().clone(), expected)) } else if ctx.find_node_at_offset_with_descend::<ast::Param>().is_some() { None } else if let Some(pat) = ctx .find_node_at_offset_with_descend::<ast::IdentPat>() .filter(ast::IdentPat::is_simple_ident) { - ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone().into())) + let expected = expected(Either::Right(pat.clone().into())); + ImportAssets::for_ident_pat(&ctx.sema, &pat).map(|it| (it, pat.syntax().clone(), expected)) } else { None } @@ -219,6 +252,7 @@ fn group_label(import_candidate: &ImportCandidate) -> GroupLabel { pub(crate) fn relevance_score( ctx: &AssistContext<'_>, import: &LocatedImport, + expected: Option<&Type>, current_module: Option<&Module>, ) -> i32 { let mut score = 0; @@ -230,6 +264,35 @@ pub(crate) fn relevance_score( hir::ItemInNs::Macros(makro) => Some(makro.module(db)), }; + if let Some(expected) = expected { + let ty = match import.item_to_import { + hir::ItemInNs::Types(module_def) | hir::ItemInNs::Values(module_def) => { + match module_def { + hir::ModuleDef::Function(function) => Some(function.ret_type(ctx.db())), + hir::ModuleDef::Adt(adt) => Some(match adt { + hir::Adt::Struct(it) => it.ty(ctx.db()), + hir::Adt::Union(it) => it.ty(ctx.db()), + hir::Adt::Enum(it) => it.ty(ctx.db()), + }), + hir::ModuleDef::Variant(variant) => Some(variant.constructor_ty(ctx.db())), + hir::ModuleDef::Const(it) => Some(it.ty(ctx.db())), + hir::ModuleDef::Static(it) => Some(it.ty(ctx.db())), + hir::ModuleDef::TypeAlias(it) => Some(it.ty(ctx.db())), + hir::ModuleDef::BuiltinType(it) => Some(it.ty(ctx.db())), + _ => None, + } + } + hir::ItemInNs::Macros(_) => None, + }; + if let Some(ty) = ty { + if ty == *expected { + score = 100000; + } else if ty.could_unify_with(ctx.db(), expected) { + score = 10000; + } + } + } + match item_module.zip(current_module) { // get the distance between the imported path and the current module // (prefer items that are more local) @@ -279,12 +342,12 @@ mod tests { use super::*; use hir::{FileRange, Semantics}; - use ide_db::{assists::AssistResolveStrategy, RootDatabase}; + use ide_db::{RootDatabase, assists::AssistResolveStrategy}; use test_fixture::WithFixture; use crate::tests::{ - check_assist, check_assist_by_label, check_assist_not_applicable, check_assist_target, - TEST_CONFIG, + TEST_CONFIG, check_assist, check_assist_by_label, check_assist_not_applicable, + check_assist_target, }; fn check_auto_import_order(before: &str, order: &[&str]) { @@ -554,7 +617,7 @@ mod baz { } ", r" - use PubMod3::PubStruct; + use PubMod1::PubStruct; PubStruct @@ -1722,4 +1785,96 @@ mod foo { ", ); } + + #[test] + fn prefers_type_match() { + check_assist( + auto_import, + r" +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: sync::atomic::Ordering) {} +fn main() { + takes_ordering(Ordering$0); +} +", + r" +use sync::atomic::Ordering; + +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: sync::atomic::Ordering) {} +fn main() { + takes_ordering(Ordering); +} +", + ); + check_assist( + auto_import, + r" +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: cmp::Ordering) {} +fn main() { + takes_ordering(Ordering$0); +} +", + r" +use cmp::Ordering; + +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: cmp::Ordering) {} +fn main() { + takes_ordering(Ordering); +} +", + ); + } + + #[test] + fn prefers_type_match2() { + check_assist( + auto_import, + r" +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: sync::atomic::Ordering) {} +fn main() { + takes_ordering(Ordering$0::V); +} +", + r" +use sync::atomic::Ordering; + +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: sync::atomic::Ordering) {} +fn main() { + takes_ordering(Ordering::V); +} +", + ); + check_assist( + auto_import, + r" +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: cmp::Ordering) {} +fn main() { + takes_ordering(Ordering$0::V); +} +", + r" +use cmp::Ordering; + +mod sync { pub mod atomic { pub enum Ordering { V } } } +mod cmp { pub enum Ordering { V } } +fn takes_ordering(_: cmp::Ordering) {} +fn main() { + takes_ordering(Ordering::V); +} +", + ); + } } |