Skip to content

Commit e75b4fc

Browse files
bors[bot]Jonas Schievink
andauthored
Merge #5189
5189: Record and suggest assoc. items of traits via ImportMap r=matklad a=jonas-schievink Fixes #5115 Co-authored-by: Jonas Schievink <[email protected]>
2 parents 57ed622 + 6ee73cd commit e75b4fc

File tree

2 files changed

+207
-9
lines changed

2 files changed

+207
-9
lines changed

crates/ra_assists/src/handlers/auto_import.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,146 @@ fn main() {
810810
);
811811
}
812812

813+
#[test]
814+
fn trait_method_cross_crate() {
815+
check_assist(
816+
auto_import,
817+
r"
818+
//- /main.rs crate:main deps:dep
819+
fn main() {
820+
let test_struct = dep::test_mod::TestStruct {};
821+
test_struct.test_meth<|>od()
822+
}
823+
//- /dep.rs crate:dep
824+
pub mod test_mod {
825+
pub trait TestTrait {
826+
fn test_method(&self);
827+
}
828+
pub struct TestStruct {}
829+
impl TestTrait for TestStruct {
830+
fn test_method(&self) {}
831+
}
832+
}
833+
",
834+
r"
835+
use dep::test_mod::TestTrait;
836+
837+
fn main() {
838+
let test_struct = dep::test_mod::TestStruct {};
839+
test_struct.test_method()
840+
}
841+
",
842+
);
843+
}
844+
845+
#[test]
846+
fn assoc_fn_cross_crate() {
847+
check_assist(
848+
auto_import,
849+
r"
850+
//- /main.rs crate:main deps:dep
851+
fn main() {
852+
dep::test_mod::TestStruct::test_func<|>tion
853+
}
854+
//- /dep.rs crate:dep
855+
pub mod test_mod {
856+
pub trait TestTrait {
857+
fn test_function();
858+
}
859+
pub struct TestStruct {}
860+
impl TestTrait for TestStruct {
861+
fn test_function() {}
862+
}
863+
}
864+
",
865+
r"
866+
use dep::test_mod::TestTrait;
867+
868+
fn main() {
869+
dep::test_mod::TestStruct::test_function
870+
}
871+
",
872+
);
873+
}
874+
875+
#[test]
876+
fn assoc_const_cross_crate() {
877+
check_assist(
878+
auto_import,
879+
r"
880+
//- /main.rs crate:main deps:dep
881+
fn main() {
882+
dep::test_mod::TestStruct::CONST<|>
883+
}
884+
//- /dep.rs crate:dep
885+
pub mod test_mod {
886+
pub trait TestTrait {
887+
const CONST: bool;
888+
}
889+
pub struct TestStruct {}
890+
impl TestTrait for TestStruct {
891+
const CONST: bool = true;
892+
}
893+
}
894+
",
895+
r"
896+
use dep::test_mod::TestTrait;
897+
898+
fn main() {
899+
dep::test_mod::TestStruct::CONST
900+
}
901+
",
902+
);
903+
}
904+
905+
#[test]
906+
fn assoc_fn_as_method_cross_crate() {
907+
check_assist_not_applicable(
908+
auto_import,
909+
r"
910+
//- /main.rs crate:main deps:dep
911+
fn main() {
912+
let test_struct = dep::test_mod::TestStruct {};
913+
test_struct.test_func<|>tion()
914+
}
915+
//- /dep.rs crate:dep
916+
pub mod test_mod {
917+
pub trait TestTrait {
918+
fn test_function();
919+
}
920+
pub struct TestStruct {}
921+
impl TestTrait for TestStruct {
922+
fn test_function() {}
923+
}
924+
}
925+
",
926+
);
927+
}
928+
929+
#[test]
930+
fn private_trait_cross_crate() {
931+
check_assist_not_applicable(
932+
auto_import,
933+
r"
934+
//- /main.rs crate:main deps:dep
935+
fn main() {
936+
let test_struct = dep::test_mod::TestStruct {};
937+
test_struct.test_meth<|>od()
938+
}
939+
//- /dep.rs crate:dep
940+
pub mod test_mod {
941+
trait TestTrait {
942+
fn test_method(&self);
943+
}
944+
pub struct TestStruct {}
945+
impl TestTrait for TestStruct {
946+
fn test_method(&self) {}
947+
}
948+
}
949+
",
950+
);
951+
}
952+
813953
#[test]
814954
fn not_applicable_for_imported_trait_for_method() {
815955
check_assist_not_applicable(

crates/ra_hir_def/src/import_map.rs

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
55
use fst::{self, Streamer};
66
use indexmap::{map::Entry, IndexMap};
77
use ra_db::CrateId;
8-
use rustc_hash::FxHasher;
8+
use ra_syntax::SmolStr;
9+
use rustc_hash::{FxHashMap, FxHasher};
10+
use smallvec::SmallVec;
911

1012
use crate::{
1113
db::DefDatabase,
1214
item_scope::ItemInNs,
1315
path::{ModPath, PathKind},
1416
visibility::Visibility,
15-
ModuleDefId, ModuleId,
17+
AssocItemId, ModuleDefId, ModuleId, TraitId,
1618
};
1719

1820
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@@ -34,6 +36,7 @@ pub struct ImportInfo {
3436
///
3537
/// Note that all paths are relative to the containing crate's root, so the crate name still needs
3638
/// to be prepended to the `ModPath` before the path is valid.
39+
#[derive(Default)]
3740
pub struct ImportMap {
3841
map: FxIndexMap<ItemInNs, ImportInfo>,
3942

@@ -45,13 +48,17 @@ pub struct ImportMap {
4548
/// the index of the first one.
4649
importables: Vec<ItemInNs>,
4750
fst: fst::Map<Vec<u8>>,
51+
52+
/// Maps names of associated items to the item's ID. Only includes items whose defining trait is
53+
/// exported.
54+
assoc_map: FxHashMap<SmolStr, SmallVec<[AssocItemId; 1]>>,
4855
}
4956

5057
impl ImportMap {
5158
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
5259
let _p = ra_prof::profile("import_map_query");
5360
let def_map = db.crate_def_map(krate);
54-
let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default());
61+
let mut import_map = Self::default();
5562

5663
// We look only into modules that are public(ly reexported), starting with the crate root.
5764
let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@@ -85,7 +92,7 @@ impl ImportMap {
8592

8693
for item in per_ns.iter_items() {
8794
let path = mk_path();
88-
match import_map.entry(item) {
95+
match import_map.map.entry(item) {
8996
Entry::Vacant(entry) => {
9097
entry.insert(ImportInfo { path, container: module });
9198
}
@@ -105,11 +112,16 @@ impl ImportMap {
105112
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
106113
worklist.push((mod_id, mk_path()));
107114
}
115+
116+
// If we've added a path to a trait, add the trait's methods to the method map.
117+
if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
118+
import_map.collect_trait_methods(db, tr);
119+
}
108120
}
109121
}
110122
}
111123

112-
let mut importables = import_map.iter().collect::<Vec<_>>();
124+
let mut importables = import_map.map.iter().collect::<Vec<_>>();
113125

114126
importables.sort_by(cmp);
115127

@@ -133,10 +145,10 @@ impl ImportMap {
133145
builder.insert(key, start as u64).unwrap();
134146
}
135147

136-
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
137-
let importables = importables.iter().map(|(item, _)| **item).collect();
148+
import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
149+
import_map.importables = importables.iter().map(|(item, _)| **item).collect();
138150

139-
Arc::new(Self { map: import_map, fst, importables })
151+
Arc::new(import_map)
140152
}
141153

142154
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
@@ -147,6 +159,13 @@ impl ImportMap {
147159
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
148160
self.map.get(&item)
149161
}
162+
163+
fn collect_trait_methods(&mut self, db: &dyn DefDatabase, tr: TraitId) {
164+
let data = db.trait_data(tr);
165+
for (name, item) in data.items.iter() {
166+
self.assoc_map.entry(name.to_string().into()).or_default().push(*item);
167+
}
168+
}
150169
}
151170

152171
impl PartialEq for ImportMap {
@@ -290,13 +309,26 @@ pub fn search_dependencies<'a>(
290309
}
291310
}
292311

312+
// Add all exported associated items whose names match the query (exactly).
313+
for map in &import_maps {
314+
if let Some(v) = map.assoc_map.get(&*query.query) {
315+
res.extend(v.iter().map(|&assoc| {
316+
ItemInNs::Types(match assoc {
317+
AssocItemId::FunctionId(it) => it.into(),
318+
AssocItemId::ConstId(it) => it.into(),
319+
AssocItemId::TypeAliasId(it) => it.into(),
320+
})
321+
}));
322+
}
323+
}
324+
293325
res
294326
}
295327

296328
#[cfg(test)]
297329
mod tests {
298330
use super::*;
299-
use crate::test_db::TestDB;
331+
use crate::{test_db::TestDB, AssocContainerId, Lookup};
300332
use insta::assert_snapshot;
301333
use itertools::Itertools;
302334
use ra_db::fixture::WithFixture;
@@ -339,6 +371,7 @@ mod tests {
339371
ItemInNs::Values(_) => "v",
340372
ItemInNs::Macros(_) => "m",
341373
};
374+
let item = assoc_to_trait(&db, item);
342375
item.krate(db.upcast()).map(|krate| {
343376
let map = db.import_map(krate);
344377
let path = map.path_of(item).unwrap();
@@ -353,6 +386,29 @@ mod tests {
353386
.join("\n")
354387
}
355388

389+
fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs {
390+
let assoc: AssocItemId = match item {
391+
ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
392+
ModuleDefId::TypeAliasId(it) => it.into(),
393+
ModuleDefId::FunctionId(it) => it.into(),
394+
ModuleDefId::ConstId(it) => it.into(),
395+
_ => return item,
396+
},
397+
_ => return item,
398+
};
399+
400+
let container = match assoc {
401+
AssocItemId::FunctionId(it) => it.lookup(db).container,
402+
AssocItemId::ConstId(it) => it.lookup(db).container,
403+
AssocItemId::TypeAliasId(it) => it.lookup(db).container,
404+
};
405+
406+
match container {
407+
AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()),
408+
_ => item,
409+
}
410+
}
411+
356412
#[test]
357413
fn smoke() {
358414
let map = import_map(
@@ -610,6 +666,7 @@ mod tests {
610666
dep::Fmt (m)
611667
dep::fmt::Display (t)
612668
dep::format (v)
669+
dep::fmt::Display (t)
613670
"###);
614671

615672
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
@@ -618,6 +675,7 @@ mod tests {
618675
dep::Fmt (t)
619676
dep::Fmt (v)
620677
dep::Fmt (m)
678+
dep::fmt::Display (t)
621679
"###);
622680
}
623681

0 commit comments

Comments
 (0)