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