mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +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]
|
#[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(
|
||||||
|
|
|
@ -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)
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue