• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{Nothing, Parse, ParseStream, Parser as _};
9 use syn::{parenthesized, token, Attribute, Error, LitStr, 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 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 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         if attr.path.is_ident("doc") {
51             match parse_doc_attribute.parse2(attr.tokens.clone()) {
52                 Ok(attr) => {
53                     if let Some(doc) = &mut parser.doc {
54                         match attr {
55                             DocAttribute::Doc(lit) => doc.push(lit),
56                             DocAttribute::Hidden => doc.hidden = true,
57                         }
58                         continue;
59                     }
60                 }
61                 Err(err) => {
62                     cx.push(err);
63                     break;
64                 }
65             }
66         } else if attr.path.is_ident("derive") {
67             match attr.parse_args_with(|attr: ParseStream| parse_derive_attribute(cx, attr)) {
68                 Ok(attr) => {
69                     if let Some(derives) = &mut parser.derives {
70                         derives.extend(attr);
71                         continue;
72                     }
73                 }
74                 Err(err) => {
75                     cx.push(err);
76                     break;
77                 }
78             }
79         } else if attr.path.is_ident("repr") {
80             match attr.parse_args_with(parse_repr_attribute) {
81                 Ok(attr) => {
82                     if let Some(repr) = &mut parser.repr {
83                         **repr = Some(attr);
84                         continue;
85                     }
86                 }
87                 Err(err) => {
88                     cx.push(err);
89                     break;
90                 }
91             }
92         } else if attr.path.is_ident("namespace") {
93             match parse_namespace_attribute.parse2(attr.tokens.clone()) {
94                 Ok(attr) => {
95                     if let Some(namespace) = &mut parser.namespace {
96                         **namespace = attr;
97                         continue;
98                     }
99                 }
100                 Err(err) => {
101                     cx.push(err);
102                     break;
103                 }
104             }
105         } else if attr.path.is_ident("cxx_name") {
106             match parse_cxx_name_attribute.parse2(attr.tokens.clone()) {
107                 Ok(attr) => {
108                     if let Some(cxx_name) = &mut parser.cxx_name {
109                         **cxx_name = Some(attr);
110                         continue;
111                     }
112                 }
113                 Err(err) => {
114                     cx.push(err);
115                     break;
116                 }
117             }
118         } else if attr.path.is_ident("rust_name") {
119             match parse_rust_name_attribute.parse2(attr.tokens.clone()) {
120                 Ok(attr) => {
121                     if let Some(rust_name) = &mut parser.rust_name {
122                         **rust_name = Some(attr);
123                         continue;
124                     }
125                 }
126                 Err(err) => {
127                     cx.push(err);
128                     break;
129                 }
130             }
131         } else if attr.path.is_ident("cfg") {
132             match cfg::parse_attribute.parse2(attr.tokens.clone()) {
133                 Ok(cfg_expr) => {
134                     if let Some(cfg) = &mut parser.cfg {
135                         cfg.merge(cfg_expr);
136                         passthrough_attrs.push(attr);
137                         continue;
138                     }
139                 }
140                 Err(err) => {
141                     cx.push(err);
142                     break;
143                 }
144             }
145         } else if attr.path.is_ident("variants_from_header")
146             && cfg!(feature = "experimental-enum-variants-from-header")
147         {
148             if let Err(err) = Nothing::parse.parse2(attr.tokens.clone()) {
149                 cx.push(err);
150             }
151             if let Some(variants_from_header) = &mut parser.variants_from_header {
152                 **variants_from_header = Some(attr);
153                 continue;
154             }
155         } else if attr.path.is_ident("allow")
156             || attr.path.is_ident("warn")
157             || attr.path.is_ident("deny")
158             || attr.path.is_ident("forbid")
159             || attr.path.is_ident("deprecated")
160             || attr.path.is_ident("must_use")
161         {
162             // https://doc.rust-lang.org/reference/attributes/diagnostics.html
163             passthrough_attrs.push(attr);
164             continue;
165         } else if attr.path.is_ident("serde") {
166             passthrough_attrs.push(attr);
167             continue;
168         } else if attr.path.segments.len() > 1 {
169             let tool = &attr.path.segments.first().unwrap().ident;
170             if tool == "rustfmt" {
171                 // Skip, rustfmt only needs to find it in the pre-expansion source file.
172                 continue;
173             } else if tool == "clippy" {
174                 passthrough_attrs.push(attr);
175                 continue;
176             }
177         }
178         if !parser.ignore_unrecognized {
179             cx.error(attr, "unsupported attribute");
180             break;
181         }
182     }
183     OtherAttrs(passthrough_attrs)
184 }
185 
186 enum DocAttribute {
187     Doc(LitStr),
188     Hidden,
189 }
190 
191 mod kw {
192     syn::custom_keyword!(hidden);
193 }
194 
parse_doc_attribute(input: ParseStream) -> Result<DocAttribute>195 fn parse_doc_attribute(input: ParseStream) -> Result<DocAttribute> {
196     let lookahead = input.lookahead1();
197     if lookahead.peek(Token![=]) {
198         input.parse::<Token![=]>()?;
199         let lit: LitStr = input.parse()?;
200         Ok(DocAttribute::Doc(lit))
201     } else if lookahead.peek(token::Paren) {
202         let content;
203         parenthesized!(content in input);
204         content.parse::<kw::hidden>()?;
205         Ok(DocAttribute::Hidden)
206     } else {
207         Err(lookahead.error())
208     }
209 }
210 
parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>>211 fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> {
212     let paths = input.parse_terminated::<Path, Token![,]>(Path::parse_mod_style)?;
213 
214     let mut derives = Vec::new();
215     for path in paths {
216         if let Some(ident) = path.get_ident() {
217             if let Some(derive) = Derive::from(ident) {
218                 derives.push(derive);
219                 continue;
220             }
221         }
222         cx.error(path, "unsupported derive");
223     }
224     Ok(derives)
225 }
226 
parse_repr_attribute(input: ParseStream) -> Result<Atom>227 fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
228     let begin = input.cursor();
229     let ident: Ident = input.parse()?;
230     if let Some(atom) = Atom::from(&ident) {
231         match atom {
232             U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
233                 return Ok(atom);
234             }
235             _ => {}
236         }
237     }
238     Err(Error::new_spanned(
239         begin.token_stream(),
240         "unrecognized repr",
241     ))
242 }
243 
parse_namespace_attribute(input: ParseStream) -> Result<Namespace>244 fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> {
245     input.parse::<Token![=]>()?;
246     let namespace = input.parse::<Namespace>()?;
247     Ok(namespace)
248 }
249 
parse_cxx_name_attribute(input: ParseStream) -> Result<ForeignName>250 fn parse_cxx_name_attribute(input: ParseStream) -> Result<ForeignName> {
251     input.parse::<Token![=]>()?;
252     if input.peek(LitStr) {
253         let lit: LitStr = input.parse()?;
254         ForeignName::parse(&lit.value(), lit.span())
255     } else {
256         let ident: Ident = input.parse()?;
257         ForeignName::parse(&ident.to_string(), ident.span())
258     }
259 }
260 
parse_rust_name_attribute(input: ParseStream) -> Result<Ident>261 fn parse_rust_name_attribute(input: ParseStream) -> Result<Ident> {
262     input.parse::<Token![=]>()?;
263     if input.peek(LitStr) {
264         let lit: LitStr = input.parse()?;
265         lit.parse()
266     } else {
267         input.parse()
268     }
269 }
270 
271 #[derive(Clone)]
272 pub struct OtherAttrs(Vec<Attribute>);
273 
274 impl OtherAttrs {
none() -> Self275     pub fn none() -> Self {
276         OtherAttrs(Vec::new())
277     }
278 
extend(&mut self, other: Self)279     pub fn extend(&mut self, other: Self) {
280         self.0.extend(other.0);
281     }
282 }
283 
284 impl ToTokens for OtherAttrs {
to_tokens(&self, tokens: &mut TokenStream)285     fn to_tokens(&self, tokens: &mut TokenStream) {
286         for attr in &self.0 {
287             let Attribute {
288                 pound_token,
289                 style,
290                 bracket_token,
291                 path,
292                 tokens: attr_tokens,
293             } = attr;
294             pound_token.to_tokens(tokens);
295             let _ = style; // ignore; render outer and inner attrs both as outer
296             bracket_token.surround(tokens, |tokens| {
297                 path.to_tokens(tokens);
298                 attr_tokens.to_tokens(tokens);
299             });
300         }
301     }
302 }
303