Skip to main content

virtue_next/parse/
body.rs

1use super::Attribute;
2use super::Visibility;
3use super::attributes::AttributeLocation;
4use super::utils::assume_group;
5use super::utils::assume_ident;
6use super::utils::assume_punct;
7use super::utils::consume_punct_if;
8use super::utils::read_tokens_until_punct;
9use crate::Error;
10use crate::Result;
11use crate::prelude::Delimiter;
12use crate::prelude::Ident;
13use crate::prelude::Literal;
14use crate::prelude::Span;
15use crate::prelude::TokenTree;
16use std::iter::Peekable;
17
18/// The body of a struct
19#[derive(Debug)]
20pub struct StructBody {
21    /// The fields of this struct, `None` if this struct has no fields
22    pub fields: Option<Fields>,
23}
24
25impl StructBody {
26    pub(crate) fn take(input: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Result<Self> {
27        match input.peek() {
28            | Some(TokenTree::Group(_)) => {},
29            | Some(TokenTree::Punct(p)) if p.as_char() == ';' => {
30                return Ok(Self { fields: None });
31            },
32            | token => return Error::wrong_token(token, "group or punct"),
33        }
34        let group = assume_group(input.next());
35        let mut stream = group.stream().into_iter().peekable();
36        let fields = match group.delimiter() {
37            | Delimiter::Brace => {
38                let fields = UnnamedField::parse_with_name(&mut stream)?;
39                Some(Fields::Struct(fields))
40            },
41            | Delimiter::Parenthesis => {
42                let fields = UnnamedField::parse(&mut stream)?;
43                Some(Fields::Tuple(fields))
44            },
45            | found => {
46                return Err(Error::InvalidRustSyntax {
47                    span: group.span(),
48                    expected: format!("brace or parenthesis, found {found:?}"),
49                });
50            },
51        };
52        Ok(Self { fields })
53    }
54}
55
56#[test]
57fn test_struct_body_take() {
58    use crate::token_stream;
59
60    let stream = &mut token_stream(
61        "struct Foo { pub bar: u8, pub(crate) baz: u32, bla: Vec<Box<dyn Future<Output = ()>>> }",
62    );
63    let (data_type, ident) = super::DataType::take(stream).unwrap();
64    assert_eq!(data_type, super::DataType::Struct);
65    assert_eq!(ident, "Foo");
66    let body = StructBody::take(stream).unwrap();
67    let fields = body.fields.as_ref().unwrap();
68
69    assert_eq!(fields.len(), 3);
70    let (ident, field) = fields.get(0).unwrap();
71    assert_eq!(ident.unwrap(), "bar");
72    assert_eq!(field.vis, Visibility::Pub);
73    assert_eq!(field.type_string(), "u8");
74
75    let (ident, field) = fields.get(1).unwrap();
76    assert_eq!(ident.unwrap(), "baz");
77    assert_eq!(field.vis, Visibility::Pub);
78    assert_eq!(field.type_string(), "u32");
79
80    let (ident, field) = fields.get(2).unwrap();
81    assert_eq!(ident.unwrap(), "bla");
82    assert_eq!(field.vis, Visibility::Default);
83    assert_eq!(field.type_string(), "Vec<Box<dynFuture<Output=()>>>");
84
85    let stream = &mut token_stream(
86        "struct Foo ( pub u8, pub(crate) u32, Vec<Box<dyn Future<Output = ()>>> )",
87    );
88    let (data_type, ident) = super::DataType::take(stream).unwrap();
89    assert_eq!(data_type, super::DataType::Struct);
90    assert_eq!(ident, "Foo");
91    let body = StructBody::take(stream).unwrap();
92    let fields = body.fields.as_ref().unwrap();
93
94    assert_eq!(fields.len(), 3);
95
96    let (ident, field) = fields.get(0).unwrap();
97    assert!(ident.is_none());
98    assert_eq!(field.vis, Visibility::Pub);
99    assert_eq!(field.type_string(), "u8");
100
101    let (ident, field) = fields.get(1).unwrap();
102    assert!(ident.is_none());
103    assert_eq!(field.vis, Visibility::Pub);
104    assert_eq!(field.type_string(), "u32");
105
106    let (ident, field) = fields.get(2).unwrap();
107    assert!(ident.is_none());
108    assert_eq!(field.vis, Visibility::Default);
109    assert_eq!(field.type_string(), "Vec<Box<dynFuture<Output=()>>>");
110
111    let stream = &mut token_stream("struct Foo;");
112    let (data_type, ident) = super::DataType::take(stream).unwrap();
113    assert_eq!(data_type, super::DataType::Struct);
114    assert_eq!(ident, "Foo");
115    let body = StructBody::take(stream).unwrap();
116    assert!(body.fields.is_none());
117
118    let stream = &mut token_stream("struct Foo {}");
119    let (data_type, ident) = super::DataType::take(stream).unwrap();
120    assert_eq!(data_type, super::DataType::Struct);
121    assert_eq!(ident, "Foo");
122    let body = StructBody::take(stream).unwrap();
123    if let Some(Fields::Struct(v)) = body.fields {
124        assert!(v.is_empty());
125    } else {
126        panic!("wrong fields {:?}", body.fields);
127    }
128
129    let stream = &mut token_stream("struct Foo ()");
130    let (data_type, ident) = super::DataType::take(stream).unwrap();
131    assert_eq!(data_type, super::DataType::Struct);
132    assert_eq!(ident, "Foo");
133    let body = StructBody::take(stream).unwrap();
134    if let Some(Fields::Tuple(v)) = body.fields {
135        assert!(v.is_empty());
136    } else {
137        panic!("wrong fields {:?}", body.fields);
138    }
139}
140
141#[test]
142fn issue_77() {
143    // https://github.com/bincode-org/virtue/issues/77
144    use crate::token_stream;
145
146    let stream = &mut token_stream("struct Test(pub [u8; 32])");
147    let (data_type, ident) = super::DataType::take(stream).unwrap();
148    assert_eq!(data_type, super::DataType::Struct);
149    assert_eq!(ident, "Test");
150    let body = StructBody::take(stream).unwrap();
151    let fields = body.fields.unwrap();
152    let Fields::Tuple(t) = fields else {
153        panic!("Fields is not a tuple")
154    };
155    assert_eq!(t.len(), 1);
156    assert_eq!(t[0].r#type[0].to_string(), "[u8 ; 32]");
157
158    let stream = &mut token_stream("struct Foo(pub (u8, ))");
159    let (data_type, ident) = super::DataType::take(stream).unwrap();
160    assert_eq!(data_type, super::DataType::Struct);
161    assert_eq!(ident, "Foo");
162    let body = StructBody::take(stream).unwrap();
163    let fields = body.fields.unwrap();
164    let Fields::Tuple(t) = fields else {
165        panic!("Fields is not a tuple")
166    };
167    assert_eq!(t.len(), 1);
168    assert_eq!(t[0].r#type[0].to_string(), "(u8 ,)");
169}
170
171/// The body of an enum
172#[derive(Debug)]
173pub struct EnumBody {
174    /// The enum's variants
175    pub variants: Vec<EnumVariant>,
176}
177
178impl EnumBody {
179    pub(crate) fn take(input: &mut Peekable<impl Iterator<Item = TokenTree>>) -> Result<Self> {
180        match input.peek() {
181            | Some(TokenTree::Group(_)) => {},
182            | Some(TokenTree::Punct(p)) if p.as_char() == ';' => {
183                return Ok(Self { variants: Vec::new() });
184            },
185            | token => return Error::wrong_token(token, "group or ;"),
186        }
187        let group = assume_group(input.next());
188        let mut variants = Vec::new();
189        let mut variant_stream = group.stream().into_iter().peekable();
190        let stream = &mut variant_stream;
191        while stream.peek().is_some() {
192            let attributes = Attribute::try_take(AttributeLocation::Variant, stream)?;
193            let ident = match super::utils::consume_ident(stream) {
194                | Some(ident) => ident,
195                | None => Error::wrong_token(stream.peek(), "ident")?,
196            };
197
198            let mut fields = None;
199            let mut value = None;
200
201            if let Some(TokenTree::Group(_)) = stream.peek() {
202                let group = assume_group(stream.next());
203                let mut inner_stream = group.stream().into_iter().peekable();
204                let stream_ref = &mut inner_stream;
205                match group.delimiter() {
206                    | Delimiter::Brace => {
207                        fields = Some(Fields::Struct(UnnamedField::parse_with_name(stream_ref)?));
208                    },
209                    | Delimiter::Parenthesis => {
210                        fields = Some(Fields::Tuple(UnnamedField::parse(stream_ref)?));
211                    },
212                    | delim => {
213                        return Err(Error::InvalidRustSyntax {
214                            span: group.span(),
215                            expected: format!("Brace or parenthesis, found {delim:?}"),
216                        });
217                    },
218                }
219            }
220            match stream.peek() {
221                | Some(TokenTree::Punct(p)) if p.as_char() == '=' => {
222                    assume_punct(stream.next(), '=');
223
224                    let first_val_token = stream.next(); // Bind #1
225                    match first_val_token {
226                        | Some(TokenTree::Literal(lit)) => {
227                            value = Some(lit);
228                        },
229                        | Some(TokenTree::Punct(p)) if p.as_char() == '-' => {
230                            let second_val_token = stream.next(); // Bind #2
231                            match second_val_token {
232                                | Some(TokenTree::Literal(lit)) => {
233                                    match lit.to_string().parse::<i64>() {
234                                        | Ok(val) => value = Some(Literal::i64_unsuffixed(-val)),
235                                        | Err(_) => {
236                                            return Err(Error::custom_at(
237                                                "parse::<i64> failed",
238                                                lit.span(),
239                                            ));
240                                        },
241                                    }
242                                },
243                                | token => return Error::wrong_token(token.as_ref(), "literal"),
244                            }
245                        },
246                        | token => return Error::wrong_token(token.as_ref(), "literal"),
247                    }
248                },
249                | Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
250                    // next field
251                },
252                | None => {
253                    // group done
254                },
255                | token => return Error::wrong_token(token, "group, comma or ="),
256            }
257
258            consume_punct_if(stream, ',');
259
260            variants.push(EnumVariant {
261                name: ident,
262                fields,
263                value,
264                attributes,
265            });
266        }
267
268        Ok(Self { variants })
269    }
270}
271
272#[test]
273fn test_enum_body_take() {
274    use crate::token_stream;
275
276    let stream = &mut token_stream("enum Foo { }");
277    let (data_type, ident) = super::DataType::take(stream).unwrap();
278    assert_eq!(data_type, super::DataType::Enum);
279    assert_eq!(ident, "Foo");
280    let body = EnumBody::take(stream).unwrap();
281    assert!(body.variants.is_empty());
282
283    let stream = &mut token_stream("enum Foo { Bar, Baz(u8), Blah { a: u32, b: u128 } }");
284    let (data_type, ident) = super::DataType::take(stream).unwrap();
285    assert_eq!(data_type, super::DataType::Enum);
286    assert_eq!(ident, "Foo");
287    let body = EnumBody::take(stream).unwrap();
288    assert_eq!(3, body.variants.len());
289
290    assert_eq!(body.variants[0].name, "Bar");
291    assert!(body.variants[0].fields.is_none());
292
293    assert_eq!(body.variants[1].name, "Baz");
294    assert!(body.variants[1].fields.is_some());
295    let fields = body.variants[1].fields.as_ref().unwrap();
296    assert_eq!(1, fields.len());
297    let (ident, field) = fields.get(0).unwrap();
298    assert!(ident.is_none());
299    assert_eq!(field.type_string(), "u8");
300
301    assert_eq!(body.variants[2].name, "Blah");
302    assert!(body.variants[2].fields.is_some());
303    let fields = body.variants[2].fields.as_ref().unwrap();
304    assert_eq!(2, fields.len());
305    let (ident, field) = fields.get(0).unwrap();
306    assert_eq!(ident.unwrap(), "a");
307    assert_eq!(field.type_string(), "u32");
308    let (ident, field) = fields.get(1).unwrap();
309    assert_eq!(ident.unwrap(), "b");
310    assert_eq!(field.type_string(), "u128");
311
312    let stream = &mut token_stream("enum Foo { Bar = -1, Baz = 2 }");
313    let (data_type, ident) = super::DataType::take(stream).unwrap();
314    assert_eq!(data_type, super::DataType::Enum);
315    assert_eq!(ident, "Foo");
316    let body = EnumBody::take(stream).unwrap();
317    assert_eq!(2, body.variants.len());
318
319    assert_eq!(body.variants[0].name, "Bar");
320    assert!(body.variants[0].fields.is_none());
321    assert_eq!(body.variants[0].get_integer(), -1);
322
323    assert_eq!(body.variants[1].name, "Baz");
324    assert!(body.variants[1].fields.is_none());
325    assert_eq!(body.variants[1].get_integer(), 2);
326
327    let stream = &mut token_stream("enum Foo { Bar(i32) = -1, Baz { a: i32 } = 2 }");
328    let (data_type, ident) = super::DataType::take(stream).unwrap();
329    assert_eq!(data_type, super::DataType::Enum);
330    assert_eq!(ident, "Foo");
331    let body = EnumBody::take(stream).unwrap();
332    assert_eq!(2, body.variants.len());
333
334    assert_eq!(body.variants[0].name, "Bar");
335    assert!(body.variants[0].fields.is_some());
336    let fields = body.variants[0].fields.as_ref().unwrap();
337    assert_eq!(fields.len(), 1);
338    assert!(matches!(fields.names()[0], IdentOrIndex::Index { index, .. } if index == 0));
339    assert_eq!(body.variants[0].get_integer(), -1);
340
341    assert_eq!(body.variants[1].name, "Baz");
342    assert!(body.variants[1].fields.is_some());
343    let fields = body.variants[1].fields.as_ref().unwrap();
344    assert_eq!(fields.len(), 1);
345    assert_eq!(fields.names().len(), 1);
346    assert!(matches!(&fields.names()[0], IdentOrIndex::Ident { ident, .. } if *ident == "a"));
347    assert_eq!(body.variants[1].get_integer(), 2);
348
349    let stream = &mut token_stream("enum Foo { Round(), Curly{}, Without }");
350    let (data_type, ident) = super::DataType::take(stream).unwrap();
351    assert_eq!(data_type, super::DataType::Enum);
352    assert_eq!(ident, "Foo");
353    let body = EnumBody::take(stream).unwrap();
354    assert_eq!(3, body.variants.len());
355
356    assert_eq!(body.variants[0].name, "Round");
357    assert!(body.variants[0].fields.is_some());
358    let fields = body.variants[0].fields.as_ref().unwrap();
359    assert!(fields.names().is_empty());
360    assert_eq!(fields.len(), 0);
361
362    assert_eq!(body.variants[1].name, "Curly");
363    assert!(body.variants[1].fields.is_some());
364    let fields = body.variants[1].fields.as_ref().unwrap();
365    assert!(fields.names().is_empty());
366    assert_eq!(fields.len(), 0);
367
368    assert_eq!(body.variants[2].name, "Without");
369    assert!(body.variants[2].fields.is_none());
370}
371
372/// A variant of an enum
373#[derive(Debug)]
374pub struct EnumVariant {
375    /// The name of the variant
376    pub name: Ident,
377    /// The field of the variant. See [`Fields`] for more info
378    pub fields: Option<Fields>,
379    /// The value of this variant. This can be one of:
380    /// - `Baz = 5`
381    /// - `Baz(i32) = 5`
382    /// - `Baz { a: i32} = 5`
383    ///
384    /// In either case this value will be `Some(Literal::i32(5))`
385    pub value: Option<Literal>,
386    /// The attributes of this variant
387    pub attributes: Vec<Attribute>,
388}
389
390#[cfg(test)]
391impl EnumVariant {
392    fn get_integer(&self) -> i64 {
393        let value = self.value.as_ref().expect("Variant has no value");
394        value
395            .to_string()
396            .parse()
397            .expect("Value is not a valid integer")
398    }
399}
400
401/// The different field types an enum variant can have.
402#[derive(Debug)]
403pub enum Fields {
404    /// Tuple-like variant
405    /// ```rs
406    /// enum Foo {
407    ///     Baz(u32)
408    /// }
409    /// struct Bar(u32);
410    /// ```
411    Tuple(Vec<UnnamedField>),
412
413    /// Struct-like variant
414    /// ```rs
415    /// enum Foo {
416    ///     Baz {
417    ///         baz: u32
418    ///     }
419    /// }
420    /// struct Bar {
421    ///     baz: u32
422    /// }
423    /// ```
424    Struct(Vec<(Ident, UnnamedField)>),
425}
426
427impl Fields {
428    /// Returns a list of names for the variant.
429    ///
430    /// ```
431    /// enum Foo {
432    ///     C(u32, u32), // will return `vec[Index { index: 0 }, Index { index: 1 }]`
433    ///     D { a: u32, b: u32 }, // will return `vec[Ident { ident: "a" }, Ident { ident: "b" }]`
434    /// }
435    #[must_use]
436    pub fn names(&self) -> Vec<IdentOrIndex> {
437        let result: Vec<IdentOrIndex> = match self {
438            | Self::Tuple(fields) => {
439                fields
440                    .iter()
441                    .enumerate()
442                    .map(|(index, field)| {
443                        IdentOrIndex::Index {
444                            index,
445                            span: field.span(),
446                            attributes: field.attributes.clone(),
447                        }
448                    })
449                    .collect()
450            },
451            | Self::Struct(fields) => {
452                fields
453                    .iter()
454                    .map(|(ident, field)| {
455                        IdentOrIndex::Ident {
456                            ident: ident.clone(),
457                            attributes: field.attributes.clone(),
458                        }
459                    })
460                    .collect()
461            },
462        };
463        result
464    }
465
466    /// Return the delimiter of the group for this variant
467    ///
468    /// ```
469    /// enum Foo {
470    ///     C(u32, u32),          // will return `Delimiter::Paranthesis`
471    ///     D { a: u32, b: u32 }, // will return `Delimiter::Brace`
472    /// }
473    /// ```
474    #[must_use]
475    pub const fn delimiter(&self) -> Delimiter {
476        match self {
477            | Self::Tuple(_) => Delimiter::Parenthesis,
478            | Self::Struct(_) => Delimiter::Brace,
479        }
480    }
481}
482
483#[cfg(test)]
484impl Fields {
485    fn len(&self) -> usize {
486        match self {
487            | Self::Tuple(fields) => fields.len(),
488            | Self::Struct(fields) => fields.len(),
489        }
490    }
491
492    fn get(
493        &self,
494        index: usize,
495    ) -> Option<(Option<&Ident>, &UnnamedField)> {
496        match self {
497            | Self::Tuple(fields) => fields.get(index).map(|f| (None, f)),
498            | Self::Struct(fields) => fields.get(index).map(|(ident, field)| (Some(ident), field)),
499        }
500    }
501}
502
503/// An unnamed field
504#[derive(Debug)]
505pub struct UnnamedField {
506    /// The visibility of the field
507    pub vis: Visibility,
508    /// The type of the field
509    pub r#type: Vec<TokenTree>,
510    /// The attributes of the field
511    pub attributes: Vec<Attribute>,
512}
513
514impl UnnamedField {
515    pub(crate) fn parse_with_name(
516        input: &mut Peekable<impl Iterator<Item = TokenTree>>
517    ) -> Result<Vec<(Ident, Self)>> {
518        let mut result = Vec::new();
519        loop {
520            let attributes = Attribute::try_take(AttributeLocation::Field, input)?;
521            let vis = Visibility::try_take(input)?;
522
523            let ident = match input.peek() {
524                | Some(TokenTree::Ident(_)) => assume_ident(input.next()),
525                | Some(x) => {
526                    return Err(Error::InvalidRustSyntax {
527                        span: x.span(),
528                        expected: format!("ident or end of group, got {x:?}"),
529                    });
530                },
531                | None => break,
532            };
533            match input.peek() {
534                | Some(TokenTree::Punct(p)) if p.as_char() == ':' => {
535                    input.next();
536                },
537                | token => return Error::wrong_token(token, ":"),
538            }
539            let r#type = read_tokens_until_punct(input, &[','])?;
540            consume_punct_if(input, ',');
541            result.push((
542                ident,
543                Self {
544                    vis,
545                    r#type,
546                    attributes,
547                },
548            ));
549        }
550        Ok(result)
551    }
552
553    pub(crate) fn parse(
554        input: &mut Peekable<impl Iterator<Item = TokenTree>>
555    ) -> Result<Vec<Self>> {
556        let mut result = Vec::new();
557        while input.peek().is_some() {
558            let attributes = Attribute::try_take(AttributeLocation::Field, input)?;
559            let vis = Visibility::try_take(input)?;
560
561            let r#type = read_tokens_until_punct(input, &[','])?;
562            consume_punct_if(input, ',');
563            result.push(Self {
564                vis,
565                r#type,
566                attributes,
567            });
568        }
569        Ok(result)
570    }
571
572    /// Return [`type`] as a string. Useful for comparing it for known values.
573    ///
574    /// [`type`]: #structfield.type
575    ///
576    /// # Panics
577    ///
578    /// Panics if an internal invariant is violated.
579    #[must_use]
580    pub fn type_string(&self) -> String {
581        self.r#type
582            .iter()
583            .map(std::string::ToString::to_string)
584            .collect()
585    }
586
587    /// Return the span of [`type`].
588    ///
589    /// **note**: Until <https://github.com/rust-lang/rust/issues/54725> is stable, this will return the first span of the type instead
590    ///
591    /// [`type`]: #structfield.type
592    ///
593    /// # Panics
594    ///
595    /// Panics if an internal invariant is violated.
596    #[must_use]
597    pub fn span(&self) -> Span {
598        // BlockedTODO: https://github.com/rust-lang/rust/issues/54725
599        // Span::join is unstable
600        // if let Some(first) = self.r#type.first() {
601        //     let mut span = first.span();
602        //     for token in self.r#type.iter().skip(1) {
603        //         span = span.join(span).unwrap();
604        //     }
605        //     span
606        // } else {
607        //     Span::call_site()
608        // }
609
610        match self.r#type.first() {
611            | Some(first) => first.span(),
612            | None => Span::call_site(),
613        }
614    }
615}
616
617/// Reference to an enum variant's field. Either by index or by ident.
618///
619/// ```
620/// enum Foo {
621///     Bar(u32), // will be IdentOrIndex::Index { index: 0, .. }
622///     Baz {
623///         a: u32, // will be IdentOrIndex::Ident { ident: "a", .. }
624///     },
625/// }
626#[derive(Debug, Clone)]
627pub enum IdentOrIndex {
628    /// The variant is a named field
629    Ident {
630        /// The name of the field
631        ident: Ident,
632        /// The attributes of the field
633        attributes: Vec<Attribute>,
634    },
635    /// The variant is an unnamed field
636    Index {
637        /// The field index
638        index: usize,
639        /// The span of the field type
640        span: Span,
641        /// The attributes of this field
642        attributes: Vec<Attribute>,
643    },
644}
645
646impl IdentOrIndex {
647    /// Get the ident. Will panic if this is an `IdentOrIndex::Index`
648    ///
649    /// # Panics
650    ///
651    /// Panics if an internal invariant is violated.
652    #[must_use]
653    pub fn unwrap_ident(&self) -> Ident {
654        match self {
655            | Self::Ident { ident, .. } => ident.clone(),
656            | x => panic!("Expected ident, found {x:?}"),
657        }
658    }
659
660    /// Convert this ident into a `TokenTree`. If this is an `Index`, will return `prefix + index` instead.
661    #[must_use]
662    pub fn to_token_tree_with_prefix(
663        &self,
664        prefix: &str,
665    ) -> TokenTree {
666        TokenTree::Ident(match self {
667            | Self::Ident { ident, .. } => (*ident).clone(),
668            | Self::Index { index, span, .. } => {
669                let name = format!("{prefix}{index}");
670                Ident::new(&name, *span)
671            },
672        })
673    }
674
675    /// Return either the index or the ident of this field with a fixed prefix. The prefix will always be added.
676    #[must_use]
677    pub fn to_string_with_prefix(
678        &self,
679        prefix: &str,
680    ) -> String {
681        match self {
682            | Self::Ident { ident, .. } => ident.to_string(),
683            | Self::Index { index, .. } => {
684                format!("{prefix}{index}")
685            },
686        }
687    }
688
689    /// Returns the attributes of this field.
690    ///
691    /// # Panics
692    ///
693    /// Panics if an internal invariant is violated.
694    #[allow(clippy::match_same_arms)]
695    #[must_use]
696    pub const fn attributes(&self) -> &Vec<Attribute> {
697        match self {
698            | Self::Ident { attributes, .. } => attributes,
699            | Self::Index { attributes, .. } => attributes,
700        }
701    }
702}
703
704impl std::fmt::Display for IdentOrIndex {
705    fn fmt(
706        &self,
707        fmt: &mut std::fmt::Formatter<'_>,
708    ) -> std::fmt::Result {
709        match self {
710            | Self::Ident { ident, .. } => write!(fmt, "{ident}"),
711            | Self::Index { index, .. } => write!(fmt, "{index}"),
712        }
713    }
714}
715
716#[test]
717fn enum_explicit_variants() {
718    use crate::token_stream;
719    let stream = &mut token_stream("{ A = 1, B = 2 }");
720    let body = EnumBody::take(stream).unwrap();
721    assert_eq!(body.variants.len(), 2);
722}