derive_more/
syn_compat.rs

1//! This module contains things adapted from syn 1.x
2//! to preserve compatibility.
3
4use quote::ToTokens;
5use syn::ext::IdentExt as _;
6use syn::parse::{Parse, ParseStream, Parser as _};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::token::Paren;
10use syn::{
11    parenthesized, token, Attribute, Ident, Lit, LitBool, MacroDelimiter, Meta,
12    MetaNameValue, Path, PathSegment, Result, Token,
13};
14
15pub(crate) trait AttributeExt {
16    fn parse_meta(&self) -> Result<ParsedMeta>;
17}
18
19impl AttributeExt for Attribute {
20    fn parse_meta(&self) -> Result<ParsedMeta> {
21        parse_nested_meta(self.meta.clone())
22    }
23}
24
25/// [`Meta`] but more like the version from syn 1.x in two important ways:
26/// * The nested metas in a list are already parsed
27/// * Paths are allowed to contain keywords.
28#[derive(Clone)]
29pub(crate) enum ParsedMeta {
30    Path(Path),
31    List(ParsedMetaList),
32    NameValue(MetaNameValue),
33}
34
35impl Parse for ParsedMeta {
36    fn parse(input: ParseStream) -> Result<Self> {
37        let path = input.call(parse_meta_path)?;
38        parse_meta_after_path(path, input)
39    }
40}
41
42impl ParsedMeta {
43    pub(crate) fn path(&self) -> &Path {
44        match self {
45            ParsedMeta::Path(path) => path,
46            ParsedMeta::List(meta) => &meta.path,
47            ParsedMeta::NameValue(meta) => &meta.path,
48        }
49    }
50}
51
52impl ToTokens for ParsedMeta {
53    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
54        match self {
55            ParsedMeta::Path(p) => p.to_tokens(tokens),
56            ParsedMeta::List(l) => l.to_tokens(tokens),
57            ParsedMeta::NameValue(n) => n.to_tokens(tokens),
58        }
59    }
60}
61
62#[derive(Clone)]
63pub(crate) struct ParsedMetaList {
64    pub path: Path,
65    pub paren_token: Paren,
66    pub nested: Punctuated<NestedMeta, Token![,]>,
67}
68
69impl ToTokens for ParsedMetaList {
70    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
71        self.path.to_tokens(tokens);
72        self.paren_token.surround(tokens, |tokens| {
73            self.nested.to_tokens(tokens);
74        });
75    }
76}
77
78#[derive(Clone)]
79pub(crate) enum NestedMeta {
80    Meta(ParsedMeta),
81    Lit(Lit),
82}
83
84impl Parse for NestedMeta {
85    fn parse(input: ParseStream) -> Result<Self> {
86        if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) {
87            input.parse().map(NestedMeta::Lit)
88        } else if input.peek(Ident::peek_any)
89            || input.peek(Token![::]) && input.peek3(Ident::peek_any)
90        {
91            input.parse().map(NestedMeta::Meta)
92        } else {
93            Err(input.error("expected identifier or literal"))
94        }
95    }
96}
97
98impl ToTokens for NestedMeta {
99    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
100        match self {
101            NestedMeta::Meta(meta) => meta.to_tokens(tokens),
102            NestedMeta::Lit(lit) => lit.to_tokens(tokens),
103        }
104    }
105}
106
107fn parse_nested_meta(meta: Meta) -> Result<ParsedMeta> {
108    match meta {
109        Meta::Path(path) => Ok(ParsedMeta::Path(path)),
110        Meta::NameValue(name_value) => Ok(ParsedMeta::NameValue(name_value)),
111        Meta::List(list) => {
112            let MacroDelimiter::Paren(paren_token) = list.delimiter else {
113                return Err(syn::Error::new(
114                    list.delimiter.span().span(),
115                    "Expected paren",
116                ));
117            };
118            Ok(ParsedMeta::List(ParsedMetaList {
119                path: list.path,
120                paren_token,
121                nested: Punctuated::parse_terminated.parse2(list.tokens)?,
122            }))
123        }
124    }
125}
126
127// Like Path::parse_mod_style but accepts keywords in the path.
128fn parse_meta_path(input: ParseStream) -> Result<Path> {
129    Ok(Path {
130        leading_colon: input.parse()?,
131        segments: {
132            let mut segments = Punctuated::new();
133            while input.peek(Ident::peek_any) {
134                let ident = Ident::parse_any(input)?;
135                segments.push_value(PathSegment::from(ident));
136                if !input.peek(Token![::]) {
137                    break;
138                }
139                let punct = input.parse()?;
140                segments.push_punct(punct);
141            }
142            if segments.is_empty() {
143                return Err(input.error("expected path"));
144            } else if segments.trailing_punct() {
145                return Err(input.error("expected path segment"));
146            }
147            segments
148        },
149    })
150}
151
152pub(crate) fn parse_meta_after_path(
153    path: Path,
154    input: ParseStream,
155) -> Result<ParsedMeta> {
156    if input.peek(token::Paren) {
157        parse_meta_list_after_path(path, input).map(ParsedMeta::List)
158    } else if input.peek(Token![=]) {
159        parse_meta_name_value_after_path(path, input).map(ParsedMeta::NameValue)
160    } else {
161        Ok(ParsedMeta::Path(path))
162    }
163}
164
165fn parse_meta_list_after_path(
166    path: Path,
167    input: ParseStream,
168) -> Result<ParsedMetaList> {
169    let content;
170    Ok(ParsedMetaList {
171        path,
172        paren_token: parenthesized!(content in input),
173        nested: content.parse_terminated(NestedMeta::parse, Token![,])?,
174    })
175}
176
177fn parse_meta_name_value_after_path(
178    path: Path,
179    input: ParseStream,
180) -> Result<MetaNameValue> {
181    Ok(MetaNameValue {
182        path,
183        eq_token: input.parse()?,
184        value: input.parse()?,
185    })
186}