• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Macro expansion utilities.
2 
3 use base_db::CrateId;
4 use cfg::CfgOptions;
5 use drop_bomb::DropBomb;
6 use hir_expand::{
7     attrs::RawAttrs, hygiene::Hygiene, mod_path::ModPath, ExpandError, ExpandResult, HirFileId,
8     InFile, MacroCallId, UnresolvedMacro,
9 };
10 use limit::Limit;
11 use syntax::{ast, Parse, SyntaxNode};
12 
13 use crate::{
14     attr::Attrs, db::DefDatabase, lower::LowerCtx, macro_id_to_def_id, path::Path, AsMacroCall,
15     MacroId, ModuleId,
16 };
17 
18 /// A subset of Expander that only deals with cfg attributes. We only need it to
19 /// avoid cyclic queries in crate def map during enum processing.
20 #[derive(Debug)]
21 pub(crate) struct CfgExpander {
22     cfg_options: CfgOptions,
23     hygiene: Hygiene,
24     krate: CrateId,
25 }
26 
27 #[derive(Debug)]
28 pub struct Expander {
29     cfg_expander: CfgExpander,
30     pub(crate) current_file_id: HirFileId,
31     pub(crate) module: ModuleId,
32     /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached.
33     recursion_depth: u32,
34     recursion_limit: Limit,
35 }
36 
37 impl CfgExpander {
new( db: &dyn DefDatabase, current_file_id: HirFileId, krate: CrateId, ) -> CfgExpander38     pub(crate) fn new(
39         db: &dyn DefDatabase,
40         current_file_id: HirFileId,
41         krate: CrateId,
42     ) -> CfgExpander {
43         let hygiene = Hygiene::new(db.upcast(), current_file_id);
44         let cfg_options = db.crate_graph()[krate].cfg_options.clone();
45         CfgExpander { cfg_options, hygiene, krate }
46     }
47 
parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs48     pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
49         Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene))
50     }
51 
is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool52     pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
53         let attrs = self.parse_attrs(db, owner);
54         attrs.is_cfg_enabled(&self.cfg_options)
55     }
56 
hygiene(&self) -> &Hygiene57     pub(crate) fn hygiene(&self) -> &Hygiene {
58         &self.hygiene
59     }
60 }
61 
62 impl Expander {
new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander63     pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
64         let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
65         let recursion_limit = db.recursion_limit(module.krate);
66         #[cfg(not(test))]
67         let recursion_limit = Limit::new(recursion_limit as usize);
68         // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
69         #[cfg(test)]
70         let recursion_limit = Limit::new(std::cmp::min(32, recursion_limit as usize));
71         Expander { cfg_expander, current_file_id, module, recursion_depth: 0, recursion_limit }
72     }
73 
enter_expand<T: ast::AstNode>( &mut self, db: &dyn DefDatabase, macro_call: ast::MacroCall, resolver: impl Fn(ModPath) -> Option<MacroId>, ) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro>74     pub fn enter_expand<T: ast::AstNode>(
75         &mut self,
76         db: &dyn DefDatabase,
77         macro_call: ast::MacroCall,
78         resolver: impl Fn(ModPath) -> Option<MacroId>,
79     ) -> Result<ExpandResult<Option<(Mark, Parse<T>)>>, UnresolvedMacro> {
80         // FIXME: within_limit should support this, instead of us having to extract the error
81         let mut unresolved_macro_err = None;
82 
83         let result = self.within_limit(db, |this| {
84             let macro_call = InFile::new(this.current_file_id, &macro_call);
85             match macro_call.as_call_id_with_errors(db.upcast(), this.module.krate(), |path| {
86                 resolver(path).map(|it| macro_id_to_def_id(db, it))
87             }) {
88                 Ok(call_id) => call_id,
89                 Err(resolve_err) => {
90                     unresolved_macro_err = Some(resolve_err);
91                     ExpandResult { value: None, err: None }
92                 }
93             }
94         });
95 
96         if let Some(err) = unresolved_macro_err {
97             Err(err)
98         } else {
99             Ok(result)
100         }
101     }
102 
enter_expand_id<T: ast::AstNode>( &mut self, db: &dyn DefDatabase, call_id: MacroCallId, ) -> ExpandResult<Option<(Mark, Parse<T>)>>103     pub fn enter_expand_id<T: ast::AstNode>(
104         &mut self,
105         db: &dyn DefDatabase,
106         call_id: MacroCallId,
107     ) -> ExpandResult<Option<(Mark, Parse<T>)>> {
108         self.within_limit(db, |_this| ExpandResult::ok(Some(call_id)))
109     }
110 
enter_expand_inner( db: &dyn DefDatabase, call_id: MacroCallId, error: Option<ExpandError>, ) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>>111     fn enter_expand_inner(
112         db: &dyn DefDatabase,
113         call_id: MacroCallId,
114         error: Option<ExpandError>,
115     ) -> ExpandResult<Option<InFile<Parse<SyntaxNode>>>> {
116         let macro_file = call_id.as_macro_file();
117         let ExpandResult { value, err } = db.parse_macro_expansion(macro_file);
118 
119         ExpandResult { value: Some(InFile::new(macro_file.into(), value.0)), err: error.or(err) }
120     }
121 
exit(&mut self, db: &dyn DefDatabase, mut mark: Mark)122     pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
123         self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
124         self.current_file_id = mark.file_id;
125         if self.recursion_depth == u32::MAX {
126             // Recursion limit has been reached somewhere in the macro expansion tree. Reset the
127             // depth only when we get out of the tree.
128             if !self.current_file_id.is_macro() {
129                 self.recursion_depth = 0;
130             }
131         } else {
132             self.recursion_depth -= 1;
133         }
134         mark.bomb.defuse();
135     }
136 
ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a>137     pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
138         LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id)
139     }
140 
to_source<T>(&self, value: T) -> InFile<T>141     pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> {
142         InFile { file_id: self.current_file_id, value }
143     }
144 
parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs145     pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
146         self.cfg_expander.parse_attrs(db, owner)
147     }
148 
cfg_options(&self) -> &CfgOptions149     pub(crate) fn cfg_options(&self) -> &CfgOptions {
150         &self.cfg_expander.cfg_options
151     }
152 
current_file_id(&self) -> HirFileId153     pub fn current_file_id(&self) -> HirFileId {
154         self.current_file_id
155     }
156 
parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path>157     pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
158         let ctx = LowerCtx::new(db, &self.cfg_expander.hygiene, self.current_file_id);
159         Path::from_src(path, &ctx)
160     }
161 
within_limit<F, T: ast::AstNode>( &mut self, db: &dyn DefDatabase, op: F, ) -> ExpandResult<Option<(Mark, Parse<T>)>> where F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,162     fn within_limit<F, T: ast::AstNode>(
163         &mut self,
164         db: &dyn DefDatabase,
165         op: F,
166     ) -> ExpandResult<Option<(Mark, Parse<T>)>>
167     where
168         F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>,
169     {
170         if self.recursion_depth == u32::MAX {
171             // Recursion limit has been reached somewhere in the macro expansion tree. We should
172             // stop expanding other macro calls in this tree, or else this may result in
173             // exponential number of macro expansions, leading to a hang.
174             //
175             // The overflow error should have been reported when it occurred (see the next branch),
176             // so don't return overflow error here to avoid diagnostics duplication.
177             cov_mark::hit!(overflow_but_not_me);
178             return ExpandResult::only_err(ExpandError::RecursionOverflowPoisoned);
179         } else if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() {
180             self.recursion_depth = u32::MAX;
181             cov_mark::hit!(your_stack_belongs_to_me);
182             return ExpandResult::only_err(ExpandError::other(
183                 "reached recursion limit during macro expansion",
184             ));
185         }
186 
187         let ExpandResult { value, err } = op(self);
188         let Some(call_id) = value else {
189             return ExpandResult { value: None, err };
190         };
191 
192         Self::enter_expand_inner(db, call_id, err).map(|value| {
193             value.and_then(|InFile { file_id, value }| {
194                 let parse = value.cast::<T>()?;
195 
196                 self.recursion_depth += 1;
197                 self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
198                 let old_file_id = std::mem::replace(&mut self.current_file_id, file_id);
199                 let mark =
200                     Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") };
201                 Some((mark, parse))
202             })
203         })
204     }
205 }
206 
207 #[derive(Debug)]
208 pub struct Mark {
209     file_id: HirFileId,
210     bomb: DropBomb,
211 }
212