//! Defines `Body`: a lowered representation of bodies of functions, statics and
//! consts.
mod lower;
-mod diagnostics;
#[cfg(test)]
mod tests;
pub mod scope;
use std::{mem, ops::Index, sync::Arc};
use base_db::CrateId;
-use cfg::CfgOptions;
+use cfg::{CfgExpr, CfgOptions};
use drop_bomb::DropBomb;
use either::Either;
use hir_expand::{
- ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult,
- HirFileId, InFile, MacroDefId,
+ ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandError, ExpandResult, HirFileId, InFile,
+ MacroCallId, MacroDefId,
};
use la_arena::{Arena, ArenaMap};
+use limit::Limit;
use profile::Count;
use rustc_hash::FxHashMap;
-use syntax::{ast, AstNode, AstPtr, SyntaxNode};
-
-pub use lower::LowerCtx;
+use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
use crate::{
attr::{Attrs, RawAttrs},
UnresolvedMacro,
};
+pub use lower::LowerCtx;
+
/// A subset of Expander that only deals with cfg attributes. We only need it to
/// avoid cyclic queries in crate def map during enum processing.
+#[derive(Debug)]
pub(crate) struct CfgExpander {
cfg_options: CfgOptions,
hygiene: Hygiene,
krate: CrateId,
}
-pub(crate) struct Expander {
+#[derive(Debug)]
+pub struct Expander {
cfg_expander: CfgExpander,
def_map: Arc<DefMap>,
current_file_id: HirFileId,
recursion_limit: usize,
}
-#[cfg(test)]
-const EXPANSION_RECURSION_LIMIT: usize = 32;
-
-#[cfg(not(test))]
-const EXPANSION_RECURSION_LIMIT: usize = 128;
-
impl CfgExpander {
pub(crate) fn new(
db: &dyn DefDatabase,
CfgExpander { cfg_options, hygiene, krate }
}
- pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::AttrsOwner) -> Attrs {
- RawAttrs::new(owner, &self.hygiene).filter(db, self.krate)
+ pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
+ RawAttrs::new(db, owner, &self.hygiene).filter(db, self.krate)
}
- pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::AttrsOwner) -> bool {
+ pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool {
let attrs = self.parse_attrs(db, owner);
attrs.is_cfg_enabled(&self.cfg_options)
}
}
impl Expander {
- pub(crate) fn new(
- db: &dyn DefDatabase,
- current_file_id: HirFileId,
- module: ModuleId,
- ) -> Expander {
+ pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander {
let cfg_expander = CfgExpander::new(db, current_file_id, module.krate);
let def_map = module.def_map(db);
let ast_id_map = db.ast_id_map(current_file_id);
}
}
- fn enter_expand_intern(
+ pub fn enter_expand<T: ast::AstNode>(
&mut self,
db: &dyn DefDatabase,
macro_call: ast::MacroCall,
- ) -> Result<
- ExpandResult<Option<(SyntaxNode, impl FnMut(&dyn DefDatabase) -> Mark + '_)>>,
- UnresolvedMacro,
- > {
- if self.recursion_limit + 1 > EXPANSION_RECURSION_LIMIT {
+ ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
+ if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() {
cov_mark::hit!(your_stack_belongs_to_me);
return Ok(ExpandResult::str_err(
"reached recursion limit during macro expansion".into(),
}
};
+ Ok(self.enter_expand_inner(db, call_id, err))
+ }
+
+ pub fn enter_expand_id<T: ast::AstNode>(
+ &mut self,
+ db: &dyn DefDatabase,
+ call_id: MacroCallId,
+ ) -> ExpandResult<Option<(Mark, T)>> {
+ self.enter_expand_inner(db, call_id, None)
+ }
+
+ fn enter_expand_inner<T: ast::AstNode>(
+ &mut self,
+ db: &dyn DefDatabase,
+ call_id: MacroCallId,
+ mut err: Option<ExpandError>,
+ ) -> ExpandResult<Option<(Mark, T)>> {
if err.is_none() {
err = db.macro_expand_error(call_id);
}
None => {
// Only `None` if the macro expansion produced no usable AST.
if err.is_none() {
- log::warn!("no error despite `parse_or_expand` failing");
+ tracing::warn!("no error despite `parse_or_expand` failing");
}
- return Ok(ExpandResult::only_err(err.unwrap_or_else(|| {
+ return ExpandResult::only_err(err.unwrap_or_else(|| {
mbe::ExpandError::Other("failed to parse macro invocation".into())
- })));
- }
- };
-
- let this = self;
-
- let advance_state = move |db: &dyn DefDatabase| {
- this.recursion_limit += 1;
- let mark = Mark {
- file_id: this.current_file_id,
- ast_id_map: mem::take(&mut this.ast_id_map),
- bomb: DropBomb::new("expansion mark dropped"),
- };
- this.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
- this.current_file_id = file_id;
- this.ast_id_map = db.ast_id_map(file_id);
- mark
- };
-
- Ok(ExpandResult { value: Some((raw_node, advance_state)), err })
- }
-
- pub(crate) fn enter_expand_raw(
- &mut self,
- db: &dyn DefDatabase,
- macro_call: ast::MacroCall,
- ) -> Result<ExpandResult<Option<(Mark, SyntaxNode)>>, UnresolvedMacro> {
- let (raw_node, mut advance_state, err) = match self.enter_expand_intern(db, macro_call)? {
- ExpandResult { value: Some((raw_node, advance_state)), err } => {
- (raw_node, advance_state, err)
+ }));
}
- ExpandResult { value: None, err } => return Ok(ExpandResult { value: None, err }),
- };
-
- log::debug!("macro expansion {:#?}", raw_node);
-
- let mark = advance_state(db);
-
- Ok(ExpandResult { value: Some((mark, raw_node)), err })
- }
-
- pub(crate) fn enter_expand<T: ast::AstNode>(
- &mut self,
- db: &dyn DefDatabase,
- macro_call: ast::MacroCall,
- ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
- let (raw_node, mut advance_state, err) = match self.enter_expand_intern(db, macro_call)? {
- ExpandResult { value: Some((raw_node, advance_state)), err } => {
- (raw_node, advance_state, err)
- }
- ExpandResult { value: None, err } => return Ok(ExpandResult { value: None, err }),
};
let node = match T::cast(raw_node) {
Some(it) => it,
None => {
// This can happen without being an error, so only forward previous errors.
- return Ok(ExpandResult { value: None, err });
+ return ExpandResult { value: None, err };
}
};
- log::debug!("macro expansion {:#?}", node.syntax());
+ tracing::debug!("macro expansion {:#?}", node.syntax());
- let mark = advance_state(db);
+ self.recursion_limit += 1;
+ let mark = Mark {
+ file_id: self.current_file_id,
+ ast_id_map: mem::take(&mut self.ast_id_map),
+ bomb: DropBomb::new("expansion mark dropped"),
+ };
+ self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id);
+ self.current_file_id = file_id;
+ self.ast_id_map = db.ast_id_map(file_id);
- Ok(ExpandResult { value: Some((mark, node)), err })
+ ExpandResult { value: Some((mark, node)), err }
}
- pub(crate) fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
+ pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) {
self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id);
self.current_file_id = mark.file_id;
self.ast_id_map = mem::take(&mut mark.ast_id_map);
InFile { file_id: self.current_file_id, value }
}
- pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::AttrsOwner) -> Attrs {
+ pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
self.cfg_expander.parse_attrs(db, owner)
}
&self.cfg_expander.cfg_options
}
- fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
- let ctx = LowerCtx::with_hygiene(&self.cfg_expander.hygiene);
+ pub fn current_file_id(&self) -> HirFileId {
+ self.current_file_id
+ }
+
+ fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
+ let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene);
Path::from_src(path, &ctx)
}
let file_local_id = self.ast_id_map.ast_id(item);
AstId::new(self.current_file_id, file_local_id)
}
+
+ fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit {
+ let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _;
+
+ #[cfg(not(test))]
+ return Limit::new(limit);
+
+ // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
+ #[cfg(test)]
+ return Limit::new(std::cmp::min(32, limit));
+ }
}
#[derive(Debug)]
-pub(crate) struct Mark {
+pub struct Mark {
file_id: HirFileId,
ast_id_map: Arc<AstIdMap>,
bomb: DropBomb,
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
/// the source map (since they're just as volatile).
- diagnostics: Vec<diagnostics::BodyDiagnostic>,
+ diagnostics: Vec<BodyDiagnostic>,
}
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
pub struct SyntheticSyntax;
+#[derive(Debug, Eq, PartialEq)]
+pub enum BodyDiagnostic {
+ InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
+ MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
+ UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
+ UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
+}
+
impl Body {
pub(crate) fn body_with_source_map_query(
db: &dyn DefDatabase,
self.field_map.get(&src).cloned()
}
- pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
- for diag in &self.diagnostics {
- diag.add_to(sink);
- }
+ /// Get a reference to the body source map's diagnostics.
+ pub fn diagnostics(&self) -> &[BodyDiagnostic] {
+ &self.diagnostics
}
}