Skip to content

Commit 9ca8946

Browse files
committed
Implement macro meta-variable expressions
1 parent 68369a0 commit 9ca8946

19 files changed

+1291
-44
lines changed

compiler/rustc_expand/src/mbe.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
crate mod macro_check;
77
crate mod macro_parser;
88
crate mod macro_rules;
9+
crate mod metavar_expr;
910
crate mod quoted;
1011
crate mod transcribe;
1112

13+
use metavar_expr::MetaVarExpr;
1214
use rustc_ast::token::{self, NonterminalKind, Token, TokenKind};
1315
use rustc_ast::tokenstream::DelimSpan;
14-
16+
use rustc_data_structures::sync::Lrc;
1517
use rustc_span::symbol::Ident;
1618
use rustc_span::Span;
1719

18-
use rustc_data_structures::sync::Lrc;
19-
2020
/// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note
2121
/// that the delimiter itself might be `NoDelim`.
2222
#[derive(Clone, PartialEq, Encodable, Decodable, Debug)]
@@ -73,8 +73,8 @@ enum KleeneOp {
7373
ZeroOrOne,
7474
}
7575

76-
/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)`
77-
/// are "first-class" token trees. Useful for parsing macros.
76+
/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, `$(...)`,
77+
/// and `${...}` are "first-class" token trees. Useful for parsing macros.
7878
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
7979
enum TokenTree {
8080
Token(Token),
@@ -85,6 +85,8 @@ enum TokenTree {
8585
MetaVar(Span, Ident),
8686
/// e.g., `$var:expr`. This is only used in the left hand side of MBE macros.
8787
MetaVarDecl(Span, Ident /* name to bind */, Option<NonterminalKind>),
88+
/// A meta-variable expression inside `${...}`
89+
MetaVarExpr(DelimSpan, MetaVarExpr),
8890
}
8991

9092
impl TokenTree {
@@ -139,7 +141,9 @@ impl TokenTree {
139141
TokenTree::Token(Token { span, .. })
140142
| TokenTree::MetaVar(span, _)
141143
| TokenTree::MetaVarDecl(span, _, _) => span,
142-
TokenTree::Delimited(span, _) | TokenTree::Sequence(span, _) => span.entire(),
144+
TokenTree::Delimited(span, _)
145+
| TokenTree::MetaVarExpr(span, _)
146+
| TokenTree::Sequence(span, _) => span.entire(),
143147
}
144148
}
145149

compiler/rustc_expand/src/mbe/macro_check.rs

+4
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ fn check_binders(
278278
binders.insert(name, BinderInfo { span, ops: ops.into() });
279279
}
280280
}
281+
// `MetaVarExpr` can not appear in the LHS of a macro arm, not even in a nested
282+
// macro definition.
283+
TokenTree::MetaVarExpr(..) => {}
281284
TokenTree::Delimited(_, ref del) => {
282285
for tt in &del.tts {
283286
check_binders(sess, node_id, tt, macros, binders, ops, valid);
@@ -335,6 +338,7 @@ fn check_occurrences(
335338
let name = MacroRulesNormalizedIdent::new(name);
336339
check_ops_is_prefix(sess, node_id, macros, binders, ops, span, name);
337340
}
341+
TokenTree::MetaVarExpr(..) => {}
338342
TokenTree::Delimited(_, ref del) => {
339343
check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid);
340344
}

compiler/rustc_expand/src/mbe/macro_parser.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,11 @@ pub(super) fn count_names(ms: &[TokenTree]) -> usize {
280280
ms.iter().fold(0, |count, elt| {
281281
count
282282
+ match *elt {
283-
TokenTree::Sequence(_, ref seq) => seq.num_captures,
284283
TokenTree::Delimited(_, ref delim) => count_names(&delim.tts),
285284
TokenTree::MetaVar(..) => 0,
286285
TokenTree::MetaVarDecl(..) => 1,
286+
TokenTree::MetaVarExpr(..) => 0,
287+
TokenTree::Sequence(_, ref seq) => seq.num_captures,
287288
TokenTree::Token(..) => 0,
288289
}
289290
})
@@ -392,7 +393,8 @@ fn nameize<I: Iterator<Item = NamedMatch>>(
392393
}
393394
Occupied(..) => return Err((sp, format!("duplicated bind name: {}", bind_name))),
394395
},
395-
TokenTree::MetaVar(..) | TokenTree::Token(..) => (),
396+
// FIXME(c410-f3r) MetaVar and MetaVarExpr should be handled instead of being ignored.
397+
TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) | TokenTree::Token(..) => {}
396398
}
397399

398400
Ok(())
@@ -603,7 +605,7 @@ fn inner_parse_loop<'root, 'tt>(
603605
// rules. NOTE that this is not necessarily an error unless _all_ items in
604606
// `cur_items` end up doing this. There may still be some other matchers that do
605607
// end up working out.
606-
TokenTree::Token(..) | TokenTree::MetaVar(..) => {}
608+
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) => {}
607609
}
608610
}
609611
}

compiler/rustc_expand/src/mbe/macro_rules.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,10 @@ fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[mbe::TokenTree]) -> bool {
584584
use mbe::TokenTree;
585585
for tt in tts {
586586
match *tt {
587-
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => (),
587+
TokenTree::Token(..)
588+
| TokenTree::MetaVar(..)
589+
| TokenTree::MetaVarDecl(..)
590+
| TokenTree::MetaVarExpr(..) => (),
588591
TokenTree::Delimited(_, ref del) => {
589592
if !check_lhs_no_empty_seq(sess, &del.tts) {
590593
return false;
@@ -673,7 +676,10 @@ impl FirstSets {
673676
let mut first = TokenSet::empty();
674677
for tt in tts.iter().rev() {
675678
match *tt {
676-
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
679+
TokenTree::Token(..)
680+
| TokenTree::MetaVar(..)
681+
| TokenTree::MetaVarDecl(..)
682+
| TokenTree::MetaVarExpr(..) => {
677683
first.replace_with(tt.clone());
678684
}
679685
TokenTree::Delimited(span, ref delimited) => {
@@ -735,7 +741,10 @@ impl FirstSets {
735741
for tt in tts.iter() {
736742
assert!(first.maybe_empty);
737743
match *tt {
738-
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
744+
TokenTree::Token(..)
745+
| TokenTree::MetaVar(..)
746+
| TokenTree::MetaVarDecl(..)
747+
| TokenTree::MetaVarExpr(..) => {
739748
first.add_one(tt.clone());
740749
return first;
741750
}
@@ -911,7 +920,10 @@ fn check_matcher_core(
911920
// First, update `last` so that it corresponds to the set
912921
// of NT tokens that might end the sequence `... token`.
913922
match *token {
914-
TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => {
923+
TokenTree::Token(..)
924+
| TokenTree::MetaVar(..)
925+
| TokenTree::MetaVarDecl(..)
926+
| TokenTree::MetaVarExpr(..) => {
915927
if token_can_be_followed_by_any(token) {
916928
// don't need to track tokens that work with any,
917929
last.replace_with_irrelevant();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::fmt::Display;
2+
use std::str::FromStr;
3+
4+
use rustc_ast::token::{self, Lit};
5+
use rustc_ast::tokenstream;
6+
use rustc_ast_pretty::pprust;
7+
use rustc_errors::{Applicability, PResult};
8+
use rustc_session::parse::ParseSess;
9+
10+
use rustc_span::symbol::Ident;
11+
use rustc_span::Span;
12+
13+
/// A meta-variable expression, for expansions based on properties of meta-variables.
14+
#[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
15+
crate enum MetaVarExpr {
16+
/// The number of repetitions of an identifier, optionally limited to a number
17+
/// of outer-most repetition depths. If the depth limit is `None` then the depth is unlimited.
18+
Count(Ident, Option<usize>),
19+
20+
/// Ignore a meta-variable for repetition without expansion.
21+
Ignore(Ident),
22+
23+
/// The index of the repetition at a particular depth, where 0 is the inner-most
24+
/// repetition. The `usize` is the depth.
25+
Index(usize),
26+
27+
/// The length of the repetition at a particular depth, where 0 is the inner-most
28+
/// repetition. The `usize` is the depth.
29+
Length(usize),
30+
}
31+
32+
impl MetaVarExpr {
33+
/// Attempt to parse a meta-variable expression from a token stream.
34+
crate fn parse<'sess>(
35+
input: &tokenstream::TokenStream,
36+
sess: &'sess ParseSess,
37+
) -> PResult<'sess, MetaVarExpr> {
38+
let mut tts = input.trees();
39+
match tts.next() {
40+
Some(tokenstream::TokenTree::Token(token)) if let Some((ident, false)) = token.ident() => {
41+
let Some(tokenstream::TokenTree::Delimited(_, token::Paren, args)) = tts.next() else {
42+
let msg = "meta-variable expression paramter must be wrapped in parentheses";
43+
return Err(sess.span_diagnostic.struct_span_err(ident.span, msg));
44+
};
45+
let mut iter = args.trees();
46+
let rslt = match &*ident.as_str() {
47+
"count" => parse_count(&mut iter, sess, ident.span)?,
48+
"ignore" => MetaVarExpr::Ignore(parse_ident(&mut iter, sess, ident.span)?),
49+
"index" => MetaVarExpr::Index(parse_depth(&mut iter, sess, ident.span)?),
50+
"length" => MetaVarExpr::Length(parse_depth(&mut iter, sess, ident.span)?),
51+
_ => {
52+
let msg = "unrecognised meta-variable expression. Supported expressions \
53+
are count, ignore, index and length";
54+
return Err(sess.span_diagnostic.struct_span_err(ident.span, msg));
55+
}
56+
};
57+
if let Some(arg) = iter.next() {
58+
let msg = "unexpected meta-variable expression argument";
59+
return Err(sess.span_diagnostic.struct_span_err(arg.span(), msg));
60+
}
61+
Ok(rslt)
62+
}
63+
Some(tokenstream::TokenTree::Token(token)) => {
64+
return Err(sess.span_diagnostic.struct_span_err(
65+
token.span,
66+
&format!(
67+
"expected meta-variable expression, found `{}`",
68+
pprust::token_to_string(&token),
69+
),
70+
));
71+
}
72+
_ => return Err(sess.span_diagnostic.struct_err("expected meta-variable expression"))
73+
}
74+
}
75+
76+
crate fn ident(&self) -> Option<&Ident> {
77+
match self {
78+
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(&ident),
79+
MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
80+
}
81+
}
82+
}
83+
84+
/// Tries to convert a literal to an arbitrary type
85+
fn convert_literal<T>(lit: Lit, sess: &ParseSess, span: Span) -> PResult<'_, T>
86+
where
87+
T: FromStr,
88+
<T as FromStr>::Err: Display,
89+
{
90+
if lit.suffix.is_some() {
91+
let msg = "literal suffixes are not supported in meta-variable expressions";
92+
return Err(sess.span_diagnostic.struct_span_err(span, msg));
93+
}
94+
lit.symbol.as_str().parse::<T>().map_err(|e| {
95+
sess.span_diagnostic.struct_span_err(
96+
span,
97+
&format!("failed to parse meta-variable expression argument: {}", e),
98+
)
99+
})
100+
}
101+
102+
/// Parse a meta-variable `count` expression: `count(ident[, depth])`
103+
fn parse_count<'sess>(
104+
iter: &mut tokenstream::Cursor,
105+
sess: &'sess ParseSess,
106+
span: Span,
107+
) -> PResult<'sess, MetaVarExpr> {
108+
let ident = parse_ident(iter, sess, span)?;
109+
let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None };
110+
Ok(MetaVarExpr::Count(ident, depth))
111+
}
112+
113+
/// Parses the depth used by index(depth) and length(depth).
114+
fn parse_depth<'sess>(
115+
iter: &mut tokenstream::Cursor,
116+
sess: &'sess ParseSess,
117+
span: Span,
118+
) -> PResult<'sess, usize> {
119+
let Some(tt) = iter.next() else { return Ok(0) };
120+
let tokenstream::TokenTree::Token(token::Token {
121+
kind: token::TokenKind::Literal(lit),
122+
span: literal_span,
123+
}) = tt else {
124+
return Err(sess.span_diagnostic.struct_span_err(
125+
span,
126+
"meta-expression depth must be a literal"
127+
));
128+
};
129+
convert_literal::<usize>(lit, sess, literal_span)
130+
}
131+
132+
/// Parses an generic ident
133+
fn parse_ident<'sess>(
134+
iter: &mut tokenstream::Cursor,
135+
sess: &'sess ParseSess,
136+
span: Span,
137+
) -> PResult<'sess, Ident> {
138+
let err_fn =
139+
|| sess.span_diagnostic.struct_span_err(span, "could not find an expected `ident` element");
140+
if let Some(tt) = iter.next() {
141+
match tt {
142+
tokenstream::TokenTree::Token(token) => {
143+
if let Some((elem, false)) = token.ident() {
144+
return Ok(elem);
145+
}
146+
let mut err = err_fn();
147+
err.span_suggestion(
148+
token.span,
149+
&format!("Try removing `{}`", pprust::token_to_string(&token)),
150+
<_>::default(),
151+
Applicability::MaybeIncorrect,
152+
);
153+
return Err(err);
154+
}
155+
tokenstream::TokenTree::Delimited(delim_span, _, _) => {
156+
let mut err = err_fn();
157+
err.span_suggestion(
158+
delim_span.entire(),
159+
"Try removing the delimiter",
160+
<_>::default(),
161+
Applicability::MaybeIncorrect,
162+
);
163+
return Err(err);
164+
}
165+
}
166+
}
167+
Err(err_fn())
168+
}
169+
170+
/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
171+
/// iterator is not modified and the result is `false`.
172+
fn try_eat_comma(iter: &mut tokenstream::Cursor) -> bool {
173+
if let Some(tokenstream::TokenTree::Token(token::Token { kind: token::Comma, .. })) =
174+
iter.look_ahead(0)
175+
{
176+
let _ = iter.next();
177+
return true;
178+
}
179+
false
180+
}

0 commit comments

Comments
 (0)