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