Skip to content

Commit dd3a606

Browse files
committed
mbe: Initial support for macro fragment fields
This only supports a `name` field of `fn` and `adt` fragments, for now, but it adds the infrastructure for adding more fields.
1 parent b7b5fc2 commit dd3a606

File tree

13 files changed

+320
-47
lines changed

13 files changed

+320
-47
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,13 @@ expand_mve_extra_tokens =
142142
*[other] these tokens
143143
}
144144
145-
expand_mve_missing_paren =
146-
expected `(`
147-
.label = for this this metavariable expression
145+
expand_mve_extra_tokens_after_field = unexpected trailing tokens after field
146+
147+
expand_mve_missing_paren_or_dot =
148+
expected `(` or `.`
149+
.label = after this metavariable expression
148150
.unexpected = unexpected token
149-
.note = metavariable expressions use function-like parentheses syntax
150-
.suggestion = try adding parentheses
151+
.note = metavariable expressions use parentheses for functions or dot for fields
151152
152153
expand_mve_unrecognized_expr =
153154
unrecognized metavariable expression
@@ -157,6 +158,8 @@ expand_mve_unrecognized_expr =
157158
expand_mve_unrecognized_var =
158159
variable `{$key}` is not recognized in meta-variable expression
159160
161+
expand_mve_expr_has_no_field = expression of type `{$pnr_type}` has no field `{$field}`
162+
160163
expand_non_inline_modules_in_proc_macro_input_are_unstable =
161164
non-inline modules in proc macro input are unstable
162165

compiler/rustc_expand/src/errors.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,17 +487,23 @@ mod metavar_exprs {
487487
pub name: String,
488488
}
489489

490+
#[derive(Diagnostic)]
491+
#[diag(expand_mve_extra_tokens_after_field)]
492+
pub(crate) struct MveExtraTokensAfterField {
493+
#[primary_span]
494+
#[suggestion(code = "", applicability = "machine-applicable")]
495+
pub span: Span,
496+
}
497+
490498
#[derive(Diagnostic)]
491499
#[note]
492-
#[diag(expand_mve_missing_paren)]
493-
pub(crate) struct MveMissingParen {
500+
#[diag(expand_mve_missing_paren_or_dot)]
501+
pub(crate) struct MveMissingParenOrDot {
494502
#[primary_span]
495503
#[label]
496504
pub ident_span: Span,
497505
#[label(expand_unexpected)]
498506
pub unexpected_span: Option<Span>,
499-
#[suggestion(code = "( /* ... */ )", applicability = "has-placeholders")]
500-
pub insert_span: Option<Span>,
501507
}
502508

503509
#[derive(Diagnostic)]
@@ -517,6 +523,15 @@ mod metavar_exprs {
517523
pub span: Span,
518524
pub key: MacroRulesNormalizedIdent,
519525
}
526+
527+
#[derive(Diagnostic)]
528+
#[diag(expand_mve_expr_has_no_field)]
529+
pub(crate) struct MveExprHasNoField {
530+
#[primary_span]
531+
pub span: Span,
532+
pub pnr_type: &'static str,
533+
pub field: Ident,
534+
}
520535
}
521536

522537
#[derive(Diagnostic)]

compiler/rustc_expand/src/mbe.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod metavar_expr;
1212
mod quoted;
1313
mod transcribe;
1414

15-
use metavar_expr::MetaVarExpr;
15+
use metavar_expr::{MetaVarExpr, MetaVarRecursiveExpr};
1616
use rustc_ast::token::{Delimiter, NonterminalKind, Token, TokenKind};
1717
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan};
1818
use rustc_macros::{Decodable, Encodable};

compiler/rustc_expand/src/mbe/metavar_expr.rs

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pub(crate) enum MetaVarExpr {
3131
/// The length of the repetition at a particular depth, where 0 is the innermost
3232
/// repetition. The `usize` is the depth.
3333
Len(usize),
34+
35+
/// An expression that can contain other expressions
36+
Recursive(MetaVarRecursiveExpr),
3437
}
3538

3639
impl MetaVarExpr {
@@ -41,19 +44,20 @@ impl MetaVarExpr {
4144
psess: &'psess ParseSess,
4245
) -> PResult<'psess, MetaVarExpr> {
4346
let mut iter = input.iter();
47+
// FIXME: Refactor this to use Parser.
48+
// All macro metavariable expressions current start with an ident.
4449
let ident = parse_ident(&mut iter, psess, outer_span)?;
45-
let next = iter.next();
46-
let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else {
47-
// No `()`; wrong or no delimiters. Point at a problematic span or a place to
48-
// add parens if it makes sense.
49-
let (unexpected_span, insert_span) = match next {
50-
Some(TokenTree::Delimited(..)) => (None, None),
51-
Some(tt) => (Some(tt.span()), None),
52-
None => (None, Some(ident.span.shrink_to_hi())),
53-
};
54-
let err =
55-
errors::MveMissingParen { ident_span: ident.span, unexpected_span, insert_span };
56-
return Err(psess.dcx().create_err(err));
50+
let args = match iter.next() {
51+
Some(TokenTree::Token(Token { kind: token::Dot, .. }, _)) => {
52+
return parse_field(ident, iter, psess, outer_span);
53+
}
54+
Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) => args,
55+
next => {
56+
return Err(psess.dcx().create_err(errors::MveMissingParenOrDot {
57+
ident_span: ident.span,
58+
unexpected_span: next.map(|tt| tt.span()),
59+
}));
60+
}
5761
};
5862

5963
// Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}`
@@ -102,10 +106,55 @@ impl MetaVarExpr {
102106
}
103107
MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => cb(aux, ident),
104108
MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => aux,
109+
MetaVarExpr::Recursive(expr) => expr.for_each_metavar(aux, cb),
110+
}
111+
}
112+
}
113+
114+
/// A recursive meta-variable expression, which may contain other meta-variable expressions.
115+
#[derive(Debug, PartialEq, Encodable, Decodable)]
116+
pub(crate) enum MetaVarRecursiveExpr {
117+
/// A single identifier, currently the only base of a recursive expression.
118+
Ident(Ident),
119+
120+
/// A macro fragment field.
121+
Field { expr: Box<MetaVarRecursiveExpr>, field: Ident },
122+
}
123+
124+
impl MetaVarRecursiveExpr {
125+
fn for_each_metavar<A>(&self, aux: A, mut cb: impl FnMut(A, &Ident) -> A) -> A {
126+
match self {
127+
Self::Ident(ident) => cb(aux, ident),
128+
Self::Field { expr, field } => expr.for_each_metavar(cb(aux, field), cb),
105129
}
106130
}
107131
}
108132

133+
/// Attempt to parse a meta-variable field.
134+
///
135+
/// The initial ident and dot have already been eaten.
136+
pub(crate) fn parse_field<'psess>(
137+
base_ident: Ident,
138+
mut iter: TokenStreamIter<'_>,
139+
psess: &'psess ParseSess,
140+
fallback_span: Span,
141+
) -> PResult<'psess, MetaVarExpr> {
142+
let mut expr = MetaVarRecursiveExpr::Ident(base_ident);
143+
let field = parse_ident(&mut iter, psess, fallback_span)?;
144+
expr = MetaVarRecursiveExpr::Field { expr: Box::new(expr), field };
145+
while let Some(TokenTree::Token(Token { kind: token::Dot, .. }, _)) = iter.next() {
146+
let field = parse_ident(&mut iter, psess, fallback_span)?;
147+
expr = MetaVarRecursiveExpr::Field { expr: Box::new(expr), field };
148+
}
149+
150+
// Check for unexpected tokens
151+
if let Some(span) = iter_span(&iter) {
152+
return Err(psess.dcx().create_err(errors::MveExtraTokensAfterField { span }));
153+
}
154+
155+
Ok(MetaVarExpr::Recursive(expr))
156+
}
157+
109158
/// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create
110159
/// a diag with the correct arg count if so.
111160
fn check_trailing_tokens<'psess>(

compiler/rustc_expand/src/mbe/quoted.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ fn maybe_emit_macro_metavar_expr_concat_feature(features: &Features, sess: &Sess
190190
}
191191
}
192192

193+
fn maybe_emit_macro_fragment_fields_feature(features: &Features, sess: &Session, span: Span) {
194+
if !features.macro_fragment_fields() {
195+
let msg = "macro fragment fields are unstable";
196+
feature_err(sess, sym::macro_fragment_fields, span, msg).emit();
197+
}
198+
}
199+
193200
/// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a
194201
/// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree`
195202
/// for use in parsing a macro.
@@ -257,18 +264,26 @@ fn parse_tree<'a>(
257264
return TokenTree::token(token::Dollar, dollar_span);
258265
}
259266
Ok(elem) => {
260-
if let MetaVarExpr::Concat(_) = elem {
261-
maybe_emit_macro_metavar_expr_concat_feature(
262-
features,
263-
sess,
264-
delim_span.entire(),
265-
);
266-
} else {
267-
maybe_emit_macro_metavar_expr_feature(
267+
match elem {
268+
MetaVarExpr::Concat(..) => {
269+
maybe_emit_macro_metavar_expr_concat_feature(
270+
features,
271+
sess,
272+
delim_span.entire(),
273+
)
274+
}
275+
MetaVarExpr::Recursive(..) => {
276+
maybe_emit_macro_fragment_fields_feature(
277+
features,
278+
sess,
279+
delim_span.entire(),
280+
)
281+
}
282+
_ => maybe_emit_macro_metavar_expr_feature(
268283
features,
269284
sess,
270285
delim_span.entire(),
271-
);
286+
),
272287
}
273288
return TokenTree::MetaVarExpr(delim_span, elem);
274289
}

compiler/rustc_expand/src/mbe/transcribe.rs

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use std::borrow::Cow;
12
use std::mem;
23

34
use rustc_ast::token::{
45
self, Delimiter, IdentIsRaw, InvisibleOrigin, Lit, LitKind, MetaVarKind, Token, TokenKind,
56
};
67
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
7-
use rustc_ast::{ExprKind, StmtKind, TyKind, UnOp};
8+
use rustc_ast::{self as ast, ExprKind, ItemKind, StmtKind, TyKind, UnOp};
89
use rustc_data_structures::fx::FxHashMap;
910
use rustc_errors::{Diag, DiagCtxtHandle, PResult, pluralize};
1011
use rustc_parse::lexer::nfc_normalize;
@@ -18,12 +19,12 @@ use smallvec::{SmallVec, smallvec};
1819

1920
use crate::errors::{
2021
CountRepetitionMisplaced, MacroVarStillRepeating, MetaVarsDifSeqMatchers, MustRepeatOnce,
21-
MveUnrecognizedVar, NoSyntaxVarsExprRepeat,
22+
MveExprHasNoField, MveUnrecognizedVar, NoSyntaxVarsExprRepeat,
2223
};
2324
use crate::mbe::macro_parser::NamedMatch;
2425
use crate::mbe::macro_parser::NamedMatch::*;
2526
use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR};
26-
use crate::mbe::{self, KleeneOp, MetaVarExpr};
27+
use crate::mbe::{self, KleeneOp, MetaVarExpr, MetaVarRecursiveExpr};
2728

2829
/// Context needed to perform transcription of metavariable expressions.
2930
struct TranscrCtx<'psess, 'itp> {
@@ -540,11 +541,105 @@ fn transcribe_metavar_expr<'tx>(
540541
return Err(out_of_bounds_err(dcx, tscx.repeats.len(), dspan.entire(), "len"));
541542
}
542543
},
544+
MetaVarExpr::Recursive(ref expr) => {
545+
let pnr = eval_metavar_recursive_expr(tscx, expr)?;
546+
return transcribe_pnr(tscx, dspan.entire(), &pnr);
547+
}
543548
};
544549
tscx.result.push(tt);
545550
Ok(())
546551
}
547552

553+
/// Evaluate recursive metavariable expressions into a `ParseNtResult`.
554+
///
555+
/// This does not do any transcription; that's handled in the caller.
556+
///
557+
/// It's okay to recurse here for now, because we expect a limited degree of nested fields. More
558+
/// complex expressions may require translating this into a proper interpreter.
559+
fn eval_metavar_recursive_expr<'psess, 'interp>(
560+
tscx: &TranscrCtx<'psess, 'interp>,
561+
expr: &MetaVarRecursiveExpr,
562+
) -> PResult<'psess, Cow<'interp, ParseNtResult>> {
563+
let dcx = tscx.psess.dcx();
564+
match expr {
565+
MetaVarRecursiveExpr::Ident(ident) => {
566+
let span = ident.span;
567+
let ident = MacroRulesNormalizedIdent::new(*ident);
568+
match matched_from_mrn_ident(dcx, span, ident, tscx.interp)? {
569+
NamedMatch::MatchedSingle(pnr) => Ok(Cow::Borrowed(pnr)),
570+
NamedMatch::MatchedSeq(named_matches) => {
571+
let Some((curr_idx, _)) = tscx.repeats.last() else {
572+
return Err(dcx.struct_span_err(span, "invalid syntax"));
573+
};
574+
match &named_matches[*curr_idx] {
575+
MatchedSeq(_) => {
576+
Err(dcx.create_err(MacroVarStillRepeating { span, ident }))
577+
}
578+
MatchedSingle(pnr) => Ok(Cow::Borrowed(pnr)),
579+
}
580+
}
581+
}
582+
}
583+
MetaVarRecursiveExpr::Field { expr: base_expr, field } => {
584+
let base_pnr = eval_metavar_recursive_expr(tscx, base_expr)?;
585+
let err_unknown_field = || {
586+
Err(dcx.create_err(MveExprHasNoField {
587+
span: field.span,
588+
pnr_type: pnr_type(&base_pnr),
589+
field: *field,
590+
}))
591+
};
592+
match (base_pnr.as_ref(), field.name) {
593+
(ParseNtResult::Adt(adt_item), sym::name) => {
594+
let ident = match adt_item.kind {
595+
ItemKind::Struct(ident, ..)
596+
| ItemKind::Enum(ident, ..)
597+
| ItemKind::Union(ident, ..) => ident,
598+
_ => dcx.span_bug(field.span, "`adt` item was not an adt"),
599+
};
600+
Ok(ident_pnr(ident))
601+
}
602+
(ParseNtResult::Fn(fn_item), sym::name) => {
603+
let f = require_fn_item(fn_item);
604+
Ok(ident_pnr(f.ident))
605+
}
606+
(_, _) => err_unknown_field(),
607+
}
608+
}
609+
}
610+
}
611+
612+
fn ident_pnr(ident: Ident) -> Cow<'static, ParseNtResult> {
613+
Cow::Owned(ParseNtResult::Ident(ident, ident.is_raw_guess().into()))
614+
}
615+
616+
fn pnr_type(pnr: &ParseNtResult) -> &'static str {
617+
match pnr {
618+
ParseNtResult::Tt(..) => "tt",
619+
ParseNtResult::Ident(..) => "ident",
620+
ParseNtResult::Lifetime(..) => "lifetime",
621+
ParseNtResult::Item(..) => "item",
622+
ParseNtResult::Fn(..) => "fn",
623+
ParseNtResult::Adt(..) => "adt",
624+
ParseNtResult::Block(..) => "block",
625+
ParseNtResult::Stmt(..) => "stmt",
626+
ParseNtResult::Pat(..) => "pat",
627+
ParseNtResult::Expr(..) => "expr",
628+
ParseNtResult::Literal(..) => "literal",
629+
ParseNtResult::Ty(..) => "ty",
630+
ParseNtResult::Meta(..) => "meta",
631+
ParseNtResult::Path(..) => "path",
632+
ParseNtResult::Vis(..) => "vis",
633+
}
634+
}
635+
636+
fn require_fn_item(item: &ast::Item) -> &ast::Fn {
637+
match item.kind {
638+
ItemKind::Fn(ref f) => &f,
639+
_ => panic!("`fn` item was not a fn"),
640+
}
641+
}
642+
548643
/// Handle the `${concat(...)}` metavariable expression.
549644
fn metavar_expr_concat<'tx>(
550645
tscx: &mut TranscrCtx<'tx, '_>,
@@ -891,8 +986,19 @@ fn matched_from_ident<'ctx, 'interp, 'rslt>(
891986
where
892987
'interp: 'rslt,
893988
{
894-
let span = ident.span;
895-
let key = MacroRulesNormalizedIdent::new(ident);
989+
matched_from_mrn_ident(dcx, ident.span, MacroRulesNormalizedIdent::new(ident), interp)
990+
}
991+
992+
/// Returns a `NamedMatch` item declared on the LHS given an arbitrary [MacroRulesNormalizedIdent]
993+
fn matched_from_mrn_ident<'ctx, 'interp, 'rslt>(
994+
dcx: DiagCtxtHandle<'ctx>,
995+
span: Span,
996+
key: MacroRulesNormalizedIdent,
997+
interp: &'interp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
998+
) -> PResult<'ctx, &'rslt NamedMatch>
999+
where
1000+
'interp: 'rslt,
1001+
{
8961002
interp.get(&key).ok_or_else(|| dcx.create_err(MveUnrecognizedVar { span, key }))
8971003
}
8981004

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,8 @@ declare_features! (
557557
(unstable, macro_attr, "CURRENT_RUSTC_VERSION", Some(83527)),
558558
/// Allow `macro_rules!` derive rules
559559
(unstable, macro_derive, "CURRENT_RUSTC_VERSION", Some(143549)),
560+
/// Allow macro fragment fields: `${fragment.field}`
561+
(incomplete, macro_fragment_fields, "CURRENT_RUSTC_VERSION", Some(147023)),
560562
/// Allow `macro_rules!` fragments `:fn` and `:adt`
561563
(incomplete, macro_fragments_more, "CURRENT_RUSTC_VERSION", Some(147023)),
562564
/// Give access to additional metadata about declarative macro meta-variables.

0 commit comments

Comments
 (0)