cxx_gen/syntax/
attrs.rs

1use crate::syntax::cfg::CfgExpr;
2use crate::syntax::namespace::Namespace;
3use crate::syntax::report::Errors;
4use crate::syntax::repr::Repr;
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<Repr>>,
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 self_type: Option<&'a mut Option<Ident>>,
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::<Repr>() {
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_ident_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("Self") {
133            match parse_rust_ident_attribute(&attr.meta) {
134                Ok(attr) => {
135                    if let Some(self_type) = &mut parser.self_type {
136                        **self_type = Some(attr);
137                        continue;
138                    }
139                }
140                Err(err) => {
141                    cx.push(err);
142                    break;
143                }
144            }
145        } else if attr_path.is_ident("cfg") {
146            match cfg::parse_attribute(&attr) {
147                Ok(cfg_expr) => {
148                    if let Some(cfg) = &mut parser.cfg {
149                        cfg.merge(cfg_expr);
150                        passthrough_attrs.push(attr);
151                        continue;
152                    }
153                }
154                Err(err) => {
155                    cx.push(err);
156                    break;
157                }
158            }
159        } else if attr_path.is_ident("allow")
160            || attr_path.is_ident("warn")
161            || attr_path.is_ident("deny")
162            || attr_path.is_ident("forbid")
163            || attr_path.is_ident("deprecated")
164            || attr_path.is_ident("must_use")
165        {
166            // https://doc.rust-lang.org/reference/attributes/diagnostics.html
167            passthrough_attrs.push(attr);
168            continue;
169        } else if attr_path.is_ident("serde") {
170            passthrough_attrs.push(attr);
171            continue;
172        } else if attr_path.segments.len() > 1 {
173            let tool = &attr_path.segments.first().unwrap().ident;
174            if tool == "rustfmt" {
175                // Skip, rustfmt only needs to find it in the pre-expansion source file.
176                continue;
177            } else if tool == "clippy" {
178                passthrough_attrs.push(attr);
179                continue;
180            }
181        }
182        if !parser.ignore_unrecognized {
183            cx.error(attr, "unsupported attribute");
184            break;
185        }
186    }
187    OtherAttrs(passthrough_attrs)
188}
189
190enum DocAttribute {
191    Doc(LitStr),
192    Hidden,
193}
194
195mod kw {
196    syn::custom_keyword!(hidden);
197}
198
199fn parse_doc_attribute(meta: &Meta) -> Result<DocAttribute> {
200    match meta {
201        Meta::NameValue(meta) => {
202            if let Expr::Lit(expr) = &meta.value {
203                if let Lit::Str(lit) = &expr.lit {
204                    return Ok(DocAttribute::Doc(lit.clone()));
205                }
206            }
207        }
208        Meta::List(meta) => {
209            meta.parse_args::<kw::hidden>()?;
210            return Ok(DocAttribute::Hidden);
211        }
212        Meta::Path(_) => {}
213    }
214    Err(Error::new_spanned(meta, "unsupported doc attribute"))
215}
216
217fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> {
218    let paths = input.parse_terminated(Path::parse_mod_style, Token![,])?;
219
220    let mut derives = Vec::new();
221    for path in paths {
222        if let Some(ident) = path.get_ident() {
223            if let Some(derive) = Derive::from(ident) {
224                derives.push(derive);
225                continue;
226            }
227        }
228        cx.error(path, "unsupported derive");
229    }
230    Ok(derives)
231}
232
233fn parse_cxx_name_attribute(meta: &Meta) -> Result<ForeignName> {
234    if let Meta::NameValue(meta) = meta {
235        match &meta.value {
236            Expr::Lit(expr) => {
237                if let Lit::Str(lit) = &expr.lit {
238                    return ForeignName::parse(&lit.value(), lit.span());
239                }
240            }
241            Expr::Path(expr) => {
242                if let Some(ident) = expr.path.get_ident() {
243                    return ForeignName::parse(&ident.to_string(), ident.span());
244                }
245            }
246            _ => {}
247        }
248    }
249    Err(Error::new_spanned(meta, "unsupported cxx_name attribute"))
250}
251
252fn parse_rust_ident_attribute(meta: &Meta) -> Result<Ident> {
253    if let Meta::NameValue(meta) = meta {
254        match &meta.value {
255            Expr::Lit(expr) => {
256                if let Lit::Str(lit) = &expr.lit {
257                    return lit.parse();
258                }
259            }
260            Expr::Path(expr) => {
261                if let Some(ident) = expr.path.get_ident() {
262                    return Ok(ident.clone());
263                }
264            }
265            _ => {}
266        }
267    }
268    Err(Error::new_spanned(
269        meta,
270        format!(
271            "unsupported `{}` attribute",
272            meta.path().get_ident().unwrap(),
273        ),
274    ))
275}
276
277#[derive(Clone)]
278pub(crate) struct OtherAttrs(Vec<Attribute>);
279
280impl OtherAttrs {
281    pub(crate) fn none() -> Self {
282        OtherAttrs(Vec::new())
283    }
284
285    pub(crate) fn extend(&mut self, other: Self) {
286        self.0.extend(other.0);
287    }
288}
289
290impl ToTokens for OtherAttrs {
291    fn to_tokens(&self, tokens: &mut TokenStream) {
292        for attr in &self.0 {
293            let Attribute {
294                pound_token,
295                style,
296                bracket_token,
297                meta,
298            } = attr;
299            pound_token.to_tokens(tokens);
300            let _ = style; // ignore; render outer and inner attrs both as outer
301            bracket_token.surround(tokens, |tokens| meta.to_tokens(tokens));
302        }
303    }
304}