Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/find_path.rs2
-rw-r--r--crates/hir-def/src/import_map.rs645
-rw-r--r--crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs2
-rw-r--r--crates/ide-completion/src/lib.rs2
-rw-r--r--crates/ide-db/src/imports/import_assets.rs8
-rw-r--r--crates/ide-db/src/items_locator.rs60
6 files changed, 259 insertions, 460 deletions
diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs
index 8c49ae1c4a..df2af4c89b 100644
--- a/crates/hir-def/src/find_path.rs
+++ b/crates/hir-def/src/find_path.rs
@@ -360,7 +360,7 @@ fn calculate_best_path(
prefer_no_std,
)?;
cov_mark::hit!(partially_imported);
- path.push_segment(info.path.segments.last()?.clone());
+ path.push_segment(info.name.clone());
Some(path)
})
});
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index ec150dc068..d742b2eef0 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -1,13 +1,14 @@
//! A map of all publicly exported items in a crate.
+use std::collections::hash_map::Entry;
use std::{fmt, hash::BuildHasherDefault};
use base_db::CrateId;
use fst::{self, Streamer};
use hir_expand::name::Name;
-use indexmap::{map::Entry, IndexMap};
+use indexmap::IndexMap;
use itertools::Itertools;
-use rustc_hash::{FxHashSet, FxHasher};
+use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use triomphe::Arc;
use crate::{
@@ -17,52 +18,23 @@ use crate::{
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
+// FIXME: Support aliases: an item may be exported under multiple names, so `ImportInfo` should
+// have `Vec<(Name, ModuleId)>` instead of `(Name, ModuleId)`.
/// Item import details stored in the `ImportMap`.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ImportInfo {
- /// A path that can be used to import the item, relative to the crate's root.
- pub path: ImportPath,
+ /// A name that can be used to import the item, relative to the crate's root.
+ pub name: Name,
/// The module containing this item.
pub container: ModuleId,
/// Whether the import is a trait associated item or not.
pub is_trait_assoc_item: bool,
}
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct ImportPath {
- pub segments: Vec<Name>,
-}
-
-impl ImportPath {
- pub fn display<'a>(&'a self, db: &'a dyn DefDatabase) -> impl fmt::Display + 'a {
- struct Display<'a> {
- db: &'a dyn DefDatabase,
- path: &'a ImportPath,
- }
- impl fmt::Display for Display<'_> {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Display::fmt(
- &self.path.segments.iter().map(|it| it.display(self.db.upcast())).format("::"),
- f,
- )
- }
- }
- Display { db, path: self }
- }
-
- fn len(&self) -> usize {
- self.segments.len()
- }
-}
-
-/// A map from publicly exported items to the path needed to import/name them from a downstream
-/// crate.
+/// A map from publicly exported items to its name.
///
/// Reexports of items are taken into account, ie. if something is exported under multiple
/// names, the one with the shortest import path will be used.
-///
-/// Note that all paths are relative to the containing crate's root, so the crate name still needs
-/// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap {
map: FxIndexMap<ItemInNs, ImportInfo>,
@@ -70,122 +42,58 @@ pub struct ImportMap {
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
/// values returned by running `fst`.
///
- /// Since a path can refer to multiple items due to namespacing, we store all items with the
- /// same path right after each other. This allows us to find all items after the FST gives us
+ /// Since a name can refer to multiple items due to namespacing, we store all items with the
+ /// same name right after each other. This allows us to find all items after the FST gives us
/// the index of the first one.
importables: Vec<ItemInNs>,
fst: fst::Map<Vec<u8>>,
}
impl ImportMap {
- pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
+ pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
let _p = profile::span("import_map_query");
- let mut import_map = collect_import_map(db, krate);
+ let map = collect_import_map(db, krate);
- let mut importables = import_map
- .map
+ let mut importables: Vec<_> = map
.iter()
- .map(|(item, info)| (item, fst_path(db, &info.path)))
- .collect::<Vec<_>>();
- importables.sort_by(|(_, fst_path), (_, fst_path2)| fst_path.cmp(fst_path2));
+ // We've only collected items, whose name cannot be tuple field.
+ .map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
+ .collect();
+ importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
// Build the FST, taking care not to insert duplicate values.
-
let mut builder = fst::MapBuilder::memory();
- let mut last_batch_start = 0;
-
- for idx in 0..importables.len() {
- let key = &importables[last_batch_start].1;
- if let Some((_, fst_path)) = importables.get(idx + 1) {
- if key == fst_path {
- continue;
- }
- }
-
- let _ = builder.insert(key, last_batch_start as u64);
-
- last_batch_start = idx + 1;
+ let iter = importables.iter().enumerate().dedup_by(|lhs, rhs| lhs.1 .1 == rhs.1 .1);
+ for (start_idx, (_, name)) in iter {
+ let _ = builder.insert(name, start_idx as u64);
}
- import_map.fst = builder.into_map();
- import_map.importables = importables.iter().map(|&(&item, _)| item).collect();
-
- Arc::new(import_map)
- }
-
- /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
- pub fn path_of(&self, item: ItemInNs) -> Option<&ImportPath> {
- self.import_info_for(item).map(|it| &it.path)
+ Arc::new(ImportMap {
+ map,
+ fst: builder.into_map(),
+ importables: importables.into_iter().map(|(item, _)| item).collect(),
+ })
}
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
self.map.get(&item)
}
-
- #[cfg(test)]
- fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
- let mut importable_paths: Vec<_> = self
- .map
- .iter()
- .map(|(item, info)| {
- let ns = match item {
- ItemInNs::Types(_) => "t",
- ItemInNs::Values(_) => "v",
- ItemInNs::Macros(_) => "m",
- };
- format!("- {} ({ns})", info.path.display(db))
- })
- .collect();
-
- importable_paths.sort();
- importable_paths.join("\n")
- }
-
- fn collect_trait_assoc_items(
- &mut self,
- db: &dyn DefDatabase,
- tr: TraitId,
- is_type_in_ns: bool,
- original_import_info: &ImportInfo,
- ) {
- let _p = profile::span("collect_trait_assoc_items");
- for (assoc_item_name, item) in &db.trait_data(tr).items {
- let module_def_id = match item {
- AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
- AssocItemId::ConstId(c) => ModuleDefId::from(*c),
- // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
- // qualifier, ergo no need to store it for imports in import_map
- AssocItemId::TypeAliasId(_) => {
- cov_mark::hit!(type_aliases_ignored);
- continue;
- }
- };
- let assoc_item = if is_type_in_ns {
- ItemInNs::Types(module_def_id)
- } else {
- ItemInNs::Values(module_def_id)
- };
-
- let mut assoc_item_info = original_import_info.clone();
- assoc_item_info.path.segments.push(assoc_item_name.to_owned());
- assoc_item_info.is_trait_assoc_item = true;
- self.map.insert(assoc_item, assoc_item_info);
- }
- }
}
-fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
+fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemInNs, ImportInfo> {
let _p = profile::span("collect_import_map");
let def_map = db.crate_def_map(krate);
- let mut import_map = ImportMap::default();
+ let mut map = FxIndexMap::default();
// We look only into modules that are public(ly reexported), starting with the crate root.
- let empty = ImportPath { segments: vec![] };
let root = def_map.module_id(DefMap::ROOT);
- let mut worklist = vec![(root, empty)];
- while let Some((module, mod_path)) = worklist.pop() {
+ let mut worklist = vec![(root, 0)];
+ // Records items' minimum module depth.
+ let mut depth_map = FxHashMap::default();
+
+ while let Some((module, depth)) = worklist.pop() {
let ext_def_map;
let mod_data = if module.krate == krate {
&def_map[module.local_id]
@@ -205,52 +113,83 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMap {
});
for (name, per_ns) in visible_items {
- let mk_path = || {
- let mut path = mod_path.clone();
- path.segments.push(name.clone());
- path
- };
-
for item in per_ns.iter_items() {
- let path = mk_path();
- let path_len = path.len();
- let import_info =
- ImportInfo { path, container: module, is_trait_assoc_item: false };
-
- if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
- import_map.collect_trait_assoc_items(
- db,
- tr,
- matches!(item, ItemInNs::Types(_)),
- &import_info,
- );
- }
+ let import_info = ImportInfo {
+ name: name.clone(),
+ container: module,
+ is_trait_assoc_item: false,
+ };
- match import_map.map.entry(item) {
+ match depth_map.entry(item) {
Entry::Vacant(entry) => {
- entry.insert(import_info);
+ entry.insert(depth);
}
Entry::Occupied(mut entry) => {
- // If the new path is shorter, prefer that one.
- if path_len < entry.get().path.len() {
- *entry.get_mut() = import_info;
+ if depth < *entry.get() {
+ entry.insert(depth);
} else {
continue;
}
}
}
- // If we've just added a path to a module, descend into it. We might traverse
- // modules multiple times, but only if the new path to it is shorter than the
- // first (else we `continue` above).
+ if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
+ collect_trait_assoc_items(
+ db,
+ &mut map,
+ tr,
+ matches!(item, ItemInNs::Types(_)),
+ &import_info,
+ );
+ }
+
+ map.insert(item, import_info);
+
+ // If we've just added a module, descend into it. We might traverse modules
+ // multiple times, but only if the module depth is smaller (else we `continue`
+ // above).
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
- worklist.push((mod_id, mk_path()));
+ worklist.push((mod_id, depth + 1));
}
}
}
}
- import_map
+ map
+}
+
+fn collect_trait_assoc_items(
+ db: &dyn DefDatabase,
+ map: &mut FxIndexMap<ItemInNs, ImportInfo>,
+ tr: TraitId,
+ is_type_in_ns: bool,
+ trait_import_info: &ImportInfo,
+) {
+ let _p = profile::span("collect_trait_assoc_items");
+ for (assoc_item_name, item) in &db.trait_data(tr).items {
+ let module_def_id = match item {
+ AssocItemId::FunctionId(f) => ModuleDefId::from(*f),
+ AssocItemId::ConstId(c) => ModuleDefId::from(*c),
+ // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
+ // qualifier, ergo no need to store it for imports in import_map
+ AssocItemId::TypeAliasId(_) => {
+ cov_mark::hit!(type_aliases_ignored);
+ continue;
+ }
+ };
+ let assoc_item = if is_type_in_ns {
+ ItemInNs::Types(module_def_id)
+ } else {
+ ItemInNs::Values(module_def_id)
+ };
+
+ let assoc_item_info = ImportInfo {
+ container: trait_import_info.container,
+ name: assoc_item_name.clone(),
+ is_trait_assoc_item: true,
+ };
+ map.insert(assoc_item, assoc_item_info);
+ }
}
impl PartialEq for ImportMap {
@@ -264,7 +203,7 @@ impl Eq for ImportMap {}
impl fmt::Debug for ImportMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let mut importable_paths: Vec<_> = self
+ let mut importable_names: Vec<_> = self
.map
.iter()
.map(|(item, _)| match item {
@@ -274,56 +213,40 @@ impl fmt::Debug for ImportMap {
})
.collect();
- importable_paths.sort();
- f.write_str(&importable_paths.join("\n"))
+ importable_names.sort();
+ f.write_str(&importable_names.join("\n"))
}
}
-fn fst_path(db: &dyn DefDatabase, path: &ImportPath) -> String {
- let _p = profile::span("fst_path");
- let mut s = path.display(db).to_string();
- s.make_ascii_lowercase();
- s
-}
-
-#[derive(Debug, Eq, PartialEq, Hash)]
-pub enum ImportKind {
- Module,
- Function,
- Adt,
- EnumVariant,
- Const,
- Static,
- Trait,
- TraitAlias,
- TypeAlias,
- BuiltinType,
- AssociatedItem,
- Macro,
-}
-
/// A way to match import map contents against the search query.
#[derive(Debug)]
-pub enum SearchMode {
+enum SearchMode {
/// Import map entry should strictly match the query string.
- Equals,
- /// Import map entry should contain the query string.
- Contains,
+ Exact,
/// Import map entry should contain all letters from the query string,
/// in the same order, but not necessary adjacent.
Fuzzy,
}
+/// Three possible ways to search for the name in associated and/or other items.
+#[derive(Debug, Clone, Copy)]
+pub enum AssocSearchMode {
+ /// Search for the name in both associated and other items.
+ Include,
+ /// Search for the name in other items only.
+ Exclude,
+ /// Search for the name in the associated items only.
+ AssocItemsOnly,
+}
+
#[derive(Debug)]
pub struct Query {
query: String,
lowercased: String,
- name_only: bool,
- assoc_items_only: bool,
search_mode: SearchMode,
+ assoc_mode: AssocSearchMode,
case_sensitive: bool,
limit: usize,
- exclude_import_kinds: FxHashSet<ImportKind>,
}
impl Query {
@@ -332,30 +255,21 @@ impl Query {
Self {
query,
lowercased,
- name_only: false,
- assoc_items_only: false,
- search_mode: SearchMode::Contains,
+ search_mode: SearchMode::Exact,
+ assoc_mode: AssocSearchMode::Include,
case_sensitive: false,
- limit: usize::max_value(),
- exclude_import_kinds: FxHashSet::default(),
+ limit: usize::MAX,
}
}
- /// Matches entries' names only, ignoring the rest of
- /// the qualifier.
- /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
- pub fn name_only(self) -> Self {
- Self { name_only: true, ..self }
+ /// Fuzzy finds items instead of exact matching.
+ pub fn fuzzy(self) -> Self {
+ Self { search_mode: SearchMode::Fuzzy, ..self }
}
- /// Matches only the entries that are associated items, ignoring the rest.
- pub fn assoc_items_only(self) -> Self {
- Self { assoc_items_only: true, ..self }
- }
-
- /// Specifies the way to search for the entries using the query.
- pub fn search_mode(self, search_mode: SearchMode) -> Self {
- Self { search_mode, ..self }
+ /// Specifies whether we want to include associated items in the result.
+ pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
+ Self { assoc_mode, ..self }
}
/// Limits the returned number of items to `limit`.
@@ -368,12 +282,6 @@ impl Query {
Self { case_sensitive: true, ..self }
}
- /// Do not include imports of the specified kind in the search results.
- pub fn exclude_import_kind(mut self, import_kind: ImportKind) -> Self {
- self.exclude_import_kinds.insert(import_kind);
- self
- }
-
fn import_matches(
&self,
db: &dyn DefDatabase,
@@ -381,49 +289,36 @@ impl Query {
enforce_lowercase: bool,
) -> bool {
let _p = profile::span("import_map::Query::import_matches");
- if import.is_trait_assoc_item {
- if self.exclude_import_kinds.contains(&ImportKind::AssociatedItem) {
- return false;
- }
- } else if self.assoc_items_only {
- return false;
+ match (import.is_trait_assoc_item, self.assoc_mode) {
+ (true, AssocSearchMode::Exclude) => return false,
+ (false, AssocSearchMode::AssocItemsOnly) => return false,
+ _ => {}
}
- let mut input = if import.is_trait_assoc_item || self.name_only {
- import.path.segments.last().unwrap().display(db.upcast()).to_string()
- } else {
- import.path.display(db).to_string()
- };
- if enforce_lowercase || !self.case_sensitive {
+ let mut input = import.name.display(db.upcast()).to_string();
+ let case_insensitive = enforce_lowercase || !self.case_sensitive;
+ if case_insensitive {
input.make_ascii_lowercase();
}
- let query_string =
- if !enforce_lowercase && self.case_sensitive { &self.query } else { &self.lowercased };
+ let query_string = if case_insensitive { &self.lowercased } else { &self.query };
match self.search_mode {
- SearchMode::Equals => &input == query_string,
- SearchMode::Contains => input.contains(query_string),
+ SearchMode::Exact => &input == query_string,
SearchMode::Fuzzy => {
- let mut unchecked_query_chars = query_string.chars();
- let mut mismatching_query_char = unchecked_query_chars.next();
-
- for input_char in input.chars() {
- match mismatching_query_char {
- None => return true,
- Some(matching_query_char) if matching_query_char == input_char => {
- mismatching_query_char = unchecked_query_chars.next();
- }
- _ => (),
+ let mut input_chars = input.chars();
+ for query_char in query_string.chars() {
+ if input_chars.find(|&it| it == query_char).is_none() {
+ return false;
}
}
- mismatching_query_char.is_none()
+ true
}
}
}
}
-/// Searches dependencies of `krate` for an importable path matching `query`.
+/// Searches dependencies of `krate` for an importable name matching `query`.
///
/// This returns a list of items that could be imported from dependencies of `krate`.
pub fn search_dependencies(
@@ -446,65 +341,44 @@ pub fn search_dependencies(
let mut stream = op.union();
- let mut all_indexed_values = FxHashSet::default();
- while let Some((_, indexed_values)) = stream.next() {
- all_indexed_values.extend(indexed_values.iter().copied());
- }
-
let mut res = FxHashSet::default();
- for indexed_value in all_indexed_values {
- let import_map = &import_maps[indexed_value.index];
- let importables = &import_map.importables[indexed_value.value as usize..];
+ while let Some((_, indexed_values)) = stream.next() {
+ for indexed_value in indexed_values {
+ let import_map = &import_maps[indexed_value.index];
+ let importables = &import_map.importables[indexed_value.value as usize..];
- let common_importable_data = &import_map.map[&importables[0]];
- if !query.import_matches(db, common_importable_data, true) {
- continue;
- }
+ let common_importable_data = &import_map.map[&importables[0]];
+ if !query.import_matches(db, common_importable_data, true) {
+ continue;
+ }
- // Path shared by the importable items in this group.
- let common_importables_path_fst = fst_path(db, &common_importable_data.path);
- // Add the items from this `ModPath` group. Those are all subsequent items in
- // `importables` whose paths match `path`.
- let iter = importables
- .iter()
- .copied()
- .take_while(|item| {
- common_importables_path_fst == fst_path(db, &import_map.map[item].path)
- })
- .filter(|&item| match item_import_kind(item) {
- Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
- None => true,
- })
- .filter(|item| {
- !query.case_sensitive // we've already checked the common importables path case-insensitively
+ // Name shared by the importable items in this group.
+ let common_importable_name =
+ common_importable_data.name.to_smol_str().to_ascii_lowercase();
+ // Add the items from this name group. Those are all subsequent items in
+ // `importables` whose name match `common_importable_name`.
+ let iter = importables
+ .iter()
+ .copied()
+ .take_while(|item| {
+ common_importable_name
+ == import_map.map[item].name.to_smol_str().to_ascii_lowercase()
+ })
+ .filter(|item| {
+ !query.case_sensitive // we've already checked the common importables name case-insensitively
|| query.import_matches(db, &import_map.map[item], false)
- });
- res.extend(iter);
+ });
+ res.extend(iter);
- if res.len() >= query.limit {
- return res;
+ if res.len() >= query.limit {
+ return res;
+ }
}
}
res
}
-fn item_import_kind(item: ItemInNs) -> Option<ImportKind> {
- Some(match item.as_module_def_id()? {
- ModuleDefId::ModuleId(_) => ImportKind::Module,
- ModuleDefId::FunctionId(_) => ImportKind::Function,
- ModuleDefId::AdtId(_) => ImportKind::Adt,
- ModuleDefId::EnumVariantId(_) => ImportKind::EnumVariant,
- ModuleDefId::ConstId(_) => ImportKind::Const,
- ModuleDefId::StaticId(_) => ImportKind::Static,
- ModuleDefId::TraitId(_) => ImportKind::Trait,
- ModuleDefId::TraitAliasId(_) => ImportKind::TraitAlias,
- ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias,
- ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType,
- ModuleDefId::MacroId(_) => ImportKind::Macro,
- })
-}
-
#[cfg(test)]
mod tests {
use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
@@ -514,16 +388,39 @@ mod tests {
use super::*;
+ impl ImportMap {
+ fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
+ let mut importable_paths: Vec<_> = self
+ .map
+ .iter()
+ .map(|(item, info)| {
+ let path = render_path(db, info);
+ let ns = match item {
+ ItemInNs::Types(_) => "t",
+ ItemInNs::Values(_) => "v",
+ ItemInNs::Macros(_) => "m",
+ };
+ format!("- {path} ({ns})")
+ })
+ .collect();
+
+ importable_paths.sort();
+ importable_paths.join("\n")
+ }
+ }
+
fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
let db = TestDB::with_files(ra_fixture);
let crate_graph = db.crate_graph();
let krate = crate_graph
.iter()
- .find(|krate| {
- crate_graph[*krate].display_name.as_ref().map(|n| n.to_string())
- == Some(crate_name.to_string())
+ .find(|&krate| {
+ crate_graph[krate]
+ .display_name
+ .as_ref()
+ .is_some_and(|it| &**it.crate_name() == crate_name)
})
- .unwrap();
+ .expect("could not find crate");
let actual = search_dependencies(db.upcast(), krate, query)
.into_iter()
@@ -534,7 +431,7 @@ mod tests {
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
Some(assoc_item_path) => (assoc_item_path, "a"),
None => (
- dependency_imports.path_of(dependency)?.display(&db).to_string(),
+ render_path(&db, dependency_imports.import_info_for(dependency)?),
match dependency {
ItemInNs::Types(ModuleDefId::FunctionId(_))
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
@@ -564,57 +461,25 @@ mod tests {
dependency_imports: &ImportMap,
dependency: ItemInNs,
) -> Option<String> {
- let dependency_assoc_item_id = match dependency {
- ItemInNs::Types(ModuleDefId::FunctionId(id))
- | ItemInNs::Values(ModuleDefId::FunctionId(id)) => AssocItemId::from(id),
- ItemInNs::Types(ModuleDefId::ConstId(id))
- | ItemInNs::Values(ModuleDefId::ConstId(id)) => AssocItemId::from(id),
- ItemInNs::Types(ModuleDefId::TypeAliasId(id))
- | ItemInNs::Values(ModuleDefId::TypeAliasId(id)) => AssocItemId::from(id),
+ let (dependency_assoc_item_id, container) = match dependency.as_module_def_id()? {
+ ModuleDefId::FunctionId(id) => (AssocItemId::from(id), id.lookup(db).container),
+ ModuleDefId::ConstId(id) => (AssocItemId::from(id), id.lookup(db).container),
+ ModuleDefId::TypeAliasId(id) => (AssocItemId::from(id), id.lookup(db).container),
_ => return None,
};
- let trait_ = assoc_to_trait(db, dependency)?;
- if let ModuleDefId::TraitId(tr) = trait_.as_module_def_id()? {
- let trait_data = db.trait_data(tr);
- let assoc_item_name =
- trait_data.items.iter().find_map(|(assoc_item_name, assoc_item_id)| {
- if &dependency_assoc_item_id == assoc_item_id {
- Some(assoc_item_name)
- } else {
- None
- }
- })?;
- return Some(format!(
- "{}::{}",
- dependency_imports.path_of(trait_)?.display(db),
- assoc_item_name.display(db.upcast())
- ));
- }
- None
- }
-
- fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
- let assoc: AssocItemId = match item {
- ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
- ModuleDefId::TypeAliasId(it) => it.into(),
- ModuleDefId::FunctionId(it) => it.into(),
- ModuleDefId::ConstId(it) => it.into(),
- _ => return None,
- },
- _ => return None,
+ let ItemContainerId::TraitId(trait_id) = container else {
+ return None;
};
- let container = match assoc {
- AssocItemId::FunctionId(it) => it.lookup(db).container,
- AssocItemId::ConstId(it) => it.lookup(db).container,
- AssocItemId::TypeAliasId(it) => it.lookup(db).container,
- };
+ let trait_info = dependency_imports.import_info_for(ItemInNs::Types(trait_id.into()))?;
- match container {
- ItemContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
- _ => None,
- }
+ let trait_data = db.trait_data(trait_id);
+ let (assoc_item_name, _) = trait_data
+ .items
+ .iter()
+ .find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
+ Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast())))
}
fn check(ra_fixture: &str, expect: Expect) {
@@ -637,6 +502,24 @@ mod tests {
expect.assert_eq(&actual)
}
+ fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
+ let mut module = info.container;
+ let mut segments = vec![&info.name];
+
+ let def_map = module.def_map(db);
+ assert!(def_map.block_id().is_none(), "block local items should not be in `ImportMap`");
+
+ while let Some(parent) = module.containing_module(db) {
+ let parent_data = &def_map[parent.local_id];
+ let (name, _) =
+ parent_data.children.iter().find(|(_, id)| **id == module.local_id).unwrap();
+ segments.push(name);
+ module = parent;
+ }
+
+ segments.iter().rev().map(|it| it.display(db.upcast())).join("::")
+ }
+
#[test]
fn smoke() {
check(
@@ -753,6 +636,7 @@ mod tests {
#[test]
fn module_reexport() {
// Reexporting modules from a dependency adds all contents to the import map.
+ // XXX: The rendered paths are relative to the defining crate.
check(
r"
//- /main.rs crate:main deps:lib
@@ -768,9 +652,9 @@ mod tests {
- module::S (t)
- module::S (v)
main:
+ - module::S (t)
+ - module::S (v)
- reexported_module (t)
- - reexported_module::S (t)
- - reexported_module::S (v)
"#]],
);
}
@@ -872,10 +756,9 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
+ Query::new("fmt".to_string()).fuzzy(),
expect![[r#"
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::FMT_CONST (a)
dep::fmt::Display::format_function (a)
dep::fmt::Display::format_method (a)
@@ -902,7 +785,9 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy).assoc_items_only(),
+ Query::new("fmt".to_string())
+ .fuzzy()
+ .assoc_search_mode(AssocSearchMode::AssocItemsOnly),
expect![[r#"
dep::fmt::Display::FMT_CONST (a)
dep::fmt::Display::format_function (a)
@@ -913,23 +798,10 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string())
- .search_mode(SearchMode::Fuzzy)
- .exclude_import_kind(ImportKind::AssociatedItem),
+ Query::new("fmt".to_string()).fuzzy().assoc_search_mode(AssocSearchMode::Exclude),
expect![[r#"
- dep::fmt (t)
- dep::fmt::Display (t)
- "#]],
- );
-
- check_search(
- ra_fixture,
- "main",
- Query::new("fmt".to_string())
- .search_mode(SearchMode::Fuzzy)
- .assoc_items_only()
- .exclude_import_kind(ImportKind::AssociatedItem),
- expect![[r#""#]],
+ dep::fmt (t)
+ "#]],
);
}
@@ -962,13 +834,12 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
+ Query::new("fmt".to_string()).fuzzy(),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
dep::format (f)
"#]],
@@ -977,26 +848,12 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
- expect![[r#"
- dep::Fmt (m)
- dep::Fmt (t)
- dep::Fmt (v)
- dep::fmt (t)
- dep::fmt::Display::fmt (a)
- "#]],
- );
-
- check_search(
- ra_fixture,
- "main",
- Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
+ Query::new("fmt".to_string()),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
"#]],
);
@@ -1037,7 +894,6 @@ mod tests {
dep::Fmt (t)
dep::Fmt (v)
dep::fmt (t)
- dep::fmt::Display (t)
dep::fmt::Display::fmt (a)
"#]],
);
@@ -1045,7 +901,7 @@ mod tests {
check_search(
ra_fixture,
"main",
- Query::new("fmt".to_string()).name_only(),
+ Query::new("fmt".to_string()),
expect![[r#"
dep::Fmt (m)
dep::Fmt (t)
@@ -1110,43 +966,10 @@ mod tests {
pub fn no() {}
"#,
"main",
- Query::new("".to_string()).limit(2),
+ Query::new("".to_string()).fuzzy().limit(1),
expect![[r#"
- dep::Fmt (m)
- dep::Fmt (t)
- dep::Fmt (v)
- dep::fmt (t)
- "#]],
- );
- }
-
- #[test]
- fn search_exclusions() {
- let ra_fixture = r#"
- //- /main.rs crate:main deps:dep
- //- /dep.rs crate:dep
-
- pub struct fmt;
- pub struct FMT;
- "#;
-
- check_search(
- ra_fixture,
- "main",
- Query::new("FMT".to_string()),
- expect![[r#"
- dep::FMT (t)
- dep::FMT (v)
- dep::fmt (t)
- dep::fmt (v)
+ dep::fmt::Display (t)
"#]],
);
-
- check_search(
- ra_fixture,
- "main",
- Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
- expect![[r#""#]],
- );
}
}
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 3bdd795bea..c03bc2f41d 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
@@ -73,7 +73,7 @@ pub(crate) fn replace_derive_with_manual_impl(
&ctx.sema,
current_crate,
NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
- items_locator::AssocItemSearch::Exclude,
+ items_locator::AssocSearchMode::Exclude,
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| match item.as_module_def()? {
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 106d4e1e52..2eaa42040a 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -231,7 +231,7 @@ pub fn resolve_completion_edits(
&sema,
current_crate,
NameToImport::exact_case_sensitive(imported_name),
- items_locator::AssocItemSearch::Include,
+ items_locator::AssocSearchMode::Include,
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
);
let import = items_with_name
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 901d592c69..e52dc35677 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -13,7 +13,7 @@ use syntax::{
use crate::{
helpers::item_name,
- items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
+ items_locator::{self, AssocSearchMode, DEFAULT_QUERY_SEARCH_LIMIT},
RootDatabase,
};
@@ -317,7 +317,7 @@ fn path_applicable_imports(
// * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
//
// see also an ignored test under FIXME comment in the qualify_path.rs module
- AssocItemSearch::Exclude,
+ AssocSearchMode::Exclude,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| {
@@ -334,7 +334,7 @@ fn path_applicable_imports(
sema,
current_crate,
path_candidate.name.clone(),
- AssocItemSearch::Include,
+ AssocSearchMode::Include,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|item| {
@@ -483,7 +483,7 @@ fn trait_applicable_items(
sema,
current_crate,
trait_candidate.assoc_item_name.clone(),
- AssocItemSearch::AssocItemsOnly,
+ AssocSearchMode::AssocItemsOnly,
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
)
.filter_map(|input| item_as_assoc(db, input))
diff --git a/crates/ide-db/src/items_locator.rs b/crates/ide-db/src/items_locator.rs
index 46f1353e2e..3f7a3ec2d0 100644
--- a/crates/ide-db/src/items_locator.rs
+++ b/crates/ide-db/src/items_locator.rs
@@ -3,10 +3,7 @@
//! 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::{self, ImportKind},
- AsAssocItem, Crate, ItemInNs, Semantics,
-};
+use hir::{import_map, AsAssocItem, Crate, ItemInNs, Semantics};
use limit::Limit;
use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
@@ -14,23 +11,14 @@ use crate::{imports::import_assets::NameToImport, symbol_index, RootDatabase};
/// A value to use, when uncertain which limit to pick.
pub static DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(40);
-/// Three possible ways to search for the name in associated and/or other items.
-#[derive(Debug, Clone, Copy)]
-pub enum AssocItemSearch {
- /// Search for the name in both associated and other items.
- Include,
- /// Search for the name in other items only.
- Exclude,
- /// Search for the name in the associated items only.
- AssocItemsOnly,
-}
+pub use import_map::AssocSearchMode;
/// Searches for importable items with the given name in the crate and its dependencies.
pub fn items_with_name<'a>(
sema: &'a Semantics<'_, RootDatabase>,
krate: Crate,
name: NameToImport,
- assoc_item_search: AssocItemSearch,
+ assoc_item_search: AssocSearchMode,
limit: Option<usize>,
) -> impl Iterator<Item = ItemInNs> + 'a {
let _p = profile::span("items_with_name").detail(|| {
@@ -48,9 +36,7 @@ pub fn items_with_name<'a>(
let mut local_query = symbol_index::Query::new(exact_name.clone());
local_query.exact();
- let external_query = import_map::Query::new(exact_name)
- .name_only()
- .search_mode(import_map::SearchMode::Equals);
+ let external_query = import_map::Query::new(exact_name);
(
local_query,
@@ -61,17 +47,8 @@ pub fn items_with_name<'a>(
let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
- .search_mode(import_map::SearchMode::Fuzzy)
- .name_only();
- match assoc_item_search {
- AssocItemSearch::Include => {}
- AssocItemSearch::Exclude => {
- external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
- }
- AssocItemSearch::AssocItemsOnly => {
- external_query = external_query.assoc_items_only();
- }
- }
+ .fuzzy()
+ .assoc_search_mode(assoc_item_search);
if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
local_query.case_sensitive();
@@ -93,13 +70,15 @@ pub fn items_with_name<'a>(
fn find_items<'a>(
sema: &'a Semantics<'_, RootDatabase>,
krate: Crate,
- assoc_item_search: AssocItemSearch,
+ assoc_item_search: AssocSearchMode,
local_query: symbol_index::Query,
external_query: import_map::Query,
) -> impl Iterator<Item = ItemInNs> + 'a {
let _p = profile::span("find_items");
let db = sema.db;
+ // NOTE: `external_query` includes `assoc_item_search`, so we don't need to
+ // filter on our own.
let external_importables =
krate.query_external_importables(db, external_query).map(|external_importable| {
match external_importable {
@@ -112,18 +91,15 @@ fn find_items<'a>(
let local_results = local_query
.search(&symbol_index::crate_symbols(db, krate))
.into_iter()
- .filter_map(|local_candidate| match local_candidate.def {
- hir::ModuleDef::Macro(macro_def) => Some(ItemInNs::Macros(macro_def)),
- def => Some(ItemInNs::from(def)),
+ .filter(move |candidate| match assoc_item_search {
+ AssocSearchMode::Include => true,
+ AssocSearchMode::Exclude => candidate.def.as_assoc_item(db).is_none(),
+ AssocSearchMode::AssocItemsOnly => candidate.def.as_assoc_item(db).is_some(),
+ })
+ .map(|local_candidate| match local_candidate.def {
+ hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
+ def => ItemInNs::from(def),
});
- external_importables.chain(local_results).filter(move |&item| match assoc_item_search {
- AssocItemSearch::Include => true,
- AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
- AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
- })
-}
-
-fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
- item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db)).is_some()
+ external_importables.chain(local_results)
}