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 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 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 struct OtherAttrs(Vec<Attribute>); 287 288impl OtherAttrs { 289 pub fn none() -> Self { 290 OtherAttrs(Vec::new()) 291 } 292 293 pub 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} 313