diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs index 19f103855d..94a08aa63c 100644 --- a/crates/ra_hir/src/code_model_api.rs +++ b/crates/ra_hir/src/code_model_api.rs @@ -327,7 +327,7 @@ impl Enum { db.enum_data(*self).name.clone() } - pub fn variants(&self, db: &impl HirDatabase) -> Vec { + pub fn variants(&self, db: &impl PersistentHirDatabase) -> Vec { db.enum_data(*self) .variants .iter() @@ -389,7 +389,7 @@ impl EnumVariant { self.parent } - pub fn name(&self, db: &impl HirDatabase) -> Option { + pub fn name(&self, db: &impl PersistentHirDatabase) -> Option { db.enum_data(self.parent).variants[self.id].name.clone() } diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs index aba0c99689..50d4e824cd 100644 --- a/crates/ra_hir/src/marks.rs +++ b/crates/ra_hir/src/marks.rs @@ -4,4 +4,6 @@ test_utils::marks!( type_var_cycles_resolve_completely type_var_cycles_resolve_as_possible type_var_resolves_to_int_var + glob_enum + glob_across_crates ); diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs index b7382d9c31..94f7db0241 100644 --- a/crates/ra_hir/src/nameres.rs +++ b/crates/ra_hir/src/nameres.rs @@ -61,7 +61,7 @@ impl ModuleScope { /// `Resolution` is basically `DefId` atm, but it should account for stuff like /// multiple namespaces, ambiguity and errors. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Resolution { /// None for unresolved pub def: PerNs, @@ -154,6 +154,8 @@ struct Resolver<'a, DB> { krate: Crate, module_tree: Arc, processed_imports: FxHashSet<(ModuleId, ImportId)>, + /// If module `a` has `use b::*`, then this contains the mapping b -> a (and the import) + glob_imports: FxHashMap>, result: ItemMap, } @@ -173,6 +175,7 @@ where krate, module_tree, processed_imports: FxHashSet::default(), + glob_imports: FxHashMap::default(), result: ItemMap::default(), } } @@ -264,14 +267,72 @@ where import: &ImportData, ) -> ReachedFixedPoint { log::debug!("resolving import: {:?}", import); - if import.is_glob { - return ReachedFixedPoint::Yes; - }; let original_module = Module { krate: self.krate, module_id }; let (def, reached_fixedpoint) = self.result.resolve_path_fp(self.db, original_module, &import.path); - if reached_fixedpoint == ReachedFixedPoint::Yes { + if reached_fixedpoint != ReachedFixedPoint::Yes { + return reached_fixedpoint; + } + + if import.is_glob { + log::debug!("glob import: {:?}", import); + match def.take_types() { + Some(ModuleDef::Module(m)) => { + if m.krate != self.krate { + tested_by!(glob_across_crates); + // glob import from other crate => we can just import everything once + let item_map = self.db.item_map(m.krate); + let scope = &item_map[m.module_id]; + let items = scope + .items + .iter() + .map(|(name, res)| (name.clone(), res.clone())) + .collect::>(); + self.update(module_id, Some(import_id), &items); + } else { + // glob import from same crate => we do an initial + // import, and then need to propagate any further + // additions + let scope = &self.result[m.module_id]; + let items = scope + .items + .iter() + .map(|(name, res)| (name.clone(), res.clone())) + .collect::>(); + self.update(module_id, Some(import_id), &items); + // record the glob import in case we add further items + self.glob_imports + .entry(m.module_id) + .or_default() + .push((module_id, import_id)); + } + } + Some(ModuleDef::Enum(e)) => { + tested_by!(glob_enum); + // glob import from enum => just import all the variants + let variants = e.variants(self.db); + let resolutions = variants + .into_iter() + .filter_map(|variant| { + let res = Resolution { + def: PerNs::both(variant.into(), e.into()), + import: Some(import_id), + }; + let name = variant.name(self.db)?; + Some((name, res)) + }) + .collect::>(); + self.update(module_id, Some(import_id), &resolutions); + } + Some(d) => { + log::debug!("glob import {:?} from non-module/enum {:?}", import, d); + } + None => { + log::debug!("glob import {:?} didn't resolve as type", import); + } + } + } else { let last_segment = import.path.segments.last().unwrap(); let name = import.alias.clone().unwrap_or_else(|| last_segment.name.clone()); log::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); @@ -284,17 +345,61 @@ where } } } - self.update(module_id, |items| { - let res = Resolution { def, import: Some(import_id) }; - items.items.insert(name, res); - }); + let resolution = Resolution { def, import: Some(import_id) }; + self.update(module_id, None, &[(name, resolution)]); } reached_fixedpoint } - fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) { + fn update( + &mut self, + module_id: ModuleId, + import: Option, + resolutions: &[(Name, Resolution)], + ) { + self.update_recursive(module_id, import, resolutions, 0) + } + + fn update_recursive( + &mut self, + module_id: ModuleId, + import: Option, + resolutions: &[(Name, Resolution)], + depth: usize, + ) { + if depth > 100 { + // prevent stack overflows (but this shouldn't be possible) + panic!("infinite recursion in glob imports!"); + } let module_items = self.result.per_module.get_mut(module_id).unwrap(); - f(module_items) + let mut changed = false; + for (name, res) in resolutions { + let existing = module_items.items.entry(name.clone()).or_default(); + if existing.def.types.is_none() && res.def.types.is_some() { + existing.def.types = res.def.types; + existing.import = import.or(res.import); + changed = true; + } + if existing.def.values.is_none() && res.def.values.is_some() { + existing.def.values = res.def.values; + existing.import = import.or(res.import); + changed = true; + } + } + if !changed { + return; + } + let glob_imports = self + .glob_imports + .get(&module_id) + .into_iter() + .flat_map(|v| v.iter()) + .cloned() + .collect::>(); + for (glob_importing_module, glob_import) in glob_imports { + // We pass the glob import so that the tracked import in those modules is that glob import + self.update_recursive(glob_importing_module, Some(glob_import), resolutions, depth + 1); + } } } diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs index 3dfad6bf2a..9c0e4ef293 100644 --- a/crates/ra_hir/src/nameres/tests.rs +++ b/crates/ra_hir/src/nameres/tests.rs @@ -164,6 +164,126 @@ fn re_exports() { ); } +#[test] +fn glob_1() { + let (item_map, module_id) = item_map( + " + //- /lib.rs + mod foo; + use foo::*; + <|> + + //- /foo/mod.rs + pub mod bar; + pub use self::bar::Baz; + pub struct Foo; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + check_module_item_map( + &item_map, + module_id, + " + Baz: t v + Foo: t v + bar: t + foo: t + ", + ); +} + +#[test] +fn glob_2() { + let (item_map, module_id) = item_map( + " + //- /lib.rs + mod foo; + use foo::*; + <|> + + //- /foo/mod.rs + pub mod bar; + pub use self::bar::*; + pub struct Foo; + + //- /foo/bar.rs + pub struct Baz; + pub use super::*; + ", + ); + check_module_item_map( + &item_map, + module_id, + " + Baz: t v + Foo: t v + bar: t + foo: t + ", + ); +} + +#[test] +fn glob_enum() { + covers!(glob_enum); + let (item_map, module_id) = item_map( + " + //- /lib.rs + enum Foo { + Bar, Baz + } + use self::Foo::*; + <|> + ", + ); + check_module_item_map( + &item_map, + module_id, + " + Bar: t v + Baz: t v + Foo: t + ", + ); +} + +#[test] +fn glob_across_crates() { + covers!(glob_across_crates); + let (mut db, sr) = MockDatabase::with_files( + " + //- /main.rs + use test_crate::*; + + //- /lib.rs + pub struct Baz; + ", + ); + let main_id = sr.files[RelativePath::new("/main.rs")]; + let lib_id = sr.files[RelativePath::new("/lib.rs")]; + + let mut crate_graph = CrateGraph::default(); + let main_crate = crate_graph.add_crate_root(main_id); + let lib_crate = crate_graph.add_crate_root(lib_id); + crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate).unwrap(); + + db.set_crate_graph(Arc::new(crate_graph)); + + let module = crate::source_binder::module_from_file_id(&db, main_id).unwrap(); + let krate = module.krate(&db).unwrap(); + let item_map = db.item_map(krate); + + check_module_item_map( + &item_map, + module.module_id, + " + Baz: t v + ", + ); +} + #[test] fn module_resolution_works_for_non_standard_filenames() { let (item_map, module_id) = item_map_custom_crate_root(