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