mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-04 19:29:07 +00:00
Prepare ImportMap for supportin multiple import paths per item
This commit is contained in:
parent
11a87c9179
commit
2339ba4440
@ -367,18 +367,18 @@ fn calculate_best_path(
|
|||||||
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
|
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
|
||||||
// that wants to import it here, but we always prefer to use the external path here.
|
// that wants to import it here, but we always prefer to use the external path here.
|
||||||
|
|
||||||
let crate_graph = db.crate_graph();
|
for dep in &db.crate_graph()[from.krate].dependencies {
|
||||||
let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
|
|
||||||
let import_map = db.import_map(dep.crate_id);
|
let import_map = db.import_map(dep.crate_id);
|
||||||
import_map.import_info_for(item).and_then(|info| {
|
let Some(import_info_for) = import_map.import_info_for(item) else { continue };
|
||||||
|
for info in import_info_for {
|
||||||
if info.is_doc_hidden {
|
if info.is_doc_hidden {
|
||||||
// the item or import is `#[doc(hidden)]`, so skip it as it is in an external crate
|
// the item or import is `#[doc(hidden)]`, so skip it as it is in an external crate
|
||||||
return None;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine best path for containing module and append last segment from `info`.
|
// Determine best path for containing module and append last segment from `info`.
|
||||||
// FIXME: we should guide this to look up the path locally, or from the same crate again?
|
// FIXME: we should guide this to look up the path locally, or from the same crate again?
|
||||||
let (mut path, path_stability) = find_path_for_module(
|
let Some((mut path, path_stability)) = find_path_for_module(
|
||||||
db,
|
db,
|
||||||
def_map,
|
def_map,
|
||||||
visited_modules,
|
visited_modules,
|
||||||
@ -388,22 +388,23 @@ fn calculate_best_path(
|
|||||||
max_len - 1,
|
max_len - 1,
|
||||||
prefixed,
|
prefixed,
|
||||||
prefer_no_std,
|
prefer_no_std,
|
||||||
)?;
|
) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
cov_mark::hit!(partially_imported);
|
cov_mark::hit!(partially_imported);
|
||||||
path.push_segment(info.name.clone());
|
path.push_segment(info.name.clone());
|
||||||
Some((
|
|
||||||
|
let path_with_stab = (
|
||||||
path,
|
path,
|
||||||
zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }),
|
zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }),
|
||||||
))
|
);
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
for path in extern_paths {
|
let new_path_with_stab = match best_path.take() {
|
||||||
let new_path = match best_path.take() {
|
Some(best_path) => select_best_path(best_path, path_with_stab, prefer_no_std),
|
||||||
Some(best_path) => select_best_path(best_path, path, prefer_no_std),
|
None => path_with_stab,
|
||||||
None => path,
|
};
|
||||||
};
|
update_best_path(&mut best_path, new_path_with_stab);
|
||||||
update_best_path(&mut best_path, new_path);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(module) = item.module(db) {
|
if let Some(module) = item.module(db) {
|
||||||
@ -593,7 +594,7 @@ mod tests {
|
|||||||
|
|
||||||
let found_path =
|
let found_path =
|
||||||
find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false);
|
find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false);
|
||||||
assert_eq!(found_path, Some(mod_path), "{prefix_kind:?}");
|
assert_eq!(found_path, Some(mod_path), "on kind: {prefix_kind:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_found_path(
|
fn check_found_path(
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault};
|
use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault};
|
||||||
|
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
use fst::{self, Streamer};
|
use fst::{self, raw::IndexedValue, Streamer};
|
||||||
use hir_expand::name::Name;
|
use hir_expand::name::Name;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
|
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
use triomphe::Arc;
|
use triomphe::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -20,8 +21,6 @@ use crate::{
|
|||||||
|
|
||||||
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
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`.
|
/// Item import details stored in the `ImportMap`.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct ImportInfo {
|
pub struct ImportInfo {
|
||||||
@ -29,22 +28,21 @@ pub struct ImportInfo {
|
|||||||
pub name: Name,
|
pub name: Name,
|
||||||
/// The module containing this item.
|
/// The module containing this item.
|
||||||
pub container: ModuleId,
|
pub container: ModuleId,
|
||||||
/// Whether the import is a trait associated item or not.
|
|
||||||
pub is_trait_assoc_item: bool,
|
|
||||||
/// Whether this item is annotated with `#[doc(hidden)]`.
|
/// Whether this item is annotated with `#[doc(hidden)]`.
|
||||||
pub is_doc_hidden: bool,
|
pub is_doc_hidden: bool,
|
||||||
/// Whether this item is annotated with `#[unstable(..)]`.
|
/// Whether this item is annotated with `#[unstable(..)]`.
|
||||||
pub is_unstable: bool,
|
pub is_unstable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImportMapIndex = FxIndexMap<ItemInNs, (SmallVec<[ImportInfo; 1]>, IsTraitAssocItem)>;
|
||||||
|
|
||||||
/// A map from publicly exported items to its name.
|
/// A map from publicly exported items to its name.
|
||||||
///
|
///
|
||||||
/// Reexports of items are taken into account, ie. if something is exported under multiple
|
/// 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.
|
/// names, the one with the shortest import path will be used.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ImportMap {
|
pub struct ImportMap {
|
||||||
map: FxIndexMap<ItemInNs, ImportInfo>,
|
map: ImportMapIndex,
|
||||||
|
|
||||||
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
|
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
|
||||||
/// values returned by running `fst`.
|
/// values returned by running `fst`.
|
||||||
///
|
///
|
||||||
@ -55,6 +53,12 @@ pub struct ImportMap {
|
|||||||
fst: fst::Map<Vec<u8>>,
|
fst: fst::Map<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum IsTraitAssocItem {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
impl ImportMap {
|
impl ImportMap {
|
||||||
pub(crate) 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 _p = profile::span("import_map_query");
|
||||||
@ -64,9 +68,13 @@ impl ImportMap {
|
|||||||
let mut importables: Vec<_> = map
|
let mut importables: Vec<_> = map
|
||||||
.iter()
|
.iter()
|
||||||
// We've only collected items, whose name cannot be tuple field.
|
// We've only collected items, whose name cannot be tuple field.
|
||||||
.map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
|
.flat_map(|(&item, (info, _))| {
|
||||||
|
info.iter()
|
||||||
|
.map(move |info| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
|
importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
|
||||||
|
importables.dedup();
|
||||||
|
|
||||||
// Build the FST, taking care not to insert duplicate values.
|
// Build the FST, taking care not to insert duplicate values.
|
||||||
let mut builder = fst::MapBuilder::memory();
|
let mut builder = fst::MapBuilder::memory();
|
||||||
@ -82,12 +90,12 @@ impl ImportMap {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
|
pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> {
|
||||||
self.map.get(&item)
|
self.map.get(&item).map(|(info, _)| &**info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemInNs, ImportInfo> {
|
fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex {
|
||||||
let _p = profile::span("collect_import_map");
|
let _p = profile::span("collect_import_map");
|
||||||
|
|
||||||
let def_map = db.crate_def_map(krate);
|
let def_map = db.crate_def_map(krate);
|
||||||
@ -140,7 +148,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
|
|||||||
let import_info = ImportInfo {
|
let import_info = ImportInfo {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
container: module,
|
container: module,
|
||||||
is_trait_assoc_item: false,
|
|
||||||
is_doc_hidden,
|
is_doc_hidden,
|
||||||
is_unstable,
|
is_unstable,
|
||||||
};
|
};
|
||||||
@ -180,6 +187,8 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
|
|||||||
depth < occ_depth
|
depth < occ_depth
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// FIXME: Remove the overwrite rules as we can now record exports and
|
||||||
|
// aliases for the same item
|
||||||
if !overwrite {
|
if !overwrite {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -197,7 +206,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.insert(item, import_info);
|
map.insert(item, (smallvec![import_info], IsTraitAssocItem::No));
|
||||||
|
|
||||||
// If we've just added a module, descend into it. We might traverse modules
|
// 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`
|
// multiple times, but only if the module depth is smaller (else we `continue`
|
||||||
@ -214,7 +223,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
|
|||||||
|
|
||||||
fn collect_trait_assoc_items(
|
fn collect_trait_assoc_items(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
map: &mut FxIndexMap<ItemInNs, ImportInfo>,
|
map: &mut ImportMapIndex,
|
||||||
tr: TraitId,
|
tr: TraitId,
|
||||||
is_type_in_ns: bool,
|
is_type_in_ns: bool,
|
||||||
trait_import_info: &ImportInfo,
|
trait_import_info: &ImportInfo,
|
||||||
@ -241,11 +250,10 @@ fn collect_trait_assoc_items(
|
|||||||
let assoc_item_info = ImportInfo {
|
let assoc_item_info = ImportInfo {
|
||||||
container: trait_import_info.container,
|
container: trait_import_info.container,
|
||||||
name: assoc_item_name.clone(),
|
name: assoc_item_name.clone(),
|
||||||
is_trait_assoc_item: true,
|
|
||||||
is_doc_hidden: attrs.has_doc_hidden(),
|
is_doc_hidden: attrs.has_doc_hidden(),
|
||||||
is_unstable: attrs.is_unstable(),
|
is_unstable: attrs.is_unstable(),
|
||||||
};
|
};
|
||||||
map.insert(assoc_item, assoc_item_info);
|
map.insert(assoc_item, (smallvec![assoc_item_info], IsTraitAssocItem::Yes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,6 +357,15 @@ impl Query {
|
|||||||
Self { case_sensitive: true, ..self }
|
Self { case_sensitive: true, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matches_assoc_mode(&self, is_trait_assoc_item: IsTraitAssocItem) -> bool {
|
||||||
|
match (is_trait_assoc_item, self.assoc_mode) {
|
||||||
|
(IsTraitAssocItem::Yes, AssocSearchMode::Exclude)
|
||||||
|
| (IsTraitAssocItem::No, AssocSearchMode::AssocItemsOnly) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the import map entry matches the query.
|
||||||
fn import_matches(
|
fn import_matches(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
@ -356,12 +373,8 @@ impl Query {
|
|||||||
enforce_lowercase: bool,
|
enforce_lowercase: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let _p = profile::span("import_map::Query::import_matches");
|
let _p = profile::span("import_map::Query::import_matches");
|
||||||
match (import.is_trait_assoc_item, self.assoc_mode) {
|
|
||||||
(true, AssocSearchMode::Exclude) => return false,
|
|
||||||
(false, AssocSearchMode::AssocItemsOnly) => return false,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// FIXME: Can we get rid of the alloc here?
|
||||||
let mut input = import.name.display(db.upcast()).to_string();
|
let mut input = import.name.display(db.upcast()).to_string();
|
||||||
let case_insensitive = enforce_lowercase || !self.case_sensitive;
|
let case_insensitive = enforce_lowercase || !self.case_sensitive;
|
||||||
if case_insensitive {
|
if case_insensitive {
|
||||||
@ -411,32 +424,55 @@ pub fn search_dependencies(
|
|||||||
|
|
||||||
let mut res = FxHashSet::default();
|
let mut res = FxHashSet::default();
|
||||||
while let Some((_, indexed_values)) = stream.next() {
|
while let Some((_, indexed_values)) = stream.next() {
|
||||||
for indexed_value in indexed_values {
|
for &IndexedValue { index, value } in indexed_values {
|
||||||
let import_map = &import_maps[indexed_value.index];
|
let import_map = &import_maps[index];
|
||||||
let importables = &import_map.importables[indexed_value.value as usize..];
|
let [importable, importables @ ..] = &import_map.importables[value as usize..] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let common_importable_data = &import_map.map[&importables[0]];
|
let &(ref importable_data, is_trait_assoc_item) = &import_map.map[importable];
|
||||||
if !query.import_matches(db, common_importable_data, true) {
|
if !query.matches_assoc_mode(is_trait_assoc_item) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name shared by the importable items in this group.
|
// FIXME: We probably need to account for other possible matches in this alias group?
|
||||||
let common_importable_name =
|
let Some(common_importable_data) =
|
||||||
common_importable_data.name.to_smol_str().to_ascii_lowercase();
|
importable_data.iter().find(|&info| query.import_matches(db, info, true))
|
||||||
// Add the items from this name group. Those are all subsequent items in
|
else {
|
||||||
// `importables` whose name match `common_importable_name`.
|
continue;
|
||||||
let iter = importables
|
};
|
||||||
.iter()
|
res.insert(*importable);
|
||||||
.copied()
|
|
||||||
.take_while(|item| {
|
if !importables.is_empty() {
|
||||||
common_importable_name
|
// FIXME: so many allocs...
|
||||||
== import_map.map[item].name.to_smol_str().to_ascii_lowercase()
|
// Name shared by the importable items in this group.
|
||||||
})
|
let common_importable_name =
|
||||||
.filter(|item| {
|
common_importable_data.name.to_smol_str().to_ascii_lowercase();
|
||||||
!query.case_sensitive // we've already checked the common importables name case-insensitively
|
// Add the items from this name group. Those are all subsequent items in
|
||||||
|| query.import_matches(db, &import_map.map[item], false)
|
// `importables` whose name match `common_importable_name`.
|
||||||
});
|
let iter = importables
|
||||||
res.extend(iter);
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.take_while(|item| {
|
||||||
|
let &(ref import_infos, assoc_mode) = &import_map.map[item];
|
||||||
|
query.matches_assoc_mode(assoc_mode)
|
||||||
|
&& import_infos.iter().any(|info| {
|
||||||
|
info.name.to_smol_str().to_ascii_lowercase()
|
||||||
|
== common_importable_name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|item| {
|
||||||
|
!query.case_sensitive || {
|
||||||
|
// we've already checked the common importables name case-insensitively
|
||||||
|
let &(ref import_infos, assoc_mode) = &import_map.map[item];
|
||||||
|
query.matches_assoc_mode(assoc_mode)
|
||||||
|
&& import_infos
|
||||||
|
.iter()
|
||||||
|
.any(|info| query.import_matches(db, info, false))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.extend(iter);
|
||||||
|
}
|
||||||
|
|
||||||
if res.len() >= query.limit {
|
if res.len() >= query.limit {
|
||||||
return res;
|
return res;
|
||||||
@ -461,6 +497,7 @@ mod tests {
|
|||||||
let mut importable_paths: Vec<_> = self
|
let mut importable_paths: Vec<_> = self
|
||||||
.map
|
.map
|
||||||
.iter()
|
.iter()
|
||||||
|
.flat_map(|(item, (info, _))| info.iter().map(move |info| (item, info)))
|
||||||
.map(|(item, info)| {
|
.map(|(item, info)| {
|
||||||
let path = render_path(db, info);
|
let path = render_path(db, info);
|
||||||
let ns = match item {
|
let ns = match item {
|
||||||
@ -499,7 +536,7 @@ mod tests {
|
|||||||
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
|
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
|
||||||
Some(assoc_item_path) => (assoc_item_path, "a"),
|
Some(assoc_item_path) => (assoc_item_path, "a"),
|
||||||
None => (
|
None => (
|
||||||
render_path(&db, dependency_imports.import_info_for(dependency)?),
|
render_path(&db, &dependency_imports.import_info_for(dependency)?[0]),
|
||||||
match dependency {
|
match dependency {
|
||||||
ItemInNs::Types(ModuleDefId::FunctionId(_))
|
ItemInNs::Types(ModuleDefId::FunctionId(_))
|
||||||
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
|
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
|
||||||
@ -547,7 +584,12 @@ mod tests {
|
|||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
|
.find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
|
||||||
Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast())))
|
// FIXME: This should check all import infos, not just the first
|
||||||
|
Some(format!(
|
||||||
|
"{}::{}",
|
||||||
|
render_path(db, &trait_info[0]),
|
||||||
|
assoc_item_name.display(db.upcast())
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
|
Loading…
Reference in New Issue
Block a user