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