From 896dfacfc47068df716fe4969a68adefadb1693e Mon Sep 17 00:00:00 2001
From: Jonas Schievink <jonasschievink@gmail.com>
Date: Thu, 21 Jan 2021 15:22:17 +0100
Subject: [PATCH] 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

diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index d5d4cf5b656..d444f4bbb17 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 91c8d45cd11..a87c80b8a1c 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<dyn AstDatabase> {
     #[salsa::invoke(DefMap::crate_def_map_query)]
     fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>;
 
+    #[salsa::invoke(DefMap::block_def_map_query)]
+    fn block_def_map(&self, krate: CrateId, block: AstId<ast::BlockExpr>) -> Arc<DefMap>;
+
     #[salsa::invoke(StructData::struct_data_query)]
     fn struct_data(&self, id: StructId) -> Arc<StructData>;
     #[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 6494cebd388..1226d7d8538 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<ast::BlockExpr>) -> &[ModItem] {
+        match &self.data {
+            Some(data) => data.inner_items.get(&block).map(|it| &**it).unwrap_or(&[]),
+            None => &[],
+        }
+    }
+
     pub fn source<S: ItemTreeNode>(&self, db: &dyn DefDatabase, of: ItemTreeId<S>) -> 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 a3200c710ae..93931a21af8 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<Arc<DefMap>>,
     root: LocalModuleId,
     modules: Arena<ModuleData>,
     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<ModuleData> = 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<ast::BlockExpr>,
+    ) -> Arc<DefMap> {
+        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<ModuleData> = 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) = &current_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<ast::BlockExpr>,
+) -> Arc<DefMap> {
+    // 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 61da5634049..cd68efbe678 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<FileAstId<ast::BlockExpr>>,
+) -> 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<ast::BlockExpr>) {
+        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<ModuleData> = 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 723481c367f..73e3a4702bf 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<DefMap> {
     db.crate_def_map(krate)
 }
 
+fn compute_block_def_map(ra_fixture: &str) -> Arc<DefMap> {
+    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 00000000000..996704308e6
--- /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 c770a236b09..9d9b6de7a15 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