From bca8029a6ed7bed26bdea284738f64f10c063ba8 Mon Sep 17 00:00:00 2001
From: Lukas Wirth <lukastw97@gmail.com>
Date: Mon, 17 Apr 2023 17:31:39 +0200
Subject: [PATCH] Move Expander and LowerCtx into separate modules

---
 crates/hir-def/src/attr.rs                    |  86 +++--
 crates/hir-def/src/body.rs                    | 347 +++---------------
 crates/hir-def/src/body/lower.rs              |  32 +-
 crates/hir-def/src/data.rs                    |  33 +-
 crates/hir-def/src/data/adt.rs                |   3 +-
 crates/hir-def/src/db.rs                      |   6 +
 crates/hir-def/src/expander.rs                | 211 +++++++++++
 crates/hir-def/src/generics.rs                |  42 ++-
 crates/hir-def/src/hir/type_ref.rs            |   2 +-
 crates/hir-def/src/import_map.rs              |   2 +-
 crates/hir-def/src/item_tree/lower.rs         |   4 +-
 crates/hir-def/src/lib.rs                     |  26 +-
 crates/hir-def/src/lower.rs                   |  46 +++
 crates/hir-def/src/nameres/collector.rs       |   6 +-
 .../hir-def/src/nameres/tests/incremental.rs  |   2 +-
 crates/hir-def/src/path.rs                    |   2 +-
 crates/hir-def/src/path/lower.rs              |   6 +-
 crates/hir-ty/src/display.rs                  |   6 +-
 crates/hir-ty/src/interner.rs                 |   2 +-
 crates/hir-ty/src/lower.rs                    |   6 +-
 crates/hir/src/semantics.rs                   |   8 +-
 crates/hir/src/source_analyzer.rs             |   6 +-
 crates/ide-db/src/line_index.rs               |   3 +
 crates/ide-db/src/symbol_index.rs             |   1 +
 crates/limit/src/lib.rs                       |   1 +
 25 files changed, 487 insertions(+), 402 deletions(-)
 create mode 100644 crates/hir-def/src/expander.rs
 create mode 100644 crates/hir-def/src/lower.rs

diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index a00337ae9c5..1ebbe160b07 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -28,8 +28,8 @@ use crate::{
     lang_item::LangItem,
     nameres::{ModuleOrigin, ModuleSource},
     src::{HasChildSource, HasSource},
-    AdtId, AttrDefId, EnumId, GenericParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroId,
-    VariantId,
+    AdtId, AssocItemLoc, AttrDefId, EnumId, GenericParamId, ItemLoc, LocalEnumVariantId,
+    LocalFieldId, Lookup, MacroId, VariantId,
 };
 
 /// Holds documentation
@@ -421,23 +421,24 @@ impl AttrsWithOwner {
             AttrDefId::EnumVariantId(it) => {
                 return db.variants_attrs(it.parent)[it.local_id].clone();
             }
+            // FIXME: DRY this up
             AttrDefId::AdtId(it) => match it {
-                AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-                AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-                AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
+                AdtId::StructId(it) => attrs_from_item_tree_loc(db, it),
+                AdtId::EnumId(it) => attrs_from_item_tree_loc(db, it),
+                AdtId::UnionId(it) => attrs_from_item_tree_loc(db, it),
             },
-            AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-            AttrDefId::TraitAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
+            AttrDefId::TraitId(it) => attrs_from_item_tree_loc(db, it),
+            AttrDefId::TraitAliasId(it) => attrs_from_item_tree_loc(db, it),
             AttrDefId::MacroId(it) => match it {
-                MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db),
-                MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-                MacroId::ProcMacroId(it) => attrs_from_item_tree(it.lookup(db).id, db),
+                MacroId::Macro2Id(it) => attrs_from_item_tree(db, it.lookup(db).id),
+                MacroId::MacroRulesId(it) => attrs_from_item_tree(db, it.lookup(db).id),
+                MacroId::ProcMacroId(it) => attrs_from_item_tree(db, it.lookup(db).id),
             },
-            AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-            AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-            AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-            AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
-            AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
+            AttrDefId::ImplId(it) => attrs_from_item_tree_loc(db, it),
+            AttrDefId::ConstId(it) => attrs_from_item_tree_assoc(db, it),
+            AttrDefId::StaticId(it) => attrs_from_item_tree_assoc(db, it),
+            AttrDefId::FunctionId(it) => attrs_from_item_tree_assoc(db, it),
+            AttrDefId::TypeAliasId(it) => attrs_from_item_tree_assoc(db, it),
             AttrDefId::GenericParamId(it) => match it {
                 GenericParamId::ConstParamId(it) => {
                     let src = it.parent().child_source(db);
@@ -458,7 +459,7 @@ impl AttrsWithOwner {
                     RawAttrs::from_attrs_owner(db.upcast(), src.with_value(&src.value[it.local_id]))
                 }
             },
-            AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db),
+            AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it),
         };
 
         let attrs = raw_attrs.filter(db.upcast(), def.krate(db));
@@ -506,28 +507,28 @@ impl AttrsWithOwner {
                 InFile::new(file_id, owner)
             }
             AttrDefId::AdtId(adt) => match adt {
-                AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-                AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-                AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+                AdtId::StructId(id) => any_has_attrs(db, id),
+                AdtId::UnionId(id) => any_has_attrs(db, id),
+                AdtId::EnumId(id) => any_has_attrs(db, id),
             },
-            AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::FunctionId(id) => any_has_attrs(db, id),
             AttrDefId::EnumVariantId(id) => {
                 let map = db.variants_attrs_source_map(id.parent);
                 let file_id = id.parent.lookup(db).id.file_id();
                 let root = db.parse_or_expand(file_id);
                 InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root)))
             }
-            AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-            AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-            AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-            AttrDefId::TraitAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-            AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::StaticId(id) => any_has_attrs(db, id),
+            AttrDefId::ConstId(id) => any_has_attrs(db, id),
+            AttrDefId::TraitId(id) => any_has_attrs(db, id),
+            AttrDefId::TraitAliasId(id) => any_has_attrs(db, id),
+            AttrDefId::TypeAliasId(id) => any_has_attrs(db, id),
             AttrDefId::MacroId(id) => match id {
-                MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-                MacroId::MacroRulesId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
-                MacroId::ProcMacroId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+                MacroId::Macro2Id(id) => any_has_attrs(db, id),
+                MacroId::MacroRulesId(id) => any_has_attrs(db, id),
+                MacroId::ProcMacroId(id) => any_has_attrs(db, id),
             },
-            AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::ImplId(id) => any_has_attrs(db, id),
             AttrDefId::GenericParamId(id) => match id {
                 GenericParamId::ConstParamId(id) => id
                     .parent()
@@ -542,7 +543,7 @@ impl AttrsWithOwner {
                     .child_source(db)
                     .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())),
             },
-            AttrDefId::ExternBlockId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new),
+            AttrDefId::ExternBlockId(id) => any_has_attrs(db, id),
         };
 
         AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs))
@@ -769,12 +770,35 @@ impl<'attr> AttrQuery<'attr> {
     }
 }
 
-fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
+fn any_has_attrs(
+    db: &dyn DefDatabase,
+    id: impl Lookup<Data = impl HasSource<Value = impl ast::HasAttrs>>,
+) -> InFile<ast::AnyHasAttrs> {
+    id.lookup(db).source(db).map(ast::AnyHasAttrs::new)
+}
+
+fn attrs_from_item_tree<N: ItemTreeNode>(db: &dyn DefDatabase, id: ItemTreeId<N>) -> RawAttrs {
     let tree = id.item_tree(db);
     let mod_item = N::id_to_mod_item(id.value);
     tree.raw_attrs(mod_item.into()).clone()
 }
 
+fn attrs_from_item_tree_loc<N: ItemTreeNode>(
+    db: &dyn DefDatabase,
+    lookup: impl Lookup<Data = ItemLoc<N>>,
+) -> RawAttrs {
+    let id = lookup.lookup(db).id;
+    attrs_from_item_tree(db, id)
+}
+
+fn attrs_from_item_tree_assoc<N: ItemTreeNode>(
+    db: &dyn DefDatabase,
+    lookup: impl Lookup<Data = AssocItemLoc<N>>,
+) -> RawAttrs {
+    let id = lookup.lookup(db).id;
+    attrs_from_item_tree(db, id)
+}
+
 pub(crate) fn variants_attrs_source_map(
     db: &dyn DefDatabase,
     def: EnumId,
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index cf2227b3797..0c0e8ac8204 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -10,284 +10,25 @@ use std::{ops::Index, sync::Arc};
 
 use base_db::CrateId;
 use cfg::{CfgExpr, CfgOptions};
-use drop_bomb::DropBomb;
 use either::Either;
-use hir_expand::{
-    ast_id_map::AstIdMap, attrs::RawAttrs, hygiene::Hygiene, name::Name, AstId, ExpandError,
-    ExpandResult, HirFileId, InFile, MacroCallId,
-};
+use hir_expand::{name::Name, HirFileId, InFile};
 use la_arena::{Arena, ArenaMap};
-use limit::Limit;
-use once_cell::unsync::OnceCell;
 use profile::Count;
 use rustc_hash::FxHashMap;
-use syntax::{ast, AstPtr, Parse, SyntaxNode, SyntaxNodePtr};
+use syntax::{ast, AstPtr, SyntaxNodePtr};
 
 use crate::{
-    attr::Attrs,
     db::DefDatabase,
+    expander::Expander,
     hir::{
         dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId, RecordFieldPat,
     },
-    item_scope::BuiltinShadowMode,
-    macro_id_to_def_id,
     nameres::DefMap,
     path::{ModPath, Path},
     src::{HasChildSource, HasSource},
-    AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId,
-    UnresolvedMacro,
+    BlockId, DefWithBodyId, HasModule, Lookup,
 };
 
-pub struct LowerCtx<'a> {
-    pub db: &'a dyn DefDatabase,
-    hygiene: Hygiene,
-    ast_id_map: Option<(HirFileId, OnceCell<Arc<AstIdMap>>)>,
-}
-
-impl<'a> LowerCtx<'a> {
-    pub fn new(db: &'a dyn DefDatabase, hygiene: &Hygiene, file_id: HirFileId) -> Self {
-        LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: Some((file_id, OnceCell::new())) }
-    }
-
-    pub fn with_file_id(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
-        LowerCtx {
-            db,
-            hygiene: Hygiene::new(db.upcast(), file_id),
-            ast_id_map: Some((file_id, OnceCell::new())),
-        }
-    }
-
-    pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self {
-        LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None }
-    }
-
-    pub(crate) fn hygiene(&self) -> &Hygiene {
-        &self.hygiene
-    }
-
-    pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> {
-        Path::from_src(ast, self)
-    }
-
-    pub(crate) fn ast_id<N: syntax::AstNode>(&self, item: &N) -> Option<AstId<N>> {
-        let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?;
-        let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id));
-        Some(InFile::new(file_id, ast_id_map.ast_id(item)))
-    }
-}
-
-/// A subset of Expander that only deals with cfg attributes. We only need it to
-/// avoid cyclic queries in crate def map during enum processing.
-#[derive(Debug)]
-pub(crate) struct CfgExpander {
-    cfg_options: CfgOptions,
-    hygiene: Hygiene,
-    krate: CrateId,
-}
-
-#[derive(Debug)]
-pub struct Expander {
-    cfg_expander: CfgExpander,
-    def_map: Arc<DefMap>,
-    current_file_id: HirFileId,
-    module: LocalModuleId,
-    /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
-    recursion_depth: usize,
-}
-
-impl CfgExpander {
-    pub(crate) fn new(
-        db: &dyn DefDatabase,
-        current_file_id: HirFileId,
-        krate: CrateId,
-    ) -> CfgExpander {
-        let hygiene = Hygiene::new(db.upcast(), current_file_id);
-        let cfg_options = db.crate_graph()[krate].cfg_options.clone();
-        CfgExpander { cfg_options, hygiene, krate }
-    }
-
-    pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
-        Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene))
-    }
-
-    pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
-        let attrs = self.parse_attrs(db, owner);
-        attrs.is_cfg_enabled(&self.cfg_options)
-    }
-
-    pub(crate) fn hygiene(&self) -> &Hygiene {
-        &self.hygiene
-    }
-}
-
-impl Expander {
-    pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
-        let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
-        let def_map = module.def_map(db);
-        Expander {
-            cfg_expander,
-            def_map,
-            current_file_id,
-            module: module.local_id,
-            recursion_depth: 0,
-        }
-    }
-
-    pub fn enter_expand<T: ast::AstNode>(
-        &mut self,
-        db: &dyn DefDatabase,
-        macro_call: ast::MacroCall,
-    ) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro> {
-        // FIXME: within_limit should support this, instead of us having to extract the error
-        let mut unresolved_macro_err = None;
-
-        let result = self.within_limit(db, |this| {
-            let macro_call = InFile::new(this.current_file_id, &macro_call);
-
-            let resolver =
-                |path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it));
-
-            match macro_call.as_call_id_with_errors(db, this.def_map.krate(), resolver) {
-                Ok(call_id) => call_id,
-                Err(resolve_err) => {
-                    unresolved_macro_err = Some(resolve_err);
-                    ExpandResult { value: None, err: None }
-                }
-            }
-        });
-
-        if let Some(err) = unresolved_macro_err {
-            Err(err)
-        } else {
-            Ok(result)
-        }
-    }
-
-    pub fn enter_expand_id<T: ast::AstNode>(
-        &mut self,
-        db: &dyn DefDatabase,
-        call_id: MacroCallId,
-    ) -> ExpandResult<Option<(Mark, Parse<T>)>> {
-        self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
-    }
-
-    fn enter_expand_inner(
-        db: &dyn DefDatabase,
-        call_id: MacroCallId,
-        error: Option<ExpandError>,
-    ) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>> {
-        let file_id = call_id.as_file();
-        let ExpandResult { value, err } = db.parse_or_expand_with_err(file_id);
-
-        ExpandResult { value: Some(InFile::new(file_id, value)), err: error.or(err) }
-    }
-
-    pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
-        self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
-        self.current_file_id = mark.file_id;
-        if self.recursion_depth == usize::MAX {
-            // Recursion limit has been reached somewhere in the macro expansion tree. Reset the
-            // depth only when we get out of the tree.
-            if !self.current_file_id.is_macro() {
-                self.recursion_depth = 0;
-            }
-        } else {
-            self.recursion_depth -= 1;
-        }
-        mark.bomb.defuse();
-    }
-
-    pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
-        LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id)
-    }
-
-    pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
-        InFile { file_id: self.current_file_id, value }
-    }
-
-    pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
-        self.cfg_expander.parse_attrs(db, owner)
-    }
-
-    pub(crate) fn cfg_options(&self) -> &CfgOptions {
-        &self.cfg_expander.cfg_options
-    }
-
-    pub fn current_file_id(&self) -> HirFileId {
-        self.current_file_id
-    }
-
-    fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
-        let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene);
-        Path::from_src(path, &ctx)
-    }
-
-    fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroId> {
-        self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros()
-    }
-
-    fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit {
-        let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _;
-
-        #[cfg(not(test))]
-        return Limit::new(limit);
-
-        // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
-        #[cfg(test)]
-        return Limit::new(std::cmp::min(32, limit));
-    }
-
-    fn within_limit<F, T: ast::AstNode>(
-        &mut self,
-        db: &dyn DefDatabase,
-        op: F,
-    ) -> ExpandResult<Option<(Mark, Parse<T>)>>
-    where
-        F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
-    {
-        if self.recursion_depth == usize::MAX {
-            // Recursion limit has been reached somewhere in the macro expansion tree. We should
-            // stop expanding other macro calls in this tree, or else this may result in
-            // exponential number of macro expansions, leading to a hang.
-            //
-            // The overflow error should have been reported when it occurred (see the next branch),
-            // so don't return overflow error here to avoid diagnostics duplication.
-            cov_mark::hit!(overflow_but_not_me);
-            return ExpandResult::only_err(ExpandError::RecursionOverflowPoisoned);
-        } else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() {
-            self.recursion_depth = usize::MAX;
-            cov_mark::hit!(your_stack_belongs_to_me);
-            return ExpandResult::only_err(ExpandError::Other(
-                "reached recursion limit during macro expansion".into(),
-            ));
-        }
-
-        let ExpandResult { value, err } = op(self);
-        let Some(call_id) = value else {
-            return ExpandResult { value: None, err };
-        };
-
-        Self::enter_expand_inner(db, call_id, err).map(|value| {
-            value.and_then(|InFile { file_id, value }| {
-                let parse = value.cast::<T>()?;
-
-                self.recursion_depth += 1;
-                self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
-                let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
-                let mark =
-                    Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
-                Some((mark, parse))
-            })
-        })
-    }
-}
-
-#[derive(Debug)]
-pub struct Mark {
-    file_id: HirFileId,
-    bomb: DropBomb,
-}
-
 /// The body of an item (function, const etc.).
 #[derive(Debug, Eq, PartialEq)]
 pub struct Body {
@@ -376,47 +117,49 @@ impl Body {
         let _p = profile::span("body_with_source_map_query");
         let mut params = None;
 
-        let (file_id, module, body, is_async_fn) = match def {
-            DefWithBodyId::FunctionId(f) => {
-                let data = db.function_data(f);
-                let f = f.lookup(db);
-                let src = f.source(db);
-                params = src.value.param_list().map(|param_list| {
-                    let item_tree = f.id.item_tree(db);
-                    let func = &item_tree[f.id.value];
-                    let krate = f.container.module(db).krate;
-                    let crate_graph = db.crate_graph();
+        let (file_id, module, body, is_async_fn) = {
+            match def {
+                DefWithBodyId::FunctionId(f) => {
+                    let data = db.function_data(f);
+                    let f = f.lookup(db);
+                    let src = f.source(db);
+                    params = src.value.param_list().map(|param_list| {
+                        let item_tree = f.id.item_tree(db);
+                        let func = &item_tree[f.id.value];
+                        let krate = f.container.module(db).krate;
+                        let crate_graph = db.crate_graph();
+                        (
+                            param_list,
+                            func.params.clone().map(move |param| {
+                                item_tree
+                                    .attrs(db, krate, param.into())
+                                    .is_cfg_enabled(&crate_graph[krate].cfg_options)
+                            }),
+                        )
+                    });
                     (
-                        param_list,
-                        func.params.clone().map(move |param| {
-                            item_tree
-                                .attrs(db, krate, param.into())
-                                .is_cfg_enabled(&crate_graph[krate].cfg_options)
-                        }),
+                        src.file_id,
+                        f.module(db),
+                        src.value.body().map(ast::Expr::from),
+                        data.has_async_kw(),
                     )
-                });
-                (
-                    src.file_id,
-                    f.module(db),
-                    src.value.body().map(ast::Expr::from),
-                    data.has_async_kw(),
-                )
-            }
-            DefWithBodyId::ConstId(c) => {
-                let c = c.lookup(db);
-                let src = c.source(db);
-                (src.file_id, c.module(db), src.value.body(), false)
-            }
-            DefWithBodyId::StaticId(s) => {
-                let s = s.lookup(db);
-                let src = s.source(db);
-                (src.file_id, s.module(db), src.value.body(), false)
-            }
-            DefWithBodyId::VariantId(v) => {
-                let e = v.parent.lookup(db);
-                let src = v.parent.child_source(db);
-                let variant = &src.value[v.local_id];
-                (src.file_id, e.container, variant.expr(), false)
+                }
+                DefWithBodyId::ConstId(c) => {
+                    let c = c.lookup(db);
+                    let src = c.source(db);
+                    (src.file_id, c.module(db), src.value.body(), false)
+                }
+                DefWithBodyId::StaticId(s) => {
+                    let s = s.lookup(db);
+                    let src = s.source(db);
+                    (src.file_id, s.module(db), src.value.body(), false)
+                }
+                DefWithBodyId::VariantId(v) => {
+                    let e = v.parent.lookup(db);
+                    let src = v.parent.child_source(db);
+                    let variant = &src.value[v.local_id];
+                    (src.file_id, e.container, variant.expr(), false)
+                }
             }
         };
         let expander = Expander::new(db, file_id, module);
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index db619b97dbe..27dfe766d33 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -24,9 +24,10 @@ use syntax::{
 };
 
 use crate::{
-    body::{Body, BodyDiagnostic, BodySourceMap, Expander, ExprPtr, LabelPtr, LowerCtx, PatPtr},
+    body::{Body, BodyDiagnostic, BodySourceMap, ExprPtr, LabelPtr, PatPtr},
     data::adt::StructKind,
     db::DefDatabase,
+    expander::Expander,
     hir::{
         dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, CaptureBy, ClosureKind, Expr,
         ExprId, Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat,
@@ -34,6 +35,8 @@ use crate::{
     },
     item_scope::BuiltinShadowMode,
     lang_item::LangItem,
+    lower::LowerCtx,
+    nameres::DefMap,
     path::{GenericArgs, Path},
     type_ref::{Mutability, Rawness, TypeRef},
     AdtId, BlockId, BlockLoc, ModuleDefId, UnresolvedMacro,
@@ -50,6 +53,7 @@ pub(super) fn lower(
     ExprCollector {
         db,
         krate,
+        def_map: db.crate_def_map(krate),
         source_map: BodySourceMap::default(),
         ast_id_map: db.ast_id_map(expander.current_file_id),
         body: Body {
@@ -75,6 +79,7 @@ pub(super) fn lower(
 struct ExprCollector<'a> {
     db: &'a dyn DefDatabase,
     expander: Expander,
+    def_map: Arc<DefMap>,
     ast_id_map: Arc<AstIdMap>,
     krate: CrateId,
     body: Body,
@@ -777,7 +782,13 @@ impl ExprCollector<'_> {
         let outer_file = self.expander.current_file_id;
 
         let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall));
-        let res = self.expander.enter_expand(self.db, mcall);
+        let module = self.expander.module.local_id;
+        let res = self.expander.enter_expand(self.db, mcall, |path| {
+            self.def_map
+                .resolve_path(self.db, module, &path, crate::item_scope::BuiltinShadowMode::Other)
+                .0
+                .take_macros()
+        });
 
         let res = match res {
             Ok(res) => res,
@@ -944,10 +955,7 @@ impl ExprCollector<'_> {
         let block_id = if block_has_items {
             let file_local_id = self.ast_id_map.ast_id(&block);
             let ast_id = AstId::new(self.expander.current_file_id, file_local_id);
-            Some(self.db.intern_block(BlockLoc {
-                ast_id,
-                module: self.expander.def_map.module_id(self.expander.module),
-            }))
+            Some(self.db.intern_block(BlockLoc { ast_id, module: self.expander.module }))
         } else {
             None
         };
@@ -956,11 +964,11 @@ impl ExprCollector<'_> {
             match block_id.map(|block_id| (self.db.block_def_map(block_id), block_id)) {
                 Some((def_map, block_id)) => {
                     self.body.block_scopes.push(block_id);
-                    (def_map.root(), def_map)
+                    (def_map.module_id(def_map.root()), def_map)
                 }
-                None => (self.expander.module, self.expander.def_map.clone()),
+                None => (self.expander.module, self.def_map.clone()),
             };
-        let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
+        let prev_def_map = mem::replace(&mut self.def_map, def_map);
         let prev_local_module = mem::replace(&mut self.expander.module, module);
 
         let mut statements = Vec::new();
@@ -982,7 +990,7 @@ impl ExprCollector<'_> {
         let expr_id = self
             .alloc_expr(mk_block(block_id, statements.into_boxed_slice(), tail), syntax_node_ptr);
 
-        self.expander.def_map = prev_def_map;
+        self.def_map = prev_def_map;
         self.expander.module = prev_local_module;
         expr_id
     }
@@ -1028,9 +1036,9 @@ impl ExprCollector<'_> {
                 let (binding, pattern) = if is_simple_ident_pat {
                     // This could also be a single-segment path pattern. To
                     // decide that, we need to try resolving the name.
-                    let (resolved, _) = self.expander.def_map.resolve_path(
+                    let (resolved, _) = self.def_map.resolve_path(
                         self.db,
-                        self.expander.module,
+                        self.expander.module.local_id,
                         &name.clone().into(),
                         BuiltinShadowMode::Other,
                     );
diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index 3fdd09b004e..73d4eebb85a 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -11,8 +11,8 @@ use syntax::{ast, Parse};
 
 use crate::{
     attr::Attrs,
-    body::{Expander, Mark},
     db::DefDatabase,
+    expander::{Expander, Mark},
     item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId},
     nameres::{
         attr_resolution::ResolvedAttr,
@@ -44,16 +44,16 @@ impl FunctionData {
     pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
         let loc = func.lookup(db);
         let krate = loc.container.module(db).krate;
-        let crate_graph = db.crate_graph();
-        let cfg_options = &crate_graph[krate].cfg_options;
         let item_tree = loc.id.item_tree(db);
         let func = &item_tree[loc.id.value];
         let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
-            db.trait_data(trait_id).visibility.clone()
+            trait_vis(db, trait_id)
         } else {
             item_tree[func.visibility].clone()
         };
 
+        let crate_graph = db.crate_graph();
+        let cfg_options = &crate_graph[krate].cfg_options;
         let enabled_params = func
             .params
             .clone()
@@ -188,7 +188,7 @@ impl TypeAliasData {
         let item_tree = loc.id.item_tree(db);
         let typ = &item_tree[loc.id.value];
         let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
-            db.trait_data(trait_id).visibility.clone()
+            trait_vis(db, trait_id)
         } else {
             item_tree[typ.visibility].clone()
         };
@@ -471,7 +471,7 @@ impl ConstData {
         let item_tree = loc.id.item_tree(db);
         let konst = &item_tree[loc.id.value];
         let visibility = if let ItemContainerId::TraitId(trait_id) = loc.container {
-            db.trait_data(trait_id).visibility.clone()
+            trait_vis(db, trait_id)
         } else {
             item_tree[konst.visibility].clone()
         };
@@ -647,8 +647,20 @@ impl<'a> AssocItemCollector<'a> {
                     let _cx = stdx::panic_context::enter(format!(
                         "collect_items MacroCall: {macro_call}"
                     ));
+                    let module = self.expander.module.local_id;
+
                     if let Ok(res) =
-                        self.expander.enter_expand::<ast::MacroItems>(self.db, macro_call)
+                        self.expander.enter_expand::<ast::MacroItems>(self.db, macro_call, |path| {
+                            self.def_map
+                                .resolve_path(
+                                    self.db,
+                                    module,
+                                    &path,
+                                    crate::item_scope::BuiltinShadowMode::Other,
+                                )
+                                .0
+                                .take_macros()
+                        })
                     {
                         self.collect_macro_items(res, &|| hir_expand::MacroCallKind::FnLike {
                             ast_id: InFile::new(file_id, call.ast_id),
@@ -692,3 +704,10 @@ impl<'a> AssocItemCollector<'a> {
         self.expander.exit(self.db, mark);
     }
 }
+
+fn trait_vis(db: &dyn DefDatabase, trait_id: TraitId) -> RawVisibility {
+    let ItemLoc { id: tree_id, .. } = trait_id.lookup(db);
+    let item_tree = tree_id.item_tree(db);
+    let tr_def = &item_tree[tree_id.value];
+    item_tree[tr_def.visibility].clone()
+}
diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs
index 0e4b033638a..290e98f8bd5 100644
--- a/crates/hir-def/src/data/adt.rs
+++ b/crates/hir-def/src/data/adt.rs
@@ -17,11 +17,12 @@ use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
 use syntax::ast::{self, HasName, HasVisibility};
 
 use crate::{
-    body::{CfgExpander, LowerCtx},
     builtin_type::{BuiltinInt, BuiltinUint},
     db::DefDatabase,
+    expander::CfgExpander,
     item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
     lang_item::LangItem,
+    lower::LowerCtx,
     nameres::diagnostics::DefDiagnostic,
     src::HasChildSource,
     src::HasSource,
diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs
index 678b0fcb7ed..cc87b03eef7 100644
--- a/crates/hir-def/src/db.rs
+++ b/crates/hir-def/src/db.rs
@@ -217,6 +217,8 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
 
     #[salsa::transparent]
     fn crate_limits(&self, crate_id: CrateId) -> CrateLimits;
+    #[salsa::transparent]
+    fn recursion_limit(&self, crate_id: CrateId) -> u32;
 
     fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
 }
@@ -240,6 +242,10 @@ fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits {
     }
 }
 
+fn recursion_limit(db: &dyn DefDatabase, crate_id: CrateId) -> u32 {
+    db.crate_limits(crate_id).recursion_limit
+}
+
 fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool {
     let file = db.crate_graph()[crate_id].root_file_id;
     let item_tree = db.file_item_tree(file.into());
diff --git a/crates/hir-def/src/expander.rs b/crates/hir-def/src/expander.rs
new file mode 100644
index 00000000000..34ed1e72f20
--- /dev/null
+++ b/crates/hir-def/src/expander.rs
@@ -0,0 +1,211 @@
+//! Macro expansion utilities.
+
+use base_db::CrateId;
+use cfg::CfgOptions;
+use drop_bomb::DropBomb;
+use hir_expand::{
+    attrs::RawAttrs, hygiene::Hygiene, mod_path::ModPath, ExpandError, ExpandResult, HirFileId,
+    InFile, MacroCallId, UnresolvedMacro,
+};
+use limit::Limit;
+use syntax::{ast, Parse, SyntaxNode};
+
+use crate::{
+    attr::Attrs, db::DefDatabase, lower::LowerCtx, macro_id_to_def_id, path::Path, AsMacroCall,
+    MacroId, ModuleId,
+};
+
+/// A subset of Expander that only deals with cfg attributes. We only need it to
+/// avoid cyclic queries in crate def map during enum processing.
+#[derive(Debug)]
+pub(crate) struct CfgExpander {
+    cfg_options: CfgOptions,
+    hygiene: Hygiene,
+    krate: CrateId,
+}
+
+#[derive(Debug)]
+pub struct Expander {
+    cfg_expander: CfgExpander,
+    pub(crate) current_file_id: HirFileId,
+    pub(crate) module: ModuleId,
+    /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
+    recursion_depth: u32,
+    recursion_limit: Limit,
+}
+
+impl CfgExpander {
+    pub(crate) fn new(
+        db: &dyn DefDatabase,
+        current_file_id: HirFileId,
+        krate: CrateId,
+    ) -> CfgExpander {
+        let hygiene = Hygiene::new(db.upcast(), current_file_id);
+        let cfg_options = db.crate_graph()[krate].cfg_options.clone();
+        CfgExpander { cfg_options, hygiene, krate }
+    }
+
+    pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
+        Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene))
+    }
+
+    pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
+        let attrs = self.parse_attrs(db, owner);
+        attrs.is_cfg_enabled(&self.cfg_options)
+    }
+
+    pub(crate) fn hygiene(&self) -> &Hygiene {
+        &self.hygiene
+    }
+}
+
+impl Expander {
+    pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
+        let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
+        let recursion_limit = db.recursion_limit(module.krate);
+        #[cfg(not(test))]
+        let recursion_limit = Limit::new(recursion_limit as usize);
+        // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
+        #[cfg(test)]
+        let recursion_limit = Limit::new(std::cmp::min(32, recursion_limit as usize));
+        Expander { cfg_expander, current_file_id, module, recursion_depth: 0, recursion_limit }
+    }
+
+    pub fn enter_expand<T: ast::AstNode>(
+        &mut self,
+        db: &dyn DefDatabase,
+        macro_call: ast::MacroCall,
+        resolver: impl Fn(ModPath) -> Option<MacroId>,
+    ) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro> {
+        // FIXME: within_limit should support this, instead of us having to extract the error
+        let mut unresolved_macro_err = None;
+
+        let result = self.within_limit(db, |this| {
+            let macro_call = InFile::new(this.current_file_id, &macro_call);
+            match macro_call.as_call_id_with_errors(db.upcast(), this.module.krate(), |path| {
+                resolver(path).map(|it| macro_id_to_def_id(db, it))
+            }) {
+                Ok(call_id) => call_id,
+                Err(resolve_err) => {
+                    unresolved_macro_err = Some(resolve_err);
+                    ExpandResult { value: None, err: None }
+                }
+            }
+        });
+
+        if let Some(err) = unresolved_macro_err {
+            Err(err)
+        } else {
+            Ok(result)
+        }
+    }
+
+    pub fn enter_expand_id<T: ast::AstNode>(
+        &mut self,
+        db: &dyn DefDatabase,
+        call_id: MacroCallId,
+    ) -> ExpandResult<Option<(Mark, Parse<T>)>> {
+        self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
+    }
+
+    fn enter_expand_inner(
+        db: &dyn DefDatabase,
+        call_id: MacroCallId,
+        error: Option<ExpandError>,
+    ) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>> {
+        let file_id = call_id.as_file();
+        let ExpandResult { value, err } = db.parse_or_expand_with_err(file_id);
+
+        ExpandResult { value: Some(InFile::new(file_id, value)), err: error.or(err) }
+    }
+
+    pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
+        self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
+        self.current_file_id = mark.file_id;
+        if self.recursion_depth == u32::MAX {
+            // Recursion limit has been reached somewhere in the macro expansion tree. Reset the
+            // depth only when we get out of the tree.
+            if !self.current_file_id.is_macro() {
+                self.recursion_depth = 0;
+            }
+        } else {
+            self.recursion_depth -= 1;
+        }
+        mark.bomb.defuse();
+    }
+
+    pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
+        LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id)
+    }
+
+    pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
+        InFile { file_id: self.current_file_id, value }
+    }
+
+    pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
+        self.cfg_expander.parse_attrs(db, owner)
+    }
+
+    pub(crate) fn cfg_options(&self) -> &CfgOptions {
+        &self.cfg_expander.cfg_options
+    }
+
+    pub fn current_file_id(&self) -> HirFileId {
+        self.current_file_id
+    }
+
+    pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
+        let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene);
+        Path::from_src(path, &ctx)
+    }
+
+    fn within_limit<F, T: ast::AstNode>(
+        &mut self,
+        db: &dyn DefDatabase,
+        op: F,
+    ) -> ExpandResult<Option<(Mark, Parse<T>)>>
+    where
+        F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
+    {
+        if self.recursion_depth == u32::MAX {
+            // Recursion limit has been reached somewhere in the macro expansion tree. We should
+            // stop expanding other macro calls in this tree, or else this may result in
+            // exponential number of macro expansions, leading to a hang.
+            //
+            // The overflow error should have been reported when it occurred (see the next branch),
+            // so don't return overflow error here to avoid diagnostics duplication.
+            cov_mark::hit!(overflow_but_not_me);
+            return ExpandResult::only_err(ExpandError::RecursionOverflowPoisoned);
+        } else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() {
+            self.recursion_depth = u32::MAX;
+            cov_mark::hit!(your_stack_belongs_to_me);
+            return ExpandResult::only_err(ExpandError::Other(
+                "reached recursion limit during macro expansion".into(),
+            ));
+        }
+
+        let ExpandResult { value, err } = op(self);
+        let Some(call_id) = value else {
+            return ExpandResult { value: None, err };
+        };
+
+        Self::enter_expand_inner(db, call_id, err).map(|value| {
+            value.and_then(|InFile { file_id, value }| {
+                let parse = value.cast::<T>()?;
+
+                self.recursion_depth += 1;
+                self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
+                let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
+                let mark =
+                    Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
+                Some((mark, parse))
+            })
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct Mark {
+    file_id: HirFileId,
+    bomb: DropBomb,
+}
diff --git a/crates/hir-def/src/generics.rs b/crates/hir-def/src/generics.rs
index c1e20d657bd..c7668577585 100644
--- a/crates/hir-def/src/generics.rs
+++ b/crates/hir-def/src/generics.rs
@@ -3,6 +3,8 @@
 //! generic parameters. See also the `Generics` type and the `generics_of` query
 //! in rustc.
 
+use std::sync::Arc;
+
 use base_db::FileId;
 use either::Either;
 use hir_expand::{
@@ -16,10 +18,12 @@ use stdx::impl_from;
 use syntax::ast::{self, HasGenericParams, HasName, HasTypeBounds};
 
 use crate::{
-    body::{Expander, LowerCtx},
     child_by_source::ChildBySource,
     db::DefDatabase,
     dyn_map::{keys, DynMap},
+    expander::Expander,
+    lower::LowerCtx,
+    nameres::DefMap,
     src::{HasChildSource, HasSource},
     type_ref::{LifetimeRef, TypeBound, TypeRef},
     AdtId, ConstParamId, GenericDefId, HasModule, LifetimeParamId, LocalLifetimeParamId,
@@ -151,7 +155,6 @@ impl GenericParams {
         def: GenericDefId,
     ) -> Interned<GenericParams> {
         let _p = profile::span("generic_params_query");
-
         macro_rules! id_to_generics {
             ($id:ident) => {{
                 let id = $id.lookup(db).id;
@@ -174,7 +177,9 @@ impl GenericParams {
 
                 // Don't create an `Expander` nor call `loc.source(db)` if not needed since this
                 // causes a reparse after the `ItemTree` has been created.
-                let mut expander = Lazy::new(|| Expander::new(db, loc.source(db).file_id, module));
+                let mut expander = Lazy::new(|| {
+                    (module.def_map(db), Expander::new(db, loc.source(db).file_id, module))
+                });
                 for param in &func_data.params {
                     generic_params.fill_implicit_impl_trait_args(db, &mut expander, param);
                 }
@@ -327,7 +332,7 @@ impl GenericParams {
     pub(crate) fn fill_implicit_impl_trait_args(
         &mut self,
         db: &dyn DefDatabase,
-        expander: &mut Expander,
+        exp: &mut Lazy<(Arc<DefMap>, Expander), impl FnOnce() -> (Arc<DefMap>, Expander)>,
         type_ref: &TypeRef,
     ) {
         type_ref.walk(&mut |type_ref| {
@@ -347,14 +352,27 @@ impl GenericParams {
             }
             if let TypeRef::Macro(mc) = type_ref {
                 let macro_call = mc.to_node(db.upcast());
-                match expander.enter_expand::<ast::Type>(db, macro_call) {
-                    Ok(ExpandResult { value: Some((mark, expanded)), .. }) => {
-                        let ctx = expander.ctx(db);
-                        let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
-                        self.fill_implicit_impl_trait_args(db, expander, &type_ref);
-                        expander.exit(db, mark);
-                    }
-                    _ => {}
+                let (def_map, expander) = &mut **exp;
+
+                let module = expander.module.local_id;
+                let resolver = |path| {
+                    def_map
+                        .resolve_path(
+                            db,
+                            module,
+                            &path,
+                            crate::item_scope::BuiltinShadowMode::Other,
+                        )
+                        .0
+                        .take_macros()
+                };
+                if let Ok(ExpandResult { value: Some((mark, expanded)), .. }) =
+                    expander.enter_expand(db, macro_call, resolver)
+                {
+                    let ctx = expander.ctx(db);
+                    let type_ref = TypeRef::from_ast(&ctx, expanded.tree());
+                    self.fill_implicit_impl_trait_args(db, &mut *exp, &type_ref);
+                    exp.1.exit(db, mark);
                 }
             }
         });
diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs
index 0e2c0d864dc..06e6be66baa 100644
--- a/crates/hir-def/src/hir/type_ref.rs
+++ b/crates/hir-def/src/hir/type_ref.rs
@@ -11,9 +11,9 @@ use intern::Interned;
 use syntax::ast::{self, HasName};
 
 use crate::{
-    body::LowerCtx,
     builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
     hir::Literal,
+    lower::LowerCtx,
     path::Path,
 };
 
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index 07a68d1598c..0480e6a51d3 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -476,7 +476,7 @@ mod tests {
     use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
     use expect_test::{expect, Expect};
 
-    use crate::{test_db::TestDB, ItemContainerId, Lookup};
+    use crate::{db::DefDatabase, test_db::TestDB, ItemContainerId, Lookup};
 
     use super::*;
 
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index c67c8bb4401..457a519872c 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -20,7 +20,7 @@ pub(super) struct Ctx<'a> {
     db: &'a dyn DefDatabase,
     tree: ItemTree,
     source_ast_id_map: Arc<AstIdMap>,
-    body_ctx: crate::body::LowerCtx<'a>,
+    body_ctx: crate::lower::LowerCtx<'a>,
 }
 
 impl<'a> Ctx<'a> {
@@ -29,7 +29,7 @@ impl<'a> Ctx<'a> {
             db,
             tree: ItemTree::default(),
             source_ast_id_map: db.ast_id_map(file),
-            body_ctx: crate::body::LowerCtx::with_file_id(db, file),
+            body_ctx: crate::lower::LowerCtx::with_file_id(db, file),
         }
     }
 
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 34d704942ab..b7f0e229eef 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -22,6 +22,9 @@ pub mod builtin_type;
 pub mod per_ns;
 pub mod item_scope;
 
+pub mod lower;
+pub mod expander;
+
 pub mod dyn_map;
 
 pub mod item_tree;
@@ -65,6 +68,7 @@ use hir_expand::{
     builtin_attr_macro::BuiltinAttrExpander,
     builtin_derive_macro::BuiltinDeriveExpander,
     builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
+    db::ExpandDatabase,
     eager::expand_eager_macro,
     hygiene::Hygiene,
     proc_macro::ProcMacroExpander,
@@ -791,7 +795,7 @@ impl AttrDefId {
 pub trait AsMacroCall {
     fn as_call_id(
         &self,
-        db: &dyn db::DefDatabase,
+        db: &dyn ExpandDatabase,
         krate: CrateId,
         resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
     ) -> Option<MacroCallId> {
@@ -800,7 +804,7 @@ pub trait AsMacroCall {
 
     fn as_call_id_with_errors(
         &self,
-        db: &dyn db::DefDatabase,
+        db: &dyn ExpandDatabase,
         krate: CrateId,
         resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
     ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro>;
@@ -809,15 +813,14 @@ pub trait AsMacroCall {
 impl AsMacroCall for InFile<&ast::MacroCall> {
     fn as_call_id_with_errors(
         &self,
-        db: &dyn db::DefDatabase,
+        db: &dyn ExpandDatabase,
         krate: CrateId,
         resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
     ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
         let expands_to = hir_expand::ExpandTo::from_call_site(self.value);
         let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
-        let h = Hygiene::new(db.upcast(), self.file_id);
-        let path =
-            self.value.path().and_then(|path| path::ModPath::from_src(db.upcast(), path, &h));
+        let h = Hygiene::new(db, self.file_id);
+        let path = self.value.path().and_then(|path| path::ModPath::from_src(db, path, &h));
 
         let Some(path) = path else {
             return Ok(ExpandResult::only_err(ExpandError::Other("malformed macro invocation".into())));
@@ -847,7 +850,7 @@ impl<T: ast::AstNode> AstIdWithPath<T> {
 }
 
 fn macro_call_as_call_id(
-    db: &dyn db::DefDatabase,
+    db: &dyn ExpandDatabase,
     call: &AstIdWithPath<ast::MacroCall>,
     expand_to: ExpandTo,
     krate: CrateId,
@@ -857,7 +860,7 @@ fn macro_call_as_call_id(
 }
 
 fn macro_call_as_call_id_(
-    db: &dyn db::DefDatabase,
+    db: &dyn ExpandDatabase,
     call: &AstIdWithPath<ast::MacroCall>,
     expand_to: ExpandTo,
     krate: CrateId,
@@ -867,13 +870,12 @@ fn macro_call_as_call_id_(
         resolver(call.path.clone()).ok_or_else(|| UnresolvedMacro { path: call.path.clone() })?;
 
     let res = if let MacroDefKind::BuiltInEager(..) = def.kind {
-        let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db.upcast()));
-
-        expand_eager_macro(db.upcast(), krate, macro_call, def, &resolver)?
+        let macro_call = InFile::new(call.ast_id.file_id, call.ast_id.to_node(db));
+        expand_eager_macro(db, krate, macro_call, def, &resolver)?
     } else {
         ExpandResult {
             value: Some(def.as_lazy_macro(
-                db.upcast(),
+                db,
                 krate,
                 MacroCallKind::FnLike { ast_id: call.ast_id, expand_to },
             )),
diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs
new file mode 100644
index 00000000000..1991d547f5d
--- /dev/null
+++ b/crates/hir-def/src/lower.rs
@@ -0,0 +1,46 @@
+//! Context for lowering paths.
+use std::sync::Arc;
+
+use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile};
+use once_cell::unsync::OnceCell;
+use syntax::ast;
+
+use crate::{db::DefDatabase, path::Path};
+
+pub struct LowerCtx<'a> {
+    pub db: &'a dyn DefDatabase,
+    hygiene: Hygiene,
+    ast_id_map: Option<(HirFileId, OnceCell<Arc<AstIdMap>>)>,
+}
+
+impl<'a> LowerCtx<'a> {
+    pub fn new(db: &'a dyn DefDatabase, hygiene: &Hygiene, file_id: HirFileId) -> Self {
+        LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: Some((file_id, OnceCell::new())) }
+    }
+
+    pub fn with_file_id(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
+        LowerCtx {
+            db,
+            hygiene: Hygiene::new(db.upcast(), file_id),
+            ast_id_map: Some((file_id, OnceCell::new())),
+        }
+    }
+
+    pub fn with_hygiene(db: &'a dyn DefDatabase, hygiene: &Hygiene) -> Self {
+        LowerCtx { db, hygiene: hygiene.clone(), ast_id_map: None }
+    }
+
+    pub(crate) fn hygiene(&self) -> &Hygiene {
+        &self.hygiene
+    }
+
+    pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> {
+        Path::from_src(ast, self)
+    }
+
+    pub(crate) fn ast_id<N: syntax::AstNode>(&self, item: &N) -> Option<AstId<N>> {
+        let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?;
+        let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id));
+        Some(InFile::new(file_id, ast_id_map.ast_id(item)))
+    }
+}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index 461b498fa01..756a8f50490 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -1111,7 +1111,7 @@ impl DefCollector<'_> {
             match &directive.kind {
                 MacroDirectiveKind::FnLike { ast_id, expand_to } => {
                     let call_id = macro_call_as_call_id(
-                        self.db,
+                        self.db.upcast(),
                         ast_id,
                         *expand_to,
                         self.def_map.krate,
@@ -1402,7 +1402,7 @@ impl DefCollector<'_> {
                 MacroDirectiveKind::FnLike { ast_id, expand_to } => {
                     // FIXME: we shouldn't need to re-resolve the macro here just to get the unresolved error!
                     let macro_call_as_call_id = macro_call_as_call_id(
-                        self.db,
+                        self.db.upcast(),
                         ast_id,
                         *expand_to,
                         self.def_map.krate,
@@ -2117,7 +2117,7 @@ impl ModCollector<'_, '_> {
 
         // Case 1: try to resolve in legacy scope and expand macro_rules
         if let Ok(res) = macro_call_as_call_id(
-            self.def_collector.db,
+            self.def_collector.db.upcast(),
             &ast_id,
             mac.expand_to,
             self.def_collector.def_map.krate,
diff --git a/crates/hir-def/src/nameres/tests/incremental.rs b/crates/hir-def/src/nameres/tests/incremental.rs
index d2e3575d5e8..34fa15f9e1f 100644
--- a/crates/hir-def/src/nameres/tests/incremental.rs
+++ b/crates/hir-def/src/nameres/tests/incremental.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use base_db::SourceDatabaseExt;
 
-use crate::{AdtId, ModuleDefId};
+use crate::{db::DefDatabase, AdtId, ModuleDefId};
 
 use super::*;
 
diff --git a/crates/hir-def/src/path.rs b/crates/hir-def/src/path.rs
index 8cd287f7f30..b9b80825497 100644
--- a/crates/hir-def/src/path.rs
+++ b/crates/hir-def/src/path.rs
@@ -7,8 +7,8 @@ use std::{
 };
 
 use crate::{
-    body::LowerCtx,
     lang_item::LangItemTarget,
+    lower::LowerCtx,
     type_ref::{ConstRefOrPath, LifetimeRef, TypeBound, TypeRef},
 };
 use hir_expand::name::Name;
diff --git a/crates/hir-def/src/path/lower.rs b/crates/hir-def/src/path/lower.rs
index c35f915b00e..721f9b76804 100644
--- a/crates/hir-def/src/path/lower.rs
+++ b/crates/hir-def/src/path/lower.rs
@@ -2,17 +2,15 @@
 
 use std::iter;
 
-use crate::type_ref::ConstRefOrPath;
+use crate::{lower::LowerCtx, type_ref::ConstRefOrPath};
 
 use either::Either;
 use hir_expand::name::{name, AsName};
 use intern::Interned;
 use syntax::ast::{self, AstNode, HasTypeBounds};
 
-use super::AssociatedTypeBinding;
 use crate::{
-    body::LowerCtx,
-    path::{GenericArg, GenericArgs, ModPath, Path, PathKind},
+    path::{AssociatedTypeBinding, GenericArg, GenericArgs, ModPath, Path, PathKind},
     type_ref::{LifetimeRef, TypeBound, TypeRef},
 };
 
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 0eef25102e8..d445abae923 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -7,7 +7,6 @@ use std::fmt::{self, Debug};
 use base_db::CrateId;
 use chalk_ir::{BoundVar, TyKind};
 use hir_def::{
-    body,
     data::adt::VariantData,
     db::DefDatabase,
     find_path,
@@ -1552,7 +1551,10 @@ impl HirDisplay for TypeRef {
             }
             TypeRef::Macro(macro_call) => {
                 let macro_call = macro_call.to_node(f.db.upcast());
-                let ctx = body::LowerCtx::with_hygiene(f.db.upcast(), &Hygiene::new_unhygienic());
+                let ctx = hir_def::lower::LowerCtx::with_hygiene(
+                    f.db.upcast(),
+                    &Hygiene::new_unhygienic(),
+                );
                 match macro_call.path() {
                     Some(path) => match Path::from_src(path, &ctx) {
                         Some(path) => path.hir_fmt(f)?,
diff --git a/crates/hir-ty/src/interner.rs b/crates/hir-ty/src/interner.rs
index 874f808cda1..19bb7f169f1 100644
--- a/crates/hir-ty/src/interner.rs
+++ b/crates/hir-ty/src/interner.rs
@@ -43,7 +43,7 @@ impl_internable!(
 );
 
 impl chalk_ir::interner::Interner for Interner {
-    type InternedType = Interned<InternedWrapper<chalk_ir::TyData<Interner>>>;
+    type InternedType = Interned<InternedWrapper<chalk_ir::TyData<Self>>>;
     type InternedLifetime = Interned<InternedWrapper<chalk_ir::LifetimeData<Self>>>;
     type InternedConst = Interned<InternedWrapper<chalk_ir::ConstData<Self>>>;
     type InternedConcreteConst = ConstScalar;
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 33dc5e2d69b..7da747b9497 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -18,9 +18,9 @@ use chalk_ir::{
 
 use either::Either;
 use hir_def::{
-    body::Expander,
     builtin_type::BuiltinType,
     data::adt::StructKind,
+    expander::Expander,
     generics::{
         TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget,
     },
@@ -378,7 +378,9 @@ impl<'a> TyLoweringContext<'a> {
                 };
                 let ty = {
                     let macro_call = macro_call.to_node(self.db.upcast());
-                    match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call) {
+                    match expander.enter_expand::<ast::Type>(self.db.upcast(), macro_call, |path| {
+                        self.resolver.resolve_path_as_macro(self.db.upcast(), &path)
+                    }) {
                         Ok(ExpandResult { value: Some((mark, expanded)), .. }) => {
                             let ctx = expander.ctx(self.db.upcast());
                             // FIXME: Report syntax errors in expansion here
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index b32efe1cb54..a9f78131501 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -7,8 +7,8 @@ use std::{cell::RefCell, fmt, iter, mem, ops};
 use base_db::{FileId, FileRange};
 use either::Either;
 use hir_def::{
-    body,
     hir::Expr,
+    lower::LowerCtx,
     macro_id_to_def_id,
     resolver::{self, HasResolver, Resolver, TypeNs},
     type_ref::Mutability,
@@ -1065,7 +1065,7 @@ impl<'db> SemanticsImpl<'db> {
 
     fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
         let analyze = self.analyze(ty.syntax())?;
-        let ctx = body::LowerCtx::with_file_id(self.db.upcast(), analyze.file_id);
+        let ctx = LowerCtx::with_file_id(self.db.upcast(), analyze.file_id);
         let ty = hir_ty::TyLoweringContext::new(self.db, &analyze.resolver)
             .lower_ty(&crate::TypeRef::from_ast(&ctx, ty.clone()));
         Some(Type::new_with_resolver(self.db, &analyze.resolver, ty))
@@ -1074,7 +1074,7 @@ impl<'db> SemanticsImpl<'db> {
     fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> {
         let analyze = self.analyze(path.syntax())?;
         let hygiene = hir_expand::hygiene::Hygiene::new(self.db.upcast(), analyze.file_id);
-        let ctx = body::LowerCtx::with_hygiene(self.db.upcast(), &hygiene);
+        let ctx = LowerCtx::with_hygiene(self.db.upcast(), &hygiene);
         let hir_path = Path::from_src(path.clone(), &ctx)?;
         match analyze.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), &hir_path)? {
             TypeNs::TraitId(id) => Some(Trait { id }),
@@ -1672,7 +1672,7 @@ impl<'a> SemanticsScope<'a> {
     /// Resolve a path as-if it was written at the given scope. This is
     /// necessary a heuristic, as it doesn't take hygiene into account.
     pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
-        let ctx = body::LowerCtx::with_file_id(self.db.upcast(), self.file_id);
+        let ctx = LowerCtx::with_file_id(self.db.upcast(), self.file_id);
         let path = Path::from_src(path.clone(), &ctx)?;
         resolve_hir_path(self.db, &self.resolver, &path)
     }
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index d0bf1c23acd..a6a51e4907c 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -13,12 +13,12 @@ use std::{
 use either::Either;
 use hir_def::{
     body::{
-        self,
         scope::{ExprScopes, ScopeId},
         Body, BodySourceMap,
     },
     hir::{ExprId, Pat, PatId},
     lang_item::LangItem,
+    lower::LowerCtx,
     macro_id_to_def_id,
     path::{ModPath, Path, PathKind},
     resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
@@ -463,7 +463,7 @@ impl SourceAnalyzer {
         db: &dyn HirDatabase,
         macro_call: InFile<&ast::MacroCall>,
     ) -> Option<Macro> {
-        let ctx = body::LowerCtx::with_file_id(db.upcast(), macro_call.file_id);
+        let ctx = LowerCtx::with_file_id(db.upcast(), macro_call.file_id);
         let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &ctx))?;
         self.resolver.resolve_path_as_macro(db.upcast(), path.mod_path()?).map(|it| it.into())
     }
@@ -575,7 +575,7 @@ impl SourceAnalyzer {
 
         // This must be a normal source file rather than macro file.
         let hygiene = Hygiene::new(db.upcast(), self.file_id);
-        let ctx = body::LowerCtx::with_hygiene(db.upcast(), &hygiene);
+        let ctx = LowerCtx::with_hygiene(db.upcast(), &hygiene);
         let hir_path = Path::from_src(path.clone(), &ctx)?;
 
         // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are
diff --git a/crates/ide-db/src/line_index.rs b/crates/ide-db/src/line_index.rs
index 16814a1e636..9fb58ebe8ab 100644
--- a/crates/ide-db/src/line_index.rs
+++ b/crates/ide-db/src/line_index.rs
@@ -109,6 +109,9 @@ impl LineIndex {
             line_wide_chars.insert(line, wide_chars);
         }
 
+        newlines.shrink_to_fit();
+        line_wide_chars.shrink_to_fit();
+
         LineIndex { newlines, line_wide_chars }
     }
 
diff --git a/crates/ide-db/src/symbol_index.rs b/crates/ide-db/src/symbol_index.rs
index a91ffd1ec4f..dcdcd17dc61 100644
--- a/crates/ide-db/src/symbol_index.rs
+++ b/crates/ide-db/src/symbol_index.rs
@@ -93,6 +93,7 @@ impl Query {
 pub trait SymbolsDatabase: HirDatabase + SourceDatabaseExt + Upcast<dyn HirDatabase> {
     /// The symbol index for a given module. These modules should only be in source roots that
     /// are inside local_roots.
+    // FIXME: We should probably LRU  this
     fn module_symbols(&self, module: Module) -> Arc<SymbolIndex>;
 
     /// The symbol index for a given source root within library_roots.
diff --git a/crates/limit/src/lib.rs b/crates/limit/src/lib.rs
index 6b2534aa461..7fb4b513a71 100644
--- a/crates/limit/src/lib.rs
+++ b/crates/limit/src/lib.rs
@@ -6,6 +6,7 @@
 use std::sync::atomic::AtomicUsize;
 
 /// Represents a struct used to enforce a numerical limit.
+#[derive(Debug)]
 pub struct Limit {
     upper_bound: usize,
     #[cfg(feature = "tracking")]