Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions crates/ra_assists/src/handlers/auto_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
76 changes: 67 additions & 9 deletions crates/ra_hir_def/src/import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>;
Expand All @@ -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>,

Expand All @@ -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![] };
Expand Down Expand Up @@ -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 });
}
Expand All @@ -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);

Expand All @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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(
Expand Down Expand Up @@ -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());
Expand All @@ -618,6 +675,7 @@ mod tests {
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
"###);
}

Expand Down