mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 20:43:21 +00:00
Merge #5189
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:
commit
e75b4fc85e
2 changed files with 207 additions and 9 deletions
|
@ -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]
|
||||
fn not_applicable_for_imported_trait_for_method() {
|
||||
check_assist_not_applicable(
|
||||
|
|
|
@ -5,14 +5,16 @@ use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc};
|
|||
use fst::{self, Streamer};
|
||||
use indexmap::{map::Entry, IndexMap};
|
||||
use ra_db::CrateId;
|
||||
use rustc_hash::FxHasher;
|
||||
use ra_syntax::SmolStr;
|
||||
use rustc_hash::{FxHashMap, FxHasher};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
db::DefDatabase,
|
||||
item_scope::ItemInNs,
|
||||
path::{ModPath, PathKind},
|
||||
visibility::Visibility,
|
||||
ModuleDefId, ModuleId,
|
||||
AssocItemId, ModuleDefId, ModuleId, TraitId,
|
||||
};
|
||||
|
||||
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
|
||||
/// to be prepended to the `ModPath` before the path is valid.
|
||||
#[derive(Default)]
|
||||
pub struct ImportMap {
|
||||
map: FxIndexMap<ItemInNs, ImportInfo>,
|
||||
|
||||
|
@ -45,13 +48,17 @@ pub struct ImportMap {
|
|||
/// the index of the first one.
|
||||
importables: Vec<ItemInNs>,
|
||||
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 {
|
||||
pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
|
||||
let _p = ra_prof::profile("import_map_query");
|
||||
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.
|
||||
let empty = ModPath { kind: PathKind::Plain, segments: vec![] };
|
||||
|
@ -85,7 +92,7 @@ impl ImportMap {
|
|||
|
||||
for item in per_ns.iter_items() {
|
||||
let path = mk_path();
|
||||
match import_map.entry(item) {
|
||||
match import_map.map.entry(item) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(ImportInfo { path, container: module });
|
||||
}
|
||||
|
@ -105,11 +112,16 @@ impl ImportMap {
|
|||
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
|
||||
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);
|
||||
|
||||
|
@ -133,10 +145,10 @@ impl ImportMap {
|
|||
builder.insert(key, start as u64).unwrap();
|
||||
}
|
||||
|
||||
let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
|
||||
let importables = importables.iter().map(|(item, _)| **item).collect();
|
||||
import_map.fst = fst::Map::new(builder.into_inner().unwrap()).unwrap();
|
||||
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.
|
||||
|
@ -147,6 +159,13 @@ impl ImportMap {
|
|||
pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> {
|
||||
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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_db::TestDB;
|
||||
use crate::{test_db::TestDB, AssocContainerId, Lookup};
|
||||
use insta::assert_snapshot;
|
||||
use itertools::Itertools;
|
||||
use ra_db::fixture::WithFixture;
|
||||
|
@ -339,6 +371,7 @@ mod tests {
|
|||
ItemInNs::Values(_) => "v",
|
||||
ItemInNs::Macros(_) => "m",
|
||||
};
|
||||
let item = assoc_to_trait(&db, item);
|
||||
item.krate(db.upcast()).map(|krate| {
|
||||
let map = db.import_map(krate);
|
||||
let path = map.path_of(item).unwrap();
|
||||
|
@ -353,6 +386,29 @@ mod tests {
|
|||
.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]
|
||||
fn smoke() {
|
||||
let map = import_map(
|
||||
|
@ -610,6 +666,7 @@ mod tests {
|
|||
dep::Fmt (m)
|
||||
dep::fmt::Display (t)
|
||||
dep::format (v)
|
||||
dep::fmt::Display (t)
|
||||
"###);
|
||||
|
||||
let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end());
|
||||
|
@ -618,6 +675,7 @@ mod tests {
|
|||
dep::Fmt (t)
|
||||
dep::Fmt (v)
|
||||
dep::Fmt (m)
|
||||
dep::fmt::Display (t)
|
||||
"###);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue