Skip to content

Commit 2339ba4

Browse files
committed
Prepare ImportMap for supportin multiple import paths per item
1 parent 11a87c9 commit 2339ba4

File tree

2 files changed

+105
-62
lines changed

2 files changed

+105
-62
lines changed

crates/hir-def/src/find_path.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -367,18 +367,18 @@ fn calculate_best_path(
367367
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
368368
// that wants to import it here, but we always prefer to use the external path here.
369369

370-
let crate_graph = db.crate_graph();
371-
let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| {
370+
for dep in &db.crate_graph()[from.krate].dependencies {
372371
let import_map = db.import_map(dep.crate_id);
373-
import_map.import_info_for(item).and_then(|info| {
372+
let Some(import_info_for) = import_map.import_info_for(item) else { continue };
373+
for info in import_info_for {
374374
if info.is_doc_hidden {
375375
// the item or import is `#[doc(hidden)]`, so skip it as it is in an external crate
376-
return None;
376+
continue;
377377
}
378378

379379
// Determine best path for containing module and append last segment from `info`.
380380
// FIXME: we should guide this to look up the path locally, or from the same crate again?
381-
let (mut path, path_stability) = find_path_for_module(
381+
let Some((mut path, path_stability)) = find_path_for_module(
382382
db,
383383
def_map,
384384
visited_modules,
@@ -388,22 +388,23 @@ fn calculate_best_path(
388388
max_len - 1,
389389
prefixed,
390390
prefer_no_std,
391-
)?;
391+
) else {
392+
continue;
393+
};
392394
cov_mark::hit!(partially_imported);
393395
path.push_segment(info.name.clone());
394-
Some((
396+
397+
let path_with_stab = (
395398
path,
396399
zip_stability(path_stability, if info.is_unstable { Unstable } else { Stable }),
397-
))
398-
})
399-
});
400+
);
400401

401-
for path in extern_paths {
402-
let new_path = match best_path.take() {
403-
Some(best_path) => select_best_path(best_path, path, prefer_no_std),
404-
None => path,
405-
};
406-
update_best_path(&mut best_path, new_path);
402+
let new_path_with_stab = match best_path.take() {
403+
Some(best_path) => select_best_path(best_path, path_with_stab, prefer_no_std),
404+
None => path_with_stab,
405+
};
406+
update_best_path(&mut best_path, new_path_with_stab);
407+
}
407408
}
408409
}
409410
if let Some(module) = item.module(db) {
@@ -593,7 +594,7 @@ mod tests {
593594

594595
let found_path =
595596
find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false);
596-
assert_eq!(found_path, Some(mod_path), "{prefix_kind:?}");
597+
assert_eq!(found_path, Some(mod_path), "on kind: {prefix_kind:?}");
597598
}
598599

599600
fn check_found_path(

crates/hir-def/src/import_map.rs

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
use std::{collections::hash_map::Entry, fmt, hash::BuildHasherDefault};
44

55
use base_db::CrateId;
6-
use fst::{self, Streamer};
6+
use fst::{self, raw::IndexedValue, Streamer};
77
use hir_expand::name::Name;
88
use indexmap::IndexMap;
99
use itertools::Itertools;
1010
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
11+
use smallvec::{smallvec, SmallVec};
1112
use triomphe::Arc;
1213

1314
use crate::{
@@ -20,31 +21,28 @@ use crate::{
2021

2122
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
2223

23-
// FIXME: Support aliases: an item may be exported under multiple names, so `ImportInfo` should
24-
// have `Vec<(Name, ModuleId)>` instead of `(Name, ModuleId)`.
2524
/// Item import details stored in the `ImportMap`.
2625
#[derive(Debug, Clone, Eq, PartialEq)]
2726
pub struct ImportInfo {
2827
/// A name that can be used to import the item, relative to the crate's root.
2928
pub name: Name,
3029
/// The module containing this item.
3130
pub container: ModuleId,
32-
/// Whether the import is a trait associated item or not.
33-
pub is_trait_assoc_item: bool,
3431
/// Whether this item is annotated with `#[doc(hidden)]`.
3532
pub is_doc_hidden: bool,
3633
/// Whether this item is annotated with `#[unstable(..)]`.
3734
pub is_unstable: bool,
3835
}
3936

37+
type ImportMapIndex = FxIndexMap<ItemInNs, (SmallVec<[ImportInfo; 1]>, IsTraitAssocItem)>;
38+
4039
/// A map from publicly exported items to its name.
4140
///
4241
/// Reexports of items are taken into account, ie. if something is exported under multiple
4342
/// names, the one with the shortest import path will be used.
4443
#[derive(Default)]
4544
pub struct ImportMap {
46-
map: FxIndexMap<ItemInNs, ImportInfo>,
47-
45+
map: ImportMapIndex,
4846
/// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the
4947
/// values returned by running `fst`.
5048
///
@@ -55,6 +53,12 @@ pub struct ImportMap {
5553
fst: fst::Map<Vec<u8>>,
5654
}
5755

56+
#[derive(Copy, Clone, PartialEq, Eq)]
57+
enum IsTraitAssocItem {
58+
Yes,
59+
No,
60+
}
61+
5862
impl ImportMap {
5963
pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
6064
let _p = profile::span("import_map_query");
@@ -64,9 +68,13 @@ impl ImportMap {
6468
let mut importables: Vec<_> = map
6569
.iter()
6670
// We've only collected items, whose name cannot be tuple field.
67-
.map(|(&item, info)| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
71+
.flat_map(|(&item, (info, _))| {
72+
info.iter()
73+
.map(move |info| (item, info.name.as_str().unwrap().to_ascii_lowercase()))
74+
})
6875
.collect();
6976
importables.sort_by(|(_, lhs_name), (_, rhs_name)| lhs_name.cmp(rhs_name));
77+
importables.dedup();
7078

7179
// Build the FST, taking care not to insert duplicate values.
7280
let mut builder = fst::MapBuilder::memory();
@@ -82,12 +90,12 @@ impl ImportMap {
8290
})
8391
}
8492

85-
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
86-
self.map.get(&item)
93+
pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> {
94+
self.map.get(&item).map(|(info, _)| &**info)
8795
}
8896
}
8997

90-
fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemInNs, ImportInfo> {
98+
fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex {
9199
let _p = profile::span("collect_import_map");
92100

93101
let def_map = db.crate_def_map(krate);
@@ -140,7 +148,6 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
140148
let import_info = ImportInfo {
141149
name: name.clone(),
142150
container: module,
143-
is_trait_assoc_item: false,
144151
is_doc_hidden,
145152
is_unstable,
146153
};
@@ -180,6 +187,8 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
180187
depth < occ_depth
181188
}
182189
};
190+
// FIXME: Remove the overwrite rules as we can now record exports and
191+
// aliases for the same item
183192
if !overwrite {
184193
continue;
185194
}
@@ -197,7 +206,7 @@ fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> FxIndexMap<ItemIn
197206
);
198207
}
199208

200-
map.insert(item, import_info);
209+
map.insert(item, (smallvec![import_info], IsTraitAssocItem::No));
201210

202211
// If we've just added a module, descend into it. We might traverse modules
203212
// 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
214223

215224
fn collect_trait_assoc_items(
216225
db: &dyn DefDatabase,
217-
map: &mut FxIndexMap<ItemInNs, ImportInfo>,
226+
map: &mut ImportMapIndex,
218227
tr: TraitId,
219228
is_type_in_ns: bool,
220229
trait_import_info: &ImportInfo,
@@ -241,11 +250,10 @@ fn collect_trait_assoc_items(
241250
let assoc_item_info = ImportInfo {
242251
container: trait_import_info.container,
243252
name: assoc_item_name.clone(),
244-
is_trait_assoc_item: true,
245253
is_doc_hidden: attrs.has_doc_hidden(),
246254
is_unstable: attrs.is_unstable(),
247255
};
248-
map.insert(assoc_item, assoc_item_info);
256+
map.insert(assoc_item, (smallvec![assoc_item_info], IsTraitAssocItem::Yes));
249257
}
250258
}
251259

@@ -349,19 +357,24 @@ impl Query {
349357
Self { case_sensitive: true, ..self }
350358
}
351359

360+
fn matches_assoc_mode(&self, is_trait_assoc_item: IsTraitAssocItem) -> bool {
361+
match (is_trait_assoc_item, self.assoc_mode) {
362+
(IsTraitAssocItem::Yes, AssocSearchMode::Exclude)
363+
| (IsTraitAssocItem::No, AssocSearchMode::AssocItemsOnly) => false,
364+
_ => true,
365+
}
366+
}
367+
368+
/// Checks whether the import map entry matches the query.
352369
fn import_matches(
353370
&self,
354371
db: &dyn DefDatabase,
355372
import: &ImportInfo,
356373
enforce_lowercase: bool,
357374
) -> bool {
358375
let _p = profile::span("import_map::Query::import_matches");
359-
match (import.is_trait_assoc_item, self.assoc_mode) {
360-
(true, AssocSearchMode::Exclude) => return false,
361-
(false, AssocSearchMode::AssocItemsOnly) => return false,
362-
_ => {}
363-
}
364376

377+
// FIXME: Can we get rid of the alloc here?
365378
let mut input = import.name.display(db.upcast()).to_string();
366379
let case_insensitive = enforce_lowercase || !self.case_sensitive;
367380
if case_insensitive {
@@ -411,32 +424,55 @@ pub fn search_dependencies(
411424

412425
let mut res = FxHashSet::default();
413426
while let Some((_, indexed_values)) = stream.next() {
414-
for indexed_value in indexed_values {
415-
let import_map = &import_maps[indexed_value.index];
416-
let importables = &import_map.importables[indexed_value.value as usize..];
427+
for &IndexedValue { index, value } in indexed_values {
428+
let import_map = &import_maps[index];
429+
let [importable, importables @ ..] = &import_map.importables[value as usize..] else {
430+
continue;
431+
};
417432

418-
let common_importable_data = &import_map.map[&importables[0]];
419-
if !query.import_matches(db, common_importable_data, true) {
433+
let &(ref importable_data, is_trait_assoc_item) = &import_map.map[importable];
434+
if !query.matches_assoc_mode(is_trait_assoc_item) {
420435
continue;
421436
}
422437

423-
// Name shared by the importable items in this group.
424-
let common_importable_name =
425-
common_importable_data.name.to_smol_str().to_ascii_lowercase();
426-
// Add the items from this name group. Those are all subsequent items in
427-
// `importables` whose name match `common_importable_name`.
428-
let iter = importables
429-
.iter()
430-
.copied()
431-
.take_while(|item| {
432-
common_importable_name
433-
== import_map.map[item].name.to_smol_str().to_ascii_lowercase()
434-
})
435-
.filter(|item| {
436-
!query.case_sensitive // we've already checked the common importables name case-insensitively
437-
|| query.import_matches(db, &import_map.map[item], false)
438-
});
439-
res.extend(iter);
438+
// FIXME: We probably need to account for other possible matches in this alias group?
439+
let Some(common_importable_data) =
440+
importable_data.iter().find(|&info| query.import_matches(db, info, true))
441+
else {
442+
continue;
443+
};
444+
res.insert(*importable);
445+
446+
if !importables.is_empty() {
447+
// FIXME: so many allocs...
448+
// Name shared by the importable items in this group.
449+
let common_importable_name =
450+
common_importable_data.name.to_smol_str().to_ascii_lowercase();
451+
// Add the items from this name group. Those are all subsequent items in
452+
// `importables` whose name match `common_importable_name`.
453+
let iter = importables
454+
.iter()
455+
.copied()
456+
.take_while(|item| {
457+
let &(ref import_infos, assoc_mode) = &import_map.map[item];
458+
query.matches_assoc_mode(assoc_mode)
459+
&& import_infos.iter().any(|info| {
460+
info.name.to_smol_str().to_ascii_lowercase()
461+
== common_importable_name
462+
})
463+
})
464+
.filter(|item| {
465+
!query.case_sensitive || {
466+
// we've already checked the common importables name case-insensitively
467+
let &(ref import_infos, assoc_mode) = &import_map.map[item];
468+
query.matches_assoc_mode(assoc_mode)
469+
&& import_infos
470+
.iter()
471+
.any(|info| query.import_matches(db, info, false))
472+
}
473+
});
474+
res.extend(iter);
475+
}
440476

441477
if res.len() >= query.limit {
442478
return res;
@@ -461,6 +497,7 @@ mod tests {
461497
let mut importable_paths: Vec<_> = self
462498
.map
463499
.iter()
500+
.flat_map(|(item, (info, _))| info.iter().map(move |info| (item, info)))
464501
.map(|(item, info)| {
465502
let path = render_path(db, info);
466503
let ns = match item {
@@ -499,7 +536,7 @@ mod tests {
499536
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
500537
Some(assoc_item_path) => (assoc_item_path, "a"),
501538
None => (
502-
render_path(&db, dependency_imports.import_info_for(dependency)?),
539+
render_path(&db, &dependency_imports.import_info_for(dependency)?[0]),
503540
match dependency {
504541
ItemInNs::Types(ModuleDefId::FunctionId(_))
505542
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
@@ -547,7 +584,12 @@ mod tests {
547584
.items
548585
.iter()
549586
.find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
550-
Some(format!("{}::{}", render_path(db, trait_info), assoc_item_name.display(db.upcast())))
587+
// FIXME: This should check all import infos, not just the first
588+
Some(format!(
589+
"{}::{}",
590+
render_path(db, &trait_info[0]),
591+
assoc_item_name.display(db.upcast())
592+
))
551593
}
552594

553595
fn check(ra_fixture: &str, expect: Expect) {

0 commit comments

Comments
 (0)