Skip to main content

rustc_expand/mbe/
metavar_expr.rs

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