Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/declarative.rs')
| -rw-r--r-- | crates/hir-expand/src/declarative.rs | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs new file mode 100644 index 0000000000..37084ee8b9 --- /dev/null +++ b/crates/hir-expand/src/declarative.rs @@ -0,0 +1,177 @@ +//! Compiled declarative macro expanders (`macro_rules!`` and `macro`) +use std::sync::OnceLock; + +use base_db::{CrateId, Edition, VersionReq}; +use span::{MacroCallId, Span}; +use syntax::{ast, AstNode}; +use triomphe::Arc; + +use crate::{ + attrs::RawAttrs, + db::ExpandDatabase, + hygiene::{apply_mark, Transparency}, + tt, AstId, ExpandError, ExpandResult, +}; + +/// Old-style `macro_rules` or the new macros 2.0 +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct DeclarativeMacroExpander { + pub mac: mbe::DeclarativeMacro<span::Span>, + pub transparency: Transparency, +} + +// FIXME: Remove this once we drop support for 1.76 +static REQUIREMENT: OnceLock<VersionReq> = OnceLock::new(); + +impl DeclarativeMacroExpander { + pub fn expand( + &self, + db: &dyn ExpandDatabase, + tt: tt::Subtree, + call_id: MacroCallId, + ) -> ExpandResult<tt::Subtree> { + let loc = db.lookup_intern_macro_call(call_id); + let toolchain = &db.crate_graph()[loc.def.krate].toolchain; + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); + match self.mac.err() { + Some(e) => ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: loc.call_site, close: loc.call_site }), + ExpandError::other(format!("invalid macro definition: {e}")), + ), + None => self + .mac + .expand( + &tt, + |s| s.ctx = apply_mark(db, s.ctx, call_id, self.transparency), + new_meta_vars, + loc.call_site, + ) + .map_err(Into::into), + } + } + + pub fn expand_unhygienic( + &self, + db: &dyn ExpandDatabase, + tt: tt::Subtree, + krate: CrateId, + call_site: Span, + ) -> ExpandResult<tt::Subtree> { + let toolchain = &db.crate_graph()[krate].toolchain; + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); + match self.mac.err() { + Some(e) => ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), + ExpandError::other(format!("invalid macro definition: {e}")), + ), + None => self.mac.expand(&tt, |_| (), new_meta_vars, call_site).map_err(Into::into), + } + } + + pub(crate) fn expander( + db: &dyn ExpandDatabase, + def_crate: CrateId, + id: AstId<ast::Macro>, + ) -> Arc<DeclarativeMacroExpander> { + let crate_data = &db.crate_graph()[def_crate]; + let is_2021 = crate_data.edition >= Edition::Edition2021; + let (root, map) = crate::db::parse_with_map(db, id.file_id); + let root = root.syntax_node(); + + let transparency = |node| { + // ... would be nice to have the item tree here + let attrs = RawAttrs::new(db, node, map.as_ref()).filter(db, def_crate); + match &*attrs + .iter() + .find(|it| { + it.path.as_ident().and_then(|it| it.as_str()) + == Some("rustc_macro_transparency") + })? + .token_tree_value()? + .token_trees + { + [tt::TokenTree::Leaf(tt::Leaf::Ident(i)), ..] => match &*i.text { + "transparent" => Some(Transparency::Transparent), + "semitransparent" => Some(Transparency::SemiTransparent), + "opaque" => Some(Transparency::Opaque), + _ => None, + }, + _ => None, + } + }; + let toolchain = crate_data.toolchain.as_ref(); + let new_meta_vars = toolchain.as_ref().map_or(false, |version| { + REQUIREMENT.get_or_init(|| VersionReq::parse(">=1.76").unwrap()).matches( + &base_db::Version { + pre: base_db::Prerelease::EMPTY, + build: base_db::BuildMetadata::EMPTY, + major: version.major, + minor: version.minor, + patch: version.patch, + }, + ) + }); + + let (mac, transparency) = match id.to_ptr(db).to_node(&root) { + ast::Macro::MacroRules(macro_rules) => ( + match macro_rules.token_tree() { + Some(arg) => { + let tt = mbe::syntax_node_to_token_tree( + arg.syntax(), + map.as_ref(), + map.span_for_range( + macro_rules.macro_rules_token().unwrap().text_range(), + ), + ); + + mbe::DeclarativeMacro::parse_macro_rules(&tt, is_2021, new_meta_vars) + } + None => mbe::DeclarativeMacro::from_err( + mbe::ParseError::Expected("expected a token tree".into()), + is_2021, + ), + }, + transparency(¯o_rules).unwrap_or(Transparency::SemiTransparent), + ), + ast::Macro::MacroDef(macro_def) => ( + match macro_def.body() { + Some(arg) => { + let tt = mbe::syntax_node_to_token_tree( + arg.syntax(), + map.as_ref(), + map.span_for_range(macro_def.macro_token().unwrap().text_range()), + ); + + mbe::DeclarativeMacro::parse_macro2(&tt, is_2021, new_meta_vars) + } + None => mbe::DeclarativeMacro::from_err( + mbe::ParseError::Expected("expected a token tree".into()), + is_2021, + ), + }, + transparency(¯o_def).unwrap_or(Transparency::Opaque), + ), + }; + Arc::new(DeclarativeMacroExpander { mac, transparency }) + } +} |