• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>
21 use base_db::CrateId;
22 use syntax::{ted, Parse, SyntaxNode};
23 use triomphe::Arc;
24 
25 use crate::{
26     ast::{self, AstNode},
27     db::ExpandDatabase,
28     hygiene::Hygiene,
29     mod_path::ModPath,
30     EagerCallInfo, ExpandError, ExpandResult, ExpandTo, InFile, MacroCallId, MacroCallKind,
31     MacroCallLoc, MacroDefId, MacroDefKind, UnresolvedMacro,
32 };
33 
expand_eager_macro_input( db: &dyn ExpandDatabase, krate: CrateId, macro_call: InFile<ast::MacroCall>, def: MacroDefId, resolver: &dyn Fn(ModPath) -> Option<MacroDefId>, ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro>34 pub fn expand_eager_macro_input(
35     db: &dyn ExpandDatabase,
36     krate: CrateId,
37     macro_call: InFile<ast::MacroCall>,
38     def: MacroDefId,
39     resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
40 ) -> Result<ExpandResult<Option<MacroCallId>>, UnresolvedMacro> {
41     assert!(matches!(def.kind, MacroDefKind::BuiltInEager(..)));
42     let token_tree = macro_call.value.token_tree();
43 
44     let Some(token_tree) = token_tree else {
45         return Ok(ExpandResult { value: None, err:
46             Some(ExpandError::other(
47                 "invalid token tree"
48             )),
49         });
50     };
51     let (parsed_args, arg_token_map) = mbe::syntax_node_to_token_tree(token_tree.syntax());
52 
53     let ast_map = db.ast_id_map(macro_call.file_id);
54     let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(&macro_call.value));
55     let expand_to = ExpandTo::from_call_site(&macro_call.value);
56 
57     // Note:
58     // When `lazy_expand` is called, its *parent* file must already exist.
59     // Here we store an eager macro id for the argument expanded subtree
60     // for that purpose.
61     let arg_id = db.intern_macro_call(MacroCallLoc {
62         def,
63         krate,
64         eager: Some(Box::new(EagerCallInfo {
65             arg: Arc::new((parsed_args, arg_token_map)),
66             arg_id: None,
67             error: None,
68         })),
69         kind: MacroCallKind::FnLike { ast_id: call_id, expand_to: ExpandTo::Expr },
70     });
71     let arg_as_expr = match db.macro_arg_text(arg_id) {
72         Some(it) => it,
73         None => {
74             return Ok(ExpandResult {
75                 value: None,
76                 err: Some(ExpandError::other("invalid token tree")),
77             })
78         }
79     };
80     let ExpandResult { value: expanded_eager_input, err } = eager_macro_recur(
81         db,
82         &Hygiene::new(db, macro_call.file_id),
83         InFile::new(arg_id.as_file(), SyntaxNode::new_root(arg_as_expr)),
84         krate,
85         resolver,
86     )?;
87     let Some(expanded_eager_input) = expanded_eager_input else {
88         return Ok(ExpandResult { value: None, err })
89     };
90     let (mut subtree, token_map) = mbe::syntax_node_to_token_tree(&expanded_eager_input);
91     subtree.delimiter = crate::tt::Delimiter::unspecified();
92 
93     let loc = MacroCallLoc {
94         def,
95         krate,
96         eager: Some(Box::new(EagerCallInfo {
97             arg: Arc::new((subtree, token_map)),
98             arg_id: Some(arg_id),
99             error: err.clone(),
100         })),
101         kind: MacroCallKind::FnLike { ast_id: call_id, expand_to },
102     };
103 
104     Ok(ExpandResult { value: Some(db.intern_macro_call(loc)), err })
105 }
106 
lazy_expand( db: &dyn ExpandDatabase, def: &MacroDefId, macro_call: InFile<ast::MacroCall>, krate: CrateId, ) -> ExpandResult<InFile<Parse<SyntaxNode>>>107 fn lazy_expand(
108     db: &dyn ExpandDatabase,
109     def: &MacroDefId,
110     macro_call: InFile<ast::MacroCall>,
111     krate: CrateId,
112 ) -> ExpandResult<InFile<Parse<SyntaxNode>>> {
113     let ast_id = db.ast_id_map(macro_call.file_id).ast_id(&macro_call.value);
114 
115     let expand_to = ExpandTo::from_call_site(&macro_call.value);
116     let id = def.as_lazy_macro(
117         db,
118         krate,
119         MacroCallKind::FnLike { ast_id: macro_call.with_value(ast_id), expand_to },
120     );
121 
122     let macro_file = id.as_macro_file();
123 
124     db.parse_macro_expansion(macro_file).map(|parse| InFile::new(macro_file.into(), parse.0))
125 }
126 
eager_macro_recur( db: &dyn ExpandDatabase, hygiene: &Hygiene, curr: InFile<SyntaxNode>, krate: CrateId, macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>, ) -> Result<ExpandResult<Option<SyntaxNode>>, UnresolvedMacro>127 fn eager_macro_recur(
128     db: &dyn ExpandDatabase,
129     hygiene: &Hygiene,
130     curr: InFile<SyntaxNode>,
131     krate: CrateId,
132     macro_resolver: &dyn Fn(ModPath) -> Option<MacroDefId>,
133 ) -> Result<ExpandResult<Option<SyntaxNode>>, UnresolvedMacro> {
134     let original = curr.value.clone_for_update();
135 
136     let children = original.descendants().filter_map(ast::MacroCall::cast);
137     let mut replacements = Vec::new();
138 
139     // Note: We only report a single error inside of eager expansions
140     let mut error = None;
141 
142     // Collect replacement
143     for child in children {
144         let def = match child.path().and_then(|path| ModPath::from_src(db, path, hygiene)) {
145             Some(path) => macro_resolver(path.clone()).ok_or(UnresolvedMacro { path })?,
146             None => {
147                 error = Some(ExpandError::other("malformed macro invocation"));
148                 continue;
149             }
150         };
151         let ExpandResult { value, err } = match def.kind {
152             MacroDefKind::BuiltInEager(..) => {
153                 let ExpandResult { value, err } = match expand_eager_macro_input(
154                     db,
155                     krate,
156                     curr.with_value(child.clone()),
157                     def,
158                     macro_resolver,
159                 ) {
160                     Ok(it) => it,
161                     Err(err) => return Err(err),
162                 };
163                 match value {
164                     Some(call) => {
165                         let ExpandResult { value, err: err2 } =
166                             db.parse_macro_expansion(call.as_macro_file());
167                         ExpandResult {
168                             value: Some(value.0.syntax_node().clone_for_update()),
169                             err: err.or(err2),
170                         }
171                     }
172                     None => ExpandResult { value: None, err },
173                 }
174             }
175             MacroDefKind::Declarative(_)
176             | MacroDefKind::BuiltIn(..)
177             | MacroDefKind::BuiltInAttr(..)
178             | MacroDefKind::BuiltInDerive(..)
179             | MacroDefKind::ProcMacro(..) => {
180                 let ExpandResult { value, err } =
181                     lazy_expand(db, &def, curr.with_value(child.clone()), krate);
182 
183                 // replace macro inside
184                 let hygiene = Hygiene::new(db, value.file_id);
185                 let ExpandResult { value, err: error } = eager_macro_recur(
186                     db,
187                     &hygiene,
188                     // FIXME: We discard parse errors here
189                     value.map(|it| it.syntax_node()),
190                     krate,
191                     macro_resolver,
192                 )?;
193                 let err = err.or(error);
194                 ExpandResult { value, err }
195             }
196         };
197         if err.is_some() {
198             error = err;
199         }
200         // check if the whole original syntax is replaced
201         if child.syntax() == &original {
202             return Ok(ExpandResult { value, err: error });
203         }
204 
205         if let Some(insert) = value {
206             replacements.push((child, insert));
207         }
208     }
209 
210     replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
211     Ok(ExpandResult { value: Some(original), err: error })
212 }
213