hir_def/expr_store/
expander.rs

1//! Macro expansion utilities.
2
3use std::mem;
4
5use base_db::Crate;
6use drop_bomb::DropBomb;
7use hir_expand::{
8    ExpandError, ExpandErrorKind, ExpandResult, HirFileId, InFile, Lookup, MacroCallId,
9    attrs::RawAttrs, eager::EagerCallBackFn, mod_path::ModPath, span_map::SpanMap,
10};
11use span::{AstIdMap, Edition, SyntaxContext};
12use syntax::ast::HasAttrs;
13use syntax::{Parse, ast};
14use triomphe::Arc;
15use tt::TextRange;
16
17use crate::attr::Attrs;
18use crate::expr_store::HygieneId;
19use crate::nameres::DefMap;
20use crate::{AsMacroCall, MacroId, UnresolvedMacro, db::DefDatabase};
21
22#[derive(Debug)]
23pub(super) struct Expander {
24    span_map: SpanMap,
25    current_file_id: HirFileId,
26    ast_id_map: Arc<AstIdMap>,
27    /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
28    recursion_depth: u32,
29    recursion_limit: usize,
30}
31
32impl Expander {
33    pub(super) fn new(
34        db: &dyn DefDatabase,
35        current_file_id: HirFileId,
36        def_map: &DefMap,
37    ) -> Expander {
38        let recursion_limit = def_map.recursion_limit() as usize;
39        let recursion_limit = if cfg!(test) {
40            // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
41            std::cmp::min(32, recursion_limit)
42        } else {
43            recursion_limit
44        };
45        Expander {
46            current_file_id,
47            recursion_depth: 0,
48            recursion_limit,
49            span_map: db.span_map(current_file_id),
50            ast_id_map: db.ast_id_map(current_file_id),
51        }
52    }
53
54    pub(super) fn ctx_for_range(&self, range: TextRange) -> SyntaxContext {
55        self.span_map.span_for_range(range).ctx
56    }
57
58    pub(super) fn hygiene_for_range(&self, db: &dyn DefDatabase, range: TextRange) -> HygieneId {
59        match self.span_map.as_ref() {
60            hir_expand::span_map::SpanMapRef::ExpansionSpanMap(span_map) => {
61                HygieneId::new(span_map.span_at(range.start()).ctx.opaque_and_semitransparent(db))
62            }
63            hir_expand::span_map::SpanMapRef::RealSpanMap(_) => HygieneId::ROOT,
64        }
65    }
66
67    pub(super) fn attrs(
68        &self,
69        db: &dyn DefDatabase,
70        krate: Crate,
71        has_attrs: &dyn HasAttrs,
72    ) -> Attrs {
73        Attrs::filter(db, krate, RawAttrs::new(db, has_attrs, self.span_map.as_ref()))
74    }
75
76    pub(super) fn is_cfg_enabled(
77        &self,
78        db: &dyn DefDatabase,
79        krate: Crate,
80        has_attrs: &dyn HasAttrs,
81    ) -> bool {
82        self.attrs(db, krate, has_attrs).is_cfg_enabled(krate.cfg_options(db))
83    }
84
85    pub(super) fn call_syntax_ctx(&self) -> SyntaxContext {
86        // FIXME:
87        SyntaxContext::root(Edition::CURRENT_FIXME)
88    }
89
90    pub(super) fn enter_expand<T: ast::AstNode>(
91        &mut self,
92        db: &dyn DefDatabase,
93        macro_call: ast::MacroCall,
94        krate: Crate,
95        resolver: impl Fn(&ModPath) -> Option<MacroId>,
96        eager_callback: EagerCallBackFn<'_>,
97    ) -> Result<ExpandResult<Option<(Mark, Option<Parse<T>>)>>, UnresolvedMacro> {
98        // FIXME: within_limit should support this, instead of us having to extract the error
99        let mut unresolved_macro_err = None;
100
101        let result = self.within_limit(db, |this| {
102            let macro_call = this.in_file(&macro_call);
103            match macro_call.as_call_id_with_errors(
104                db,
105                krate,
106                |path| resolver(path).map(|it| db.macro_def(it)),
107                eager_callback,
108            ) {
109                Ok(call_id) => call_id,
110                Err(resolve_err) => {
111                    unresolved_macro_err = Some(resolve_err);
112                    ExpandResult { value: None, err: None }
113                }
114            }
115        });
116
117        if let Some(err) = unresolved_macro_err { Err(err) } else { Ok(result) }
118    }
119
120    pub(super) fn enter_expand_id<T: ast::AstNode>(
121        &mut self,
122        db: &dyn DefDatabase,
123        call_id: MacroCallId,
124    ) -> ExpandResult<Option<(Mark, Option<Parse<T>>)>> {
125        self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
126    }
127
128    pub(super) fn exit(&mut self, Mark { file_id, span_map, ast_id_map, mut bomb }: Mark) {
129        self.span_map = span_map;
130        self.current_file_id = file_id;
131        self.ast_id_map = ast_id_map;
132        if self.recursion_depth == u32::MAX {
133            // Recursion limit has been reached somewhere in the macro expansion tree. Reset the
134            // depth only when we get out of the tree.
135            if !self.current_file_id.is_macro() {
136                self.recursion_depth = 0;
137            }
138        } else {
139            self.recursion_depth -= 1;
140        }
141        bomb.defuse();
142    }
143
144    pub(super) fn in_file<T>(&self, value: T) -> InFile<T> {
145        InFile { file_id: self.current_file_id, value }
146    }
147
148    pub(super) fn current_file_id(&self) -> HirFileId {
149        self.current_file_id
150    }
151
152    fn within_limit<F, T: ast::AstNode>(
153        &mut self,
154        db: &dyn DefDatabase,
155        op: F,
156    ) -> ExpandResult<Option<(Mark, Option<Parse<T>>)>>
157    where
158        F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
159    {
160        if self.recursion_depth == u32::MAX {
161            // Recursion limit has been reached somewhere in the macro expansion tree. We should
162            // stop expanding other macro calls in this tree, or else this may result in
163            // exponential number of macro expansions, leading to a hang.
164            //
165            // The overflow error should have been reported when it occurred (see the next branch),
166            // so don't return overflow error here to avoid diagnostics duplication.
167            cov_mark::hit!(overflow_but_not_me);
168            return ExpandResult::ok(None);
169        }
170
171        let ExpandResult { value, err } = op(self);
172        let Some(call_id) = value else {
173            return ExpandResult { value: None, err };
174        };
175        if self.recursion_depth as usize > self.recursion_limit {
176            self.recursion_depth = u32::MAX;
177            cov_mark::hit!(your_stack_belongs_to_me);
178            return ExpandResult::only_err(ExpandError::new(
179                db.macro_arg_considering_derives(call_id, &call_id.lookup(db).kind).2,
180                ExpandErrorKind::RecursionOverflow,
181            ));
182        }
183
184        let res = db.parse_macro_expansion(call_id);
185
186        let err = err.or(res.err);
187        ExpandResult {
188            value: {
189                let parse = res.value.0.cast::<T>();
190
191                self.recursion_depth += 1;
192                let old_file_id = std::mem::replace(&mut self.current_file_id, call_id.into());
193                let old_span_map =
194                    std::mem::replace(&mut self.span_map, db.span_map(self.current_file_id));
195                let prev_ast_id_map =
196                    mem::replace(&mut self.ast_id_map, db.ast_id_map(self.current_file_id));
197                let mark = Mark {
198                    file_id: old_file_id,
199                    span_map: old_span_map,
200                    ast_id_map: prev_ast_id_map,
201                    bomb: DropBomb::new("expansion mark dropped"),
202                };
203                Some((mark, parse))
204            },
205            err,
206        }
207    }
208
209    pub(super) fn ast_id_map(&self) -> &AstIdMap {
210        &self.ast_id_map
211    }
212}
213
214#[derive(Debug)]
215pub(super) struct Mark {
216    file_id: HirFileId,
217    span_map: SpanMap,
218    ast_id_map: Arc<AstIdMap>,
219    bomb: DropBomb,
220}