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 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 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 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 fn into_arbitrary(self) -> Box<dyn ToTokens> {
822 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}