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, ¯o_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