1use 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: 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 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 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 let mut unresolved_macro_err = None;
100
101 let result = self.within_limit(db, |this| {
102 let macro_call = this.in_file(¯o_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 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 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}