Skip to main content

rustc_expand/mbe/
metavar_expr.rs

1use rustc_ast::token::{self, Delimiter, IdentIsRaw, Lit, Token, TokenKind};
2use rustc_ast::tokenstream::{TokenStream, TokenStreamIter, TokenTree};
3use rustc_ast::{LitIntType, LitKind};
4use rustc_ast_pretty::pprust;
5use rustc_errors::{Applicability, PResult};
6use rustc_macros::{Decodable, Encodable};
7use rustc_session::parse::ParseSess;
8use rustc_span::{Ident, Span, Symbol, sym};
9
10use crate::diagnostics;
11
12pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
13pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";
14
15/// A meta-variable expression, for expansions based on properties of meta-variables.
16#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MetaVarExpr {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            MetaVarExpr::Concat(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Concat",
                    &__self_0),
            MetaVarExpr::Count(__self_0, __self_1) =>
                ::core::fmt::Formatter::debug_tuple_field2_finish(f, "Count",
                    __self_0, &__self_1),
            MetaVarExpr::Ignore(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Ignore",
                    &__self_0),
            MetaVarExpr::Index(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Index",
                    &__self_0),
            MetaVarExpr::Len(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Len",
                    &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for MetaVarExpr {
    #[inline]
    fn eq(&self, other: &MetaVarExpr) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (MetaVarExpr::Concat(__self_0), MetaVarExpr::Concat(__arg1_0))
                    => __self_0 == __arg1_0,
                (MetaVarExpr::Count(__self_0, __self_1),
                    MetaVarExpr::Count(__arg1_0, __arg1_1)) =>
                    __self_0 == __arg1_0 && __self_1 == __arg1_1,
                (MetaVarExpr::Ignore(__self_0), MetaVarExpr::Ignore(__arg1_0))
                    => __self_0 == __arg1_0,
                (MetaVarExpr::Index(__self_0), MetaVarExpr::Index(__arg1_0))
                    => __self_0 == __arg1_0,
                (MetaVarExpr::Len(__self_0), MetaVarExpr::Len(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq, const _: () =
    {
        impl<__E: ::rustc_span::SpanEncoder> ::rustc_serialize::Encodable<__E>
            for MetaVarExpr {
            fn encode(&self, __encoder: &mut __E) {
                let disc =
                    match *self {
                        MetaVarExpr::Concat(ref __binding_0) => { 0usize }
                        MetaVarExpr::Count(ref __binding_0, ref __binding_1) => {
                            1usize
                        }
                        MetaVarExpr::Ignore(ref __binding_0) => { 2usize }
                        MetaVarExpr::Index(ref __binding_0) => { 3usize }
                        MetaVarExpr::Len(ref __binding_0) => { 4usize }
                    };
                ::rustc_serialize::Encoder::emit_u8(__encoder, disc as u8);
                match *self {
                    MetaVarExpr::Concat(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                    MetaVarExpr::Count(ref __binding_0, ref __binding_1) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_1,
                            __encoder);
                    }
                    MetaVarExpr::Ignore(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                    MetaVarExpr::Index(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                    MetaVarExpr::Len(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                }
            }
        }
    };Encodable, const _: () =
    {
        impl<__D: ::rustc_span::SpanDecoder> ::rustc_serialize::Decodable<__D>
            for MetaVarExpr {
            fn decode(__decoder: &mut __D) -> Self {
                match ::rustc_serialize::Decoder::read_u8(__decoder) as usize
                    {
                    0usize => {
                        MetaVarExpr::Concat(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    1usize => {
                        MetaVarExpr::Count(::rustc_serialize::Decodable::decode(__decoder),
                            ::rustc_serialize::Decodable::decode(__decoder))
                    }
                    2usize => {
                        MetaVarExpr::Ignore(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    3usize => {
                        MetaVarExpr::Index(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    4usize => {
                        MetaVarExpr::Len(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    n => {
                        ::core::panicking::panic_fmt(format_args!("invalid enum variant tag while decoding `MetaVarExpr`, expected 0..5, actual {0}",
                                n));
                    }
                }
            }
        }
    };Decodable)]
17pub(crate) enum MetaVarExpr {
18    /// Unification of two or more identifiers.
19    Concat(Box<[MetaVarExprConcatElem]>),
20
21    /// The number of repetitions of an identifier.
22    Count(Ident, usize),
23
24    /// Ignore a meta-variable for repetition without expansion.
25    Ignore(Ident),
26
27    /// The index of the repetition at a particular depth, where 0 is the innermost
28    /// repetition. The `usize` is the depth.
29    Index(usize),
30
31    /// The length of the repetition at a particular depth, where 0 is the innermost
32    /// repetition. The `usize` is the depth.
33    Len(usize),
34}
35
36impl MetaVarExpr {
37    /// Attempt to parse a meta-variable expression from a token stream.
38    pub(crate) fn parse<'psess>(
39        input: &TokenStream,
40        outer_span: Span,
41        psess: &'psess ParseSess,
42    ) -> PResult<'psess, MetaVarExpr> {
43        let mut iter = input.iter();
44        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 = diagnostics::MveMissingParen {
55                ident_span: ident.span,
56                unexpected_span,
57                insert_span,
58            };
59            return Err(psess.dcx().create_err(err));
60        };
61
62        // Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}`
63        if iter.peek().is_some() {
64            let span = iter_span(&iter).expect("checked is_some above");
65            let err = diagnostics::MveExtraTokens {
66                span,
67                ident_span: ident.span,
68                extra_count: iter.count(),
69                ..Default::default()
70            };
71            return Err(psess.dcx().create_err(err));
72        }
73
74        let mut iter = args.iter();
75        let rslt = match ident.name {
76            sym::concat => parse_concat(&mut iter, psess, outer_span, ident.span)?,
77            sym::count => parse_count(&mut iter, psess, ident.span)?,
78            sym::ignore => {
79                eat_dollar(&mut iter, psess, ident.span)?;
80                MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?)
81            }
82            sym::index => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
83            sym::len => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?),
84            _ => {
85                let err = diagnostics::MveUnrecognizedExpr {
86                    span: ident.span,
87                    valid_expr_list: "`count`, `ignore`, `index`, `len`, and `concat`",
88                };
89                return Err(psess.dcx().create_err(err));
90            }
91        };
92        check_trailing_tokens(&mut iter, psess, ident)?;
93        Ok(rslt)
94    }
95
96    pub(crate) fn for_each_metavar<A>(&self, mut aux: A, mut cb: impl FnMut(A, &Ident) -> A) -> A {
97        match self {
98            MetaVarExpr::Concat(elems) => {
99                for elem in elems {
100                    if let MetaVarExprConcatElem::Var(ident) = elem {
101                        aux = cb(aux, ident)
102                    }
103                }
104                aux
105            }
106            MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => cb(aux, ident),
107            MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => aux,
108        }
109    }
110}
111
112/// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create
113/// a diag with the correct arg count if so.
114fn check_trailing_tokens<'psess>(
115    iter: &mut TokenStreamIter<'_>,
116    psess: &'psess ParseSess,
117    ident: Ident,
118) -> PResult<'psess, ()> {
119    if iter.peek().is_none() {
120        // All tokens consumed, as expected
121        return Ok(());
122    }
123
124    // `None` for max indicates the arg count must be exact, `Some` indicates a range is accepted.
125    let (min_or_exact_args, max_args) = match ident.name {
126        sym::concat => {
    ::core::panicking::panic_fmt(format_args!("concat takes unlimited tokens but didn\'t eat them all"));
}panic!("concat takes unlimited tokens but didn't eat them all"),
127        sym::ignore => (1, None),
128        // 1 or 2 args
129        sym::count => (1, Some(2)),
130        // 0 or 1 arg
131        sym::index | sym::len => (0, Some(1)),
132        other => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("unknown MVEs should be rejected earlier (got `{0}`)",
                other)));
}unreachable!("unknown MVEs should be rejected earlier (got `{other}`)"),
133    };
134
135    let err = diagnostics::MveExtraTokens {
136        span: iter_span(iter).expect("checked is_none above"),
137        ident_span: ident.span,
138        extra_count: iter.count(),
139
140        exact_args_note: if max_args.is_some() { None } else { Some(()) },
141        range_args_note: if max_args.is_some() { Some(()) } else { None },
142        min_or_exact_args,
143        max_args: max_args.unwrap_or_default(),
144        name: ident.to_string(),
145    };
146    Err(psess.dcx().create_err(err))
147}
148
149/// Returns a span encompassing all tokens in the iterator if there is at least one item.
150fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
151    let mut iter = iter.clone(); // cloning is cheap
152    let first_sp = iter.next()?.span();
153    let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
154    let span = first_sp.with_hi(last_sp.hi());
155    Some(span)
156}
157
158/// Indicates what is placed in a `concat` parameter. For example, literals
159/// (`${concat("foo", "bar")}`) or adhoc identifiers (`${concat(foo, bar)}`).
160#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MetaVarExprConcatElem {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            MetaVarExprConcatElem::Ident(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Ident",
                    &__self_0),
            MetaVarExprConcatElem::Literal(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Literal", &__self_0),
            MetaVarExprConcatElem::Var(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Var",
                    &__self_0),
        }
    }
}Debug, const _: () =
    {
        impl<__D: ::rustc_span::SpanDecoder> ::rustc_serialize::Decodable<__D>
            for MetaVarExprConcatElem {
            fn decode(__decoder: &mut __D) -> Self {
                match ::rustc_serialize::Decoder::read_u8(__decoder) as usize
                    {
                    0usize => {
                        MetaVarExprConcatElem::Ident(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    1usize => {
                        MetaVarExprConcatElem::Literal(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    2usize => {
                        MetaVarExprConcatElem::Var(::rustc_serialize::Decodable::decode(__decoder))
                    }
                    n => {
                        ::core::panicking::panic_fmt(format_args!("invalid enum variant tag while decoding `MetaVarExprConcatElem`, expected 0..3, actual {0}",
                                n));
                    }
                }
            }
        }
    };Decodable, const _: () =
    {
        impl<__E: ::rustc_span::SpanEncoder> ::rustc_serialize::Encodable<__E>
            for MetaVarExprConcatElem {
            fn encode(&self, __encoder: &mut __E) {
                let disc =
                    match *self {
                        MetaVarExprConcatElem::Ident(ref __binding_0) => { 0usize }
                        MetaVarExprConcatElem::Literal(ref __binding_0) => {
                            1usize
                        }
                        MetaVarExprConcatElem::Var(ref __binding_0) => { 2usize }
                    };
                ::rustc_serialize::Encoder::emit_u8(__encoder, disc as u8);
                match *self {
                    MetaVarExprConcatElem::Ident(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                    MetaVarExprConcatElem::Literal(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                    MetaVarExprConcatElem::Var(ref __binding_0) => {
                        ::rustc_serialize::Encodable::<__E>::encode(__binding_0,
                            __encoder);
                    }
                }
            }
        }
    };Encodable, #[automatically_derived]
impl ::core::cmp::PartialEq for MetaVarExprConcatElem {
    #[inline]
    fn eq(&self, other: &MetaVarExprConcatElem) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (MetaVarExprConcatElem::Ident(__self_0),
                    MetaVarExprConcatElem::Ident(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (MetaVarExprConcatElem::Literal(__self_0),
                    MetaVarExprConcatElem::Literal(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (MetaVarExprConcatElem::Var(__self_0),
                    MetaVarExprConcatElem::Var(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq)]
161pub(crate) enum MetaVarExprConcatElem {
162    /// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be
163    /// interpreted as a literal.
164    Ident(Ident),
165    /// For example, a number or a string.
166    Literal(Symbol),
167    /// Identifier WITH a preceding dollar sign, which means that this identifier should be
168    /// expanded and interpreted as a variable.
169    Var(Ident),
170}
171
172/// Parse a meta-variable `concat` expression: `concat($metavar, ident, ...)`.
173fn parse_concat<'psess>(
174    iter: &mut TokenStreamIter<'_>,
175    psess: &'psess ParseSess,
176    outer_span: Span,
177    expr_ident_span: Span,
178) -> PResult<'psess, MetaVarExpr> {
179    let mut result = Vec::new();
180    loop {
181        let is_var = try_eat_dollar(iter);
182        let token = parse_token(iter, psess, outer_span)?;
183        let element = if is_var {
184            MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?)
185        } else if let TokenKind::Literal(Lit { kind: token::LitKind::Str, symbol, suffix: None }) =
186            token.kind
187        {
188            MetaVarExprConcatElem::Literal(symbol)
189        } else {
190            match parse_ident_from_token(psess, token) {
191                Err(err) => {
192                    err.cancel();
193                    return Err(psess
194                        .dcx()
195                        .struct_span_err(token.span, UNSUPPORTED_CONCAT_ELEM_ERR));
196                }
197                Ok(elem) => MetaVarExprConcatElem::Ident(elem),
198            }
199        };
200        result.push(element);
201        if iter.peek().is_none() {
202            break;
203        }
204        if !try_eat_comma(iter) {
205            return Err(psess.dcx().struct_span_err(outer_span, "expected comma"));
206        }
207    }
208    if result.len() < 2 {
209        return Err(psess
210            .dcx()
211            .struct_span_err(expr_ident_span, "`concat` must have at least two elements"));
212    }
213    Ok(MetaVarExpr::Concat(result.into()))
214}
215
216/// Parse a meta-variable `count` expression: `count(ident[, depth])`
217fn parse_count<'psess>(
218    iter: &mut TokenStreamIter<'_>,
219    psess: &'psess ParseSess,
220    span: Span,
221) -> PResult<'psess, MetaVarExpr> {
222    eat_dollar(iter, psess, span)?;
223    let ident = parse_ident(iter, psess, span)?;
224    let depth = if try_eat_comma(iter) {
225        if iter.peek().is_none() {
226            return Err(psess.dcx().struct_span_err(
227                span,
228                "`count` followed by a comma must have an associated index indicating its depth",
229            ));
230        }
231        parse_depth(iter, psess, span)?
232    } else {
233        0
234    };
235    Ok(MetaVarExpr::Count(ident, depth))
236}
237
238/// Parses the depth used by index(depth) and len(depth).
239fn parse_depth<'psess>(
240    iter: &mut TokenStreamIter<'_>,
241    psess: &'psess ParseSess,
242    span: Span,
243) -> PResult<'psess, usize> {
244    let Some(tt) = iter.next() else { return Ok(0) };
245    let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else {
246        return Err(psess
247            .dcx()
248            .struct_span_err(span, "meta-variable expression depth must be a literal"));
249    };
250    if let Ok(lit_kind) = LitKind::from_token_lit(*lit)
251        && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
252        && let Ok(n_usize) = usize::try_from(n_u128.get())
253    {
254        Ok(n_usize)
255    } else {
256        let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
257        Err(psess.dcx().struct_span_err(span, msg))
258    }
259}
260
261/// Parses an generic ident
262fn parse_ident<'psess>(
263    iter: &mut TokenStreamIter<'_>,
264    psess: &'psess ParseSess,
265    fallback_span: Span,
266) -> PResult<'psess, Ident> {
267    let token = parse_token(iter, psess, fallback_span)?;
268    parse_ident_from_token(psess, token)
269}
270
271fn parse_ident_from_token<'psess>(
272    psess: &'psess ParseSess,
273    token: &Token,
274) -> PResult<'psess, Ident> {
275    if let Some((elem, is_raw)) = token.ident() {
276        if let IdentIsRaw::Yes = is_raw {
277            return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR));
278        }
279        return Ok(elem);
280    }
281    let token_str = pprust::token_to_string(token);
282    let mut err = psess
283        .dcx()
284        .struct_span_err(token.span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("expected identifier, found `{0}`",
                token_str))
    })format!("expected identifier, found `{token_str}`"));
285    err.span_suggestion(
286        token.span,
287        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("try removing `{0}`", token_str))
    })format!("try removing `{token_str}`"),
288        "",
289        Applicability::MaybeIncorrect,
290    );
291    Err(err)
292}
293
294fn parse_token<'psess, 't>(
295    iter: &mut TokenStreamIter<'t>,
296    psess: &'psess ParseSess,
297    fallback_span: Span,
298) -> PResult<'psess, &'t Token> {
299    let Some(tt) = iter.next() else {
300        return Err(psess.dcx().struct_span_err(fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR));
301    };
302    let TokenTree::Token(token, _) = tt else {
303        return Err(psess.dcx().struct_span_err(tt.span(), UNSUPPORTED_CONCAT_ELEM_ERR));
304    };
305    Ok(token)
306}
307
308/// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
309/// iterator is not modified and the result is `false`.
310fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
311    if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.peek() {
312        let _ = iter.next();
313        return true;
314    }
315    false
316}
317
318/// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the
319/// iterator is not modified and the result is `false`.
320fn try_eat_dollar(iter: &mut TokenStreamIter<'_>) -> bool {
321    if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.peek() {
322        let _ = iter.next();
323        return true;
324    }
325    false
326}
327
328/// Expects that the next item is a dollar sign.
329fn eat_dollar<'psess>(
330    iter: &mut TokenStreamIter<'_>,
331    psess: &'psess ParseSess,
332    span: Span,
333) -> PResult<'psess, ()> {
334    if try_eat_dollar(iter) {
335        return Ok(());
336    }
337    Err(psess.dcx().struct_span_err(
338        span,
339        "meta-variables within meta-variable expressions must be referenced using a dollar sign",
340    ))
341}