From 896dfacfc47068df716fe4969a68adefadb1693e Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 21 Jan 2021 15:22:17 +0100 Subject: Add name resolution query for block expressions --- crates/hir/src/db.rs | 14 ++--- crates/hir_def/src/db.rs | 7 ++- crates/hir_def/src/item_tree.rs | 7 +++ crates/hir_def/src/nameres.rs | 95 +++++++++++++++++++++++++------ crates/hir_def/src/nameres/collector.rs | 58 +++++++++++++------ crates/hir_def/src/nameres/tests.rs | 20 +++++++ crates/hir_def/src/nameres/tests/block.rs | 47 +++++++++++++++ crates/ide_db/src/apply_change.rs | 1 + 8 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 crates/hir_def/src/nameres/tests/block.rs (limited to 'crates') diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index d5d4cf5b6..d444f4bbb 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -1,13 +1,13 @@ //! FIXME: write short doc here pub use hir_def::db::{ - AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery, - CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, EnumDataQuery, ExprScopesQuery, - FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, InternConstQuery, - InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery, - InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery, - ItemTreeQuery, LangItemQuery, StaticDataQuery, StructDataQuery, TraitDataQuery, - TypeAliasDataQuery, UnionDataQuery, + AttrsQuery, BlockDefMapQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, + CrateDefMapQueryQuery, CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, EnumDataQuery, + ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, + InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, + InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, + InternUnionQuery, ItemTreeQuery, LangItemQuery, StaticDataQuery, StructDataQuery, + TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, HygieneFrameQuery, InternEagerExpansionQuery, diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs index 91c8d45cd..a87c80b8a 100644 --- a/crates/hir_def/src/db.rs +++ b/crates/hir_def/src/db.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use base_db::{salsa, CrateId, SourceDatabase, Upcast}; -use hir_expand::{db::AstDatabase, HirFileId}; +use hir_expand::{db::AstDatabase, AstId, HirFileId}; use la_arena::ArenaMap; -use syntax::SmolStr; +use syntax::{ast, SmolStr}; use crate::{ adt::{EnumData, StructData}, @@ -55,6 +55,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(DefMap::crate_def_map_query)] fn crate_def_map_query(&self, krate: CrateId) -> Arc; + #[salsa::invoke(DefMap::block_def_map_query)] + fn block_def_map(&self, krate: CrateId, block: AstId) -> Arc; + #[salsa::invoke(StructData::struct_data_query)] fn struct_data(&self, id: StructId) -> Arc; #[salsa::invoke(StructData::union_data_query)] diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 6494cebd3..1226d7d85 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs @@ -195,6 +195,13 @@ impl ItemTree { } } + pub fn inner_items_of_block(&self, block: FileAstId) -> &[ModItem] { + match &self.data { + Some(data) => data.inner_items.get(&block).map(|it| &**it).unwrap_or(&[]), + None => &[], + } + } + pub fn source(&self, db: &dyn DefDatabase, of: ItemTreeId) -> S::Source { // This unwrap cannot fail, since it has either succeeded above, or resulted in an empty // ItemTree (in which case there is no valid `FileItemTreeId` to call this method with). diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index a3200c710..93931a21a 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs @@ -61,7 +61,7 @@ use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile}; use la_arena::Arena; use rustc_hash::FxHashMap; use stdx::format_to; -use syntax::ast; +use syntax::{ast, AstNode}; use crate::{ db::DefDatabase, @@ -75,6 +75,7 @@ use crate::{ /// Contains all top-level defs from a macro-expanded crate #[derive(Debug, PartialEq, Eq)] pub struct DefMap { + parent: Option>, root: LocalModuleId, modules: Arena, krate: CrateId, @@ -181,24 +182,50 @@ impl DefMap { let _p = profile::span("crate_def_map_query").detail(|| { db.crate_graph()[krate].display_name.as_deref().unwrap_or_default().to_string() }); - let def_map = { - let edition = db.crate_graph()[krate].edition; - let mut modules: Arena = Arena::default(); - let root = modules.alloc(ModuleData::default()); - DefMap { - krate, - edition, - extern_prelude: FxHashMap::default(), - prelude: None, - root, - modules, - diagnostics: Vec::new(), - } - }; - let def_map = collector::collect_defs(db, def_map); + let edition = db.crate_graph()[krate].edition; + let def_map = DefMap::empty(krate, edition); + let def_map = collector::collect_defs(db, def_map, None); + Arc::new(def_map) + } + + pub(crate) fn block_def_map_query( + db: &dyn DefDatabase, + krate: CrateId, + block: AstId, + ) -> Arc { + let item_tree = db.item_tree(block.file_id); + let block_items = item_tree.inner_items_of_block(block.value); + + let parent = parent_def_map(db, krate, block); + + if block_items.is_empty() { + // If there are no inner items, nothing new is brought into scope, so we can just return + // the parent DefMap. This keeps DefMap parent chains short. + return parent; + } + + let mut def_map = DefMap::empty(krate, parent.edition); + def_map.parent = Some(parent); + + let def_map = collector::collect_defs(db, def_map, Some(block.value)); Arc::new(def_map) } + fn empty(krate: CrateId, edition: Edition) -> DefMap { + let mut modules: Arena = Arena::default(); + let root = modules.alloc(ModuleData::default()); + DefMap { + parent: None, + krate, + edition, + extern_prelude: FxHashMap::default(), + prelude: None, + root, + modules, + diagnostics: Vec::new(), + } + } + pub fn add_diagnostics( &self, db: &dyn DefDatabase, @@ -251,7 +278,12 @@ impl DefMap { // even), as this should be a great debugging aid. pub fn dump(&self) -> String { let mut buf = String::new(); - go(&mut buf, self, "crate", self.root); + let mut current_map = self; + while let Some(parent) = ¤t_map.parent { + go(&mut buf, current_map, "block scope", current_map.root); + current_map = &**parent; + } + go(&mut buf, current_map, "crate", current_map.root); return buf; fn go(buf: &mut String, map: &DefMap, path: &str, module: LocalModuleId) { @@ -303,6 +335,35 @@ impl ModuleData { } } +fn parent_def_map( + db: &dyn DefDatabase, + krate: CrateId, + block: AstId, +) -> Arc { + // FIXME: store this info in the item tree instead of reparsing here + let ast_id_map = db.ast_id_map(block.file_id); + let block_ptr = ast_id_map.get(block.value); + let root = match db.parse_or_expand(block.file_id) { + Some(it) => it, + None => { + return Arc::new(DefMap::empty(krate, Edition::Edition2018)); + } + }; + let ast = block_ptr.to_node(&root); + + for ancestor in ast.syntax().ancestors().skip(1) { + if let Some(block_expr) = ast::BlockExpr::cast(ancestor) { + let ancestor_id = ast_id_map.ast_id(&block_expr); + let ast_id = InFile::new(block.file_id, ancestor_id); + let parent_map = db.block_def_map(krate, ast_id); + return parent_map; + } + } + + // No enclosing block scope, so the parent is the crate-level DefMap. + db.crate_def_map(krate) +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleSource { SourceFile(ast::SourceFile), diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 61da56340..cd68efbe6 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -45,7 +45,11 @@ const GLOB_RECURSION_LIMIT: usize = 100; const EXPANSION_DEPTH_LIMIT: usize = 128; const FIXED_POINT_LIMIT: usize = 8192; -pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap) -> DefMap { +pub(super) fn collect_defs( + db: &dyn DefDatabase, + mut def_map: DefMap, + block: Option>, +) -> DefMap { let crate_graph = db.crate_graph(); // populate external prelude @@ -93,6 +97,14 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap) -> DefMap exports_proc_macros: false, from_glob_import: Default::default(), }; + match block { + Some(block) => { + collector.seed_with_inner(block); + } + None => { + collector.seed_with_top_level(); + } + } collector.collect(); collector.finish() } @@ -228,7 +240,7 @@ struct DefCollector<'a> { } impl DefCollector<'_> { - fn collect(&mut self) { + fn seed_with_top_level(&mut self) { let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id; let item_tree = self.db.item_tree(file_id.into()); let module_id = self.def_map.root; @@ -248,7 +260,31 @@ impl DefCollector<'_> { } .collect(item_tree.top_level_items()); } + } + + fn seed_with_inner(&mut self, block: FileAstId) { + let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id; + let item_tree = self.db.item_tree(file_id.into()); + let module_id = self.def_map.root; + self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id }; + if item_tree + .top_level_attrs(self.db, self.def_map.krate) + .cfg() + .map_or(true, |cfg| self.cfg_options.check(&cfg) != Some(false)) + { + ModCollector { + def_collector: &mut *self, + macro_depth: 0, + module_id, + file_id: file_id.into(), + item_tree: &item_tree, + mod_dir: ModDir::root(), + } + .collect(item_tree.inner_items_of_block(block)); + } + } + fn collect(&mut self) { // main name resolution fixed-point loop. let mut i = 0; loop { @@ -1470,7 +1506,6 @@ impl ModCollector<'_, '_> { mod tests { use crate::{db::DefDatabase, test_db::TestDB}; use base_db::{fixture::WithFixture, SourceDatabase}; - use la_arena::Arena; use super::*; @@ -1489,6 +1524,7 @@ mod tests { exports_proc_macros: false, from_glob_import: Default::default(), }; + collector.seed_with_top_level(); collector.collect(); collector.def_map } @@ -1497,20 +1533,8 @@ mod tests { let (db, _file_id) = TestDB::with_single_file(&code); let krate = db.test_crate(); - let def_map = { - let edition = db.crate_graph()[krate].edition; - let mut modules: Arena = Arena::default(); - let root = modules.alloc(ModuleData::default()); - DefMap { - krate, - edition, - extern_prelude: FxHashMap::default(), - prelude: None, - root, - modules, - diagnostics: Vec::new(), - } - }; + let edition = db.crate_graph()[krate].edition; + let def_map = DefMap::empty(krate, edition); do_collect_defs(&db, def_map) } diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs index 723481c36..73e3a4702 100644 --- a/crates/hir_def/src/nameres/tests.rs +++ b/crates/hir_def/src/nameres/tests.rs @@ -4,11 +4,13 @@ mod macros; mod mod_resolution; mod diagnostics; mod primitives; +mod block; use std::sync::Arc; use base_db::{fixture::WithFixture, SourceDatabase}; use expect_test::{expect, Expect}; +use hir_expand::db::AstDatabase; use test_utils::mark; use crate::{db::DefDatabase, nameres::*, test_db::TestDB}; @@ -19,12 +21,30 @@ fn compute_crate_def_map(ra_fixture: &str) -> Arc { db.crate_def_map(krate) } +fn compute_block_def_map(ra_fixture: &str) -> Arc { + let (db, position) = TestDB::with_position(ra_fixture); + let module = db.module_for_file(position.file_id); + let ast_map = db.ast_id_map(position.file_id.into()); + let ast = db.parse(position.file_id); + let block: ast::BlockExpr = + syntax::algo::find_node_at_offset(&ast.syntax_node(), position.offset).unwrap(); + let block_id = ast_map.ast_id(&block); + + db.block_def_map(module.krate, InFile::new(position.file_id.into(), block_id)) +} + fn check(ra_fixture: &str, expect: Expect) { let def_map = compute_crate_def_map(ra_fixture); let actual = def_map.dump(); expect.assert_eq(&actual); } +fn check_at(ra_fixture: &str, expect: Expect) { + let def_map = compute_block_def_map(ra_fixture); + let actual = def_map.dump(); + expect.assert_eq(&actual); +} + #[test] fn crate_def_map_smoke_test() { check( diff --git a/crates/hir_def/src/nameres/tests/block.rs b/crates/hir_def/src/nameres/tests/block.rs new file mode 100644 index 000000000..996704308 --- /dev/null +++ b/crates/hir_def/src/nameres/tests/block.rs @@ -0,0 +1,47 @@ +use super::*; + +#[test] +fn inner_item_smoke() { + check_at( + r#" +//- /lib.rs +struct inner {} +fn outer() { + $0 + fn inner() {} +} +"#, + expect![[r#" + block scope + inner: v + crate + inner: t + outer: v + "#]], + ); +} + +#[test] +fn use_from_crate() { + check_at( + r#" +//- /lib.rs +struct Struct; +fn outer() { + use Struct; + use crate::Struct as CrateStruct; + use self::Struct as SelfStruct; + $0 +} +"#, + expect![[r#" + block scope + CrateStruct: t v + SelfStruct: t v + Struct: t v + crate + Struct: t v + outer: v + "#]], + ); +} diff --git a/crates/ide_db/src/apply_change.rs b/crates/ide_db/src/apply_change.rs index c770a236b..9d9b6de7a 100644 --- a/crates/ide_db/src/apply_change.rs +++ b/crates/ide_db/src/apply_change.rs @@ -149,6 +149,7 @@ impl RootDatabase { // DefDatabase hir::db::ItemTreeQuery + hir::db::BlockDefMapQuery hir::db::CrateDefMapQueryQuery hir::db::StructDataQuery hir::db::UnionDataQuery -- cgit v1.2.3 From f8f44cfb9b598efea7cfbabdad356912e9b09afd Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 21 Jan 2021 15:24:15 +0100 Subject: Fall back to parent DefMaps when resolving paths --- crates/hir_def/src/nameres/path_resolution.rs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'crates') diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs index 096a7d0ac..ec90f4e65 100644 --- a/crates/hir_def/src/nameres/path_resolution.rs +++ b/crates/hir_def/src/nameres/path_resolution.rs @@ -103,6 +103,43 @@ impl DefMap { original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, + ) -> ResolvePathResult { + let mut result = ResolvePathResult::empty(ReachedFixedPoint::No); + result.segment_index = Some(usize::max_value()); + + let mut current_map = self; + loop { + let new = current_map.resolve_path_fp_with_macro_single( + db, + mode, + original_module, + path, + shadow, + ); + + // Merge `new` into `result`. + result.resolved_def = result.resolved_def.or(new.resolved_def); + if result.reached_fixedpoint == ReachedFixedPoint::No { + result.reached_fixedpoint = new.reached_fixedpoint; + } + // FIXME: this doesn't seem right; what if the different namespace resolutions come from different crates? + result.krate = result.krate.or(new.krate); + result.segment_index = result.segment_index.min(new.segment_index); + + match ¤t_map.parent { + Some(map) => current_map = map, + None => return result, + } + } + } + + pub(super) fn resolve_path_fp_with_macro_single( + &self, + db: &dyn DefDatabase, + mode: ResolveMode, + original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, ) -> ResolvePathResult { let mut segments = path.segments.iter().enumerate(); let mut curr_per_ns: PerNs = match path.kind { -- cgit v1.2.3 From d62c9c6c6305cbba31069457e33c9d4c6a4b40d6 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 21 Jan 2021 16:23:22 +0100 Subject: Fix lowering with multiple block expressions --- crates/hir_def/src/item_tree/lower.rs | 38 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) (limited to 'crates') diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index 56fe569ff..61cbbbc8f 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs @@ -151,23 +151,31 @@ impl Ctx { fn collect_inner_items(&mut self, container: &SyntaxNode) { let forced_vis = self.forced_visibility.take(); - let mut current_block = None; + let mut block_stack = Vec::new(); for event in container.preorder().skip(1) { - if let WalkEvent::Enter(node) = event { - match_ast! { - match node { - ast::BlockExpr(block) => { - current_block = Some(self.source_ast_id_map.ast_id(&block)); - }, - ast::Item(item) => { - let mod_items = self.lower_mod_item(&item, true); - if let (Some(mod_items), Some(block)) = (mod_items, current_block) { - if !mod_items.0.is_empty() { - self.data().inner_items.entry(block).or_default().extend(mod_items.0.iter().copied()); + match event { + WalkEvent::Enter(node) => { + match_ast! { + match node { + ast::BlockExpr(block) => { + block_stack.push(self.source_ast_id_map.ast_id(&block)); + }, + ast::Item(item) => { + let mod_items = self.lower_mod_item(&item, true); + let current_block = block_stack.last(); + if let (Some(mod_items), Some(block)) = (mod_items, current_block) { + if !mod_items.0.is_empty() { + self.data().inner_items.entry(*block).or_default().extend(mod_items.0.iter().copied()); + } } - } - }, - _ => {} + }, + _ => {} + } + } + } + WalkEvent::Leave(node) => { + if ast::BlockExpr::cast(node).is_some() { + block_stack.pop(); } } } -- cgit v1.2.3 From ec4a1dc297eb90dde4c22c682a35606aaa50b4d4 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 21 Jan 2021 16:23:50 +0100 Subject: Add test that merges inner and outer names --- crates/hir_def/src/nameres/tests/block.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'crates') diff --git a/crates/hir_def/src/nameres/tests/block.rs b/crates/hir_def/src/nameres/tests/block.rs index 996704308..ab7ec9d62 100644 --- a/crates/hir_def/src/nameres/tests/block.rs +++ b/crates/hir_def/src/nameres/tests/block.rs @@ -45,3 +45,28 @@ fn outer() { "#]], ); } + +#[test] +fn merge_namespaces() { + check_at( + r#" +//- /lib.rs +struct name {} +fn outer() { + fn name() {} + + use name as imported; // should import both `name`s + + $0 +} +"#, + expect![[r#" + block scope + imported: t v + name: v + crate + name: t + outer: v + "#]], + ); +} -- cgit v1.2.3