diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 3bc2eee1e7..dfc91c7343 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3105,10 +3105,10 @@ impl From for ItemInNs { } impl ItemInNs { - pub fn as_module_def(self) -> Option { + pub fn into_module_def(self) -> ModuleDef { match self { - ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id), - ItemInNs::Macros(_) => None, + ItemInNs::Types(id) | ItemInNs::Values(id) => id, + ItemInNs::Macros(id) => ModuleDef::Macro(id), } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index f030c1862a..c576d07a0e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -39,8 +39,8 @@ use stdx::TupleExt; use syntax::{ algo::skip_trivia_token, ast::{self, HasAttrs as _, HasGenericParams}, - AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, - TextSize, + AstNode, AstToken, Direction, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, + TextRange, TextSize, }; use triomphe::Arc; @@ -1540,6 +1540,21 @@ impl<'db> SemanticsImpl<'db> { Some(items.iter_items().map(|(item, _)| item.into())) } + pub fn resolve_mod_path_relative( + &self, + to: Module, + segments: impl IntoIterator, + ) -> Option> { + let items = to.id.resolver(self.db.upcast()).resolve_module_path_in_items( + self.db.upcast(), + &ModPath::from_segments( + hir_def::path::PathKind::Plain, + segments.into_iter().map(|it| Name::new(&it, SyntaxContextId::ROOT)), + ), + ); + Some(items.iter_items().map(|(item, _)| item.into())) + } + fn resolve_variant(&self, record_lit: ast::RecordExpr) -> Option { self.analyze(record_lit.syntax())?.resolve_variant(self.db, record_lit) } diff --git a/crates/ide-assists/src/handlers/qualify_method_call.rs b/crates/ide-assists/src/handlers/qualify_method_call.rs index 14518c4d2c..c3600af5a6 100644 --- a/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -86,7 +86,7 @@ fn item_for_path_search(db: &dyn HirDatabase, item: ItemInNs) -> Option Option { - item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)) + item.into_module_def().as_assoc_item(db) } #[cfg(test)] diff --git a/crates/ide-assists/src/handlers/qualify_path.rs b/crates/ide-assists/src/handlers/qualify_path.rs index ac88861fe4..849b8a42c6 100644 --- a/crates/ide-assists/src/handlers/qualify_path.rs +++ b/crates/ide-assists/src/handlers/qualify_path.rs @@ -51,7 +51,7 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let candidate = import_assets.import_candidate(); let qualify_candidate = match syntax_under_caret.clone() { NodeOrToken::Node(syntax_under_caret) => match candidate { - ImportCandidate::Path(candidate) if candidate.qualifier.is_some() => { + ImportCandidate::Path(candidate) if !candidate.qualifier.is_empty() => { cov_mark::hit!(qualify_path_qualifier_start); let path = ast::Path::cast(syntax_under_caret)?; let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); @@ -219,11 +219,9 @@ fn find_trait_method( } fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option { - let item_module_def = item.as_module_def()?; - - match item_module_def { + match item.into_module_def() { hir::ModuleDef::Trait(trait_) => Some(trait_), - _ => item_module_def.as_assoc_item(db)?.container_trait(db), + item_module_def => item_module_def.as_assoc_item(db)?.container_trait(db), } } @@ -247,7 +245,7 @@ fn label( let import_path = &import.import_path; match candidate { - ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => { + ImportCandidate::Path(candidate) if candidate.qualifier.is_empty() => { format!("Qualify as `{}`", import_path.display(db, edition)) } _ => format!("Qualify with `{}`", import_path.display(db, edition)), diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 2dec876215..31e828eae2 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -78,7 +78,7 @@ pub(crate) fn replace_derive_with_manual_impl( NameToImport::exact_case_sensitive(path.segments().last()?.to_string()), items_locator::AssocSearchMode::Exclude, ) - .filter_map(|item| match item.as_module_def()? { + .filter_map(|item| match item.into_module_def() { ModuleDef::Trait(trait_) => Some(trait_), _ => None, }) diff --git a/crates/ide-assists/src/handlers/wrap_return_type.rs b/crates/ide-assists/src/handlers/wrap_return_type.rs index 2d918a5b1c..658600cd2d 100644 --- a/crates/ide-assists/src/handlers/wrap_return_type.rs +++ b/crates/ide-assists/src/handlers/wrap_return_type.rs @@ -198,7 +198,7 @@ fn wrapper_alias( ); ctx.sema.resolve_mod_path(ret_type.syntax(), &wrapper_path).and_then(|def| { - def.filter_map(|def| match def.as_module_def()? { + def.filter_map(|def| match def.into_module_def() { hir::ModuleDef::TypeAlias(alias) => { let enum_ty = alias.ty(ctx.db()).as_adt()?.as_enum()?; (&enum_ty == core_wrapper).then_some(alias) diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index 4b949e0d65..f31b0d4910 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs @@ -1720,3 +1720,45 @@ fn function() { "#]], ); } + +#[test] +fn intrinsics() { + check( + r#" + //- /core.rs crate:core + pub mod intrinsics { + extern "rust-intrinsic" { + pub fn transmute(src: Src) -> Dst; + } + } + pub mod mem { + pub use crate::intrinsics::transmute; + } + //- /main.rs crate:main deps:core + fn function() { + transmute$0 + } + "#, + expect![[r#" + fn transmute(…) (use core::mem::transmute) unsafe fn(Src) -> Dst + "#]], + ); + check( + r#" +//- /core.rs crate:core +pub mod intrinsics { + extern "rust-intrinsic" { + pub fn transmute(src: Src) -> Dst; + } +} +pub mod mem { + pub use crate::intrinsics::transmute; +} +//- /main.rs crate:main deps:core +fn function() { + mem::transmute$0 +} +"#, + expect![""], + ); +} diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs index 82a182806a..dab36bf20b 100644 --- a/crates/ide-db/src/imports/import_assets.rs +++ b/crates/ide-db/src/imports/import_assets.rs @@ -2,10 +2,10 @@ use hir::{ db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, Crate, HasCrate, ImportPathConfig, - ItemInNs, ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics, + ItemInNs, ModPath, Module, ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Trait, TyFingerprint, Type, }; -use itertools::{EitherOrBoth, Itertools}; +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{ ast::{self, make, HasName}, @@ -13,7 +13,6 @@ use syntax::{ }; use crate::{ - helpers::item_name, items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT}, FxIndexSet, RootDatabase, }; @@ -52,7 +51,7 @@ pub struct TraitImportCandidate { #[derive(Debug)] pub struct PathImportCandidate { /// Optional qualifier before name. - pub qualifier: Option>, + pub qualifier: Vec, /// The name the item (struct, trait, enum, etc.) should have. pub name: NameToImport, } @@ -264,7 +263,6 @@ impl ImportAssets { Some(it) => it, None => return >::default().into_iter(), }; - let krate = self.module_with_candidate.krate(); let scope_definitions = self.scope_definitions(sema); let mod_path = |item| { @@ -279,11 +277,14 @@ impl ImportAssets { }; match &self.import_candidate { - ImportCandidate::Path(path_candidate) => { - path_applicable_imports(sema, krate, path_candidate, mod_path, |item_to_import| { - !scope_definitions.contains(&ScopeDef::from(item_to_import)) - }) - } + ImportCandidate::Path(path_candidate) => path_applicable_imports( + sema, + &scope, + krate, + path_candidate, + mod_path, + |item_to_import| !scope_definitions.contains(&ScopeDef::from(item_to_import)), + ), ImportCandidate::TraitAssocItem(trait_candidate) | ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items( sema, @@ -315,6 +316,7 @@ impl ImportAssets { fn path_applicable_imports( sema: &Semantics<'_, RootDatabase>, + scope: &SemanticsScope<'_>, current_crate: Crate, path_candidate: &PathImportCandidate, mod_path: impl Fn(ItemInNs) -> Option + Copy, @@ -322,8 +324,8 @@ fn path_applicable_imports( ) -> FxIndexSet { let _p = tracing::info_span!("ImportAssets::path_applicable_imports").entered(); - match &path_candidate.qualifier { - None => { + match &*path_candidate.qualifier { + [] => { items_locator::items_with_name( sema, current_crate, @@ -348,89 +350,107 @@ fn path_applicable_imports( .take(DEFAULT_QUERY_SEARCH_LIMIT.inner()) .collect() } - Some(qualifier) => items_locator::items_with_name( + [first_qsegment, qualifier_rest @ ..] => items_locator::items_with_name( sema, current_crate, - path_candidate.name.clone(), - AssocSearchMode::Include, + NameToImport::Exact(first_qsegment.to_string(), true), + AssocSearchMode::Exclude, ) - .filter_map(|item| import_for_item(sema.db, mod_path, qualifier, item, scope_filter)) + .filter_map(|item| { + import_for_item( + sema, + scope, + mod_path, + &path_candidate.name, + item, + qualifier_rest, + scope_filter, + ) + }) .take(DEFAULT_QUERY_SEARCH_LIMIT.inner()) .collect(), } } fn import_for_item( - db: &RootDatabase, + sema: &Semantics<'_, RootDatabase>, + scope: &SemanticsScope<'_>, mod_path: impl Fn(ItemInNs) -> Option, + candidate: &NameToImport, + resolved_qualifier: ItemInNs, unresolved_qualifier: &[SmolStr], - original_item: ItemInNs, scope_filter: impl Fn(ItemInNs) -> bool, ) -> Option { let _p = tracing::info_span!("ImportAssets::import_for_item").entered(); - let [first_segment, ..] = unresolved_qualifier else { return None }; - let item_as_assoc = item_as_assoc(db, original_item); - - let (original_item_candidate, trait_item_to_import) = match item_as_assoc { - Some(assoc_item) => match assoc_item.container(db) { - AssocItemContainer::Trait(trait_) => { - let trait_ = ItemInNs::from(ModuleDef::from(trait_)); - (trait_, Some(trait_)) + let qualifier = { + let mut adjusted_resolved_qualifier = resolved_qualifier; + if !unresolved_qualifier.is_empty() { + match resolved_qualifier { + ItemInNs::Types(ModuleDef::Module(module)) => { + adjusted_resolved_qualifier = sema + .resolve_mod_path_relative(module, unresolved_qualifier.iter().cloned())? + .next()?; + } + // can't resolve multiple segments for non-module item path bases + _ => return None, } - AssocItemContainer::Impl(impl_) => { - (ItemInNs::from(ModuleDef::from(impl_.self_ty(db).as_adt()?)), None) - } - }, - None => (original_item, None), + } + + match adjusted_resolved_qualifier { + ItemInNs::Types(def) => def, + _ => return None, + } }; - let import_path_candidate = mod_path(original_item_candidate)?; - - let mut import_path_candidate_segments = import_path_candidate.segments().iter().rev(); - let predicate = |it: EitherOrBoth<&SmolStr, &Name>| match it { - // segments match, check next one - EitherOrBoth::Both(a, b) if b.as_str() == &**a => None, - // segments mismatch / qualifier is longer than the path, bail out - EitherOrBoth::Both(..) | EitherOrBoth::Left(_) => Some(false), - // all segments match and we have exhausted the qualifier, proceed - EitherOrBoth::Right(_) => Some(true), - }; - if item_as_assoc.is_none() { - let item_name = item_name(db, original_item)?; - let last_segment = import_path_candidate_segments.next()?; - if *last_segment != item_name { - return None; - } - } - let ends_with = unresolved_qualifier - .iter() - .rev() - .zip_longest(import_path_candidate_segments) - .find_map(predicate) - .unwrap_or(true); - if !ends_with { - return None; - } - - let segment_import = find_import_for_segment(db, original_item_candidate, first_segment)?; - - Some(match (segment_import == original_item_candidate, trait_item_to_import) { - (true, Some(_)) => { - // FIXME we should be able to import both the trait and the segment, - // but it's unclear what to do with overlapping edits (merge imports?) - // especially in case of lazy completion edit resolutions. - return None; - } - (false, Some(trait_to_import)) if scope_filter(trait_to_import) => { - LocatedImport::new(mod_path(trait_to_import)?, trait_to_import, original_item) - } - (true, None) if scope_filter(original_item_candidate) => { - LocatedImport::new(import_path_candidate, original_item_candidate, original_item) - } - (false, None) if scope_filter(segment_import) => { - LocatedImport::new(mod_path(segment_import)?, segment_import, original_item) + let import_path_candidate = mod_path(resolved_qualifier)?; + let ty = match qualifier { + ModuleDef::Module(module) => { + return items_locator::items_with_name_in_module( + sema, + module, + candidate.clone(), + AssocSearchMode::Exclude, + ) + .find(|&it| scope_filter(it)) + .map(|item| LocatedImport::new(import_path_candidate, resolved_qualifier, item)) } + // FIXME + ModuleDef::Trait(_) => return None, + // FIXME + ModuleDef::TraitAlias(_) => return None, + ModuleDef::TypeAlias(alias) => alias.ty(sema.db), + ModuleDef::BuiltinType(builtin) => builtin.ty(sema.db), + ModuleDef::Adt(adt) => adt.ty(sema.db), _ => return None, + }; + ty.iterate_path_candidates(sema.db, scope, &FxHashSet::default(), None, None, |assoc| { + // FIXME: Support extra trait imports + if assoc.container_or_implemented_trait(sema.db).is_some() { + return None; + } + let name = assoc.name(sema.db)?; + let is_match = match candidate { + NameToImport::Prefix(text, true) => name.as_str().starts_with(text), + NameToImport::Prefix(text, false) => { + name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| { + name_char.eq_ignore_ascii_case(&candidate_char) + }) + } + NameToImport::Exact(text, true) => name.as_str() == text, + NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text), + NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)), + NameToImport::Fuzzy(text, false) => text + .chars() + .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))), + }; + if !is_match { + return None; + } + Some(LocatedImport::new( + import_path_candidate.clone(), + resolved_qualifier, + assoc_to_item(assoc), + )) }) } @@ -453,45 +473,6 @@ fn item_for_path_search_assoc(db: &RootDatabase, assoc_item: AssocItem) -> Optio }) } -fn find_import_for_segment( - db: &RootDatabase, - original_item: ItemInNs, - unresolved_first_segment: &str, -) -> Option { - let segment_is_name = item_name(db, original_item) - .map(|name| name.eq_ident(unresolved_first_segment)) - .unwrap_or(false); - - Some(if segment_is_name { - original_item - } else { - let matching_module = - module_with_segment_name(db, unresolved_first_segment, original_item)?; - ItemInNs::from(ModuleDef::from(matching_module)) - }) -} - -fn module_with_segment_name( - db: &RootDatabase, - segment_name: &str, - candidate: ItemInNs, -) -> Option { - let mut current_module = match candidate { - ItemInNs::Types(module_def_id) => module_def_id.module(db), - ItemInNs::Values(module_def_id) => module_def_id.module(db), - ItemInNs::Macros(macro_def_id) => ModuleDef::from(macro_def_id).module(db), - }; - while let Some(module) = current_module { - if let Some(module_name) = module.name(db) { - if module_name.eq_ident(segment_name) { - return Some(module); - } - } - current_module = module.parent(db); - } - None -} - fn trait_applicable_items( sema: &Semantics<'_, RootDatabase>, current_crate: Crate, @@ -703,7 +684,7 @@ impl ImportCandidate { return None; } Some(ImportCandidate::Path(PathImportCandidate { - qualifier: None, + qualifier: vec![], name: NameToImport::exact_case_sensitive(name.to_string()), })) } @@ -730,7 +711,7 @@ fn path_import_candidate( .segments() .map(|seg| seg.name_ref().map(|name| SmolStr::new(name.text()))) .collect::>>()?; - ImportCandidate::Path(PathImportCandidate { qualifier: Some(qualifier), name }) + ImportCandidate::Path(PathImportCandidate { qualifier, name }) } else { return None; } @@ -754,10 +735,10 @@ fn path_import_candidate( } Some(_) => return None, }, - None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }), + None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name }), }) } fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option { - item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)) + item.into_module_def().as_assoc_item(db) } diff --git a/crates/ide-db/src/items_locator.rs b/crates/ide-db/src/items_locator.rs index 47549a1d00..7f66ea0c10 100644 --- a/crates/ide-db/src/items_locator.rs +++ b/crates/ide-db/src/items_locator.rs @@ -3,10 +3,14 @@ //! The main reason for this module to exist is the fact that project's items and dependencies' items //! are located in different caches, with different APIs. use either::Either; -use hir::{import_map, Crate, ItemInNs, Semantics}; +use hir::{import_map, Crate, ItemInNs, Module, Semantics}; use limit::Limit; -use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase}; +use crate::{ + imports::import_assets::NameToImport, + symbol_index::{self, SymbolsDatabase as _}, + RootDatabase, +}; /// A value to use, when uncertain which limit to pick. pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(100); @@ -20,8 +24,7 @@ pub fn items_with_name<'a>( name: NameToImport, assoc_item_search: AssocSearchMode, ) -> impl Iterator + 'a { - let krate_name = krate.display_name(sema.db).map(|name| name.to_string()); - let _p = tracing::info_span!("items_with_name", name = name.text(), assoc_item_search = ?assoc_item_search, crate = ?krate_name) + let _p = tracing::info_span!("items_with_name", name = name.text(), assoc_item_search = ?assoc_item_search, crate = ?krate.display_name(sema.db).map(|name| name.to_string())) .entered(); let prefix = matches!(name, NameToImport::Prefix(..)); @@ -66,6 +69,54 @@ pub fn items_with_name<'a>( find_items(sema, krate, local_query, external_query) } +/// Searches for importable items with the given name in the crate and its dependencies. +pub fn items_with_name_in_module<'a>( + sema: &'a Semantics<'_, RootDatabase>, + module: Module, + name: NameToImport, + assoc_item_search: AssocSearchMode, +) -> impl Iterator + 'a { + let _p = tracing::info_span!("items_with_name_in", name = name.text(), assoc_item_search = ?assoc_item_search, ?module) + .entered(); + + let prefix = matches!(name, NameToImport::Prefix(..)); + let local_query = match name { + NameToImport::Prefix(exact_name, case_sensitive) + | NameToImport::Exact(exact_name, case_sensitive) => { + let mut local_query = symbol_index::Query::new(exact_name.clone()); + local_query.assoc_search_mode(assoc_item_search); + if prefix { + local_query.prefix(); + } else { + local_query.exact(); + } + if case_sensitive { + local_query.case_sensitive(); + } + local_query + } + NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => { + let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone()); + local_query.fuzzy(); + local_query.assoc_search_mode(assoc_item_search); + + if case_sensitive { + local_query.case_sensitive(); + } + + local_query + } + }; + let mut local_results = Vec::new(); + local_query.search(&[sema.db.module_symbols(module)], |local_candidate| { + local_results.push(match local_candidate.def { + hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def), + def => ItemInNs::from(def), + }) + }); + local_results.into_iter() +} + fn find_items<'a>( sema: &'a Semantics<'_, RootDatabase>, krate: Crate,