Skip to main content

hir_expand/
eager.rs

1//! Eager expansion related utils
2//!
3//! Here is a dump of a discussion from Vadim Petrochenkov about Eager Expansion and
4//! Its name resolution :
5//!
6//! > Eagerly expanded macros (and also macros eagerly expanded by eagerly expanded macros,
7//! > which actually happens in practice too!) are resolved at the location of the "root" macro
8//! > that performs the eager expansion on its arguments.
9//! > If some name cannot be resolved at the eager expansion time it's considered unresolved,
10//! > even if becomes available later (e.g. from a glob import or other macro).
11//!
12//! > Eagerly expanded macros don't add anything to the module structure of the crate and
13//! > don't build any speculative module structures, i.e. they are expanded in a "flat"
14//! > way even if tokens in them look like modules.
15//!
16//! > In other words, it kinda works for simple cases for which it was originally intended,
17//! > and we need to live with it because it's available on stable and widely relied upon.
18//!
19//!
20//! See the full discussion : <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Eager.20expansion.20of.20built-in.20macros>
21use base_db::{Crate, SourceDatabase};
22use span::SyntaxContext;
23use syntax::{
24    AstPtr, Parse, SyntaxElement, SyntaxNode, TextSize, WalkEvent, syntax_editor::SyntaxEditor,
25};
26use syntax_bridge::DocCommentDesugarMode;
27
28use crate::{
29    AstId, EagerCallInfo, ExpandError, ExpandErrorKind, ExpandResult, ExpandTo, ExpansionSpanMap,
30    InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind,
31    ast::{self, AstNode},
32    mod_path::ModPath,
33};
34
35pub type EagerCallBackFn<'a> = &'a mut dyn FnMut(
36    InFile<(syntax::AstPtr<ast::MacroCall>, span::FileAstId<ast::MacroCall>)>,
37    MacroCallId,
38);
39
40pub fn expand_eager_macro_input(
41    db: &dyn SourceDatabase,
42    krate: Crate,
43    macro_call: &ast::MacroCall,
44    ast_id: AstId<ast::MacroCall>,
45    def: MacroDefId,
46    call_site: SyntaxContext,
47    macro_depth: u32,
48    recursion_limit: u32,
49    resolver: &dyn Fn(&ModPath) -> Option<MacroDefId>,
50    eager_callback: EagerCallBackFn<'_>,
51) -> ExpandResult<Option<MacroCallId>> {
52    let expand_to = ExpandTo::from_call_site(macro_call);
53
54    // Note:
55    // When `lazy_expand` is called, its *parent* file must already exist.
56    // Here we store an eager macro id for the argument expanded subtree
57    // for that purpose.
58    let loc = MacroCallLoc {
59        def,
60        krate,
61        kind: MacroCallKind::FnLike { ast_id, expand_to: ExpandTo::Expr, eager: None },
62        ctxt: call_site,
63        macro_depth,
64    };
65    let arg_id = MacroCallId::new(db, loc);
66    #[expect(deprecated, reason = "builtin eager macros are never derives")]
67    let (_, _, span) = arg_id.macro_arg(db);
68    let ExpandResult { value: (arg_exp, arg_exp_map), err: parse_err } =
69        arg_id.parse_macro_expansion(db);
70
71    let mut arg_map = ExpansionSpanMap::empty();
72
73    let ExpandResult { value: expanded_eager_input, err } = {
74        eager_macro_recur(
75            db,
76            arg_exp_map,
77            &mut arg_map,
78            TextSize::new(0),
79            InFile::new(arg_id.into(), arg_exp.syntax_node()),
80            krate,
81            call_site,
82            macro_depth,
83            recursion_limit,
84            resolver,
85            eager_callback,
86        )
87    };
88    let err = parse_err.clone().or(err);
89    if cfg!(debug_assertions) {
90        arg_map.finish();
91    }
92
93    let Some((expanded_eager_input, _mapping)) = expanded_eager_input else {
94        return ExpandResult { value: None, err };
95    };
96
97    let mut subtree = syntax_bridge::syntax_node_to_token_tree(
98        &expanded_eager_input,
99        arg_map,
100        *span,
101        DocCommentDesugarMode::Mbe,
102    );
103
104    subtree.set_top_subtree_delimiter_kind(crate::tt::DelimiterKind::Invisible);
105
106    let loc = MacroCallLoc {
107        def,
108        krate,
109        kind: MacroCallKind::FnLike {
110            ast_id,
111            expand_to,
112            eager: Some(Box::new(EagerCallInfo {
113                arg: subtree,
114                arg_id,
115                error: err.clone(),
116                span: *span,
117            })),
118        },
119        ctxt: call_site,
120        macro_depth,
121    };
122
123    ExpandResult { value: Some(MacroCallId::new(db, loc)), err }
124}
125
126fn lazy_expand<'db>(
127    db: &'db dyn SourceDatabase,
128    def: &MacroDefId,
129    macro_call: &ast::MacroCall,
130    ast_id: AstId<ast::MacroCall>,
131    krate: Crate,
132    call_site: SyntaxContext,
133    macro_depth: u32,
134    eager_callback: EagerCallBackFn<'_>,
135) -> ExpandResult<(InFile<Parse<SyntaxNode>>, &'db ExpansionSpanMap)> {
136    let expand_to = ExpandTo::from_call_site(macro_call);
137    let id = def.make_call(
138        db,
139        krate,
140        MacroCallKind::FnLike { ast_id, expand_to, eager: None },
141        call_site,
142        macro_depth,
143    );
144    eager_callback(ast_id.map(|ast_id| (AstPtr::new(macro_call), ast_id)), id);
145
146    id.parse_macro_expansion(db)
147        .as_ref()
148        .map(|parse| (InFile::new(id.into(), parse.0.clone()), &parse.1))
149}
150
151fn eager_macro_recur(
152    db: &dyn SourceDatabase,
153    span_map: &ExpansionSpanMap,
154    expanded_map: &mut ExpansionSpanMap,
155    mut offset: TextSize,
156    curr: InFile<SyntaxNode>,
157    krate: Crate,
158    call_site: SyntaxContext,
159    macro_depth: u32,
160    recursion_limit: u32,
161    macro_resolver: &dyn Fn(&ModPath) -> Option<MacroDefId>,
162    eager_callback: EagerCallBackFn<'_>,
163) -> ExpandResult<Option<(SyntaxNode, TextSize)>> {
164    let (editor, _) = SyntaxEditor::new(curr.value.clone());
165    let original = curr.value.clone();
166
167    let mut replacements = Vec::new();
168
169    // FIXME: We only report a single error inside of eager expansions
170    let mut error = None;
171    let mut children = original.preorder_with_tokens();
172
173    // Collect replacement
174    while let Some(child) = children.next() {
175        let call = match child {
176            WalkEvent::Enter(SyntaxElement::Node(child)) => match ast::MacroCall::cast(child) {
177                Some(it) => {
178                    children.skip_subtree();
179                    it
180                }
181                _ => continue,
182            },
183            WalkEvent::Enter(_) => continue,
184            WalkEvent::Leave(child) => {
185                if let SyntaxElement::Token(t) = child {
186                    let start = t.text_range().start();
187                    offset += t.text_range().len();
188                    expanded_map.push(offset, span_map.span_at(start));
189                }
190                continue;
191            }
192        };
193
194        let def = match call.path().and_then(|path| {
195            ModPath::from_src(db, path, &mut |range| span_map.span_at(range.start()).ctx)
196        }) {
197            Some(path) => match macro_resolver(&path) {
198                Some(def) => def,
199                None => {
200                    let edition = krate.data(db).edition;
201                    error = Some(ExpandError::other(
202                        span_map.span_at(call.syntax().text_range().start()),
203                        format!("unresolved macro {}", path.display(db, edition)),
204                    ));
205                    offset += call.syntax().text_range().len();
206                    continue;
207                }
208            },
209            None => {
210                error = Some(ExpandError::other(
211                    span_map.span_at(call.syntax().text_range().start()),
212                    "malformed macro invocation",
213                ));
214                offset += call.syntax().text_range().len();
215                continue;
216            }
217        };
218        let ast_id = curr.file_id.ast_id_map(db).ast_id(&call);
219
220        if macro_depth > recursion_limit {
221            return ExpandResult::only_err(ExpandError::new(
222                span_map.span_at(call.syntax().text_range().start()),
223                ExpandErrorKind::RecursionOverflow,
224            ));
225        }
226
227        let ExpandResult { value, err } = match def.kind {
228            MacroDefKind::BuiltInEager(..) => {
229                let ExpandResult { value, err } = expand_eager_macro_input(
230                    db,
231                    krate,
232                    &call,
233                    curr.with_value(ast_id),
234                    def,
235                    call_site,
236                    macro_depth + 1,
237                    recursion_limit,
238                    macro_resolver,
239                    eager_callback,
240                );
241                match value {
242                    Some(call_id) => {
243                        eager_callback(
244                            curr.with_value(ast_id).map(|ast_id| (AstPtr::new(&call), ast_id)),
245                            call_id,
246                        );
247                        let ExpandResult { value: (parse, map), err: err2 } =
248                            call_id.parse_macro_expansion(db);
249
250                        map.iter().for_each(|(o, span)| expanded_map.push(o + offset, span));
251
252                        let syntax_node = parse.syntax_node();
253                        ExpandResult {
254                            value: Some((
255                                syntax_node.clone(),
256                                offset + syntax_node.text_range().len(),
257                            )),
258                            err: err.clone().or_else(|| err2.clone()),
259                        }
260                    }
261                    None => ExpandResult { value: None, err },
262                }
263            }
264            MacroDefKind::Declarative(..)
265            | MacroDefKind::BuiltIn(..)
266            | MacroDefKind::BuiltInAttr(..)
267            | MacroDefKind::BuiltInDerive(..)
268            | MacroDefKind::ProcMacro(..)
269            | MacroDefKind::UnimplementedBuiltIn(..) => {
270                let ExpandResult { value: (parse, tm), err } = lazy_expand(
271                    db,
272                    &def,
273                    &call,
274                    curr.with_value(ast_id),
275                    krate,
276                    call_site,
277                    macro_depth,
278                    eager_callback,
279                );
280
281                // replace macro inside
282                let ExpandResult { value, err: error } = eager_macro_recur(
283                    db,
284                    tm,
285                    expanded_map,
286                    offset,
287                    // FIXME: We discard parse errors here
288                    parse.as_ref().map(|it| it.syntax_node()),
289                    krate,
290                    call_site,
291                    macro_depth + 1,
292                    recursion_limit,
293                    macro_resolver,
294                    eager_callback,
295                );
296                let err = err.or(error);
297
298                ExpandResult { value, err }
299            }
300        };
301        if err.is_some() {
302            error = err;
303        }
304        // check if the whole original syntax is replaced
305        if call.syntax() == &original {
306            return ExpandResult { value, err: error };
307        }
308
309        match value {
310            Some((insert, new_offset)) => {
311                replacements.push((call, insert));
312                offset = new_offset;
313            }
314            None => offset += call.syntax().text_range().len(),
315        }
316    }
317
318    replacements.into_iter().rev().for_each(|(old, new)| editor.replace(old.syntax(), new));
319    let original = editor.finish().new_root().clone();
320    ExpandResult { value: Some((original, offset)), err: error }
321}