1 use crate::syntax::cfg::CfgExpr;
2 use crate::syntax::namespace::Namespace;
3 use crate::syntax::report::Errors;
4 use crate::syntax::Atom::{self, *};
5 use crate::syntax::{cfg, Derive, Doc, ForeignName};
6 use proc_macro2::{Ident, TokenStream};
7 use quote::ToTokens;
8 use syn::parse::ParseStream;
9 use syn::{Attribute, Error, Expr, Lit, LitStr, Meta, Path, Result, Token};
10 
11 // Intended usage:
12 //
13 //     let mut doc = Doc::new();
14 //     let mut cxx_name = None;
15 //     let mut rust_name = None;
16 //     /* ... */
17 //     let attrs = attrs::parse(
18 //         cx,
19 //         item.attrs,
20 //         attrs::Parser {
21 //             doc: Some(&mut doc),
22 //             cxx_name: Some(&mut cxx_name),
23 //             rust_name: Some(&mut rust_name),
24 //             /* ... */
25 //             ..Default::default()
26 //         },
27 //     );
28 //
29 #[derive(Default)]
30 pub(crate) struct Parser<'a> {
31     pub cfg: Option<&'a mut CfgExpr>,
32     pub doc: Option<&'a mut Doc>,
33     pub derives: Option<&'a mut Vec<Derive>>,
34     pub repr: Option<&'a mut Option<Atom>>,
35     pub namespace: Option<&'a mut Namespace>,
36     pub cxx_name: Option<&'a mut Option<ForeignName>>,
37     pub rust_name: Option<&'a mut Option<Ident>>,
38     pub variants_from_header: Option<&'a mut Option<Attribute>>,
39     pub ignore_unrecognized: bool,
40 
41     // Suppress clippy needless_update lint ("struct update has no effect, all
42     // the fields in the struct have already been specified") when preemptively
43     // writing `..Default::default()`.
44     pub(crate) _more: (),
45 }
46 
parse(cx: &mut Errors, attrs: Vec<Attribute>, mut parser: Parser) -> OtherAttrs47 pub(crate) fn parse(cx: &mut Errors, attrs: Vec<Attribute>, mut parser: Parser) -> OtherAttrs {
48     let mut passthrough_attrs = Vec::new();
49     for attr in attrs {
50         let attr_path = attr.path();
51         if attr_path.is_ident("doc") {
52             match parse_doc_attribute(&attr.meta) {
53                 Ok(attr) => {
54                     if let Some(doc) = &mut parser.doc {
55                         match attr {
56                             DocAttribute::Doc(lit) => doc.push(lit),
57                             DocAttribute::Hidden => doc.hidden = true,
58                         }
59                         continue;
60                     }
61                 }
62                 Err(err) => {
63                     cx.push(err);
64                     break;
65                 }
66             }
67         } else if attr_path.is_ident("derive") {
68             match attr.parse_args_with(|attr: ParseStream| parse_derive_attribute(cx, attr)) {
69                 Ok(attr) => {
70                     if let Some(derives) = &mut parser.derives {
71                         derives.extend(attr);
72                         continue;
73                     }
74                 }
75                 Err(err) => {
76                     cx.push(err);
77                     break;
78                 }
79             }
80         } else if attr_path.is_ident("repr") {
81             match attr.parse_args_with(parse_repr_attribute) {
82                 Ok(attr) => {
83                     if let Some(repr) = &mut parser.repr {
84                         **repr = Some(attr);
85                         continue;
86                     }
87                 }
88                 Err(err) => {
89                     cx.push(err);
90                     break;
91                 }
92             }
93         } else if attr_path.is_ident("namespace") {
94             match Namespace::parse_meta(&attr.meta) {
95                 Ok(attr) => {
96                     if let Some(namespace) = &mut parser.namespace {
97                         **namespace = attr;
98                         continue;
99                     }
100                 }
101                 Err(err) => {
102                     cx.push(err);
103                     break;
104                 }
105             }
106         } else if attr_path.is_ident("cxx_name") {
107             match parse_cxx_name_attribute(&attr.meta) {
108                 Ok(attr) => {
109                     if let Some(cxx_name) = &mut parser.cxx_name {
110                         **cxx_name = Some(attr);
111                         continue;
112                     }
113                 }
114                 Err(err) => {
115                     cx.push(err);
116                     break;
117                 }
118             }
119         } else if attr_path.is_ident("rust_name") {
120             match parse_rust_name_attribute(&attr.meta) {
121                 Ok(attr) => {
122                     if let Some(rust_name) = &mut parser.rust_name {
123                         **rust_name = Some(attr);
124                         continue;
125                     }
126                 }
127                 Err(err) => {
128                     cx.push(err);
129                     break;
130                 }
131             }
132         } else if attr_path.is_ident("cfg") {
133             match cfg::parse_attribute(&attr) {
134                 Ok(cfg_expr) => {
135                     if let Some(cfg) = &mut parser.cfg {
136                         cfg.merge(cfg_expr);
137                         passthrough_attrs.push(attr);
138                         continue;
139                     }
140                 }
141                 Err(err) => {
142                     cx.push(err);
143                     break;
144                 }
145             }
146         } else if attr_path.is_ident("variants_from_header")
147             && cfg!(feature = "experimental-enum-variants-from-header")
148         {
149             if let Err(err) = attr.meta.require_path_only() {
150                 cx.push(err);
151             }
152             if let Some(variants_from_header) = &mut parser.variants_from_header {
153                 **variants_from_header = Some(attr);
154                 continue;
155             }
156         } else if attr_path.is_ident("allow")
157             || attr_path.is_ident("warn")
158             || attr_path.is_ident("deny")
159             || attr_path.is_ident("forbid")
160             || attr_path.is_ident("deprecated")
161             || attr_path.is_ident("must_use")
162         {
163             // https://doc.rust-lang.org/reference/attributes/diagnostics.html
164             passthrough_attrs.push(attr);
165             continue;
166         } else if attr_path.is_ident("serde") {
167             passthrough_attrs.push(attr);
168             continue;
169         } else if attr_path.segments.len() > 1 {
170             let tool = &attr_path.segments.first().unwrap().ident;
171             if tool == "rustfmt" {
172                 // Skip, rustfmt only needs to find it in the pre-expansion source file.
173                 continue;
174             } else if tool == "clippy" {
175                 passthrough_attrs.push(attr);
176                 continue;
177             }
178         }
179         if !parser.ignore_unrecognized {
180             cx.error(attr, "unsupported attribute");
181             break;
182         }
183     }
184     OtherAttrs(passthrough_attrs)
185 }
186 
187 enum DocAttribute {
188     Doc(LitStr),
189     Hidden,
190 }
191 
192 mod kw {
193     syn::custom_keyword!(hidden);
194 }
195 
parse_doc_attribute(meta: &Meta) -> Result<DocAttribute>196 fn parse_doc_attribute(meta: &Meta) -> Result<DocAttribute> {
197     match meta {
198         Meta::NameValue(meta) => {
199             if let Expr::Lit(expr) = &meta.value {
200                 if let Lit::Str(lit) = &expr.lit {
201                     return Ok(DocAttribute::Doc(lit.clone()));
202                 }
203             }
204         }
205         Meta::List(meta) => {
206             meta.parse_args::<kw::hidden>()?;
207             return Ok(DocAttribute::Hidden);
208         }
209         Meta::Path(_) => {}
210     }
211     Err(Error::new_spanned(meta, "unsupported doc attribute"))
212 }
213 
parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>>214 fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> {
215     let paths = input.parse_terminated(Path::parse_mod_style, Token![,])?;
216 
217     let mut derives = Vec::new();
218     for path in paths {
219         if let Some(ident) = path.get_ident() {
220             if let Some(derive) = Derive::from(ident) {
221                 derives.push(derive);
222                 continue;
223             }
224         }
225         cx.error(path, "unsupported derive");
226     }
227     Ok(derives)
228 }
229 
parse_repr_attribute(input: ParseStream) -> Result<Atom>230 fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
231     let begin = input.cursor();
232     let ident: Ident = input.parse()?;
233     if let Some(atom) = Atom::from(&ident) {
234         match atom {
235             U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
236                 return Ok(atom);
237             }
238             _ => {}
239         }
240     }
241     Err(Error::new_spanned(
242         begin.token_stream(),
243         "unrecognized repr",
244     ))
245 }
246 
parse_cxx_name_attribute(meta: &Meta) -> Result<ForeignName>247 fn parse_cxx_name_attribute(meta: &Meta) -> Result<ForeignName> {
248     if let Meta::NameValue(meta) = meta {
249         match &meta.value {
250             Expr::Lit(expr) => {
251                 if let Lit::Str(lit) = &expr.lit {
252                     return ForeignName::parse(&lit.value(), lit.span());
253                 }
254             }
255             Expr::Path(expr) => {
256                 if let Some(ident) = expr.path.get_ident() {
257                     return ForeignName::parse(&ident.to_string(), ident.span());
258                 }
259             }
260             _ => {}
261         }
262     }
263     Err(Error::new_spanned(meta, "unsupported cxx_name attribute"))
264 }
265 
parse_rust_name_attribute(meta: &Meta) -> Result<Ident>266 fn parse_rust_name_attribute(meta: &Meta) -> Result<Ident> {
267     if let Meta::NameValue(meta) = meta {
268         match &meta.value {
269             Expr::Lit(expr) => {
270                 if let Lit::Str(lit) = &expr.lit {
271                     return lit.parse();
272                 }
273             }
274             Expr::Path(expr) => {
275                 if let Some(ident) = expr.path.get_ident() {
276                     return Ok(ident.clone());
277                 }
278             }
279             _ => {}
280         }
281     }
282     Err(Error::new_spanned(meta, "unsupported rust_name attribute"))
283 }
284 
285 #[derive(Clone)]
286 pub(crate) struct OtherAttrs(Vec<Attribute>);
287 
288 impl OtherAttrs {
none() -> Self289     pub(crate) fn none() -> Self {
290         OtherAttrs(Vec::new())
291     }
292 
extend(&mut self, other: Self)293     pub(crate) fn extend(&mut self, other: Self) {
294         self.0.extend(other.0);
295     }
296 }
297 
298 impl ToTokens for OtherAttrs {
to_tokens(&self, tokens: &mut TokenStream)299     fn to_tokens(&self, tokens: &mut TokenStream) {
300         for attr in &self.0 {
301             let Attribute {
302                 pound_token,
303                 style,
304                 bracket_token,
305                 meta,
306             } = attr;
307             pound_token.to_tokens(tokens);
308             let _ = style; // ignore; render outer and inner attrs both as outer
309             bracket_token.surround(tokens, |tokens| meta.to_tokens(tokens));
310         }
311     }
312 }
313