Skip to content

Commit d0eca08

Browse files
committed
Implement macro meta-variable expression
1 parent 8756ed2 commit d0eca08

8 files changed

+836
-93
lines changed

compiler/rustc_expand/src/mbe/transcribe.rs

+147-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use crate::base::ExtCtxt;
2-
use crate::mbe;
32
use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, NamedMatch};
4-
3+
use crate::mbe::{self, MetaVarExpr};
54
use rustc_ast::mut_visit::{self, MutVisitor};
6-
use rustc_ast::token::{self, NtTT, Token};
5+
use rustc_ast::token::{self, NtTT, Token, TokenKind};
76
use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree, TreeAndSpacing};
87
use rustc_data_structures::fx::FxHashMap;
98
use rustc_data_structures::sync::Lrc;
109
use rustc_errors::{pluralize, PResult};
10+
use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
1111
use rustc_span::hygiene::{LocalExpnId, Transparency};
12-
use rustc_span::symbol::MacroRulesNormalizedIdent;
12+
use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
1313
use rustc_span::Span;
1414

1515
use smallvec::{smallvec, SmallVec};
@@ -411,13 +411,150 @@ fn lockstep_iter_size(
411411
}
412412
}
413413

414+
/// Used solely by the `count` meta-variable expression, counts the outer-most repetitions at a
415+
/// given optional nested depth.
416+
///
417+
/// For example, a macro parameter of `$( { $( $foo:ident ),* } )*` called with `{ a, b } { c }`:
418+
///
419+
/// * `[ $( ${count(foo)} ),* ]` will return [2, 1] with a, b = 2 and c = 1
420+
/// * `[ $( ${count(foo, 0)} ),* ]` will be the same as `[ $( ${count(foo)} ),* ]`
421+
/// * `[ $( ${count(foo, 1)} ),* ]` will return an error because `${count(foo, 1)}` is
422+
/// declared inside a single repetition and the index `1` implies two nested repetitions.
423+
fn count_repetitions<'a>(
424+
cx: &ExtCtxt<'a>,
425+
depth_opt: Option<usize>,
426+
mut matched: &NamedMatch,
427+
repeats: &[(usize, usize)],
428+
sp: &DelimSpan,
429+
) -> PResult<'a, usize> {
430+
// Recursively count the number of matches in `matched` at given depth
431+
// (or at the top-level of `matched` if no depth is given).
432+
fn count<'a>(
433+
cx: &ExtCtxt<'a>,
434+
declared_lhs_depth: usize,
435+
depth_opt: Option<usize>,
436+
matched: &NamedMatch,
437+
sp: &DelimSpan,
438+
) -> PResult<'a, usize> {
439+
match matched {
440+
MatchedNonterminal(_) => {
441+
if declared_lhs_depth == 0 {
442+
return Err(cx.struct_span_err(
443+
sp.entire(),
444+
"`count` can not be placed inside the inner-most repetition",
445+
));
446+
}
447+
match depth_opt {
448+
None => Ok(1),
449+
Some(_) => Err(out_of_bounds_err(cx, declared_lhs_depth, sp.entire(), "count")),
450+
}
451+
}
452+
MatchedSeq(ref named_matches) => {
453+
let new_declared_lhs_depth = declared_lhs_depth + 1;
454+
match depth_opt {
455+
None => named_matches
456+
.iter()
457+
.map(|elem| count(cx, new_declared_lhs_depth, None, elem, sp))
458+
.sum(),
459+
Some(0) => Ok(named_matches.len()),
460+
Some(depth) => named_matches
461+
.iter()
462+
.map(|elem| count(cx, new_declared_lhs_depth, Some(depth - 1), elem, sp))
463+
.sum(),
464+
}
465+
}
466+
}
467+
}
468+
// `repeats` records all of the nested levels at which we are currently
469+
// matching meta-variables. The meta-var-expr `count($x)` only counts
470+
// matches that occur in this "subtree" of the `NamedMatch` where we
471+
// are currently transcribing, so we need to descend to that subtree
472+
// before we start counting. `matched` contains the various levels of the
473+
// tree as we descend, and its final value is the subtree we are currently at.
474+
for &(idx, _) in repeats {
475+
if let MatchedSeq(ref ads) = matched {
476+
matched = &ads[idx];
477+
}
478+
}
479+
count(cx, 0, depth_opt, matched, sp)
480+
}
481+
482+
/// Returns a `NamedMatch` item declared on the RHS given an arbitrary [Ident]
483+
fn matched_from_ident<'ctx, 'interp, 'rslt>(
484+
cx: &ExtCtxt<'ctx>,
485+
ident: Ident,
486+
interp: &'interp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
487+
) -> PResult<'ctx, &'rslt NamedMatch>
488+
where
489+
'interp: 'rslt,
490+
{
491+
let span = ident.span;
492+
let key = MacroRulesNormalizedIdent::new(ident);
493+
interp.get(&key).ok_or_else(|| {
494+
cx.struct_span_err(
495+
span,
496+
&format!("variable `{}` is not recognized in meta-variable expression", key),
497+
)
498+
})
499+
}
500+
501+
/// Used by meta-variable expressions when an user input is out of the actual declared bounds. For
502+
/// example, index(999999) in an repetition of only three elements.
503+
fn out_of_bounds_err<'a>(
504+
cx: &ExtCtxt<'a>,
505+
max: usize,
506+
span: Span,
507+
ty: &str,
508+
) -> DiagnosticBuilder<'a, ErrorGuaranteed> {
509+
cx.struct_span_err(span, &format!("{ty} depth must be less than {max}"))
510+
}
511+
414512
fn transcribe_metavar_expr<'a>(
415-
_cx: &ExtCtxt<'a>,
416-
_expr: mbe::MetaVarExpr,
417-
_interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
418-
_repeats: &[(usize, usize)],
419-
_result: &mut Vec<TreeAndSpacing>,
420-
_sp: &DelimSpan,
513+
cx: &ExtCtxt<'a>,
514+
expr: MetaVarExpr,
515+
interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
516+
repeats: &[(usize, usize)],
517+
result: &mut Vec<TreeAndSpacing>,
518+
sp: &DelimSpan,
421519
) -> PResult<'a, ()> {
520+
match expr {
521+
MetaVarExpr::Count(original_ident, depth_opt) => {
522+
let matched = matched_from_ident(cx, original_ident, interp)?;
523+
let count = count_repetitions(cx, depth_opt, matched, &repeats, sp)?;
524+
let tt = TokenTree::token(
525+
TokenKind::lit(token::Integer, sym::integer(count), None),
526+
sp.entire(),
527+
);
528+
result.push(tt.into());
529+
}
530+
MetaVarExpr::Ignore(original_ident) => {
531+
// Used to ensure that `original_ident` is present in the LHS
532+
let _ = matched_from_ident(cx, original_ident, interp)?;
533+
}
534+
MetaVarExpr::Index(depth) => match repeats.iter().nth_back(depth) {
535+
Some((index, _)) => {
536+
result.push(
537+
TokenTree::token(
538+
TokenKind::lit(token::Integer, sym::integer(*index), None),
539+
sp.entire(),
540+
)
541+
.into(),
542+
);
543+
}
544+
None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "index")),
545+
},
546+
MetaVarExpr::Length(depth) => match repeats.iter().nth_back(depth) {
547+
Some((_, length)) => {
548+
result.push(
549+
TokenTree::token(
550+
TokenKind::lit(token::Integer, sym::integer(*length), None),
551+
sp.entire(),
552+
)
553+
.into(),
554+
);
555+
}
556+
None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "length")),
557+
},
558+
}
422559
Ok(())
423560
}

0 commit comments

Comments
 (0)