| //! Macro expansion utilities. |
| |
| use std::cell::OnceCell; |
| |
| use base_db::CrateId; |
| use cfg::CfgOptions; |
| use drop_bomb::DropBomb; |
| use hir_expand::{ |
| attrs::RawAttrs, mod_path::ModPath, span_map::SpanMap, ExpandError, ExpandErrorKind, |
| ExpandResult, HirFileId, InFile, Lookup, MacroCallId, |
| }; |
| use limit::Limit; |
| use span::SyntaxContextId; |
| use syntax::{ast, Parse}; |
| use triomphe::Arc; |
| |
| use crate::{ |
| attr::Attrs, db::DefDatabase, lower::LowerCtx, path::Path, AsMacroCall, MacroId, ModuleId, |
| UnresolvedMacro, |
| }; |
| |
| #[derive(Debug)] |
| pub struct Expander { |
| cfg_options: Arc<CfgOptions>, |
| span_map: OnceCell<SpanMap>, |
| current_file_id: HirFileId, |
| pub(crate) module: ModuleId, |
| /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. |
| recursion_depth: u32, |
| recursion_limit: Limit, |
| } |
| |
| impl Expander { |
| pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { |
| let recursion_limit = module.def_map(db).recursion_limit() as usize; |
| let recursion_limit = Limit::new(if cfg!(test) { |
| // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug |
| std::cmp::min(32, recursion_limit) |
| } else { |
| recursion_limit |
| }); |
| Expander { |
| current_file_id, |
| module, |
| recursion_depth: 0, |
| recursion_limit, |
| cfg_options: db.crate_graph()[module.krate].cfg_options.clone(), |
| span_map: OnceCell::new(), |
| } |
| } |
| |
| pub fn krate(&self) -> CrateId { |
| self.module.krate |
| } |
| |
| pub fn syntax_context(&self) -> SyntaxContextId { |
| // FIXME: |
| SyntaxContextId::ROOT |
| } |
| |
| pub fn 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> { |
| // FIXME: within_limit should support this, instead of us having to extract the error |
| let mut unresolved_macro_err = None; |
| |
| let result = self.within_limit(db, |this| { |
| let macro_call = this.in_file(¯o_call); |
| match macro_call.as_call_id( |
| db.upcast(), |
| this.module, |
| |path| resolver(path).map(|it| db.macro_def(it)), |
| |module| this.module.def_map(db).path_for_module(db, module), |
| ) { |
| Ok(call_id) => call_id, |
| Err(resolve_err) => { |
| unresolved_macro_err = Some(resolve_err); |
| ExpandResult { value: None, err: None } |
| } |
| } |
| }); |
| |
| if let Some(err) = unresolved_macro_err { |
| Err(err) |
| } else { |
| Ok(result) |
| } |
| } |
| |
| pub fn enter_expand_id<T: ast::AstNode>( |
| &mut self, |
| db: &dyn DefDatabase, |
| call_id: MacroCallId, |
| ) -> ExpandResult<Option<(Mark, Parse<T>)>> { |
| self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) |
| } |
| |
| pub fn exit(&mut self, mut mark: Mark) { |
| self.span_map = mark.span_map; |
| self.current_file_id = mark.file_id; |
| if self.recursion_depth == u32::MAX { |
| // Recursion limit has been reached somewhere in the macro expansion tree. Reset the |
| // depth only when we get out of the tree. |
| if !self.current_file_id.is_macro() { |
| self.recursion_depth = 0; |
| } |
| } else { |
| self.recursion_depth -= 1; |
| } |
| mark.bomb.defuse(); |
| } |
| |
| pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> { |
| LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone()) |
| } |
| |
| pub(crate) fn in_file<T>(&self, value: T) -> InFile<T> { |
| InFile { file_id: self.current_file_id, value } |
| } |
| |
| pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { |
| Attrs::filter( |
| db, |
| self.krate(), |
| RawAttrs::new( |
| db.upcast(), |
| owner, |
| self.span_map.get_or_init(|| db.span_map(self.current_file_id)).as_ref(), |
| ), |
| ) |
| } |
| |
| pub(crate) fn cfg_options(&self) -> &CfgOptions { |
| &self.cfg_options |
| } |
| |
| pub fn current_file_id(&self) -> HirFileId { |
| self.current_file_id |
| } |
| |
| pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> { |
| let ctx = LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone()); |
| Path::from_src(&ctx, path) |
| } |
| |
| fn 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>>, |
| { |
| if self.recursion_depth == u32::MAX { |
| // Recursion limit has been reached somewhere in the macro expansion tree. We should |
| // stop expanding other macro calls in this tree, or else this may result in |
| // exponential number of macro expansions, leading to a hang. |
| // |
| // The overflow error should have been reported when it occurred (see the next branch), |
| // so don't return overflow error here to avoid diagnostics duplication. |
| cov_mark::hit!(overflow_but_not_me); |
| return ExpandResult::ok(None); |
| } |
| |
| let ExpandResult { value, err } = op(self); |
| let Some(call_id) = value else { |
| return ExpandResult { value: None, err }; |
| }; |
| if self.recursion_limit.check(self.recursion_depth as usize + 1).is_err() { |
| self.recursion_depth = u32::MAX; |
| cov_mark::hit!(your_stack_belongs_to_me); |
| return ExpandResult::only_err(ExpandError::new( |
| db.macro_arg_considering_derives(call_id, &call_id.lookup(db.upcast()).kind).2, |
| ExpandErrorKind::RecursionOverflow, |
| )); |
| } |
| |
| let macro_file = call_id.as_macro_file(); |
| let res = db.parse_macro_expansion(macro_file); |
| |
| let err = err.or(res.err); |
| ExpandResult { |
| value: match &err { |
| // If proc-macro is disabled or unresolved, we want to expand to a missing expression |
| // instead of an empty tree which might end up in an empty block. |
| Some(e) if matches!(e.kind(), ExpandErrorKind::MissingProcMacroExpander(_)) => None, |
| _ => (|| { |
| let parse = res.value.0.cast::<T>()?; |
| |
| self.recursion_depth += 1; |
| let old_span_map = OnceCell::new(); |
| if let Some(prev) = self.span_map.take() { |
| _ = old_span_map.set(prev); |
| }; |
| _ = self.span_map.set(SpanMap::ExpansionSpanMap(res.value.1)); |
| let old_file_id = |
| std::mem::replace(&mut self.current_file_id, macro_file.into()); |
| let mark = Mark { |
| file_id: old_file_id, |
| span_map: old_span_map, |
| bomb: DropBomb::new("expansion mark dropped"), |
| }; |
| Some((mark, parse)) |
| })(), |
| }, |
| err, |
| } |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct Mark { |
| file_id: HirFileId, |
| span_map: OnceCell<SpanMap>, |
| bomb: DropBomb, |
| } |