Make basic use of spans for macro expansion errors

This commit is contained in:
Lukas Wirth 2024-07-29 14:52:40 +02:00
parent 7beac14cba
commit ae9c553902
24 changed files with 392 additions and 333 deletions

View file

@ -45,7 +45,7 @@ fn benchmark_expand_macro_rules() {
invocations
.into_iter()
.map(|(id, tt)| {
let res = rules[&id].expand(&tt, |_| (), true, DUMMY, Edition::CURRENT);
let res = rules[&id].expand(&tt, |_| (), DUMMY, Edition::CURRENT);
assert!(res.err.is_none());
res.value.0.token_trees.len()
})
@ -118,7 +118,7 @@ fn invocation_fixtures(
},
token_trees: token_trees.into_boxed_slice(),
};
if it.expand(&subtree, |_| (), true, DUMMY, Edition::CURRENT).err.is_none() {
if it.expand(&subtree, |_| (), DUMMY, Edition::CURRENT).err.is_none() {
res.push((name.clone(), subtree));
break;
}

View file

@ -9,13 +9,12 @@ use intern::Symbol;
use rustc_hash::FxHashMap;
use span::{Edition, Span};
use crate::{parser::MetaVarKind, ExpandError, ExpandResult, MatchedArmIndex};
use crate::{parser::MetaVarKind, ExpandError, ExpandErrorKind, ExpandResult, MatchedArmIndex};
pub(crate) fn expand_rules(
rules: &[crate::Rule],
input: &tt::Subtree<Span>,
marker: impl Fn(&mut Span) + Copy,
new_meta_vars: bool,
call_site: Span,
def_site_edition: Edition,
) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
@ -27,13 +26,8 @@ pub(crate) fn expand_rules(
// If we find a rule that applies without errors, we're done.
// Unconditionally returning the transcription here makes the
// `test_repeat_bad_var` test fail.
let ExpandResult { value, err: transcribe_err } = transcriber::transcribe(
&rule.rhs,
&new_match.bindings,
marker,
new_meta_vars,
call_site,
);
let ExpandResult { value, err: transcribe_err } =
transcriber::transcribe(&rule.rhs, &new_match.bindings, marker, call_site);
if transcribe_err.is_none() {
return ExpandResult::ok((value, Some(idx as u32)));
}
@ -52,7 +46,7 @@ pub(crate) fn expand_rules(
if let Some((match_, rule, idx)) = match_ {
// if we got here, there was no match without errors
let ExpandResult { value, err: transcribe_err } =
transcriber::transcribe(&rule.rhs, &match_.bindings, marker, new_meta_vars, call_site);
transcriber::transcribe(&rule.rhs, &match_.bindings, marker, call_site);
ExpandResult { value: (value, idx.try_into().ok()), err: match_.err.or(transcribe_err) }
} else {
ExpandResult::new(
@ -63,7 +57,7 @@ pub(crate) fn expand_rules(
},
None,
),
ExpandError::NoMatchingRule,
ExpandError::new(call_site, ExpandErrorKind::NoMatchingRule),
)
}
}

View file

@ -70,7 +70,7 @@ use crate::{
expander::{Binding, Bindings, ExpandResult, Fragment},
expect_fragment,
parser::{MetaVarKind, Op, RepeatKind, Separator},
ExpandError, MetaTemplate, ValueResult,
ExpandError, ExpandErrorKind, MetaTemplate, ValueResult,
};
impl Bindings {
@ -510,11 +510,17 @@ fn match_loop_inner<'t>(
if matches!(rhs, tt::Leaf::Literal(it) if it.symbol == lhs.symbol) {
item.dot.next();
} else {
res.add_err(ExpandError::UnexpectedToken);
res.add_err(ExpandError::new(
*rhs.span(),
ExpandErrorKind::UnexpectedToken,
));
item.is_error = true;
}
} else {
res.add_err(ExpandError::binding_error(format!("expected literal: `{lhs}`")));
res.add_err(ExpandError::binding_error(
src.clone().next().map_or(delim_span.close, |it| it.first_span()),
format!("expected literal: `{lhs}`"),
));
item.is_error = true;
}
try_push!(next_items, item);
@ -524,11 +530,17 @@ fn match_loop_inner<'t>(
if matches!(rhs, tt::Leaf::Ident(it) if it.sym == lhs.sym) {
item.dot.next();
} else {
res.add_err(ExpandError::UnexpectedToken);
res.add_err(ExpandError::new(
*rhs.span(),
ExpandErrorKind::UnexpectedToken,
));
item.is_error = true;
}
} else {
res.add_err(ExpandError::binding_error(format!("expected ident: `{lhs}`")));
res.add_err(ExpandError::binding_error(
src.clone().next().map_or(delim_span.close, |it| it.first_span()),
format!("expected ident: `{lhs}`"),
));
item.is_error = true;
}
try_push!(next_items, item);
@ -538,8 +550,8 @@ fn match_loop_inner<'t>(
let error = if let Ok(rhs) = fork.expect_glued_punct() {
let first_is_single_quote = rhs[0].char == '\'';
let lhs = lhs.iter().map(|it| it.char);
let rhs = rhs.iter().map(|it| it.char);
if lhs.clone().eq(rhs) {
let rhs_ = rhs.iter().map(|it| it.char);
if lhs.clone().eq(rhs_) {
// HACK: here we use `meta_result` to pass `TtIter` back to caller because
// it might have been advanced multiple times. `ValueResult` is
// insignificant.
@ -552,13 +564,19 @@ fn match_loop_inner<'t>(
if first_is_single_quote {
// If the first punct token is a single quote, that's a part of a lifetime
// ident, not a punct.
ExpandError::UnexpectedToken
ExpandError::new(
rhs.get(1).map_or(rhs[0].span, |it| it.span),
ExpandErrorKind::UnexpectedToken,
)
} else {
let lhs = lhs.collect::<String>();
ExpandError::binding_error(format!("expected punct: `{lhs}`"))
ExpandError::binding_error(rhs[0].span, format!("expected punct: `{lhs}`"))
}
} else {
ExpandError::UnexpectedToken
ExpandError::new(
src.clone().next().map_or(delim_span.close, |it| it.first_span()),
ExpandErrorKind::UnexpectedToken,
)
};
res.add_err(error);
@ -651,7 +669,7 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree<Span>, edition: Edition)
if let Some(item) = error_recover_item {
res.bindings = bindings_builder.build(&item);
}
res.add_err(ExpandError::UnexpectedToken);
res.add_err(ExpandError::new(span.open, ExpandErrorKind::UnexpectedToken));
}
return res;
}
@ -670,7 +688,7 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree<Span>, edition: Edition)
src = it;
res.unmatched_tts += src.len();
}
res.add_err(ExpandError::LeftoverTokens);
res.add_err(ExpandError::new(span.open, ExpandErrorKind::LeftoverTokens));
if let Some(error_recover_item) = error_recover_item {
res.bindings = bindings_builder.build(&error_recover_item);
@ -746,9 +764,10 @@ fn match_meta_var(
) -> ExpandResult<Option<Fragment>> {
let fragment = match kind {
MetaVarKind::Path => {
return expect_fragment(input, parser::PrefixEntryPoint::Path, edition).map(|it| {
it.map(|it| tt::TokenTree::subtree_or_wrap(it, delim_span)).map(Fragment::Path)
});
return expect_fragment(input, parser::PrefixEntryPoint::Path, edition, delim_span)
.map(|it| {
it.map(|it| tt::TokenTree::subtree_or_wrap(it, delim_span)).map(Fragment::Path)
});
}
MetaVarKind::Expr => {
// `expr` should not match underscores, let expressions, or inline const. The latter
@ -763,37 +782,54 @@ fn match_meta_var(
|| it.sym == sym::let_
|| it.sym == sym::const_ =>
{
return ExpandResult::only_err(ExpandError::NoMatchingRule)
return ExpandResult::only_err(ExpandError::new(
it.span,
ExpandErrorKind::NoMatchingRule,
))
}
_ => {}
};
return expect_fragment(input, parser::PrefixEntryPoint::Expr, edition).map(|tt| {
tt.map(|tt| match tt {
tt::TokenTree::Leaf(leaf) => tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(*leaf.span()),
token_trees: Box::new([leaf.into()]),
},
tt::TokenTree::Subtree(mut s) => {
if s.delimiter.kind == tt::DelimiterKind::Invisible {
s.delimiter.kind = tt::DelimiterKind::Parenthesis;
return expect_fragment(input, parser::PrefixEntryPoint::Expr, edition, delim_span)
.map(|tt| {
tt.map(|tt| match tt {
tt::TokenTree::Leaf(leaf) => tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(*leaf.span()),
token_trees: Box::new([leaf.into()]),
},
tt::TokenTree::Subtree(mut s) => {
if s.delimiter.kind == tt::DelimiterKind::Invisible {
s.delimiter.kind = tt::DelimiterKind::Parenthesis;
}
s
}
s
}
})
.map(Fragment::Expr)
});
})
.map(Fragment::Expr)
});
}
MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => {
let span = input.next_span();
let tt_result = match kind {
MetaVarKind::Ident => input
.expect_ident()
.map(|ident| tt::Leaf::from(ident.clone()).into())
.map_err(|()| ExpandError::binding_error("expected ident")),
MetaVarKind::Tt => {
expect_tt(input).map_err(|()| ExpandError::binding_error("expected token tree"))
}
MetaVarKind::Lifetime => expect_lifetime(input)
.map_err(|()| ExpandError::binding_error("expected lifetime")),
.map_err(|()| {
ExpandError::binding_error(
span.unwrap_or(delim_span.close),
"expected ident",
)
}),
MetaVarKind::Tt => expect_tt(input).map_err(|()| {
ExpandError::binding_error(
span.unwrap_or(delim_span.close),
"expected token tree",
)
}),
MetaVarKind::Lifetime => expect_lifetime(input).map_err(|()| {
ExpandError::binding_error(
span.unwrap_or(delim_span.close),
"expected lifetime",
)
}),
MetaVarKind::Literal => {
let neg = eat_char(input, '-');
input
@ -808,7 +844,12 @@ fn match_meta_var(
}),
}
})
.map_err(|()| ExpandError::binding_error("expected literal"))
.map_err(|()| {
ExpandError::binding_error(
span.unwrap_or(delim_span.close),
"expected literal",
)
})
}
_ => unreachable!(),
};
@ -823,7 +864,7 @@ fn match_meta_var(
MetaVarKind::Item => parser::PrefixEntryPoint::Item,
MetaVarKind::Vis => parser::PrefixEntryPoint::Vis,
};
expect_fragment(input, fragment, edition).map(|it| it.map(Fragment::Tokens))
expect_fragment(input, fragment, edition, delim_span).map(|it| it.map(Fragment::Tokens))
}
fn collect_vars(collector_fun: &mut impl FnMut(Symbol), pattern: &MetaTemplate) {

View file

@ -8,14 +8,17 @@ use tt::Delimiter;
use crate::{
expander::{Binding, Bindings, Fragment},
parser::{MetaVarKind, Op, RepeatKind, Separator},
CountError, ExpandError, ExpandResult, MetaTemplate,
ExpandError, ExpandErrorKind, ExpandResult, MetaTemplate,
};
impl Bindings {
fn get(&self, name: &Symbol) -> Result<&Binding, ExpandError> {
fn get(&self, name: &Symbol, span: Span) -> Result<&Binding, ExpandError> {
match self.inner.get(name) {
Some(binding) => Ok(binding),
None => Err(ExpandError::UnresolvedBinding(Box::new(Box::from(name.as_str())))),
None => Err(ExpandError::new(
span,
ExpandErrorKind::UnresolvedBinding(Box::new(Box::from(name.as_str()))),
)),
}
}
@ -27,10 +30,10 @@ impl Bindings {
marker: impl Fn(&mut Span),
) -> Result<Fragment, ExpandError> {
macro_rules! binding_err {
($($arg:tt)*) => { ExpandError::binding_error(format!($($arg)*)) };
($($arg:tt)*) => { ExpandError::binding_error(span, format!($($arg)*)) };
}
let mut b = self.get(name)?;
let mut b = self.get(name, span)?;
for nesting_state in nesting.iter_mut() {
nesting_state.hit = true;
b = match b {
@ -142,10 +145,9 @@ pub(super) fn transcribe(
template: &MetaTemplate,
bindings: &Bindings,
marker: impl Fn(&mut Span) + Copy,
new_meta_vars: bool,
call_site: Span,
) -> ExpandResult<tt::Subtree<Span>> {
let mut ctx = ExpandCtx { bindings, nesting: Vec::new(), new_meta_vars, call_site };
let mut ctx = ExpandCtx { bindings, nesting: Vec::new(), call_site };
let mut arena: Vec<tt::TokenTree<Span>> = Vec::new();
expand_subtree(&mut ctx, template, None, &mut arena, marker)
}
@ -165,7 +167,6 @@ struct NestingState {
struct ExpandCtx<'a> {
bindings: &'a Bindings,
nesting: Vec<NestingState>,
new_meta_vars: bool,
call_site: Span,
}
@ -263,7 +264,7 @@ fn expand_subtree(
);
}
Op::Count { name, depth } => {
let mut binding = match ctx.bindings.get(name) {
let mut binding = match ctx.bindings.get(name, ctx.call_site) {
Ok(b) => b,
Err(e) => {
if err.is_none() {
@ -299,29 +300,11 @@ fn expand_subtree(
}
}
let res = if ctx.new_meta_vars {
count(binding, 0, depth.unwrap_or(0))
} else {
count_old(binding, 0, *depth)
};
let res = count(binding, 0, depth.unwrap_or(0));
let c = match res {
Ok(c) => c,
Err(e) => {
// XXX: It *might* make sense to emit a dummy integer value like `0` here.
// That would type inference a bit more robust in cases like
// `v[${count(t)}]` where index doesn't matter, but also could lead to
// wrong infefrence for cases like `tup.${count(t)}` where index itself
// does matter.
if err.is_none() {
err = Some(e.into());
}
continue;
}
};
arena.push(
tt::Leaf::Literal(tt::Literal {
symbol: Symbol::integer(c),
symbol: Symbol::integer(res),
span: ctx.call_site,
suffix: None,
kind: tt::LitKind::Integer,
@ -353,7 +336,7 @@ fn expand_var(
match ctx.bindings.get_fragment(v, id, &mut ctx.nesting, marker) {
Ok(it) => ExpandResult::ok(it),
Err(ExpandError::UnresolvedBinding(_)) => {
Err(e) if matches!(e.inner.1, ExpandErrorKind::UnresolvedBinding(_)) => {
// Note that it is possible to have a `$var` inside a macro which is not bound.
// For example:
// ```
@ -435,7 +418,7 @@ fn expand_repeat(
}
.into(),
),
err: Some(ExpandError::LimitExceeded),
err: Some(ExpandError::new(ctx.call_site, ExpandErrorKind::LimitExceeded)),
};
}
@ -481,16 +464,16 @@ fn expand_repeat(
let tt = tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(ctx.call_site),
token_trees: buf.into_boxed_slice(),
}
.into();
};
if RepeatKind::OneOrMore == kind && counter == 0 {
let span = tt.delimiter.open;
return ExpandResult {
value: Fragment::Tokens(tt),
err: Some(ExpandError::UnexpectedToken),
value: Fragment::Tokens(tt.into()),
err: Some(ExpandError::new(span, ExpandErrorKind::UnexpectedToken)),
};
}
ExpandResult { value: Fragment::Tokens(tt), err }
ExpandResult { value: Fragment::Tokens(tt.into()), err }
}
fn push_fragment(ctx: &ExpandCtx<'_>, buf: &mut Vec<tt::TokenTree<Span>>, fragment: Fragment) {
@ -557,44 +540,16 @@ fn fix_up_and_push_path_tt(
/// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth
/// defined by the metavar expression.
fn count(binding: &Binding, depth_curr: usize, depth_max: usize) -> Result<usize, CountError> {
fn count(binding: &Binding, depth_curr: usize, depth_max: usize) -> usize {
match binding {
Binding::Nested(bs) => {
if depth_curr == depth_max {
Ok(bs.len())
bs.len()
} else {
bs.iter().map(|b| count(b, depth_curr + 1, depth_max)).sum()
}
}
Binding::Empty => Ok(0),
Binding::Fragment(_) | Binding::Missing(_) => Ok(1),
}
}
fn count_old(
binding: &Binding,
our_depth: usize,
count_depth: Option<usize>,
) -> Result<usize, CountError> {
match binding {
Binding::Nested(bs) => match count_depth {
None => bs.iter().map(|b| count_old(b, our_depth + 1, None)).sum(),
Some(0) => Ok(bs.len()),
Some(d) => bs.iter().map(|b| count_old(b, our_depth + 1, Some(d - 1))).sum(),
},
Binding::Empty => Ok(0),
Binding::Fragment(_) | Binding::Missing(_) => {
if our_depth == 0 {
// `${count(t)}` is placed inside the innermost repetition. This includes cases
// where `t` is not a repeated fragment.
Err(CountError::Misplaced)
} else if count_depth.is_none() {
Ok(1)
} else {
// We've reached at the innermost repeated fragment, but the user wants us to go
// further!
Err(CountError::OutOfBounds)
}
}
Binding::Empty => 0,
Binding::Fragment(_) | Binding::Missing(_) => 1,
}
}

View file

@ -15,10 +15,11 @@ mod to_parser_input;
mod benchmark;
use span::{Edition, Span, SyntaxContextId};
use stdx::impl_from;
use tt::iter::TtIter;
use tt::DelimSpan;
use std::fmt;
use std::sync::Arc;
use crate::parser::{MetaTemplate, MetaVarKind, Op};
@ -64,39 +65,45 @@ impl fmt::Display for ParseError {
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ExpandError {
pub struct ExpandError {
pub inner: Arc<(Span, ExpandErrorKind)>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ExpandErrorKind {
BindingError(Box<Box<str>>),
UnresolvedBinding(Box<Box<str>>),
LeftoverTokens,
ConversionError,
LimitExceeded,
NoMatchingRule,
UnexpectedToken,
CountError(CountError),
}
impl_from!(CountError for ExpandError);
impl ExpandError {
fn binding_error(e: impl Into<Box<str>>) -> ExpandError {
ExpandError::BindingError(Box::new(e.into()))
fn new(span: Span, kind: ExpandErrorKind) -> ExpandError {
ExpandError { inner: Arc::new((span, kind)) }
}
fn binding_error(span: Span, e: impl Into<Box<str>>) -> ExpandError {
ExpandError { inner: Arc::new((span, ExpandErrorKind::BindingError(Box::new(e.into())))) }
}
}
impl fmt::Display for ExpandError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.1.fmt(f)
}
}
impl fmt::Display for ExpandError {
impl fmt::Display for ExpandErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExpandError::NoMatchingRule => f.write_str("no rule matches input tokens"),
ExpandError::UnexpectedToken => f.write_str("unexpected token in input"),
ExpandError::BindingError(e) => f.write_str(e),
ExpandError::UnresolvedBinding(binding) => {
ExpandErrorKind::NoMatchingRule => f.write_str("no rule matches input tokens"),
ExpandErrorKind::UnexpectedToken => f.write_str("unexpected token in input"),
ExpandErrorKind::BindingError(e) => f.write_str(e),
ExpandErrorKind::UnresolvedBinding(binding) => {
f.write_str("could not find binding ")?;
f.write_str(binding)
}
ExpandError::ConversionError => f.write_str("could not convert tokens"),
ExpandError::LimitExceeded => f.write_str("Expand exceed limit"),
ExpandError::LeftoverTokens => f.write_str("leftover tokens"),
ExpandError::CountError(e) => e.fmt(f),
ExpandErrorKind::LimitExceeded => f.write_str("Expand exceed limit"),
ExpandErrorKind::LeftoverTokens => f.write_str("leftover tokens"),
}
}
}
@ -248,11 +255,10 @@ impl DeclarativeMacro {
&self,
tt: &tt::Subtree<Span>,
marker: impl Fn(&mut Span) + Copy,
new_meta_vars: bool,
call_site: Span,
def_site_edition: Edition,
) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
expander::expand_rules(&self.rules, tt, marker, new_meta_vars, call_site, def_site_edition)
expander::expand_rules(&self.rules, tt, marker, call_site, def_site_edition)
}
}
@ -355,11 +361,12 @@ impl<T: Default, E> From<Result<T, E>> for ValueResult<T, E> {
}
}
fn expect_fragment<S: Copy + fmt::Debug>(
tt_iter: &mut TtIter<'_, S>,
fn expect_fragment(
tt_iter: &mut TtIter<'_, Span>,
entry_point: ::parser::PrefixEntryPoint,
edition: ::parser::Edition,
) -> ExpandResult<Option<tt::TokenTree<S>>> {
delim_span: DelimSpan<Span>,
) -> ExpandResult<Option<tt::TokenTree<Span>>> {
use ::parser;
let buffer = tt::buffer::TokenBuffer::from_tokens(tt_iter.as_slice());
let parser_input = to_parser_input::to_parser_input(edition, &buffer);
@ -387,7 +394,10 @@ fn expect_fragment<S: Copy + fmt::Debug>(
}
let err = if error || !cursor.is_root() {
Some(ExpandError::binding_error(format!("expected {entry_point:?}")))
Some(ExpandError::binding_error(
buffer.begin().token_tree().map_or(delim_span.close, |tt| tt.span()),
format!("expected {entry_point:?}"),
))
} else {
None
};

View file

@ -212,15 +212,12 @@ where
}
/// Split token tree with separate expr: $($e:expr)SEP*
pub fn parse_exprs_with_sep<S>(
tt: &tt::Subtree<S>,
pub fn parse_exprs_with_sep(
tt: &tt::Subtree<span::Span>,
sep: char,
span: S,
span: span::Span,
edition: Edition,
) -> Vec<tt::Subtree<S>>
where
S: Copy + fmt::Debug,
{
) -> Vec<tt::Subtree<span::Span>> {
if tt.token_trees.is_empty() {
return Vec::new();
}
@ -229,7 +226,12 @@ where
let mut res = Vec::new();
while iter.peek_n(0).is_some() {
let expanded = crate::expect_fragment(&mut iter, parser::PrefixEntryPoint::Expr, edition);
let expanded = crate::expect_fragment(
&mut iter,
parser::PrefixEntryPoint::Expr,
edition,
tt::DelimSpan { open: tt.delimiter.open, close: tt.delimiter.close },
);
res.push(match expanded.value {
None => break,