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.rs177
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(&macro_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(&macro_def).unwrap_or(Transparency::Opaque),
+ ),
+ };
+ Arc::new(DeclarativeMacroExpander { mac, transparency })
+ }
+}