5a83d1be66
The general problem we are dealing with here is this: ``` macro_rules! thrice { ($e:expr) => { $e * 3} } fn main() { let x = thrice!(1 + 2); } ``` we really want this to print 9 rather than 7. The way rustc solves this is rather ad-hoc. In rustc, token trees are allowed to include whole AST fragments, so 1+2 is passed through macro expansion as a single unit. This is a significant violation of token tree model. In rust-analyzer, we intended to handle this in a more elegant way, using token trees with "invisible" delimiters. The idea was is that we introduce a new kind of parenthesis, "left $"/"right $", and let the parser intelligently handle this. The idea was inspired by the relevant comment in the proc_macro crate: https://doc.rust-lang.org/stable/proc_macro/enum.Delimiter.html#variant.None > An implicit delimiter, that may, for example, appear around tokens > coming from a “macro variable” $var. It is important to preserve > operator priorities in cases like $var * 3 where $var is 1 + 2. > Implicit delimiters might not survive roundtrip of a token stream > through a string. Now that we are older and wiser, we conclude that the idea doesn't work. _First_, the comment in the proc-macro crate is wishful thinking. Rustc currently completely ignores none delimiters. It solves the (1 + 2) * 3 problem by having magical token trees which can't be duplicated: * https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer/topic/TIL.20that.20token.20streams.20are.20magic * https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Handling.20of.20Delimiter.3A.3ANone.20by.20the.20parser _Second_, it's not like our implementation in rust-analyzer works. We special-case expressions (as opposed to treating all kinds of $var captures the same) and we don't know how parser error recovery should work with these dollar-parenthesis. So, in this PR we simplify the whole thing away by not pretending that we are doing something proper and instead just explicitly special-casing expressions by wrapping them into real `()`. In the future, to maintain bug-parity with `rustc` what we are going to do is probably adding an explicit `CAPTURED_EXPR` *token* which we can explicitly account for in the parser. If/when rustc starts handling delimiter=none properly, we'll port that logic as well, in addition to special handling.
260 lines
8.7 KiB
Rust
260 lines
8.7 KiB
Rust
//! Transcriber takes a template, like `fn $ident() {}`, a set of bindings like
|
|
//! `$ident => foo`, interpolates variables in the template, to get `fn foo() {}`
|
|
|
|
use syntax::SmolStr;
|
|
use tt::{Delimiter, Subtree};
|
|
|
|
use super::ExpandResult;
|
|
use crate::{
|
|
expander::{Binding, Bindings, Fragment},
|
|
parser::{Op, RepeatKind, Separator},
|
|
ExpandError, MetaTemplate,
|
|
};
|
|
|
|
impl Bindings {
|
|
fn contains(&self, name: &str) -> bool {
|
|
self.inner.contains_key(name)
|
|
}
|
|
|
|
fn get(&self, name: &str, nesting: &mut [NestingState]) -> Result<&Fragment, ExpandError> {
|
|
let mut b: &Binding = self.inner.get(name).ok_or_else(|| {
|
|
ExpandError::BindingError(format!("could not find binding `{}`", name))
|
|
})?;
|
|
for nesting_state in nesting.iter_mut() {
|
|
nesting_state.hit = true;
|
|
b = match b {
|
|
Binding::Fragment(_) => break,
|
|
Binding::Nested(bs) => bs.get(nesting_state.idx).ok_or_else(|| {
|
|
nesting_state.at_end = true;
|
|
ExpandError::BindingError(format!("could not find nested binding `{}`", name))
|
|
})?,
|
|
Binding::Empty => {
|
|
nesting_state.at_end = true;
|
|
return Err(ExpandError::BindingError(format!(
|
|
"could not find empty binding `{}`",
|
|
name
|
|
)));
|
|
}
|
|
};
|
|
}
|
|
match b {
|
|
Binding::Fragment(it) => Ok(it),
|
|
Binding::Nested(_) => Err(ExpandError::BindingError(format!(
|
|
"expected simple binding, found nested binding `{}`",
|
|
name
|
|
))),
|
|
Binding::Empty => Err(ExpandError::BindingError(format!(
|
|
"expected simple binding, found empty binding `{}`",
|
|
name
|
|
))),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn transcribe(
|
|
template: &MetaTemplate,
|
|
bindings: &Bindings,
|
|
) -> ExpandResult<tt::Subtree> {
|
|
let mut ctx = ExpandCtx { bindings, nesting: Vec::new() };
|
|
let mut arena: Vec<tt::TokenTree> = Vec::new();
|
|
expand_subtree(&mut ctx, template, None, &mut arena)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct NestingState {
|
|
idx: usize,
|
|
/// `hit` is currently necessary to tell `expand_repeat` if it should stop
|
|
/// because there is no variable in use by the current repetition
|
|
hit: bool,
|
|
/// `at_end` is currently necessary to tell `expand_repeat` if it should stop
|
|
/// because there is no more value available for the current repetition
|
|
at_end: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ExpandCtx<'a> {
|
|
bindings: &'a Bindings,
|
|
nesting: Vec<NestingState>,
|
|
}
|
|
|
|
fn expand_subtree(
|
|
ctx: &mut ExpandCtx,
|
|
template: &MetaTemplate,
|
|
delimiter: Option<Delimiter>,
|
|
arena: &mut Vec<tt::TokenTree>,
|
|
) -> ExpandResult<tt::Subtree> {
|
|
// remember how many elements are in the arena now - when returning, we want to drain exactly how many elements we added. This way, the recursive uses of the arena get their own "view" of the arena, but will reuse the allocation
|
|
let start_elements = arena.len();
|
|
let mut err = None;
|
|
for op in template.iter() {
|
|
match op {
|
|
Op::Leaf(tt) => arena.push(tt.clone().into()),
|
|
Op::Subtree { tokens, delimiter } => {
|
|
let ExpandResult { value: tt, err: e } =
|
|
expand_subtree(ctx, tokens, *delimiter, arena);
|
|
err = err.or(e);
|
|
arena.push(tt.into());
|
|
}
|
|
Op::Var { name, id, .. } => {
|
|
let ExpandResult { value: fragment, err: e } = expand_var(ctx, name, *id);
|
|
err = err.or(e);
|
|
push_fragment(arena, fragment);
|
|
}
|
|
Op::Repeat { tokens: subtree, kind, separator } => {
|
|
let ExpandResult { value: fragment, err: e } =
|
|
expand_repeat(ctx, subtree, *kind, separator, arena);
|
|
err = err.or(e);
|
|
push_fragment(arena, fragment)
|
|
}
|
|
}
|
|
}
|
|
// drain the elements added in this instance of expand_subtree
|
|
let tts = arena.drain(start_elements..arena.len()).collect();
|
|
ExpandResult { value: tt::Subtree { delimiter, token_trees: tts }, err }
|
|
}
|
|
|
|
fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
|
|
// We already handle $crate case in mbe parser
|
|
debug_assert!(v != "crate");
|
|
|
|
if !ctx.bindings.contains(v) {
|
|
// Note that it is possible to have a `$var` inside a macro which is not bound.
|
|
// For example:
|
|
// ```
|
|
// macro_rules! foo {
|
|
// ($a:ident, $b:ident, $c:tt) => {
|
|
// macro_rules! bar {
|
|
// ($bi:ident) => {
|
|
// fn $bi() -> u8 {$c}
|
|
// }
|
|
// }
|
|
// }
|
|
// ```
|
|
// We just treat it a normal tokens
|
|
let tt = tt::Subtree {
|
|
delimiter: None,
|
|
token_trees: vec![
|
|
tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
|
|
tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
|
|
],
|
|
}
|
|
.into();
|
|
ExpandResult::ok(Fragment::Tokens(tt))
|
|
} else {
|
|
ctx.bindings.get(v, &mut ctx.nesting).map_or_else(
|
|
|e| ExpandResult { value: Fragment::Tokens(tt::TokenTree::empty()), err: Some(e) },
|
|
|b| ExpandResult::ok(b.clone()),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn expand_repeat(
|
|
ctx: &mut ExpandCtx,
|
|
template: &MetaTemplate,
|
|
kind: RepeatKind,
|
|
separator: &Option<Separator>,
|
|
arena: &mut Vec<tt::TokenTree>,
|
|
) -> ExpandResult<Fragment> {
|
|
let mut buf: Vec<tt::TokenTree> = Vec::new();
|
|
ctx.nesting.push(NestingState { idx: 0, at_end: false, hit: false });
|
|
// Dirty hack to make macro-expansion terminate.
|
|
// This should be replaced by a proper macro-by-example implementation
|
|
let limit = 65536;
|
|
let mut has_seps = 0;
|
|
let mut counter = 0;
|
|
|
|
loop {
|
|
let ExpandResult { value: mut t, err: e } = expand_subtree(ctx, template, None, arena);
|
|
let nesting_state = ctx.nesting.last_mut().unwrap();
|
|
if nesting_state.at_end || !nesting_state.hit {
|
|
break;
|
|
}
|
|
nesting_state.idx += 1;
|
|
nesting_state.hit = false;
|
|
|
|
counter += 1;
|
|
if counter == limit {
|
|
tracing::warn!(
|
|
"expand_tt in repeat pattern exceed limit => {:#?}\n{:#?}",
|
|
template,
|
|
ctx
|
|
);
|
|
return ExpandResult {
|
|
value: Fragment::Tokens(Subtree::default().into()),
|
|
err: Some(ExpandError::Other("Expand exceed limit".to_string())),
|
|
};
|
|
}
|
|
|
|
if e.is_some() {
|
|
continue;
|
|
}
|
|
|
|
t.delimiter = None;
|
|
push_subtree(&mut buf, t);
|
|
|
|
if let Some(ref sep) = separator {
|
|
match sep {
|
|
Separator::Ident(ident) => {
|
|
has_seps = 1;
|
|
buf.push(tt::Leaf::from(ident.clone()).into());
|
|
}
|
|
Separator::Literal(lit) => {
|
|
has_seps = 1;
|
|
buf.push(tt::Leaf::from(lit.clone()).into());
|
|
}
|
|
|
|
Separator::Puncts(puncts) => {
|
|
has_seps = puncts.len();
|
|
for punct in puncts {
|
|
buf.push(tt::Leaf::from(*punct).into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if RepeatKind::ZeroOrOne == kind {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ctx.nesting.pop().unwrap();
|
|
for _ in 0..has_seps {
|
|
buf.pop();
|
|
}
|
|
|
|
// Check if it is a single token subtree without any delimiter
|
|
// e.g {Delimiter:None> ['>'] /Delimiter:None>}
|
|
let tt = tt::Subtree { delimiter: None, token_trees: buf }.into();
|
|
|
|
if RepeatKind::OneOrMore == kind && counter == 0 {
|
|
return ExpandResult {
|
|
value: Fragment::Tokens(tt),
|
|
err: Some(ExpandError::UnexpectedToken),
|
|
};
|
|
}
|
|
ExpandResult::ok(Fragment::Tokens(tt))
|
|
}
|
|
|
|
fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) {
|
|
match fragment {
|
|
Fragment::Tokens(tt::TokenTree::Subtree(tt)) => push_subtree(buf, tt),
|
|
Fragment::Expr(tt::TokenTree::Subtree(mut tt)) => {
|
|
if tt.delimiter.is_none() {
|
|
tt.delimiter = Some(tt::Delimiter {
|
|
id: tt::TokenId::unspecified(),
|
|
kind: tt::DelimiterKind::Parenthesis,
|
|
})
|
|
}
|
|
buf.push(tt.into())
|
|
}
|
|
Fragment::Tokens(tt) | Fragment::Expr(tt) => buf.push(tt),
|
|
}
|
|
}
|
|
|
|
fn push_subtree(buf: &mut Vec<tt::TokenTree>, tt: tt::Subtree) {
|
|
match tt.delimiter {
|
|
None => buf.extend(tt.token_trees),
|
|
Some(_) => buf.push(tt.into()),
|
|
}
|
|
}
|