cxx_gen/syntax/
attrs.rs

1use crate::syntax::cfg::CfgExpr;
2use crate::syntax::namespace::Namespace;
3use crate::syntax::report::Errors;
4use crate::syntax::Atom::{self, *};
5use crate::syntax::{cfg, Derive, Doc, ForeignName};
6use proc_macro2::{Ident, TokenStream};
7use quote::ToTokens;
8use syn::parse::ParseStream;
9use 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)]
30pub(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
47pub(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
187enum DocAttribute {
188    Doc(LitStr),
189    Hidden,
190}
191
192mod kw {
193    syn::custom_keyword!(hidden);
194}
195
196fn 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
214fn 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
230fn 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
247fn 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
266fn 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)]
286pub(crate) struct OtherAttrs(Vec<Attribute>);
287
288impl OtherAttrs {
289    pub(crate) fn none() -> Self {
290        OtherAttrs(Vec::new())
291    }
292
293    pub(crate) fn extend(&mut self, other: Self) {
294        self.0.extend(other.0);
295    }
296}
297
298impl ToTokens for OtherAttrs {
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}