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