tamasfe_macro_utils/
attr.rs1use std::collections::HashSet;
2
3use proc_macro2::{TokenStream, TokenTree};
4use proc_macro_error::{abort, emit_error};
5use quote::{ToTokens, TokenStreamExt};
6use syn::{
7 ext::IdentExt, parenthesized, parse::{discouraged::Speculative, Parser}, parse::Parse,
8 punctuated::IntoIter, punctuated::Punctuated, spanned::Spanned, token, Ident, Token,
9};
10
11#[derive(Debug, Clone)]
12pub enum AttrNamedValue {
13 Eq {
14 eq: Token!(=),
15 content: TokenStream,
16 },
17 Parens {
18 parens: token::Paren,
19 content: TokenStream,
20 },
21}
22
23impl AttrNamedValue {
24 pub fn parse<T: Parse>(self) -> syn::Result<T> {
25 match self {
26 AttrNamedValue::Eq { content, .. } => syn::parse2(content),
27 AttrNamedValue::Parens { content, .. } => syn::parse2(content),
28 }
29 }
30
31 pub fn parse_with<P: Parser>(self, parser: P) -> syn::Result<P::Output> {
32 match self {
33 AttrNamedValue::Eq { content, .. } => parser.parse2(content),
34 AttrNamedValue::Parens { content, .. } => parser.parse2(content),
35 }
36 }
37}
38
39impl ToTokens for AttrNamedValue {
40 fn to_tokens(&self, tokens: &mut TokenStream) {
41 match self {
42 AttrNamedValue::Eq { content, eq } => {
43 eq.to_tokens(tokens);
44 content.to_tokens(tokens);
45 }
46 AttrNamedValue::Parens { content, parens } => {
47 parens.surround(tokens, |tokens| {
48 content.to_tokens(tokens);
49 });
50 }
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub enum AttrParam {
58 Named { name: Ident, value: AttrNamedValue },
59 Unnamed(TokenStream),
60}
61
62impl AttrParam {
63 pub fn parse<T: Parse>(self) -> syn::Result<T> {
64 match self {
65 AttrParam::Named { value, .. } => value.parse(),
66 AttrParam::Unnamed(ts) => syn::parse2(ts),
67 }
68 }
69}
70
71impl ToTokens for AttrParam {
72 fn to_tokens(&self, tokens: &mut TokenStream) {
73 match self {
74 AttrParam::Named { name, value } => {
75 name.to_tokens(tokens);
76 value.to_tokens(tokens);
77 }
78 AttrParam::Unnamed(ts) => ts.to_tokens(tokens),
79 }
80 }
81}
82
83impl Parse for AttrParam {
84 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
85 let named_input = input.fork();
86
87 if let Ok(name) = named_input.call(Ident::parse_any) {
88 if named_input.peek(token::Paren) {
89 let inner;
90 let parens = parenthesized!(inner in named_input);
91 let content = inner.parse()?;
92 input.advance_to(&named_input);
93 return Ok(AttrParam::Named {
94 name,
95 value: AttrNamedValue::Parens { parens, content },
96 });
97 } else if named_input.peek(Token!(=)) {
98 let eq = named_input.parse::<Token!(=)>()?;
99 input.advance_to(&named_input);
100 return Ok(AttrParam::Named {
101 name,
102 value: AttrNamedValue::Eq {
103 eq,
104 content: parse_until_comma(input),
105 },
106 });
107 }
108 }
109
110 Ok(AttrParam::Unnamed(parse_until_comma(input)))
111 }
112}
113
114#[derive(Debug, Clone)]
115pub struct AttrParams(Punctuated<AttrParam, Token!(,)>);
116
117impl ToTokens for AttrParams {
118 fn to_tokens(&self, tokens: &mut TokenStream) {
119 for attr in &self.0 {
120 attr.to_tokens(tokens);
121 }
122 }
123}
124
125impl Parse for AttrParams {
126 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
127 Ok(Self(Punctuated::<AttrParam, Token!(,)>::parse_terminated(
128 input,
129 )?))
130 }
131}
132
133#[allow(dead_code)]
134impl AttrParams {
135 pub fn is_empty(&self) -> bool {
136 self.0.is_empty()
137 }
138
139 pub fn len(&self) -> usize {
140 self.0.len()
141 }
142
143 pub fn iter(&self) -> impl Iterator<Item = &AttrParam> {
144 self.0.iter()
145 }
146
147 pub fn no_duplicates(&self) {
149 self.no_duplicates_by(|_| true)
150 }
151
152 pub fn no_duplicates_by<F: Fn(&Ident) -> bool>(&self, f: F) {
154 let mut names: HashSet<&Ident> = HashSet::new();
155 for attr in &self.0 {
156 if let AttrParam::Named { name, .. } = attr {
157 if let Some(existing) = names.get(name) {
158 if f(name) {
159 emit_error!(existing.span(), r#"parameter "{}" is given here"#, name);
160 abort!(name.span(), r#"parameter "{}" already exists"#, name);
161 }
162 }
163 names.insert(name);
164 }
165 }
166 }
167
168 pub fn retain_known<F: Fn(&Ident) -> bool>(&self, f: F) {
169 for attr in &self.0 {
170 if let AttrParam::Named { name, .. } = attr {
171 if !f(name) {
172 abort!(name.span(), r#"unknown parameter "{}""#, name);
173 }
174 }
175 }
176 }
177
178 pub fn has_named(&self) -> bool {
179 for attr in &self.0 {
180 if let AttrParam::Named { .. } = attr {
181 return true;
182 }
183 }
184 false
185 }
186
187 pub fn no_unnamed(&self) {
188 if !self.has_named() {
189 abort!(self.span(), r#"only named parameters are expected"#);
190 }
191 }
192
193 pub fn no_names_mixed(&self) {
195 let mut named: Option<bool> = None;
196 for attr in &self.0 {
197 match attr {
198 AttrParam::Named { name, .. } => {
199 if let Some(named) = &named {
200 if !*named {
201 abort!(
202 name.span(),
203 r#"named and unnamed parameters cannot be mixed"#
204 );
205 }
206 }
207 named = Some(true);
208 }
209 AttrParam::Unnamed(content) => {
210 if let Some(named) = &named {
211 if *named {
212 abort!(
213 content.span(),
214 r#"named and unnamed parameters cannot be mixed"#
215 );
216 }
217 }
218 named = Some(false);
219 }
220 }
221 }
222 }
223}
224
225impl IntoIterator for AttrParams {
226 type Item = AttrParam;
227 type IntoIter = IntoIter<AttrParam>;
228
229 fn into_iter(self) -> Self::IntoIter {
230 self.0.into_iter()
231 }
232}
233
234fn parse_until_comma(input: syn::parse::ParseStream) -> TokenStream {
235 let mut ts = TokenStream::new();
236 input
237 .step(|cursor| {
238 let mut rest = *cursor;
239 while let Some((tt, next)) = rest.token_tree() {
240 match &tt {
241 TokenTree::Punct(punct) if punct.as_char() == ',' => {
242 return Ok(((), rest));
243 }
244 tt => {
245 ts.append(tt.clone());
246 rest = next
247 }
248 }
249 }
250 Ok(((), rest))
251 })
252 .unwrap();
253 ts
254}