mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00

This PR touches a lot of parts. But the main changes are changing `hir_expand::Name` to be raw edition-dependently and only when necessary (unrelated to how the user originally wrote the identifier), and changing `is_keyword()` and `is_raw_identifier()` to be edition-aware (this was done in #17896, but the FIXMEs were fixed here). It is possible that I missed some cases, but most IDE parts should properly escape (or not escape) identifiers now. The rules of thumb are: - If we show the identifier to the user, its rawness should be determined by the edition of the edited crate. This is nice for IDE features, but really important for changes we insert to the source code. - For tests, I chose `Edition::CURRENT` (so we only have to (maybe) update tests when an edition becomes stable, to avoid churn). - For debugging tools (helper methods and logs), I used `Edition::LATEST`.
1041 lines
32 KiB
Rust
1041 lines
32 KiB
Rust
//! A map of all publicly exported items in a crate.
|
|
|
|
use std::fmt;
|
|
|
|
use base_db::CrateId;
|
|
use fst::{raw::IndexedValue, Automaton, Streamer};
|
|
use hir_expand::name::Name;
|
|
use itertools::Itertools;
|
|
use rustc_hash::FxHashSet;
|
|
use smallvec::SmallVec;
|
|
use span::Edition;
|
|
use stdx::{format_to, TupleExt};
|
|
use syntax::ToSmolStr;
|
|
use triomphe::Arc;
|
|
|
|
use crate::{
|
|
db::DefDatabase,
|
|
item_scope::{ImportOrExternCrate, ItemInNs},
|
|
nameres::DefMap,
|
|
visibility::Visibility,
|
|
AssocItemId, FxIndexMap, ModuleDefId, ModuleId, TraitId,
|
|
};
|
|
|
|
/// Item import details stored in the `ImportMap`.
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct ImportInfo {
|
|
/// A name that can be used to import the item, relative to the container.
|
|
pub name: Name,
|
|
/// The module containing this item.
|
|
pub container: ModuleId,
|
|
/// Whether this item is annotated with `#[doc(hidden)]`.
|
|
pub is_doc_hidden: bool,
|
|
/// Whether this item is annotated with `#[unstable(..)]`.
|
|
pub is_unstable: bool,
|
|
}
|
|
|
|
/// A map from publicly exported items to its name.
|
|
///
|
|
/// Reexports of items are taken into account.
|
|
#[derive(Default)]
|
|
pub struct ImportMap {
|
|
/// Maps from `ItemInNs` to information of imports that bring the item into scope.
|
|
item_to_info_map: ImportMapIndex,
|
|
/// List of keys stored in [`Self::item_to_info_map`], sorted lexicographically by their
|
|
/// [`Name`]. Indexed by the values returned by running `fst`.
|
|
///
|
|
/// Since a name can refer to multiple items due to namespacing and import aliases, we store all
|
|
/// items with the same name right after each other. This allows us to find all items after the
|
|
/// fst gives us the index of the first one.
|
|
///
|
|
/// The [`u32`] is the index into the smallvec in the value of [`Self::item_to_info_map`].
|
|
importables: Vec<(ItemInNs, u32)>,
|
|
fst: fst::Map<Vec<u8>>,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
|
enum IsTraitAssocItem {
|
|
Yes,
|
|
No,
|
|
}
|
|
|
|
type ImportMapIndex = FxIndexMap<ItemInNs, (SmallVec<[ImportInfo; 1]>, IsTraitAssocItem)>;
|
|
|
|
impl ImportMap {
|
|
pub fn dump(&self, db: &dyn DefDatabase) -> String {
|
|
let mut out = String::new();
|
|
for (k, v) in self.item_to_info_map.iter() {
|
|
format_to!(out, "{:?} ({:?}) -> ", k, v.1);
|
|
for v in &v.0 {
|
|
format_to!(
|
|
out,
|
|
"{}:{:?}, ",
|
|
v.name.display(db.upcast(), Edition::CURRENT),
|
|
v.container
|
|
);
|
|
}
|
|
format_to!(out, "\n");
|
|
}
|
|
out
|
|
}
|
|
|
|
pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> {
|
|
let _p = tracing::info_span!("import_map_query").entered();
|
|
|
|
let map = Self::collect_import_map(db, krate);
|
|
|
|
let mut importables: Vec<_> = map
|
|
.iter()
|
|
// We've only collected items, whose name cannot be tuple field so unwrapping is fine.
|
|
.flat_map(|(&item, (info, _))| {
|
|
info.iter().enumerate().map(move |(idx, info)| {
|
|
(item, info.name.unescaped().display(db.upcast()).to_smolstr(), idx as u32)
|
|
})
|
|
})
|
|
.collect();
|
|
importables.sort_by(|(_, l_info, _), (_, r_info, _)| {
|
|
let lhs_chars = l_info.chars().map(|c| c.to_ascii_lowercase());
|
|
let rhs_chars = r_info.chars().map(|c| c.to_ascii_lowercase());
|
|
lhs_chars.cmp(rhs_chars)
|
|
});
|
|
importables.dedup();
|
|
|
|
// Build the FST, taking care not to insert duplicate values.
|
|
let mut builder = fst::MapBuilder::memory();
|
|
let mut iter = importables
|
|
.iter()
|
|
.enumerate()
|
|
.dedup_by(|&(_, (_, lhs, _)), &(_, (_, rhs, _))| lhs.eq_ignore_ascii_case(rhs));
|
|
|
|
let mut insert = |name: &str, start, end| {
|
|
builder.insert(name.to_ascii_lowercase(), ((start as u64) << 32) | end as u64).unwrap()
|
|
};
|
|
|
|
if let Some((mut last, (_, name, _))) = iter.next() {
|
|
debug_assert_eq!(last, 0);
|
|
let mut last_name = name;
|
|
for (next, (_, next_name, _)) in iter {
|
|
insert(last_name, last, next);
|
|
last = next;
|
|
last_name = next_name;
|
|
}
|
|
insert(last_name, last, importables.len());
|
|
}
|
|
|
|
let importables = importables.into_iter().map(|(item, _, idx)| (item, idx)).collect();
|
|
Arc::new(ImportMap { item_to_info_map: map, fst: builder.into_map(), importables })
|
|
}
|
|
|
|
pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> {
|
|
self.item_to_info_map.get(&item).map(|(info, _)| &**info)
|
|
}
|
|
|
|
fn collect_import_map(db: &dyn DefDatabase, krate: CrateId) -> ImportMapIndex {
|
|
let _p = tracing::info_span!("collect_import_map").entered();
|
|
|
|
let def_map = db.crate_def_map(krate);
|
|
let mut map = FxIndexMap::default();
|
|
|
|
// We look only into modules that are public(ly reexported), starting with the crate root.
|
|
let root = def_map.module_id(DefMap::ROOT);
|
|
let mut worklist = vec![root];
|
|
let mut visited = FxHashSet::default();
|
|
|
|
while let Some(module) = worklist.pop() {
|
|
if !visited.insert(module) {
|
|
continue;
|
|
}
|
|
let ext_def_map;
|
|
let mod_data = if module.krate == krate {
|
|
&def_map[module.local_id]
|
|
} else {
|
|
// The crate might reexport a module defined in another crate.
|
|
ext_def_map = module.def_map(db);
|
|
&ext_def_map[module.local_id]
|
|
};
|
|
|
|
let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
|
|
let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
|
|
if per_ns.is_none() {
|
|
None
|
|
} else {
|
|
Some((name, per_ns))
|
|
}
|
|
});
|
|
|
|
for (name, per_ns) in visible_items {
|
|
for (item, import) in per_ns.iter_items() {
|
|
let attr_id = if let Some(import) = import {
|
|
match import {
|
|
ImportOrExternCrate::ExternCrate(id) => Some(id.into()),
|
|
ImportOrExternCrate::Import(id) => Some(id.import.into()),
|
|
}
|
|
} else {
|
|
match item {
|
|
ItemInNs::Types(id) | ItemInNs::Values(id) => id.try_into().ok(),
|
|
ItemInNs::Macros(id) => Some(id.into()),
|
|
}
|
|
};
|
|
let (is_doc_hidden, is_unstable) = attr_id.map_or((false, false), |attr_id| {
|
|
let attrs = db.attrs(attr_id);
|
|
(attrs.has_doc_hidden(), attrs.is_unstable())
|
|
});
|
|
|
|
let import_info = ImportInfo {
|
|
name: name.clone(),
|
|
container: module,
|
|
is_doc_hidden,
|
|
is_unstable,
|
|
};
|
|
|
|
if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
|
|
Self::collect_trait_assoc_items(
|
|
db,
|
|
&mut map,
|
|
tr,
|
|
matches!(item, ItemInNs::Types(_)),
|
|
&import_info,
|
|
);
|
|
}
|
|
|
|
let (infos, _) =
|
|
map.entry(item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::No));
|
|
infos.reserve_exact(1);
|
|
infos.push(import_info);
|
|
|
|
// If we've just added a module, descend into it.
|
|
if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
|
|
worklist.push(mod_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
map.shrink_to_fit();
|
|
map
|
|
}
|
|
|
|
fn collect_trait_assoc_items(
|
|
db: &dyn DefDatabase,
|
|
map: &mut ImportMapIndex,
|
|
tr: TraitId,
|
|
is_type_in_ns: bool,
|
|
trait_import_info: &ImportInfo,
|
|
) {
|
|
let _p = tracing::info_span!("collect_trait_assoc_items").entered();
|
|
for &(ref assoc_item_name, item) in &db.trait_data(tr).items {
|
|
let module_def_id = match item {
|
|
AssocItemId::FunctionId(f) => ModuleDefId::from(f),
|
|
AssocItemId::ConstId(c) => ModuleDefId::from(c),
|
|
// cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
|
|
// qualifier, ergo no need to store it for imports in import_map
|
|
AssocItemId::TypeAliasId(_) => {
|
|
cov_mark::hit!(type_aliases_ignored);
|
|
continue;
|
|
}
|
|
};
|
|
let assoc_item = if is_type_in_ns {
|
|
ItemInNs::Types(module_def_id)
|
|
} else {
|
|
ItemInNs::Values(module_def_id)
|
|
};
|
|
|
|
let attrs = &db.attrs(item.into());
|
|
let assoc_item_info = ImportInfo {
|
|
container: trait_import_info.container,
|
|
name: assoc_item_name.clone(),
|
|
is_doc_hidden: attrs.has_doc_hidden(),
|
|
is_unstable: attrs.is_unstable(),
|
|
};
|
|
|
|
let (infos, _) =
|
|
map.entry(assoc_item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::Yes));
|
|
infos.reserve_exact(1);
|
|
infos.push(assoc_item_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Eq for ImportMap {}
|
|
impl PartialEq for ImportMap {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
// `fst` and `importables` are built from `map`, so we don't need to compare them.
|
|
self.item_to_info_map == other.item_to_info_map
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for ImportMap {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let mut importable_names: Vec<_> = self
|
|
.item_to_info_map
|
|
.iter()
|
|
.map(|(item, (infos, _))| {
|
|
let l = infos.len();
|
|
match item {
|
|
ItemInNs::Types(it) => format!("- {it:?} (t) [{l}]",),
|
|
ItemInNs::Values(it) => format!("- {it:?} (v) [{l}]",),
|
|
ItemInNs::Macros(it) => format!("- {it:?} (m) [{l}]",),
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
importable_names.sort();
|
|
f.write_str(&importable_names.join("\n"))
|
|
}
|
|
}
|
|
|
|
/// A way to match import map contents against the search query.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub enum SearchMode {
|
|
/// Import map entry should strictly match the query string.
|
|
Exact,
|
|
/// Import map entry should contain all letters from the query string,
|
|
/// in the same order, but not necessary adjacent.
|
|
Fuzzy,
|
|
/// Import map entry should match the query string by prefix.
|
|
Prefix,
|
|
}
|
|
|
|
impl SearchMode {
|
|
pub fn check(self, query: &str, case_sensitive: bool, candidate: &str) -> bool {
|
|
match self {
|
|
SearchMode::Exact if case_sensitive => candidate == query,
|
|
SearchMode::Exact => candidate.eq_ignore_ascii_case(query),
|
|
SearchMode::Prefix => {
|
|
query.len() <= candidate.len() && {
|
|
let prefix = &candidate[..query.len()];
|
|
if case_sensitive {
|
|
prefix == query
|
|
} else {
|
|
prefix.eq_ignore_ascii_case(query)
|
|
}
|
|
}
|
|
}
|
|
SearchMode::Fuzzy => {
|
|
let mut name = candidate;
|
|
query.chars().all(|query_char| {
|
|
let m = if case_sensitive {
|
|
name.match_indices(query_char).next()
|
|
} else {
|
|
name.match_indices([query_char, query_char.to_ascii_uppercase()]).next()
|
|
};
|
|
match m {
|
|
Some((index, _)) => {
|
|
name = &name[index + 1..];
|
|
true
|
|
}
|
|
None => false,
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Three possible ways to search for the name in associated and/or other items.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum AssocSearchMode {
|
|
/// Search for the name in both associated and other items.
|
|
Include,
|
|
/// Search for the name in other items only.
|
|
Exclude,
|
|
/// Search for the name in the associated items only.
|
|
AssocItemsOnly,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Query {
|
|
query: String,
|
|
lowercased: String,
|
|
search_mode: SearchMode,
|
|
assoc_mode: AssocSearchMode,
|
|
case_sensitive: bool,
|
|
}
|
|
|
|
impl Query {
|
|
pub fn new(query: String) -> Self {
|
|
let lowercased = query.to_lowercase();
|
|
Self {
|
|
query,
|
|
lowercased,
|
|
search_mode: SearchMode::Exact,
|
|
assoc_mode: AssocSearchMode::Include,
|
|
case_sensitive: false,
|
|
}
|
|
}
|
|
|
|
/// Fuzzy finds items instead of exact matching.
|
|
pub fn fuzzy(self) -> Self {
|
|
Self { search_mode: SearchMode::Fuzzy, ..self }
|
|
}
|
|
|
|
pub fn prefix(self) -> Self {
|
|
Self { search_mode: SearchMode::Prefix, ..self }
|
|
}
|
|
|
|
pub fn exact(self) -> Self {
|
|
Self { search_mode: SearchMode::Exact, ..self }
|
|
}
|
|
|
|
/// Specifies whether we want to include associated items in the result.
|
|
pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
|
|
Self { assoc_mode, ..self }
|
|
}
|
|
|
|
/// Respect casing of the query string when matching.
|
|
pub fn case_sensitive(self) -> Self {
|
|
Self { case_sensitive: true, ..self }
|
|
}
|
|
|
|
fn matches_assoc_mode(&self, is_trait_assoc_item: IsTraitAssocItem) -> bool {
|
|
!matches!(
|
|
(is_trait_assoc_item, self.assoc_mode),
|
|
(IsTraitAssocItem::Yes, AssocSearchMode::Exclude)
|
|
| (IsTraitAssocItem::No, AssocSearchMode::AssocItemsOnly)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Searches dependencies of `krate` for an importable name matching `query`.
|
|
///
|
|
/// This returns a list of items that could be imported from dependencies of `krate`.
|
|
pub fn search_dependencies(
|
|
db: &dyn DefDatabase,
|
|
krate: CrateId,
|
|
query: &Query,
|
|
) -> FxHashSet<ItemInNs> {
|
|
let _p = tracing::info_span!("search_dependencies", ?query).entered();
|
|
|
|
let graph = db.crate_graph();
|
|
|
|
let import_maps: Vec<_> =
|
|
graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
|
|
|
|
let mut op = fst::map::OpBuilder::new();
|
|
|
|
match query.search_mode {
|
|
SearchMode::Exact => {
|
|
let automaton = fst::automaton::Str::new(&query.lowercased);
|
|
|
|
for map in &import_maps {
|
|
op = op.add(map.fst.search(&automaton));
|
|
}
|
|
search_maps(db, &import_maps, op.union(), query)
|
|
}
|
|
SearchMode::Fuzzy => {
|
|
let automaton = fst::automaton::Subsequence::new(&query.lowercased);
|
|
|
|
for map in &import_maps {
|
|
op = op.add(map.fst.search(&automaton));
|
|
}
|
|
search_maps(db, &import_maps, op.union(), query)
|
|
}
|
|
SearchMode::Prefix => {
|
|
let automaton = fst::automaton::Str::new(&query.lowercased).starts_with();
|
|
|
|
for map in &import_maps {
|
|
op = op.add(map.fst.search(&automaton));
|
|
}
|
|
search_maps(db, &import_maps, op.union(), query)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn search_maps(
|
|
db: &dyn DefDatabase,
|
|
import_maps: &[Arc<ImportMap>],
|
|
mut stream: fst::map::Union<'_>,
|
|
query: &Query,
|
|
) -> FxHashSet<ItemInNs> {
|
|
let mut res = FxHashSet::default();
|
|
while let Some((_, indexed_values)) = stream.next() {
|
|
for &IndexedValue { index: import_map_idx, value } in indexed_values {
|
|
let end = (value & 0xFFFF_FFFF) as usize;
|
|
let start = (value >> 32) as usize;
|
|
let ImportMap { item_to_info_map, importables, .. } = &*import_maps[import_map_idx];
|
|
let importables = &importables[start..end];
|
|
|
|
let iter = importables
|
|
.iter()
|
|
.copied()
|
|
.filter_map(|(item, info_idx)| {
|
|
let (import_infos, assoc_mode) = &item_to_info_map[&item];
|
|
query
|
|
.matches_assoc_mode(*assoc_mode)
|
|
.then(|| (item, &import_infos[info_idx as usize]))
|
|
})
|
|
.filter(|&(_, info)| {
|
|
query.search_mode.check(
|
|
&query.query,
|
|
query.case_sensitive,
|
|
&info.name.unescaped().display(db.upcast()).to_smolstr(),
|
|
)
|
|
});
|
|
res.extend(iter.map(TupleExt::head));
|
|
}
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use base_db::{SourceDatabase, Upcast};
|
|
use expect_test::{expect, Expect};
|
|
use test_fixture::WithFixture;
|
|
|
|
use crate::{test_db::TestDB, ItemContainerId, Lookup};
|
|
|
|
use super::*;
|
|
|
|
impl ImportMap {
|
|
fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
|
|
let mut importable_paths: Vec<_> = self
|
|
.item_to_info_map
|
|
.iter()
|
|
.flat_map(|(item, (info, _))| info.iter().map(move |info| (item, info)))
|
|
.map(|(item, info)| {
|
|
let path = render_path(db, info);
|
|
let ns = match item {
|
|
ItemInNs::Types(_) => "t",
|
|
ItemInNs::Values(_) => "v",
|
|
ItemInNs::Macros(_) => "m",
|
|
};
|
|
format!("- {path} ({ns})")
|
|
})
|
|
.collect();
|
|
|
|
importable_paths.sort();
|
|
importable_paths.join("\n")
|
|
}
|
|
}
|
|
|
|
fn check_search(ra_fixture: &str, crate_name: &str, query: Query, expect: Expect) {
|
|
let db = TestDB::with_files(ra_fixture);
|
|
let crate_graph = db.crate_graph();
|
|
let krate = crate_graph
|
|
.iter()
|
|
.find(|&krate| {
|
|
crate_graph[krate]
|
|
.display_name
|
|
.as_ref()
|
|
.is_some_and(|it| &**it.crate_name() == crate_name)
|
|
})
|
|
.expect("could not find crate");
|
|
|
|
let actual = search_dependencies(db.upcast(), krate, &query)
|
|
.into_iter()
|
|
.filter_map(|dependency| {
|
|
let dependency_krate = dependency.krate(db.upcast())?;
|
|
let dependency_imports = db.import_map(dependency_krate);
|
|
|
|
let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
|
|
Some(assoc_item_path) => (assoc_item_path, "a"),
|
|
None => (
|
|
render_path(&db, &dependency_imports.import_info_for(dependency)?[0]),
|
|
match dependency {
|
|
ItemInNs::Types(ModuleDefId::FunctionId(_))
|
|
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
|
|
ItemInNs::Types(_) => "t",
|
|
ItemInNs::Values(_) => "v",
|
|
ItemInNs::Macros(_) => "m",
|
|
},
|
|
),
|
|
};
|
|
|
|
Some(format!(
|
|
"{}::{} ({})\n",
|
|
crate_graph[dependency_krate].display_name.as_ref()?,
|
|
path,
|
|
mark
|
|
))
|
|
})
|
|
// HashSet iteration order isn't defined - it's different on
|
|
// x86_64 and i686 at the very least
|
|
.sorted()
|
|
.collect::<String>();
|
|
expect.assert_eq(&actual)
|
|
}
|
|
|
|
fn assoc_item_path(
|
|
db: &dyn DefDatabase,
|
|
dependency_imports: &ImportMap,
|
|
dependency: ItemInNs,
|
|
) -> Option<String> {
|
|
let (dependency_assoc_item_id, container) = match dependency.as_module_def_id()? {
|
|
ModuleDefId::FunctionId(id) => (AssocItemId::from(id), id.lookup(db).container),
|
|
ModuleDefId::ConstId(id) => (AssocItemId::from(id), id.lookup(db).container),
|
|
ModuleDefId::TypeAliasId(id) => (AssocItemId::from(id), id.lookup(db).container),
|
|
_ => return None,
|
|
};
|
|
|
|
let ItemContainerId::TraitId(trait_id) = container else {
|
|
return None;
|
|
};
|
|
|
|
let trait_info = dependency_imports.import_info_for(ItemInNs::Types(trait_id.into()))?;
|
|
|
|
let trait_data = db.trait_data(trait_id);
|
|
let (assoc_item_name, _) = trait_data
|
|
.items
|
|
.iter()
|
|
.find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
|
|
// FIXME: This should check all import infos, not just the first
|
|
Some(format!(
|
|
"{}::{}",
|
|
render_path(db, &trait_info[0]),
|
|
assoc_item_name.display(db.upcast(), Edition::CURRENT)
|
|
))
|
|
}
|
|
|
|
fn check(ra_fixture: &str, expect: Expect) {
|
|
let db = TestDB::with_files(ra_fixture);
|
|
let crate_graph = db.crate_graph();
|
|
|
|
let actual = crate_graph
|
|
.iter()
|
|
.filter_map(|krate| {
|
|
let cdata = &crate_graph[krate];
|
|
let name = cdata.display_name.as_ref()?;
|
|
|
|
let map = db.import_map(krate);
|
|
|
|
Some(format!("{name}:\n{}\n", map.fmt_for_test(db.upcast())))
|
|
})
|
|
.sorted()
|
|
.collect::<String>();
|
|
|
|
expect.assert_eq(&actual)
|
|
}
|
|
|
|
fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
|
|
let mut module = info.container;
|
|
let mut segments = vec![&info.name];
|
|
|
|
let def_map = module.def_map(db);
|
|
assert!(def_map.block_id().is_none(), "block local items should not be in `ImportMap`");
|
|
|
|
while let Some(parent) = module.containing_module(db) {
|
|
let parent_data = &def_map[parent.local_id];
|
|
let (name, _) =
|
|
parent_data.children.iter().find(|(_, id)| **id == module.local_id).unwrap();
|
|
segments.push(name);
|
|
module = parent;
|
|
}
|
|
|
|
segments.iter().rev().map(|it| it.display(db.upcast(), Edition::CURRENT)).join("::")
|
|
}
|
|
|
|
#[test]
|
|
fn smoke() {
|
|
check(
|
|
r"
|
|
//- /main.rs crate:main deps:lib
|
|
|
|
mod private {
|
|
pub use lib::Pub;
|
|
pub struct InPrivateModule;
|
|
}
|
|
|
|
pub mod publ1 {
|
|
use lib::Pub;
|
|
}
|
|
|
|
pub mod real_pub {
|
|
pub use lib::Pub;
|
|
}
|
|
pub mod real_pu2 { // same path length as above
|
|
pub use lib::Pub;
|
|
}
|
|
|
|
//- /lib.rs crate:lib
|
|
pub struct Pub {}
|
|
pub struct Pub2; // t + v
|
|
struct Priv;
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- Pub (t)
|
|
- Pub2 (t)
|
|
- Pub2 (v)
|
|
main:
|
|
- publ1 (t)
|
|
- real_pu2 (t)
|
|
- real_pu2::Pub (t)
|
|
- real_pub (t)
|
|
- real_pub::Pub (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn prefers_shortest_path() {
|
|
check(
|
|
r"
|
|
//- /main.rs crate:main
|
|
|
|
pub mod sub {
|
|
pub mod subsub {
|
|
pub struct Def {}
|
|
}
|
|
|
|
pub use super::sub::subsub::Def;
|
|
}
|
|
",
|
|
expect![[r#"
|
|
main:
|
|
- sub (t)
|
|
- sub::Def (t)
|
|
- sub::subsub (t)
|
|
- sub::subsub::Def (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn type_reexport_cross_crate() {
|
|
// Reexports need to be visible from a crate, even if the original crate exports the item
|
|
// at a shorter path.
|
|
check(
|
|
r"
|
|
//- /main.rs crate:main deps:lib
|
|
pub mod m {
|
|
pub use lib::S;
|
|
}
|
|
//- /lib.rs crate:lib
|
|
pub struct S;
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- S (t)
|
|
- S (v)
|
|
main:
|
|
- m (t)
|
|
- m::S (t)
|
|
- m::S (v)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn macro_reexport() {
|
|
check(
|
|
r"
|
|
//- /main.rs crate:main deps:lib
|
|
pub mod m {
|
|
pub use lib::pub_macro;
|
|
}
|
|
//- /lib.rs crate:lib
|
|
#[macro_export]
|
|
macro_rules! pub_macro {
|
|
() => {};
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- pub_macro (m)
|
|
main:
|
|
- m (t)
|
|
- m::pub_macro (m)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn module_reexport() {
|
|
// Reexporting modules from a dependency adds all contents to the import map.
|
|
// XXX: The rendered paths are relative to the defining crate.
|
|
check(
|
|
r"
|
|
//- /main.rs crate:main deps:lib
|
|
pub use lib::module as reexported_module;
|
|
//- /lib.rs crate:lib
|
|
pub mod module {
|
|
pub struct S;
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- module (t)
|
|
- module::S (t)
|
|
- module::S (v)
|
|
main:
|
|
- module::S (t)
|
|
- module::S (v)
|
|
- reexported_module (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cyclic_module_reexport() {
|
|
// A cyclic reexport does not hang.
|
|
check(
|
|
r"
|
|
//- /lib.rs crate:lib
|
|
pub mod module {
|
|
pub struct S;
|
|
pub use super::sub::*;
|
|
}
|
|
|
|
pub mod sub {
|
|
pub use super::module;
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- module (t)
|
|
- module::S (t)
|
|
- module::S (v)
|
|
- module::module (t)
|
|
- sub (t)
|
|
- sub::module (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn private_macro() {
|
|
check(
|
|
r"
|
|
//- /lib.rs crate:lib
|
|
macro_rules! private_macro {
|
|
() => {};
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn namespacing() {
|
|
check(
|
|
r"
|
|
//- /lib.rs crate:lib
|
|
pub struct Thing; // t + v
|
|
#[macro_export]
|
|
macro_rules! Thing { // m
|
|
() => {};
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- Thing (m)
|
|
- Thing (t)
|
|
- Thing (v)
|
|
"#]],
|
|
);
|
|
|
|
check(
|
|
r"
|
|
//- /lib.rs crate:lib
|
|
pub mod Thing {} // t
|
|
#[macro_export]
|
|
macro_rules! Thing { // m
|
|
() => {};
|
|
}
|
|
",
|
|
expect![[r#"
|
|
lib:
|
|
- Thing (m)
|
|
- Thing (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fuzzy_import_trait_and_assoc_items() {
|
|
cov_mark::check!(type_aliases_ignored);
|
|
let ra_fixture = r#"
|
|
//- /main.rs crate:main deps:dep
|
|
//- /dep.rs crate:dep
|
|
pub mod fmt {
|
|
pub trait Display {
|
|
type FmtTypeAlias;
|
|
const FMT_CONST: bool;
|
|
|
|
fn format_function();
|
|
fn format_method(&self);
|
|
}
|
|
}
|
|
"#;
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()).fuzzy(),
|
|
expect![[r#"
|
|
dep::fmt (t)
|
|
dep::fmt::Display::FMT_CONST (a)
|
|
dep::fmt::Display::format_function (a)
|
|
dep::fmt::Display::format_method (a)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assoc_items_filtering() {
|
|
let ra_fixture = r#"
|
|
//- /main.rs crate:main deps:dep
|
|
//- /dep.rs crate:dep
|
|
pub mod fmt {
|
|
pub trait Display {
|
|
type FmtTypeAlias;
|
|
const FMT_CONST: bool;
|
|
|
|
fn format_function();
|
|
fn format_method(&self);
|
|
}
|
|
}
|
|
"#;
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()).fuzzy().assoc_search_mode(AssocSearchMode::AssocItemsOnly),
|
|
expect![[r#"
|
|
dep::fmt::Display::FMT_CONST (a)
|
|
dep::fmt::Display::format_function (a)
|
|
dep::fmt::Display::format_method (a)
|
|
"#]],
|
|
);
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()).fuzzy().assoc_search_mode(AssocSearchMode::Exclude),
|
|
expect![[r#"
|
|
dep::fmt (t)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn search_mode() {
|
|
let ra_fixture = r#"
|
|
//- /main.rs crate:main deps:dep
|
|
//- /dep.rs crate:dep deps:tdep
|
|
use tdep::fmt as fmt_dep;
|
|
pub mod fmt {
|
|
pub trait Display {
|
|
fn fmt();
|
|
}
|
|
}
|
|
#[macro_export]
|
|
macro_rules! Fmt {
|
|
() => {};
|
|
}
|
|
pub struct Fmt;
|
|
|
|
pub fn format() {}
|
|
pub fn no() {}
|
|
|
|
//- /tdep.rs crate:tdep
|
|
pub mod fmt {
|
|
pub struct NotImportableFromMain;
|
|
}
|
|
"#;
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()).fuzzy(),
|
|
expect![[r#"
|
|
dep::Fmt (m)
|
|
dep::Fmt (t)
|
|
dep::Fmt (v)
|
|
dep::fmt (t)
|
|
dep::fmt::Display::fmt (a)
|
|
dep::format (f)
|
|
"#]],
|
|
);
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()),
|
|
expect![[r#"
|
|
dep::Fmt (m)
|
|
dep::Fmt (t)
|
|
dep::Fmt (v)
|
|
dep::fmt (t)
|
|
dep::fmt::Display::fmt (a)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn name_only() {
|
|
let ra_fixture = r#"
|
|
//- /main.rs crate:main deps:dep
|
|
//- /dep.rs crate:dep deps:tdep
|
|
use tdep::fmt as fmt_dep;
|
|
pub mod fmt {
|
|
pub trait Display {
|
|
fn fmt();
|
|
}
|
|
}
|
|
#[macro_export]
|
|
macro_rules! Fmt {
|
|
() => {};
|
|
}
|
|
pub struct Fmt;
|
|
|
|
pub fn format() {}
|
|
pub fn no() {}
|
|
|
|
//- /tdep.rs crate:tdep
|
|
pub mod fmt {
|
|
pub struct NotImportableFromMain;
|
|
}
|
|
"#;
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("fmt".to_owned()),
|
|
expect![[r#"
|
|
dep::Fmt (m)
|
|
dep::Fmt (t)
|
|
dep::Fmt (v)
|
|
dep::fmt (t)
|
|
dep::fmt::Display::fmt (a)
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn search_casing() {
|
|
let ra_fixture = r#"
|
|
//- /main.rs crate:main deps:dep
|
|
//- /dep.rs crate:dep
|
|
|
|
pub struct fmt;
|
|
pub struct FMT;
|
|
"#;
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("FMT".to_owned()),
|
|
expect![[r#"
|
|
dep::FMT (t)
|
|
dep::FMT (v)
|
|
dep::fmt (t)
|
|
dep::fmt (v)
|
|
"#]],
|
|
);
|
|
|
|
check_search(
|
|
ra_fixture,
|
|
"main",
|
|
Query::new("FMT".to_owned()).case_sensitive(),
|
|
expect![[r#"
|
|
dep::FMT (t)
|
|
dep::FMT (v)
|
|
"#]],
|
|
);
|
|
}
|
|
}
|