1use std::mem;
4
5use base_db::{Crate, SourceDatabase};
6use cfg::CfgOptions;
7use drop_bomb::DropBomb;
8use hir_expand::{
9 AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, InFile, Lookup, MacroCallId,
10 eager::EagerCallBackFn, mod_path::ModPath, span_map::SpanMap,
11};
12use span::{AstIdMap, SyntaxContext};
13use syntax::ast::HasAttrs;
14use syntax::{AstNode, Parse, ast};
15use tt::TextRange;
16
17use crate::{
18 MacroId, UnresolvedMacro, attrs::AttrFlags, expr_store::HygieneId, macro_call_as_call_id,
19 nameres::DefMap,
20};
21
22#[derive(Debug)]
23pub(super) struct Expander<'db> {
24 span_map: SpanMap<'db>,
25 current_file_id: HirFileId,
26 ast_id_map: &'db AstIdMap,
27 macro_depth: u32,
29 recursion_limit: u32,
30}
31
32impl<'db> Expander<'db> {
33 pub(super) fn new(
34 db: &'db dyn SourceDatabase,
35 current_file_id: HirFileId,
36 def_map: &'db DefMap,
37 ) -> Expander<'db> {
38 let recursion_limit = def_map.recursion_limit();
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 macro_depth: current_file_id.macro_expansion_depth(db),
48 recursion_limit,
49 span_map: current_file_id.span_map(db),
50 ast_id_map: current_file_id.ast_id_map(db),
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 SourceDatabase, range: TextRange) -> HygieneId {
59 match self.span_map {
60 SpanMap::ExpansionSpanMap(span_map) => {
61 HygieneId::new(span_map.span_at(range.start()).ctx.opaque_and_semiopaque(db))
62 }
63 SpanMap::RealSpanMap(_) => HygieneId::ROOT,
64 }
65 }
66
67 pub(super) fn is_cfg_enabled(
68 &self,
69 owner: &dyn HasAttrs,
70 cfg_options: &CfgOptions,
71 ) -> Result<(), cfg::CfgExpr> {
72 AttrFlags::is_cfg_enabled_for(owner, cfg_options)
73 }
74
75 pub(super) fn enter_expand<T: ast::AstNode>(
76 &mut self,
77 db: &'db dyn SourceDatabase,
78 macro_call: ast::MacroCall,
79 krate: Crate,
80 resolver: impl Fn(&ModPath) -> Option<MacroId>,
81 eager_callback: EagerCallBackFn<'_>,
82 ) -> Result<ExpandResult<Option<(Mark<'db>, Option<Parse<T>>)>>, UnresolvedMacro> {
83 let mut unresolved_macro_err = None;
85
86 let result = self.within_limit(db, |this| {
87 let macro_call = this.in_file(¯o_call);
88
89 let expands_to = hir_expand::ExpandTo::from_call_site(macro_call.value);
90 let ast_id = AstId::new(macro_call.file_id, this.ast_id_map().ast_id(macro_call.value));
91 let path = macro_call.value.path().and_then(|path| {
92 let range = path.syntax().text_range();
93 let mod_path = ModPath::from_src(db, path, &mut |range| {
94 this.span_map.span_for_range(range).ctx
95 })?;
96 let call_site = this.span_map.span_for_range(range);
97 Some((call_site, mod_path))
98 });
99
100 let Some((call_site, path)) = path else {
101 return ExpandResult::only_err(ExpandError::other(
102 this.span_map.span_for_range(macro_call.value.syntax().text_range()),
103 "malformed macro invocation",
104 ));
105 };
106
107 match macro_call_as_call_id(
108 db,
109 ast_id,
110 &path,
111 call_site.ctx,
112 expands_to,
113 krate,
114 this.macro_depth + 1,
115 this.recursion_limit,
116 |path| resolver(path).map(|it| it.definition(db)),
117 eager_callback,
118 ) {
119 Ok(call_id) => call_id,
120 Err(resolve_err) => {
121 unresolved_macro_err = Some(resolve_err);
122 ExpandResult { value: None, err: None }
123 }
124 }
125 });
126
127 if let Some(err) = unresolved_macro_err { Err(err) } else { Ok(result) }
128 }
129
130 pub(super) fn enter_expand_id<T: ast::AstNode>(
131 &mut self,
132 db: &'db dyn SourceDatabase,
133 call_id: MacroCallId,
134 ) -> ExpandResult<Option<(Mark<'db>, Option<Parse<T>>)>> {
135 self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
136 }
137
138 pub(super) fn exit(&mut self, Mark { file_id, span_map, ast_id_map, mut bomb }: Mark<'db>) {
139 self.span_map = span_map;
140 self.current_file_id = file_id;
141 self.ast_id_map = ast_id_map;
142 if self.macro_depth == u32::MAX {
143 if !self.current_file_id.is_macro() {
146 self.macro_depth = 0;
147 }
148 } else {
149 self.macro_depth -= 1;
150 }
151 bomb.defuse();
152 }
153
154 pub(super) fn in_file<T>(&self, value: T) -> InFile<T> {
155 InFile { file_id: self.current_file_id, value }
156 }
157
158 pub(super) fn current_file_id(&self) -> HirFileId {
159 self.current_file_id
160 }
161
162 fn within_limit<F, T: ast::AstNode>(
163 &mut self,
164 db: &'db dyn SourceDatabase,
165 op: F,
166 ) -> ExpandResult<Option<(Mark<'db>, Option<Parse<T>>)>>
167 where
168 F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
169 {
170 if self.macro_depth == u32::MAX {
171 cov_mark::hit!(overflow_but_not_me);
178 return ExpandResult::ok(None);
179 }
180
181 let ExpandResult { value, err } = op(self);
182 let Some(call_id) = value else {
183 return ExpandResult { value: None, err };
184 };
185 if self.macro_depth > self.recursion_limit {
186 self.macro_depth = u32::MAX;
187 cov_mark::hit!(your_stack_belongs_to_me);
188 return ExpandResult::only_err(ExpandError::new(
189 call_id.macro_arg_considering_derives(db, &call_id.lookup(db).kind).2,
190 ExpandErrorKind::RecursionOverflow,
191 ));
192 }
193
194 let res = call_id.parse_macro_expansion(db);
195
196 let err = err.or_else(|| res.err.clone());
197 ExpandResult {
198 value: {
199 let parse = res.value.0.clone().cast::<T>();
200
201 self.macro_depth += 1;
202 let old_file_id = std::mem::replace(&mut self.current_file_id, call_id.into());
203 let old_span_map =
204 std::mem::replace(&mut self.span_map, SpanMap::ExpansionSpanMap(&res.value.1));
205 let prev_ast_id_map =
206 mem::replace(&mut self.ast_id_map, self.current_file_id.ast_id_map(db));
207 let mark = Mark {
208 file_id: old_file_id,
209 span_map: old_span_map,
210 ast_id_map: prev_ast_id_map,
211 bomb: DropBomb::new("expansion mark dropped"),
212 };
213 Some((mark, parse))
214 },
215 err,
216 }
217 }
218
219 #[inline]
220 pub(super) fn ast_id_map(&self) -> &AstIdMap {
221 self.ast_id_map
222 }
223
224 #[inline]
225 pub(super) fn span_map(&self) -> SpanMap<'_> {
226 self.span_map
227 }
228}
229
230#[derive(Debug)]
231pub(super) struct Mark<'db> {
232 file_id: HirFileId,
233 span_map: SpanMap<'db>,
234 ast_id_map: &'db AstIdMap,
235 bomb: DropBomb,
236}