mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-04 01:08:47 +00:00
194 lines
7.2 KiB
Rust
194 lines
7.2 KiB
Rust
//! Compiled declarative macro expanders (`macro_rules!`` and `macro`)
|
|
use std::sync::OnceLock;
|
|
|
|
use base_db::{CrateId, VersionReq};
|
|
use intern::sym;
|
|
use mbe::DocCommentDesugarMode;
|
|
use span::{Edition, MacroCallId, Span, SyntaxContextId};
|
|
use stdx::TupleExt;
|
|
use syntax::{ast, AstNode};
|
|
use triomphe::Arc;
|
|
|
|
use crate::{
|
|
attrs::RawAttrs,
|
|
db::ExpandDatabase,
|
|
hygiene::{apply_mark, Transparency},
|
|
tt, AstId, ExpandError, ExpandResult, Lookup,
|
|
};
|
|
|
|
/// Old-style `macro_rules` or the new macros 2.0
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub struct DeclarativeMacroExpander {
|
|
pub mac: mbe::DeclarativeMacro,
|
|
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,
|
|
span: Span,
|
|
) -> ExpandResult<(tt::Subtree, Option<u32>)> {
|
|
let loc = db.lookup_intern_macro_call(call_id);
|
|
let toolchain = db.toolchain(loc.def.krate);
|
|
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(_) => ExpandResult::new(
|
|
(tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), None),
|
|
ExpandError::MacroDefinition,
|
|
),
|
|
None => self
|
|
.mac
|
|
.expand(
|
|
&tt,
|
|
|s| s.ctx = apply_mark(db, s.ctx, call_id, self.transparency),
|
|
new_meta_vars,
|
|
span,
|
|
loc.def.edition,
|
|
)
|
|
.map_err(Into::into),
|
|
}
|
|
}
|
|
|
|
pub fn expand_unhygienic(
|
|
&self,
|
|
db: &dyn ExpandDatabase,
|
|
tt: tt::Subtree,
|
|
krate: CrateId,
|
|
call_site: Span,
|
|
def_site_edition: Edition,
|
|
) -> ExpandResult<tt::Subtree> {
|
|
let toolchain = db.toolchain(krate);
|
|
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(_) => ExpandResult::new(
|
|
tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
|
|
ExpandError::MacroDefinition,
|
|
),
|
|
None => self
|
|
.mac
|
|
.expand(&tt, |_| (), new_meta_vars, call_site, def_site_edition)
|
|
.map(TupleExt::head)
|
|
.map_err(Into::into),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn expander(
|
|
db: &dyn ExpandDatabase,
|
|
def_crate: CrateId,
|
|
id: AstId<ast::Macro>,
|
|
) -> Arc<DeclarativeMacroExpander> {
|
|
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()
|
|
.map(|it| *it == sym::rustc_macro_transparency.clone())
|
|
.unwrap_or(false)
|
|
})?
|
|
.token_tree_value()?
|
|
.token_trees
|
|
{
|
|
[tt::TokenTree::Leaf(tt::Leaf::Ident(i)), ..] => match &i.sym {
|
|
s if *s == sym::transparent => Some(Transparency::Transparent),
|
|
s if *s == sym::semitransparent => Some(Transparency::SemiTransparent),
|
|
s if *s == sym::opaque => Some(Transparency::Opaque),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}
|
|
};
|
|
let ctx_edition = |ctx: SyntaxContextId| {
|
|
let crate_graph = db.crate_graph();
|
|
if ctx.is_root() {
|
|
crate_graph[def_crate].edition
|
|
} else {
|
|
let data = db.lookup_intern_syntax_context(ctx);
|
|
// UNWRAP-SAFETY: Only the root context has no outer expansion
|
|
crate_graph[data.outer_expn.unwrap().lookup(db).def.krate].edition
|
|
}
|
|
};
|
|
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(),
|
|
),
|
|
DocCommentDesugarMode::Mbe,
|
|
);
|
|
|
|
mbe::DeclarativeMacro::parse_macro_rules(&tt, ctx_edition)
|
|
}
|
|
None => mbe::DeclarativeMacro::from_err(mbe::ParseError::Expected(
|
|
"expected a token tree".into(),
|
|
)),
|
|
},
|
|
transparency(¯o_rules).unwrap_or(Transparency::SemiTransparent),
|
|
),
|
|
ast::Macro::MacroDef(macro_def) => (
|
|
match macro_def.body() {
|
|
Some(body) => {
|
|
let span =
|
|
map.span_for_range(macro_def.macro_token().unwrap().text_range());
|
|
let args = macro_def.args().map(|args| {
|
|
mbe::syntax_node_to_token_tree(
|
|
args.syntax(),
|
|
map.as_ref(),
|
|
span,
|
|
DocCommentDesugarMode::Mbe,
|
|
)
|
|
});
|
|
let body = mbe::syntax_node_to_token_tree(
|
|
body.syntax(),
|
|
map.as_ref(),
|
|
span,
|
|
DocCommentDesugarMode::Mbe,
|
|
);
|
|
|
|
mbe::DeclarativeMacro::parse_macro2(args.as_ref(), &body, ctx_edition)
|
|
}
|
|
None => mbe::DeclarativeMacro::from_err(mbe::ParseError::Expected(
|
|
"expected a token tree".into(),
|
|
)),
|
|
},
|
|
transparency(¯o_def).unwrap_or(Transparency::Opaque),
|
|
),
|
|
};
|
|
Arc::new(DeclarativeMacroExpander { mac, transparency })
|
|
}
|
|
}
|