codama_syn_helpers/
meta.rs

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    /// A path followed by an equal sign and a single Meta — e.g. `my_attribute = my_value`.
16    PathValue(PathValue),
17    /// A path followed by a wrapped list of Metas — e.g. `my_attribute(one, two, three)`.
18    /// This accepts an optional equal sign before the list — e.g. `my_attribute = (one, two, three)`.
19    PathList(PathList),
20    /// An expression — e.g. `my_attribute`, `42`, `"hello"` or `a + b`.
21    Expr(Expr),
22    /// A verbatim token stream — e.g. `[<=>]`.
23    /// This is the fallback for any other Meta that does not match the other variants.
24    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    /// Returns the path as a string if it is a valid path
56    /// or an empty string if it is not a path.
57    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({})] attribute", directive)));
138        };
139        Ok(self)
140    }
141}
142
143impl PathValue {
144    /// Get the equal sign and value tokens.
145    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    /// Get all tokens after the path, including the equal sign if present.
155    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    /// Get an equivalent `MetaList` from the path list.
163    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    /// Iterate over all metas in the list.
172    pub fn each(&self, logic: impl FnMut(Meta) -> syn::Result<()>) -> syn::Result<()> {
173        self.as_meta_list().each(logic)
174    }
175
176    /// Parse all metas in the list.
177    pub fn parse_metas(&self) -> syn::Result<Vec<Meta>> {
178        self.as_meta_list().parse_metas()
179    }
180
181    /// Parse all arguments as comma-separated types.
182    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
245/// Parse a path without segment arguments and allowing any reserved keyword
246/// except `true` and `false` on the first segment.
247fn 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
270/// Parses a custom token stream inside delimiters.
271fn 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}