Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21464 from Veykril/push-myvkmuxpzkvp
fix: Fix path symbol search not respecting re-exports
Lukas Wirth 3 months ago
parent 05e5e72 · parent f27ddc9 · commit a0daa3e
-rw-r--r--crates/hir/src/lib.rs17
-rw-r--r--crates/ide-db/src/symbol_index.rs97
2 files changed, 91 insertions, 23 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 78be5a7e8f..252d71fb80 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -610,6 +610,23 @@ impl Module {
res
}
+ pub fn modules_in_scope(&self, db: &dyn HirDatabase, pub_only: bool) -> Vec<(Name, Module)> {
+ let def_map = self.id.def_map(db);
+ let scope = &def_map[self.id].scope;
+
+ let mut res = Vec::new();
+
+ for (name, item) in scope.types() {
+ if let ModuleDefId::ModuleId(m) = item.def
+ && (!pub_only || item.vis == Visibility::Public)
+ {
+ res.push((name.clone(), Module { id: m }));
+ }
+ }
+
+ res
+ }
+
/// Returns a `ModuleScope`: a set of items, visible in this module.
pub fn scope(
self,
diff --git a/crates/ide-db/src/symbol_index.rs b/crates/ide-db/src/symbol_index.rs
index c95b541748..d7f4c66f46 100644
--- a/crates/ide-db/src/symbol_index.rs
+++ b/crates/ide-db/src/symbol_index.rs
@@ -35,6 +35,7 @@ use hir::{
import_map::{AssocSearchMode, SearchMode},
symbols::{FileSymbol, SymbolCollector},
};
+use itertools::Itertools;
use rayon::prelude::*;
use salsa::Update;
@@ -224,12 +225,10 @@ pub fn world_symbols(db: &RootDatabase, mut query: Query) -> Vec<FileSymbol<'_>>
// Search for crates by name (handles "::" and "::foo" queries)
let indices: Vec<_> = if query.is_crate_search() {
query.only_types = false;
- query.libs = true;
vec![SymbolIndex::extern_prelude_symbols(db)]
// If we have a path filter, resolve it to target modules
} else if !query.path_filter.is_empty() {
query.only_types = false;
- query.libs = true;
let target_modules = resolve_path_to_modules(
db,
&query.path_filter,
@@ -313,11 +312,11 @@ fn resolve_path_to_modules(
// If anchor_to_crate is true, first segment MUST be a crate name
// If anchor_to_crate is false, first segment could be a crate OR a module in local crates
- let mut candidate_modules: Vec<Module> = vec![];
+ let mut candidate_modules: Vec<(Module, bool)> = vec![];
// Add crate root modules for matching crates
for krate in matching_crates {
- candidate_modules.push(krate.root_module(db));
+ candidate_modules.push((krate.root_module(db), krate.origin(db).is_local()));
}
// If not anchored to crate, also search for modules matching first segment in local crates
@@ -329,7 +328,7 @@ fn resolve_path_to_modules(
if let Some(name) = child.name(db)
&& names_match(name.as_str(), first_segment)
{
- candidate_modules.push(child);
+ candidate_modules.push((child, true));
}
}
}
@@ -340,11 +339,14 @@ fn resolve_path_to_modules(
for segment in rest_segments {
candidate_modules = candidate_modules
.into_iter()
- .flat_map(|module| {
- module.children(db).filter(|child| {
- child.name(db).is_some_and(|name| names_match(name.as_str(), segment))
- })
+ .flat_map(|(module, local)| {
+ module
+ .modules_in_scope(db, !local)
+ .into_iter()
+ .filter(|(name, _)| names_match(name.as_str(), segment))
+ .map(move |(_, module)| (module, local))
})
+ .unique()
.collect();
if candidate_modules.is_empty() {
@@ -352,7 +354,7 @@ fn resolve_path_to_modules(
}
}
- candidate_modules
+ candidate_modules.into_iter().map(|(module, _)| module).collect()
}
#[derive(Default)]
@@ -839,7 +841,7 @@ pub struct Foo;
assert_eq!(item, "foo");
assert!(anchor);
- // Trailing :: (module browsing)
+ // Trailing ::
let (path, item, anchor) = Query::parse_path_query("foo::");
assert_eq!(path, vec!["foo"]);
assert_eq!(item, "");
@@ -909,7 +911,7 @@ pub mod nested {
}
#[test]
- fn test_module_browsing() {
+ fn test_path_search_module() {
let (mut db, _) = RootDatabase::with_many_files(
r#"
//- /lib.rs crate:main
@@ -1066,20 +1068,11 @@ pub fn root_fn() {}
let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"RootItem"), "Expected RootItem at crate root in {:?}", names);
- // Browse crate root
let query = Query::new("mylib::".to_owned());
let symbols = world_symbols(&db, query);
let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
- assert!(
- names.contains(&"RootItem"),
- "Expected RootItem when browsing crate root in {:?}",
- names
- );
- assert!(
- names.contains(&"root_fn"),
- "Expected root_fn when browsing crate root in {:?}",
- names
- );
+ assert!(names.contains(&"RootItem"), "Expected RootItem {:?}", names);
+ assert!(names.contains(&"root_fn"), "Expected root_fn {:?}", names);
}
#[test]
@@ -1163,4 +1156,62 @@ pub struct FooStruct;
let symbols = world_symbols(&db, query);
assert!(symbols.is_empty(), "Expected empty results for non-matching crate pattern");
}
+
+ #[test]
+ fn test_path_search_with_use_reexport() {
+ // Test that module resolution works for `use` items (re-exports), not just `mod` items
+ let (mut db, _) = RootDatabase::with_many_files(
+ r#"
+//- /lib.rs crate:main
+mod inner;
+pub use inner::nested;
+
+//- /inner.rs
+pub mod nested {
+ pub struct NestedStruct;
+ pub fn nested_fn() {}
+}
+"#,
+ );
+
+ let mut local_roots = FxHashSet::default();
+ local_roots.insert(WORKSPACE);
+ LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
+
+ // Search via the re-exported path (main::nested::NestedStruct)
+ // This should work because `nested` is in scope via `pub use inner::nested`
+ let query = Query::new("main::nested::NestedStruct".to_owned());
+ let symbols = world_symbols(&db, query);
+ let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
+ assert!(
+ names.contains(&"NestedStruct"),
+ "Expected NestedStruct via re-exported path in {:?}",
+ names
+ );
+
+ // Also verify the original path still works
+ let query = Query::new("main::inner::nested::NestedStruct".to_owned());
+ let symbols = world_symbols(&db, query);
+ let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
+ assert!(
+ names.contains(&"NestedStruct"),
+ "Expected NestedStruct via original path in {:?}",
+ names
+ );
+
+ // Browse the re-exported module
+ let query = Query::new("main::nested::".to_owned());
+ let symbols = world_symbols(&db, query);
+ let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
+ assert!(
+ names.contains(&"NestedStruct"),
+ "Expected NestedStruct when browsing re-exported module in {:?}",
+ names
+ );
+ assert!(
+ names.contains(&"nested_fn"),
+ "Expected nested_fn when browsing re-exported module in {:?}",
+ names
+ );
+ }
}