1use std::{collections::HashSet, marker::PhantomData};
2
3use args::ParseRequiredArgs;
4use meta::ParseMeta;
5use opt_args::ParseOptionalArgs;
6use proc_macro2::{TokenStream, TokenTree};
7use quote::ToTokens;
8use rest_args::ParseRestArgs;
9use syn::{
10 buffer::Cursor,
11 parse::{ParseStream, Parser},
12 Attribute, Error, Meta, Result, Token,
13};
14
15pub mod args;
16pub mod find_attr;
17pub mod meta;
18pub mod opt_args;
19pub mod rest_args;
20
21pub trait ParseAttrTrait: Sized {
22 type Output;
23 fn parse(self, input: ParseStream) -> Result<Self::Output>;
24
25 fn parse_attr(self, input: &Attribute) -> Result<Self::Output> {
26 (|input: ParseStream| self.parse(input)).parse2(match &input.meta {
27 Meta::Path(_) => TokenStream::new(),
28 Meta::List(list) => list.tokens.clone(),
29 Meta::NameValue(meta) => {
30 return Err(Error::new_spanned(
31 meta,
32 "expect `path` or `list(...)`, found `key = value`",
33 ))
34 }
35 })
36 }
37
38 fn parse_concat_attrs<'r, I>(self, input: I) -> Result<Self::Output>
39 where
40 I: Iterator<Item = &'r Attribute>,
41 {
42 let parser = |input: ParseStream| self.parse(input);
43 let mut concatenated = TokenStream::new();
44
45 for attr in input {
46 let tokens = attr.meta.require_list()?.tokens.clone();
47 if tokens.is_empty() {
48 continue;
49 }
50
51 let mut trail_comma = false;
52 concatenated.extend(tokens.into_iter().map(|token| {
53 trail_comma = matches!(&token, TokenTree::Punct(p) if p.as_char() == ',');
54 token
55 }));
56
57 if !trail_comma {
58 <Token![,]>::default().to_tokens(&mut concatenated);
59 }
60 }
61
62 parser.parse2(concatenated)
63 }
64}
65
66pub struct Marker<T>(PhantomData<T>);
67
68impl<ReqArgs, OptArgs, RestArgs, Meta> ParseAttrTrait
69 for ParseArgs<Marker<ReqArgs>, Marker<OptArgs>, Marker<RestArgs>, Meta>
70where
71 ReqArgs: ParseRequiredArgs,
72 OptArgs: ParseOptionalArgs,
73 RestArgs: ParseRestArgs,
74 Meta: ParseMeta,
75{
76 type Output = ParseArgs<ReqArgs::Output, OptArgs::Output, RestArgs, Meta::Output>;
77
78 fn parse(mut self, input: ParseStream) -> Result<Self::Output> {
79 Ok(ParseArgs {
80 args: ReqArgs::parse(input)?,
81 opt_args: OptArgs::parse(input)?,
82 rest_args: RestArgs::parse(input)?,
83 meta: {
84 let mut specified_paths = HashSet::new();
85 let cursor = input.cursor();
86 syn::meta::parser(|nested| {
87 let id = nested.path.require_ident()?.to_string();
88
89 if specified_paths.contains(&id) {
90 return Err(Error::new_spanned(
91 nested.path,
92 format!("path `{id}` has been specified",),
93 ));
94 }
95
96 if !self.meta.parse(&nested)? {
97 return Err(Error::new_spanned(
98 nested.path,
99 format!("attribute `{id}` is not expected, or the calling form is not compliant"),
100 ));
101 }
102
103 specified_paths.insert(id);
104 Ok(())
105 })
106 .parse2(cursor.token_stream())?;
107
108 input.step(|_| Ok(((), Cursor::empty()))).unwrap();
110
111 self.meta.finish()?
112 },
113 })
114 }
115}
116
117#[derive(Debug)]
118pub struct ParseArgs<ReqArgs, OptArgs, RestArgs, Meta> {
119 pub args: ReqArgs,
120 pub opt_args: OptArgs,
121 pub rest_args: RestArgs,
122 pub meta: Meta,
123}
124
125impl ParseArgs<Marker<()>, Marker<()>, Marker<()>, ()> {
126 pub fn new() -> Self {
127 ParseArgs {
128 args: marker(),
129 opt_args: marker(),
130 rest_args: marker(),
131 meta: (),
132 }
133 }
134}
135
136impl<ReqArgs, OptArgs, RestArgs, Meta> ParseArgs<ReqArgs, OptArgs, RestArgs, Meta> {
137 pub fn args<T: ParseRequiredArgs>(self) -> ParseArgs<Marker<T>, OptArgs, RestArgs, Meta> {
138 ParseArgs {
139 args: marker(),
140 opt_args: self.opt_args,
141 rest_args: self.rest_args,
142 meta: self.meta,
143 }
144 }
145
146 pub fn opt_args<T: ParseOptionalArgs>(self) -> ParseArgs<ReqArgs, Marker<T>, RestArgs, Meta> {
147 ParseArgs {
148 args: self.args,
149 opt_args: marker(),
150 rest_args: self.rest_args,
151 meta: self.meta,
152 }
153 }
154
155 pub fn rest_args<T: ParseRestArgs>(self) -> ParseArgs<ReqArgs, OptArgs, Marker<T>, Meta> {
156 ParseArgs {
157 args: self.args,
158 opt_args: self.opt_args,
159 rest_args: marker(),
160 meta: self.meta,
161 }
162 }
163
164 pub fn meta<T: ParseMeta>(self, meta: T) -> ParseArgs<ReqArgs, OptArgs, RestArgs, T> {
165 ParseArgs {
166 args: self.args,
167 opt_args: self.opt_args,
168 rest_args: self.rest_args,
169 meta,
170 }
171 }
172}
173
174fn with_comma(input: ParseStream) -> Result<()> {
175 if !input.is_empty() {
176 input.parse::<Token![,]>()?;
177 }
178
179 Ok(())
180}
181
182fn marker<T>() -> Marker<T> {
183 Marker(PhantomData)
184}