snafu_derive/
parse.rs

1use std::collections::BTreeSet;
2
3use crate::{ModuleName, ProvideKind, SnafuAttribute};
4use proc_macro2::TokenStream;
5use quote::{format_ident, ToTokens};
6use syn::{
7    parenthesized,
8    parse::{Parse, ParseStream, Result},
9    punctuated::Punctuated,
10    token, Expr, Ident, Lit, LitBool, LitStr, Path, Type,
11};
12
13mod kw {
14    use syn::custom_keyword;
15
16    custom_keyword!(backtrace);
17    custom_keyword!(context);
18    custom_keyword!(crate_root);
19    custom_keyword!(display);
20    custom_keyword!(implicit);
21    custom_keyword!(module);
22    custom_keyword!(provide);
23    custom_keyword!(source);
24    custom_keyword!(visibility);
25    custom_keyword!(whatever);
26
27    custom_keyword!(from);
28
29    custom_keyword!(suffix);
30
31    custom_keyword!(chain);
32    custom_keyword!(opt);
33    custom_keyword!(priority);
34}
35
36pub(crate) fn attributes_from_syn(
37    attrs: Vec<syn::Attribute>,
38) -> super::MultiSynResult<Vec<SnafuAttribute>> {
39    let mut ours = Vec::new();
40    let mut errs = Vec::new();
41
42    for attr in attrs {
43        if attr.path.is_ident("snafu") {
44            let attr_list = Punctuated::<Attribute, token::Comma>::parse_terminated;
45
46            match attr.parse_args_with(attr_list) {
47                Ok(attrs) => {
48                    ours.extend(attrs.into_iter().map(Into::into));
49                }
50                Err(e) => errs.push(e),
51            }
52        } else if attr.path.is_ident("doc") {
53            // Ignore any errors that occur while parsing the doc
54            // comment. This isn't our attribute so we shouldn't
55            // assume that we know what values are acceptable.
56            if let Ok(comment) = syn::parse2::<DocComment>(attr.tokens) {
57                ours.push(comment.into());
58            }
59        }
60    }
61
62    for attr in &ours {
63        if let SnafuAttribute::Provide(tt, ProvideKind::Expression(p)) = attr {
64            if p.is_chain && !p.is_ref {
65                errs.push(syn::Error::new_spanned(
66                    tt,
67                    "May only chain to references; please add `ref` flag",
68                ));
69            }
70        }
71    }
72
73    if errs.is_empty() {
74        Ok(ours)
75    } else {
76        Err(errs)
77    }
78}
79
80enum Attribute {
81    Backtrace(Backtrace),
82    Context(Context),
83    CrateRoot(CrateRoot),
84    Display(Display),
85    Implicit(Implicit),
86    Module(Module),
87    Provide(Provide),
88    Source(Source),
89    Visibility(Visibility),
90    Whatever(Whatever),
91}
92
93impl From<Attribute> for SnafuAttribute {
94    fn from(other: Attribute) -> Self {
95        use self::Attribute::*;
96
97        match other {
98            Backtrace(b) => SnafuAttribute::Backtrace(b.to_token_stream(), b.into_bool()),
99            Context(c) => SnafuAttribute::Context(c.to_token_stream(), c.into_component()),
100            CrateRoot(cr) => SnafuAttribute::CrateRoot(cr.to_token_stream(), cr.into_arbitrary()),
101            Display(d) => SnafuAttribute::Display(d.to_token_stream(), d.into_display()),
102            Implicit(d) => SnafuAttribute::Implicit(d.to_token_stream(), d.into_bool()),
103            Module(v) => SnafuAttribute::Module(v.to_token_stream(), v.into_value()),
104            Provide(v) => SnafuAttribute::Provide(v.to_token_stream(), v.into_value()),
105            Source(s) => SnafuAttribute::Source(s.to_token_stream(), s.into_components()),
106            Visibility(v) => SnafuAttribute::Visibility(v.to_token_stream(), v.into_arbitrary()),
107            Whatever(o) => SnafuAttribute::Whatever(o.to_token_stream()),
108        }
109    }
110}
111
112impl Parse for Attribute {
113    fn parse(input: ParseStream) -> Result<Self> {
114        let lookahead = input.lookahead1();
115        if lookahead.peek(kw::backtrace) {
116            input.parse().map(Attribute::Backtrace)
117        } else if lookahead.peek(kw::context) {
118            input.parse().map(Attribute::Context)
119        } else if lookahead.peek(kw::crate_root) {
120            input.parse().map(Attribute::CrateRoot)
121        } else if lookahead.peek(kw::display) {
122            input.parse().map(Attribute::Display)
123        } else if lookahead.peek(kw::implicit) {
124            input.parse().map(Attribute::Implicit)
125        } else if lookahead.peek(kw::module) {
126            input.parse().map(Attribute::Module)
127        } else if lookahead.peek(kw::provide) {
128            input.parse().map(Attribute::Provide)
129        } else if lookahead.peek(kw::source) {
130            input.parse().map(Attribute::Source)
131        } else if lookahead.peek(kw::visibility) {
132            input.parse().map(Attribute::Visibility)
133        } else if lookahead.peek(kw::whatever) {
134            input.parse().map(Attribute::Whatever)
135        } else {
136            Err(lookahead.error())
137        }
138    }
139}
140
141struct Backtrace {
142    backtrace_token: kw::backtrace,
143    arg: MaybeArg<LitBool>,
144}
145
146impl Backtrace {
147    fn into_bool(self) -> bool {
148        self.arg.into_option().map_or(true, |a| a.value)
149    }
150}
151
152impl Parse for Backtrace {
153    fn parse(input: ParseStream) -> Result<Self> {
154        Ok(Self {
155            backtrace_token: input.parse()?,
156            arg: input.parse()?,
157        })
158    }
159}
160
161impl ToTokens for Backtrace {
162    fn to_tokens(&self, tokens: &mut TokenStream) {
163        self.backtrace_token.to_tokens(tokens);
164        self.arg.to_tokens(tokens);
165    }
166}
167
168struct Context {
169    context_token: kw::context,
170    arg: MaybeArg<ContextArg>,
171}
172
173impl Context {
174    fn into_component(self) -> super::Context {
175        use super::{Context::*, SuffixKind};
176
177        match self.arg.into_option() {
178            None => Flag(true),
179            Some(arg) => match arg {
180                ContextArg::Flag { value } => Flag(value.value),
181                ContextArg::Suffix {
182                    suffix:
183                        SuffixArg::Flag {
184                            value: LitBool { value: true, .. },
185                        },
186                    ..
187                } => Suffix(SuffixKind::Default),
188                ContextArg::Suffix {
189                    suffix:
190                        SuffixArg::Flag {
191                            value: LitBool { value: false, .. },
192                        },
193                    ..
194                } => Suffix(SuffixKind::None),
195                ContextArg::Suffix {
196                    suffix: SuffixArg::Suffix { suffix, .. },
197                    ..
198                } => Suffix(SuffixKind::Some(suffix)),
199            },
200        }
201    }
202}
203
204impl Parse for Context {
205    fn parse(input: ParseStream) -> Result<Self> {
206        Ok(Self {
207            context_token: input.parse()?,
208            arg: input.parse()?,
209        })
210    }
211}
212
213impl ToTokens for Context {
214    fn to_tokens(&self, tokens: &mut TokenStream) {
215        self.context_token.to_tokens(tokens);
216        self.arg.to_tokens(tokens);
217    }
218}
219
220enum ContextArg {
221    Flag {
222        value: LitBool,
223    },
224    Suffix {
225        suffix_token: kw::suffix,
226        paren_token: token::Paren,
227        suffix: SuffixArg,
228    },
229}
230
231impl Parse for ContextArg {
232    fn parse(input: ParseStream) -> Result<Self> {
233        let lookahead = input.lookahead1();
234        if lookahead.peek(LitBool) {
235            Ok(ContextArg::Flag {
236                value: input.parse()?,
237            })
238        } else if lookahead.peek(kw::suffix) {
239            let content;
240            Ok(ContextArg::Suffix {
241                suffix_token: input.parse()?,
242                paren_token: parenthesized!(content in input),
243                suffix: content.parse()?,
244            })
245        } else {
246            Err(lookahead.error())
247        }
248    }
249}
250
251impl ToTokens for ContextArg {
252    fn to_tokens(&self, tokens: &mut TokenStream) {
253        match self {
254            ContextArg::Flag { value } => {
255                value.to_tokens(tokens);
256            }
257            ContextArg::Suffix {
258                suffix_token,
259                paren_token,
260                suffix,
261            } => {
262                suffix_token.to_tokens(tokens);
263                paren_token.surround(tokens, |tokens| {
264                    suffix.to_tokens(tokens);
265                })
266            }
267        }
268    }
269}
270
271enum SuffixArg {
272    Flag { value: LitBool },
273    Suffix { suffix: Ident },
274}
275
276impl Parse for SuffixArg {
277    fn parse(input: ParseStream) -> Result<Self> {
278        let lookahead = input.lookahead1();
279        if lookahead.peek(LitBool) {
280            Ok(SuffixArg::Flag {
281                value: input.parse()?,
282            })
283        } else if lookahead.peek(syn::Ident) {
284            Ok(SuffixArg::Suffix {
285                suffix: input.parse()?,
286            })
287        } else {
288            Err(lookahead.error())
289        }
290    }
291}
292
293impl ToTokens for SuffixArg {
294    fn to_tokens(&self, tokens: &mut TokenStream) {
295        match self {
296            SuffixArg::Flag { value } => {
297                value.to_tokens(tokens);
298            }
299            SuffixArg::Suffix { suffix } => {
300                suffix.to_tokens(tokens);
301            }
302        }
303    }
304}
305
306struct CrateRoot {
307    crate_root_token: kw::crate_root,
308    paren_token: token::Paren,
309    arg: Path,
310}
311
312impl CrateRoot {
313    // TODO: Remove boxed trait object
314    fn into_arbitrary(self) -> Box<dyn ToTokens> {
315        Box::new(self.arg)
316    }
317}
318
319impl Parse for CrateRoot {
320    fn parse(input: ParseStream) -> Result<Self> {
321        let content;
322        Ok(Self {
323            crate_root_token: input.parse()?,
324            paren_token: parenthesized!(content in input),
325            arg: content.parse()?,
326        })
327    }
328}
329
330impl ToTokens for CrateRoot {
331    fn to_tokens(&self, tokens: &mut TokenStream) {
332        self.crate_root_token.to_tokens(tokens);
333        self.paren_token.surround(tokens, |tokens| {
334            self.arg.to_tokens(tokens);
335        });
336    }
337}
338
339struct Display {
340    display_token: kw::display,
341    paren_token: token::Paren,
342    args: Punctuated<Expr, token::Comma>,
343}
344
345impl Display {
346    fn into_display(self) -> crate::Display {
347        let exprs: Vec<_> = self.args.into_iter().collect();
348        let mut shorthand_names = BTreeSet::new();
349        let mut assigned_names = BTreeSet::new();
350
351        // Do a best-effort parsing here; if we fail, the compiler
352        // will likely spit out something more useful when it tries to
353        // parse it.
354        if let Some((Expr::Lit(l), args)) = exprs.split_first() {
355            if let Lit::Str(s) = &l.lit {
356                let format_str = s.value();
357                let names = extract_field_names(&format_str).map(|n| format_ident!("{}", n));
358                shorthand_names.extend(names);
359            }
360
361            for arg in args {
362                if let Expr::Assign(a) = arg {
363                    if let Expr::Path(p) = &*a.left {
364                        assigned_names.extend(p.path.get_ident().cloned());
365                    }
366                }
367            }
368        }
369
370        crate::Display {
371            exprs,
372            shorthand_names,
373            assigned_names,
374        }
375    }
376}
377
378pub(crate) fn extract_field_names(mut s: &str) -> impl Iterator<Item = &str> {
379    std::iter::from_fn(move || loop {
380        let open_curly = s.find('{')?;
381        s = &s[open_curly + '{'.len_utf8()..];
382
383        if s.starts_with('{') {
384            s = &s['{'.len_utf8()..];
385            continue;
386        }
387
388        let end_curly = s.find('}')?;
389        let format_contents = &s[..end_curly];
390
391        let name = match format_contents.find(':') {
392            Some(idx) => &format_contents[..idx],
393            None => format_contents,
394        };
395
396        if name.is_empty() {
397            continue;
398        }
399
400        return Some(name);
401    })
402}
403
404impl Parse for Display {
405    fn parse(input: ParseStream) -> Result<Self> {
406        let content;
407        Ok(Self {
408            display_token: input.parse()?,
409            paren_token: parenthesized!(content in input),
410            args: Punctuated::parse_terminated(&content)?,
411        })
412    }
413}
414
415impl ToTokens for Display {
416    fn to_tokens(&self, tokens: &mut TokenStream) {
417        self.display_token.to_tokens(tokens);
418        self.paren_token.surround(tokens, |tokens| {
419            self.args.to_tokens(tokens);
420        });
421    }
422}
423
424struct DocComment {
425    eq_token: token::Eq,
426    str: LitStr,
427}
428
429impl DocComment {
430    fn into_value(self) -> String {
431        self.str.value()
432    }
433}
434
435impl From<DocComment> for SnafuAttribute {
436    fn from(other: DocComment) -> Self {
437        SnafuAttribute::DocComment(other.to_token_stream(), other.into_value())
438    }
439}
440
441impl Parse for DocComment {
442    fn parse(input: ParseStream) -> Result<Self> {
443        Ok(Self {
444            eq_token: input.parse()?,
445            str: input.parse()?,
446        })
447    }
448}
449
450impl ToTokens for DocComment {
451    fn to_tokens(&self, tokens: &mut TokenStream) {
452        self.eq_token.to_tokens(tokens);
453        self.str.to_tokens(tokens);
454    }
455}
456
457struct Implicit {
458    implicit_token: kw::implicit,
459    arg: MaybeArg<LitBool>,
460}
461
462impl Implicit {
463    fn into_bool(self) -> bool {
464        self.arg.into_option().map_or(true, |a| a.value)
465    }
466}
467
468impl Parse for Implicit {
469    fn parse(input: ParseStream) -> Result<Self> {
470        Ok(Self {
471            implicit_token: input.parse()?,
472            arg: input.parse()?,
473        })
474    }
475}
476
477impl ToTokens for Implicit {
478    fn to_tokens(&self, tokens: &mut TokenStream) {
479        self.implicit_token.to_tokens(tokens);
480        self.arg.to_tokens(tokens);
481    }
482}
483
484struct Module {
485    module_token: kw::module,
486    arg: MaybeArg<Ident>,
487}
488
489impl Module {
490    fn into_value(self) -> ModuleName {
491        match self.arg.into_option() {
492            None => ModuleName::Default,
493            Some(name) => ModuleName::Custom(name),
494        }
495    }
496}
497
498impl Parse for Module {
499    fn parse(input: ParseStream) -> Result<Self> {
500        Ok(Self {
501            module_token: input.parse()?,
502            arg: input.parse()?,
503        })
504    }
505}
506
507impl ToTokens for Module {
508    fn to_tokens(&self, tokens: &mut TokenStream) {
509        self.module_token.to_tokens(tokens);
510        self.arg.to_tokens(tokens);
511    }
512}
513
514struct Provide {
515    provide_token: kw::provide,
516    arg: MaybeArg<ProvideArg>,
517}
518
519impl Provide {
520    fn into_value(self) -> ProvideKind {
521        match self.arg.into_option() {
522            None => ProvideKind::Flag(true),
523            Some(ProvideArg::Flag { value }) => ProvideKind::Flag(value.value),
524            Some(ProvideArg::Expression {
525                flags,
526                ty,
527                arrow: _,
528                expr,
529            }) => ProvideKind::Expression(crate::Provide {
530                is_chain: flags.is_chain(),
531                is_opt: flags.is_opt(),
532                is_priority: flags.is_priority(),
533                is_ref: flags.is_ref(),
534                ty,
535                expr,
536            }),
537        }
538    }
539}
540
541impl Parse for Provide {
542    fn parse(input: ParseStream) -> Result<Self> {
543        Ok(Self {
544            provide_token: input.parse()?,
545            arg: input.parse()?,
546        })
547    }
548}
549
550impl ToTokens for Provide {
551    fn to_tokens(&self, tokens: &mut TokenStream) {
552        self.provide_token.to_tokens(tokens);
553        self.arg.to_tokens(tokens);
554    }
555}
556
557enum ProvideArg {
558    Flag {
559        value: LitBool,
560    },
561    Expression {
562        flags: ProvideFlags,
563        ty: Type,
564        arrow: token::FatArrow,
565        expr: Expr,
566    },
567}
568
569impl Parse for ProvideArg {
570    fn parse(input: ParseStream) -> Result<Self> {
571        if input.peek(LitBool) {
572            Ok(ProvideArg::Flag {
573                value: input.parse()?,
574            })
575        } else {
576            Ok(ProvideArg::Expression {
577                flags: input.parse()?,
578                ty: input.parse()?,
579                arrow: input.parse()?,
580                expr: input.parse()?,
581            })
582        }
583    }
584}
585
586impl ToTokens for ProvideArg {
587    fn to_tokens(&self, tokens: &mut TokenStream) {
588        match self {
589            ProvideArg::Flag { value } => {
590                value.to_tokens(tokens);
591            }
592            ProvideArg::Expression {
593                flags,
594                ty,
595                arrow,
596                expr,
597            } => {
598                flags.to_tokens(tokens);
599                ty.to_tokens(tokens);
600                arrow.to_tokens(tokens);
601                expr.to_tokens(tokens);
602            }
603        }
604    }
605}
606
607struct ProvideFlags(Punctuated<ProvideFlag, token::Comma>);
608
609impl ProvideFlags {
610    fn is_chain(&self) -> bool {
611        self.0.iter().any(ProvideFlag::is_chain)
612    }
613
614    fn is_opt(&self) -> bool {
615        self.0.iter().any(ProvideFlag::is_opt)
616    }
617
618    fn is_priority(&self) -> bool {
619        self.0.iter().any(ProvideFlag::is_priority)
620    }
621
622    fn is_ref(&self) -> bool {
623        self.0.iter().any(ProvideFlag::is_ref)
624    }
625}
626
627impl Parse for ProvideFlags {
628    fn parse(input: ParseStream) -> Result<Self> {
629        let mut flags = Punctuated::new();
630
631        while ProvideFlag::peek(input) {
632            flags.push_value(input.parse()?);
633            flags.push_punct(input.parse()?);
634        }
635
636        Ok(Self(flags))
637    }
638}
639
640impl ToTokens for ProvideFlags {
641    fn to_tokens(&self, tokens: &mut TokenStream) {
642        self.0.to_tokens(tokens)
643    }
644}
645
646enum ProvideFlag {
647    Chain(kw::chain),
648    Opt(kw::opt),
649    Priority(kw::priority),
650    Ref(token::Ref),
651}
652
653impl ProvideFlag {
654    fn peek(input: ParseStream) -> bool {
655        input.peek(kw::chain)
656            || input.peek(kw::opt)
657            || input.peek(kw::priority)
658            || input.peek(token::Ref)
659    }
660
661    fn is_chain(&self) -> bool {
662        match self {
663            ProvideFlag::Chain(_) => true,
664            _ => false,
665        }
666    }
667
668    fn is_opt(&self) -> bool {
669        match self {
670            ProvideFlag::Opt(_) => true,
671            _ => false,
672        }
673    }
674
675    fn is_priority(&self) -> bool {
676        match self {
677            ProvideFlag::Priority(_) => true,
678            _ => false,
679        }
680    }
681
682    fn is_ref(&self) -> bool {
683        match self {
684            ProvideFlag::Ref(_) => true,
685            _ => false,
686        }
687    }
688}
689
690impl Parse for ProvideFlag {
691    fn parse(input: ParseStream) -> Result<Self> {
692        let lookahead = input.lookahead1();
693
694        if lookahead.peek(kw::chain) {
695            input.parse().map(ProvideFlag::Chain)
696        } else if lookahead.peek(kw::opt) {
697            input.parse().map(ProvideFlag::Opt)
698        } else if lookahead.peek(kw::priority) {
699            input.parse().map(ProvideFlag::Priority)
700        } else if lookahead.peek(token::Ref) {
701            input.parse().map(ProvideFlag::Ref)
702        } else {
703            Err(lookahead.error())
704        }
705    }
706}
707
708impl ToTokens for ProvideFlag {
709    fn to_tokens(&self, tokens: &mut TokenStream) {
710        match self {
711            ProvideFlag::Chain(v) => v.to_tokens(tokens),
712            ProvideFlag::Opt(v) => v.to_tokens(tokens),
713            ProvideFlag::Priority(v) => v.to_tokens(tokens),
714            ProvideFlag::Ref(v) => v.to_tokens(tokens),
715        }
716    }
717}
718
719struct Source {
720    source_token: kw::source,
721    args: MaybeArg<Punctuated<SourceArg, token::Comma>>,
722}
723
724impl Source {
725    fn into_components(self) -> Vec<super::Source> {
726        match self.args.into_option() {
727            None => vec![super::Source::Flag(true)],
728            Some(args) => args
729                .into_iter()
730                .map(|sa| match sa {
731                    SourceArg::Flag { value } => super::Source::Flag(value.value),
732                    SourceArg::From { r#type, expr, .. } => super::Source::From(r#type, expr),
733                })
734                .collect(),
735        }
736    }
737}
738
739impl Parse for Source {
740    fn parse(input: ParseStream) -> Result<Self> {
741        Ok(Self {
742            source_token: input.parse()?,
743            args: MaybeArg::parse_with(&input, Punctuated::parse_terminated)?,
744        })
745    }
746}
747
748impl ToTokens for Source {
749    fn to_tokens(&self, tokens: &mut TokenStream) {
750        self.source_token.to_tokens(tokens);
751        self.args.to_tokens(tokens);
752    }
753}
754
755enum SourceArg {
756    Flag {
757        value: LitBool,
758    },
759    From {
760        from_token: kw::from,
761        paren_token: token::Paren,
762        r#type: Type,
763        comma_token: token::Comma,
764        expr: Expr,
765    },
766}
767
768impl Parse for SourceArg {
769    fn parse(input: ParseStream) -> Result<Self> {
770        let lookahead = input.lookahead1();
771        if lookahead.peek(LitBool) {
772            Ok(SourceArg::Flag {
773                value: input.parse()?,
774            })
775        } else if lookahead.peek(kw::from) {
776            let content;
777            Ok(SourceArg::From {
778                from_token: input.parse()?,
779                paren_token: parenthesized!(content in input),
780                r#type: content.parse()?,
781                comma_token: content.parse()?,
782                expr: content.parse()?,
783            })
784        } else {
785            Err(lookahead.error())
786        }
787    }
788}
789
790impl ToTokens for SourceArg {
791    fn to_tokens(&self, tokens: &mut TokenStream) {
792        match self {
793            SourceArg::Flag { value } => {
794                value.to_tokens(tokens);
795            }
796            SourceArg::From {
797                from_token,
798                paren_token,
799                r#type,
800                comma_token,
801                expr,
802            } => {
803                from_token.to_tokens(tokens);
804                paren_token.surround(tokens, |tokens| {
805                    r#type.to_tokens(tokens);
806                    comma_token.to_tokens(tokens);
807                    expr.to_tokens(tokens);
808                })
809            }
810        }
811    }
812}
813
814struct Visibility {
815    visibility_token: kw::visibility,
816    visibility: MaybeArg<syn::Visibility>,
817}
818
819impl Visibility {
820    // TODO: Remove boxed trait object
821    fn into_arbitrary(self) -> Box<dyn ToTokens> {
822        // TODO: Move this default value out of parsing
823        self.visibility
824            .into_option()
825            .map_or_else(super::private_visibility, |v| Box::new(v))
826    }
827}
828
829impl Parse for Visibility {
830    fn parse(input: ParseStream) -> Result<Self> {
831        Ok(Self {
832            visibility_token: input.parse()?,
833            visibility: input.parse()?,
834        })
835    }
836}
837
838impl ToTokens for Visibility {
839    fn to_tokens(&self, tokens: &mut TokenStream) {
840        self.visibility_token.to_tokens(tokens);
841        self.visibility.to_tokens(tokens);
842    }
843}
844
845struct Whatever {
846    whatever_token: kw::whatever,
847}
848
849impl Parse for Whatever {
850    fn parse(input: ParseStream) -> Result<Self> {
851        Ok(Self {
852            whatever_token: input.parse()?,
853        })
854    }
855}
856
857impl ToTokens for Whatever {
858    fn to_tokens(&self, tokens: &mut TokenStream) {
859        self.whatever_token.to_tokens(tokens);
860    }
861}
862
863enum MaybeArg<T> {
864    None,
865    Some {
866        paren_token: token::Paren,
867        content: T,
868    },
869}
870
871impl<T> MaybeArg<T> {
872    fn into_option(self) -> Option<T> {
873        match self {
874            MaybeArg::None => None,
875            MaybeArg::Some { content, .. } => Some(content),
876        }
877    }
878
879    fn parse_with<F>(input: ParseStream<'_>, parser: F) -> Result<Self>
880    where
881        F: FnOnce(ParseStream<'_>) -> Result<T>,
882    {
883        let lookahead = input.lookahead1();
884        if lookahead.peek(token::Paren) {
885            let content;
886            Ok(MaybeArg::Some {
887                paren_token: parenthesized!(content in input),
888                content: parser(&content)?,
889            })
890        } else {
891            Ok(MaybeArg::None)
892        }
893    }
894}
895
896impl<T: Parse> Parse for MaybeArg<T> {
897    fn parse(input: ParseStream) -> Result<Self> {
898        Self::parse_with(input, Parse::parse)
899    }
900}
901
902impl<T: ToTokens> ToTokens for MaybeArg<T> {
903    fn to_tokens(&self, tokens: &mut TokenStream) {
904        if let MaybeArg::Some {
905            paren_token,
906            content,
907        } = self
908        {
909            paren_token.surround(tokens, |tokens| {
910                content.to_tokens(tokens);
911            });
912        }
913    }
914}
915
916#[cfg(test)]
917mod test {
918    use super::*;
919
920    fn names(s: &str) -> Vec<&str> {
921        extract_field_names(s).collect::<Vec<_>>()
922    }
923
924    #[test]
925    fn ignores_positional_arguments() {
926        assert_eq!(names("{}"), [] as [&str; 0]);
927    }
928
929    #[test]
930    fn finds_named_argument() {
931        assert_eq!(names("{a}"), ["a"]);
932    }
933
934    #[test]
935    fn finds_multiple_named_arguments() {
936        assert_eq!(names("{a} {b}"), ["a", "b"]);
937    }
938
939    #[test]
940    fn ignores_escaped_braces() {
941        assert_eq!(names("{{a}}"), [] as [&str; 0]);
942    }
943
944    #[test]
945    fn finds_named_arguments_around_escaped() {
946        assert_eq!(names("{a} {{b}} {c}"), ["a", "c"]);
947    }
948
949    #[test]
950    fn ignores_format_spec() {
951        assert_eq!(names("{a:?}"), ["a"]);
952    }
953}