1use crate::extensions::*;
2use derive_more::From;
3use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
4use quote::{ToTokens, TokenStreamExt};
5use std::fmt::Display;
6use syn::{
7 ext::IdentExt,
8 parse::discouraged::Speculative,
9 token::{Brace, Bracket, Paren},
10 Expr, MacroDelimiter, MetaList, Path, Token,
11};
12
13#[derive(Debug, From)]
14pub enum Meta {
15 PathValue(PathValue),
17 PathList(PathList),
20 Expr(Expr),
22 Verbatim(TokenStream),
25}
26
27#[derive(Debug)]
28pub struct PathValue {
29 pub path: Path,
30 pub eq_token: Token![=],
31 pub value: Box<Meta>,
32}
33
34#[derive(Debug)]
35pub struct PathList {
36 pub path: Path,
37 pub eq_token: Option<Token![=]>,
38 pub delimiter: MacroDelimiter,
39 pub tokens: TokenStream,
40}
41
42impl Meta {
43 pub fn path(&self) -> syn::Result<&Path> {
44 match self {
45 Meta::PathList(pl) => Ok(&pl.path),
46 Meta::PathValue(pv) => Ok(&pv.path),
47 Meta::Expr(expr) => match expr {
48 Expr::Path(expr) => Ok(&expr.path),
49 _ => Err(expr.error("expected a path")),
50 },
51 Meta::Verbatim(tokens) => Err(tokens.error("expected a path")),
52 }
53 }
54
55 pub fn path_str(&self) -> String {
58 self.path().map_or("".into(), |path| path.to_string())
59 }
60
61 pub fn is_path_or_list(&self) -> bool {
62 matches!(self, Meta::PathList(_) | Meta::Expr(Expr::Path(_)))
63 }
64
65 pub fn is_path_or_empty_list(&self) -> bool {
66 match self {
67 Meta::PathList(pl) if pl.tokens.is_empty() => true,
68 Meta::Expr(Expr::Path(_)) => true,
69 _ => false,
70 }
71 }
72
73 pub fn as_path(&self) -> syn::Result<&Path> {
74 match self {
75 Meta::Expr(Expr::Path(expr)) => Ok(&expr.path),
76 Meta::PathList(pl) => Err(pl
77 .tokens_after_path()
78 .error("unexpected tokens, expected a single path")),
79 Meta::PathValue(pv) => Err(pv
80 .tokens_after_path()
81 .error("unexpected tokens, expected a single path")),
82 meta => Err(meta.error("expected a path")),
83 }
84 }
85
86 pub fn as_path_list(&self) -> syn::Result<&PathList> {
87 match self {
88 Meta::PathList(pl) => Ok(pl),
89 Meta::PathValue(pv) => Err(pv.value.error(format!(
90 "expected a list: `{} = (...)`",
91 pv.path.to_string()
92 ))),
93 Meta::Expr(Expr::Path(expr)) => Err(expr.error(format!(
94 "expected a list: `{0}(...)` or `{0} = (...)`",
95 expr.path.to_string()
96 ))),
97 meta => Err(meta
98 .error("expected a path followed by a list: `my_path(...)` or `my_path = (...)`")),
99 }
100 }
101
102 pub fn as_path_value(&self) -> syn::Result<&PathValue> {
103 match self {
104 Meta::PathValue(pv) => Ok(pv),
105 Meta::PathList(pl) => match pl.eq_token {
106 Some(_) => Err(pl.tokens.error("expected a single value, found a list")),
107 None => Err(pl.tokens.error(format!(
108 "expected `=` followed by a single value: `{} = ...`",
109 pl.path.to_string(),
110 ))),
111 },
112 Meta::Expr(Expr::Path(expr)) => Err(expr.error(format!(
113 "expected a value for that path: `{} = ...`",
114 expr.path.to_string()
115 ))),
116 meta => Err(meta.error("expected a path followed by a value: `my_path = ...`")),
117 }
118 }
119
120 pub fn as_expr(&self) -> syn::Result<&Expr> {
121 match self {
122 Meta::Expr(expr) => Ok(expr),
123 meta => Err(meta.error("expected a valid expression")),
124 }
125 }
126
127 pub fn as_verbatim(&self, msg: impl Display) -> syn::Result<&TokenStream> {
128 match self {
129 Meta::Verbatim(tokens) => Ok(tokens),
130 meta => Err(meta.error(msg)),
131 }
132 }
133
134 pub fn assert_directive(&self, directive: &str) -> syn::Result<&Self> {
135 let path = self.path()?;
136 if !path.is_strict(directive) {
137 return Err(path.error(format!("expected #[codama({directive})] attribute")));
138 };
139 Ok(self)
140 }
141}
142
143impl PathValue {
144 pub fn tokens_after_path(&self) -> TokenStream {
146 let mut tokens = TokenStream::new();
147 self.eq_token.to_tokens(&mut tokens);
148 self.value.to_tokens(&mut tokens);
149 tokens
150 }
151}
152
153impl PathList {
154 pub fn tokens_after_path(&self) -> TokenStream {
156 let mut tokens = TokenStream::new();
157 self.eq_token.to_tokens(&mut tokens);
158 delimiters_to_tokens(&self.delimiter, &self.tokens, &mut tokens);
159 tokens
160 }
161
162 pub fn as_meta_list(&self) -> MetaList {
164 MetaList {
165 path: self.path.clone(),
166 delimiter: self.delimiter.clone(),
167 tokens: self.tokens.clone(),
168 }
169 }
170
171 pub fn each(&self, logic: impl FnMut(Meta) -> syn::Result<()>) -> syn::Result<()> {
173 self.as_meta_list().each(logic)
174 }
175
176 pub fn parse_metas(&self) -> syn::Result<Vec<Meta>> {
178 self.as_meta_list().parse_metas()
179 }
180
181 pub fn parse_comma_args<T: syn::parse::Parse>(&self) -> syn::Result<Vec<T>> {
183 self.as_meta_list().parse_comma_args()
184 }
185}
186
187impl syn::parse::Parse for Meta {
188 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
189 let fork = input.fork();
190 match fork.call(parse_meta_path) {
191 Ok(path) => {
192 if fork.peek(Paren)
193 || fork.peek(Bracket)
194 || fork.peek(Brace)
195 || (fork.peek(Token![=])
196 && (fork.peek2(Paren) || fork.peek2(Bracket) || fork.peek2(Brace)))
197 {
198 Ok(Self::PathList(input.parse()?))
199 } else if fork.peek(Token![=]) {
200 Ok(Self::PathValue(input.parse()?))
201 } else {
202 input.advance_to(&fork);
203 Ok(Self::Expr(syn::Expr::Path(syn::ExprPath {
204 attrs: Vec::new(),
205 qself: None,
206 path,
207 })))
208 }
209 }
210 Err(_) => match fork.parse::<Expr>() {
211 Ok(expr) => {
212 input.advance_to(&fork);
213 Ok(Self::Expr(expr))
214 }
215 _ => Ok(Self::Verbatim(input.parse_arg()?)),
216 },
217 }
218 }
219}
220
221impl syn::parse::Parse for PathValue {
222 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
223 Ok(Self {
224 path: input.call(parse_meta_path)?,
225 eq_token: input.parse()?,
226 value: input.parse()?,
227 })
228 }
229}
230
231impl syn::parse::Parse for PathList {
232 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
233 let path = input.call(parse_meta_path)?;
234 let eq_token = input.parse()?;
235 let (delimiter, tokens) = input.call(parse_delimiters)?;
236 Ok(Self {
237 path,
238 eq_token,
239 delimiter,
240 tokens,
241 })
242 }
243}
244
245fn parse_meta_path(input: syn::parse::ParseStream) -> syn::Result<Path> {
248 let fork = input.fork();
249 let ident = syn::Ident::parse_any(&fork)?;
250 if ident == "true" || ident == "false" {
251 return Err(ident.error("unexpected reserved keyword"));
252 }
253 Ok(Path {
254 leading_colon: input.parse()?,
255 segments: {
256 let mut segments = syn::punctuated::Punctuated::new();
257 let ident = syn::Ident::parse_any(input)?;
258 segments.push_value(syn::PathSegment::from(ident));
259 while input.peek(Token![::]) {
260 let punct = input.parse()?;
261 segments.push_punct(punct);
262 let ident = syn::Ident::parse_any(input)?;
263 segments.push_value(syn::PathSegment::from(ident));
264 }
265 segments
266 },
267 })
268}
269
270fn parse_delimiters(input: syn::parse::ParseStream) -> syn::Result<(MacroDelimiter, TokenStream)> {
272 input.step(|cursor| match cursor.token_tree() {
273 Some((TokenTree::Group(g), rest)) => {
274 let span = g.delim_span();
275 let delimiter = match g.delimiter() {
276 Delimiter::Parenthesis => MacroDelimiter::Paren(Paren(span)),
277 Delimiter::Brace => MacroDelimiter::Brace(Brace(span)),
278 Delimiter::Bracket => MacroDelimiter::Bracket(Bracket(span)),
279 _ => return Err(cursor.error("expected delimiter")),
280 };
281 Ok(((delimiter, g.stream()), rest))
282 }
283 _ => Err(cursor.error("expected delimiter")),
284 })
285}
286
287fn delimiters_to_tokens(delimiter: &MacroDelimiter, inner: &TokenStream, tokens: &mut TokenStream) {
288 let (delim, span) = match delimiter {
289 MacroDelimiter::Paren(paren) => (Delimiter::Parenthesis, paren.span),
290 MacroDelimiter::Brace(brace) => (Delimiter::Brace, brace.span),
291 MacroDelimiter::Bracket(bracket) => (Delimiter::Bracket, bracket.span),
292 };
293 let mut group = Group::new(delim, inner.clone());
294 group.set_span(span.join());
295 tokens.append(group);
296}
297
298impl ToTokens for Meta {
299 fn to_tokens(&self, tokens: &mut TokenStream) {
300 match self {
301 Meta::PathList(m) => m.to_tokens(tokens),
302 Meta::PathValue(m) => m.to_tokens(tokens),
303 Meta::Expr(m) => m.to_tokens(tokens),
304 Meta::Verbatim(m) => m.to_tokens(tokens),
305 }
306 }
307}
308
309impl ToTokens for PathValue {
310 fn to_tokens(&self, tokens: &mut TokenStream) {
311 self.path.to_tokens(tokens);
312 self.eq_token.to_tokens(tokens);
313 self.value.to_tokens(tokens);
314 }
315}
316
317impl ToTokens for PathList {
318 fn to_tokens(&self, tokens: &mut TokenStream) {
319 self.path.to_tokens(tokens);
320 self.eq_token.to_tokens(tokens);
321 delimiters_to_tokens(&self.delimiter, &self.tokens, tokens);
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 macro_rules! meta {
330 ($($attr:tt)*) => {{
331 syn::parse_str::<Meta>(stringify!($($attr)*)).unwrap()
332 }};
333 }
334
335 #[test]
336 fn parse_path() {
337 let meta = meta! { foo };
338 let Meta::Expr(syn::Expr::Path(syn::ExprPath { path, .. })) = meta else {
339 panic!("expected Meta::Path");
340 };
341 assert!(path.is_strict("foo"));
342 }
343
344 #[test]
345 fn parse_path_list() {
346 let meta = meta! { foo(1, 2, 3) };
347 let Meta::PathList(meta) = meta else {
348 panic!("expected Meta::List");
349 };
350 assert!(meta.path.is_strict("foo"));
351 assert!(meta.eq_token.is_none());
352 assert_eq!(meta.tokens.to_string(), "1 , 2 , 3");
353 }
354
355 #[test]
356 fn parse_path_list_with_equal_sign() {
357 let meta = meta! { foo = [1, 2, 3] };
358 let Meta::PathList(meta) = meta else {
359 panic!("expected Meta::List");
360 };
361 assert!(meta.path.is_strict("foo"));
362 assert!(meta.eq_token.is_some());
363 assert_eq!(meta.tokens.to_string(), "1 , 2 , 3");
364 }
365
366 #[test]
367 fn parse_path_value_with_expression() {
368 let meta: Meta = meta! { foo = 42 };
369 let Meta::PathValue(meta) = meta else {
370 panic!("expected Meta::PathValue");
371 };
372 assert!(meta.path.is_strict("foo"));
373 let expr = meta.value.as_expr().unwrap();
374 assert_eq!(expr.as_unsigned_integer::<usize>().unwrap(), 42);
375 }
376
377 #[test]
378 fn parse_path_value_with_boolean() {
379 let meta: Meta = meta! { foo = true };
380 let Meta::PathValue(meta) = meta else {
381 panic!("expected Meta::PathValue");
382 };
383 assert!(meta.path.is_strict("foo"));
384 let expr = meta.value.as_expr().unwrap();
385 assert!(expr.as_bool().unwrap());
386 }
387
388 #[test]
389 fn parse_path_value_with_verbatim() {
390 let meta = meta! { foo = ?what? };
391 let Meta::PathValue(meta) = meta else {
392 panic!("expected Meta::PathValue");
393 };
394 assert!(meta.path.is_strict("foo"));
395 let value = meta.value.as_verbatim("expected verbatim").unwrap();
396 assert_eq!(value.to_string(), "? what ?");
397 }
398
399 #[test]
400 fn parse_path_value_with_list() {
401 let meta = meta! { foo = bar(1, 2, 3) };
402 let Meta::PathValue(meta) = meta else {
403 panic!("expected Meta::PathValue");
404 };
405 assert!(meta.path.is_strict("foo"));
406 let value = meta.value.as_path_list().unwrap();
407 assert!(value.path.is_strict("bar"));
408 assert_eq!(value.tokens.to_string(), "1 , 2 , 3");
409 }
410
411 #[test]
412 fn parse_expr() {
413 let meta = meta! { "hello" };
414 let Meta::Expr(expr) = meta else {
415 panic!("expected Meta::Expr");
416 };
417 assert_eq!(expr.to_token_stream().to_string(), "\"hello\"");
418 }
419
420 #[test]
421 fn parse_verbatim() {
422 let meta = meta! { [==> 42 <==] };
423 let Meta::Verbatim(verbatim) = meta else {
424 panic!("expected Meta::Verbatim");
425 };
426 assert_eq!(verbatim.to_string(), "[==> 42 <==]");
427 }
428
429 #[test]
430 fn parse_verbatim_list() -> syn::Result<()> {
431 let meta = meta! { foo([==> 1 <==], [==> 2 <==]) };
432 let list = meta.as_path_list()?;
433 assert_eq!(list.path.to_string(), "foo");
434 let metas = list.parse_metas()?;
435 assert_eq!(metas.len(), 2);
436 assert_eq!(
437 metas[0].as_verbatim("expected [==> n <==]")?.to_string(),
438 "[==> 1 <==]"
439 );
440 assert_eq!(
441 metas[1].as_verbatim("expected [==> n <==]")?.to_string(),
442 "[==> 2 <==]"
443 );
444 Ok(())
445 }
446
447 #[test]
448 fn path() -> syn::Result<()> {
449 assert_eq!(meta! { foo }.path()?.to_string(), "foo");
450 assert_eq!(meta! { foo(42) }.path()?.to_string(), "foo");
451 assert_eq!(meta! { foo = 42 }.path()?.to_string(), "foo");
452 assert_eq!(meta! { foo = bar(42) }.path()?.to_string(), "foo");
453 assert_eq!(
454 meta! { [verbatim] }.path().unwrap_err().to_string(),
455 "expected a path"
456 );
457 Ok(())
458 }
459
460 #[test]
461 fn is_path_or_empty_list() {
462 assert!(meta! { foo }.is_path_or_empty_list());
463 assert!(meta! { some_node }.is_path_or_empty_list());
464 assert!(meta! { foo() }.is_path_or_empty_list());
465 assert!(meta! { some_node() }.is_path_or_empty_list());
466 assert!(!meta! { foo = 42 }.is_path_or_empty_list());
467 assert!(!meta! { foo = bar(1, 2, 3) }.is_path_or_empty_list());
468 assert!(!meta! { foo(answer = 42) }.is_path_or_empty_list());
469 assert!(!meta! { some_node(hello) }.is_path_or_empty_list());
470 assert!(!meta! { 42 }.is_path_or_empty_list());
471 assert!(!meta! { [verbatim] }.is_path_or_empty_list());
472 }
473
474 #[test]
475 fn as_path() {
476 assert_eq!(meta! { foo }.as_path().unwrap().to_string(), "foo");
477
478 assert_eq!(
479 meta! { foo(42) }.as_path().unwrap_err().to_string(),
480 "unexpected tokens, expected a single path"
481 );
482 assert_eq!(
483 meta! { foo = 42 }.as_path().unwrap_err().to_string(),
484 "unexpected tokens, expected a single path"
485 );
486 assert_eq!(
487 meta! { foo = bar(42) }.as_path().unwrap_err().to_string(),
488 "unexpected tokens, expected a single path"
489 );
490 assert_eq!(
491 meta! { [verbatim] }.as_path().unwrap_err().to_string(),
492 "expected a path"
493 );
494 }
495
496 #[test]
497 fn as_path_list() {
498 let meta = meta! { foo(1, 2, 3) };
499 let list = meta.as_path_list().unwrap();
500 assert!(list.path.is_strict("foo"));
501 assert_eq!(list.tokens.to_string(), "1 , 2 , 3");
502
503 assert_eq!(
504 meta! { foo }.as_path_list().unwrap_err().to_string(),
505 "expected a list: `foo(...)` or `foo = (...)`"
506 );
507 assert_eq!(
508 meta! { foo = 42 }.as_path_list().unwrap_err().to_string(),
509 "expected a list: `foo = (...)`"
510 );
511 assert_eq!(
512 meta! { foo = bar(42) }
513 .as_path_list()
514 .unwrap_err()
515 .to_string(),
516 "expected a list: `foo = (...)`"
517 );
518 assert_eq!(
519 meta! { [verbatim] }.as_path_list().unwrap_err().to_string(),
520 "expected a path followed by a list: `my_path(...)` or `my_path = (...)`"
521 );
522 }
523}