derive_more/
syn_compat.rs1use 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#[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
127fn 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}