5189: Record and suggest assoc. items of traits via ImportMap r=matklad a=jonas-schievink

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/5115

Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
bors[bot] 2020-07-02 13:55:14 +00:00 committed by GitHub
commit e75b4fc85e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 207 additions and 9 deletions

View file

@ -810,6 +810,146 @@ fn main() {
); );
} }
#[test]
fn trait_method_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_meth<|>od()
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_method()
}
",
);
}
#[test]
fn assoc_fn_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::test_func<|>tion
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
dep::test_mod::TestStruct::test_function
}
",
);
}
#[test]
fn assoc_const_cross_crate() {
check_assist(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
dep::test_mod::TestStruct::CONST<|>
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
const CONST: bool;
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
const CONST: bool = true;
}
}
",
r"
use dep::test_mod::TestTrait;
fn main() {
dep::test_mod::TestStruct::CONST
}
",
);
}
#[test]
fn assoc_fn_as_method_cross_crate() {
check_assist_not_applicable(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_func<|>tion()
}
//- /dep.rs crate:dep
pub mod test_mod {
pub trait TestTrait {
fn test_function();
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_function() {}
}
}
",
);
}
#[test]
fn private_trait_cross_crate() {
check_assist_not_applicable(
auto_import,
r"
//- /main.rs crate:main deps:dep
fn main() {
let test_struct = dep::test_mod::TestStruct {};
test_struct.test_meth<|>od()
}
//- /dep.rs crate:dep
pub mod test_mod {
trait TestTrait {
fn test_method(&self);
}
pub struct TestStruct {}
impl TestTrait for TestStruct {
fn test_method(&self) {}
}
}
",
);
}
#[test] #[test]
fn not_applicable_for_imported_trait_for_method() { fn not_applicable_for_imported_trait_for_method() {
check_assist_not_applicable( check_assist_not_applicable(

View file

@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
use fst::{self, Streamer}; use fst::{self, Streamer};
use indexmap::{map::Entry, IndexMap}; use indexmap::{map::Entry, IndexMap};
use ra_db::CrateId; use ra_db::CrateId;
use rustc_hash::FxHasher; use ra_syntax::SmolStr;
use rustc_hash::{FxHashMap, FxHasher};
use smallvec::SmallVec;
use crate::{ use crate::{
db::DefDatabase, db::DefDatabase,
item_scope::ItemInNs, item_scope::ItemInNs,
path::{ModPath, PathKind}, path::{ModPath, PathKind},
visibility::Visibility, visibility::Visibility,
ModuleDefId, ModuleId, AssocItemId, ModuleDefId, ModuleId, TraitId,
}; };
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
@ -34,6 +36,7 @@ pub struct ImportInfo {
/// ///
/// Note that all paths are relative to the containing crate's root, so the crate name still needs /// 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. /// to be prepended to the `ModPath` before the path is valid.
#[derive(Default)]
pub struct ImportMap { pub struct ImportMap {
map: FxIndexMap<ItemInNs, ImportInfo>, map: FxIndexMap<ItemInNs, ImportInfo>,
@ -45,13 +48,17 @@ pub struct ImportMap {
/// the index of the first one. /// the index of the first one.
importables: Vec<ItemInNs>, importables: Vec<ItemInNs>,
fst: fst::Map<Vec<u8>>, fst: fst::Map<Vec<u8>>,
/// Maps names of associated items to the item's ID. Only includes items whose defining trait is
/// exported.
assoc_map: FxHashMap<SmolStr, SmallVec<[AssocItemId; 1]>>,
} }
impl ImportMap { impl ImportMap {
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
let _p = ra_prof::profile("import_map_query"); let _p = ra_prof::profile("import_map_query");
let def_map = db.crate_def_map(krate); let def_map = db.crate_def_map(krate);
let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default()); let mut import_map = Self::default();
// We look only into modules that are public(ly reexported), starting with the crate root. // We look only into modules that are public(ly reexported), starting with the crate root.
let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
@ -85,7 +92,7 @@ impl ImportMap {
for item in per_ns.iter_items() { for item in per_ns.iter_items() {
let path = mk_path(); let path = mk_path();
match import_map.entry(item) { match import_map.map.entry(item) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
entry.insert(ImportInfo { path, container: module }); entry.insert(ImportInfo { path, container: module });
} }
@ -105,11 +112,16 @@ impl ImportMap {
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
worklist.push((mod_id, mk_path())); worklist.push((mod_id, mk_path()));
} }
// If we've added a path to a trait, add the trait's methods to the method map.
if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
import_map.collect_trait_methods(db, tr);
}
} }
} }
} }
let mut importables = import_map.iter().collect::<Vec<_>>(); let mut importables = import_map.map.iter().collect::<Vec<_>>();
importables.sort_by(cmp); importables.sort_by(cmp);
@ -133,10 +145,10 @@ impl ImportMap {
builder.insert(key, start as u64).unwrap(); builder.insert(key, start as u64).unwrap();
} }
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
let importables = importables.iter().map(|(item, _)| **item).collect(); import_map.importables = importables.iter().map(|(item, _)| **item).collect();
Arc::new(Self { map: import_map, fst, importables }) Arc::new(import_map)
} }
/// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root.
@ -147,6 +159,13 @@ 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)
} }
fn collect_trait_methods(&mut self, db: &dyn DefDatabase, tr: TraitId) {
let data = db.trait_data(tr);
for (name, item) in data.items.iter() {
self.assoc_map.entry(name.to_string().into()).or_default().push(*item);
}
}
} }
impl PartialEq for ImportMap { impl PartialEq for ImportMap {
@ -290,13 +309,26 @@ pub fn search_dependencies<'a>(
} }
} }
// Add all exported associated items whose names match the query (exactly).
for map in &import_maps {
if let Some(v) = map.assoc_map.get(&*query.query) {
res.extend(v.iter().map(|&assoc| {
ItemInNs::Types(match assoc {
AssocItemId::FunctionId(it) => it.into(),
AssocItemId::ConstId(it) => it.into(),
AssocItemId::TypeAliasId(it) => it.into(),
})
}));
}
}
res res
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_db::TestDB; use crate::{test_db::TestDB, AssocContainerId, Lookup};
use insta::assert_snapshot; use insta::assert_snapshot;
use itertools::Itertools; use itertools::Itertools;
use ra_db::fixture::WithFixture; use ra_db::fixture::WithFixture;
@ -339,6 +371,7 @@ mod tests {
ItemInNs::Values(_) => "v", ItemInNs::Values(_) => "v",
ItemInNs::Macros(_) => "m", ItemInNs::Macros(_) => "m",
}; };
let item = assoc_to_trait(&db, item);
item.krate(db.upcast()).map(|krate| { item.krate(db.upcast()).map(|krate| {
let map = db.import_map(krate); let map = db.import_map(krate);
let path = map.path_of(item).unwrap(); let path = map.path_of(item).unwrap();
@ -353,6 +386,29 @@ mod tests {
.join("\n") .join("\n")
} }
fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> 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 item,
},
_ => return item,
};
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,
};
match container {
AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()),
_ => item,
}
}
#[test] #[test]
fn smoke() { fn smoke() {
let map = import_map( let map = import_map(
@ -610,6 +666,7 @@ mod tests {
dep::Fmt (m) dep::Fmt (m)
dep::fmt::Display (t) dep::fmt::Display (t)
dep::format (v) dep::format (v)
dep::fmt::Display (t)
"###); "###);
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
@ -618,6 +675,7 @@ mod tests {
dep::Fmt (t) dep::Fmt (t)
dep::Fmt (v) dep::Fmt (v)
dep::Fmt (m) dep::Fmt (m)
dep::fmt::Display (t)
"###); "###);
} }