| 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| 4 | |
| 5 | #![allow (clippy::redundant_closure_call)] |
| 6 | |
| 7 | use syn::ext::IdentExt; |
| 8 | |
| 9 | pub trait IterHelpers: Iterator { |
| 10 | fn try_skip_map<F, T, E>(&mut self, f: F) -> Result<Vec<T>, E> |
| 11 | where |
| 12 | F: FnMut(&Self::Item) -> Result<Option<T>, E>; |
| 13 | } |
| 14 | |
| 15 | impl<I> IterHelpers for I |
| 16 | where |
| 17 | I: Iterator, |
| 18 | { |
| 19 | fn try_skip_map<F, T, E>(&mut self, mut f: F) -> Result<Vec<T>, E> |
| 20 | where |
| 21 | F: FnMut(&Self::Item) -> Result<Option<T>, E>, |
| 22 | { |
| 23 | let mut out: Vec = Vec::new(); |
| 24 | for item: ::Item in self { |
| 25 | if let Some(x: T) = f(&item)? { |
| 26 | out.push(x); |
| 27 | } |
| 28 | } |
| 29 | Ok(out) |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | pub trait SynItemHelpers: SynAttributeHelpers { |
| 34 | fn exported_name(&self) -> Option<String>; |
| 35 | } |
| 36 | |
| 37 | impl SynItemHelpers for syn::ItemFn { |
| 38 | fn exported_name(&self) -> Option<String> { |
| 39 | self.attrs |
| 40 | .attr_name_value_lookup(name:"export_name" ) |
| 41 | .or_else(|| { |
| 42 | if self.is_no_mangle() { |
| 43 | Some(self.sig.ident.unraw().to_string()) |
| 44 | } else { |
| 45 | None |
| 46 | } |
| 47 | }) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | impl SynItemHelpers for syn::ImplItemMethod { |
| 52 | fn exported_name(&self) -> Option<String> { |
| 53 | self.attrs |
| 54 | .attr_name_value_lookup(name:"export_name" ) |
| 55 | .or_else(|| { |
| 56 | if self.is_no_mangle() { |
| 57 | Some(self.sig.ident.unraw().to_string()) |
| 58 | } else { |
| 59 | None |
| 60 | } |
| 61 | }) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | impl SynItemHelpers for syn::ItemStatic { |
| 66 | fn exported_name(&self) -> Option<String> { |
| 67 | self.attrs |
| 68 | .attr_name_value_lookup(name:"export_name" ) |
| 69 | .or_else(|| { |
| 70 | if self.is_no_mangle() { |
| 71 | Some(self.ident.unraw().to_string()) |
| 72 | } else { |
| 73 | None |
| 74 | } |
| 75 | }) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | /// Returns whether this attribute causes us to skip at item. This basically |
| 80 | /// checks for `#[cfg(test)]`, `#[test]`, `/// cbindgen::ignore` and |
| 81 | /// variations thereof. |
| 82 | fn is_skip_item_attr(attr: &syn::Meta) -> bool { |
| 83 | match *attr { |
| 84 | syn::Meta::Path(ref path) => { |
| 85 | // TODO(emilio): It'd be great if rustc allowed us to use a syntax |
| 86 | // like `#[cbindgen::ignore]` or such. |
| 87 | path.is_ident("test" ) |
| 88 | } |
| 89 | syn::Meta::List(ref list) => { |
| 90 | if !list.path.is_ident("cfg" ) { |
| 91 | return false; |
| 92 | } |
| 93 | list.nested.iter().any(|nested| match *nested { |
| 94 | syn::NestedMeta::Meta(ref meta) => is_skip_item_attr(meta), |
| 95 | syn::NestedMeta::Lit(..) => false, |
| 96 | }) |
| 97 | } |
| 98 | syn::Meta::NameValue(ref name_value) => { |
| 99 | if name_value.path.is_ident("doc" ) { |
| 100 | if let syn::Lit::Str(ref content) = name_value.lit { |
| 101 | // FIXME(emilio): Maybe should use the general annotation |
| 102 | // mechanism, but it seems overkill for this. |
| 103 | if content.value().trim() == "cbindgen:ignore" { |
| 104 | return true; |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | false |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | pub trait SynAttributeHelpers { |
| 114 | /// Returns the list of attributes for an item. |
| 115 | fn attrs(&self) -> &[syn::Attribute]; |
| 116 | |
| 117 | /// Searches for attributes like `#[test]`. |
| 118 | /// Example: |
| 119 | /// - `item.has_attr_word("test")` => `#[test]` |
| 120 | fn has_attr_word(&self, name: &str) -> bool { |
| 121 | self.attrs() |
| 122 | .iter() |
| 123 | .filter_map(|x| x.parse_meta().ok()) |
| 124 | .any(|attr| { |
| 125 | if let syn::Meta::Path(ref path) = attr { |
| 126 | path.is_ident(name) |
| 127 | } else { |
| 128 | false |
| 129 | } |
| 130 | }) |
| 131 | } |
| 132 | |
| 133 | fn find_deprecated_note(&self) -> Option<String> { |
| 134 | let attrs = self.attrs(); |
| 135 | // #[deprecated = ""] |
| 136 | if let Some(note) = attrs.attr_name_value_lookup("deprecated" ) { |
| 137 | return Some(note); |
| 138 | } |
| 139 | |
| 140 | // #[deprecated] |
| 141 | if attrs.has_attr_word("deprecated" ) { |
| 142 | return Some(String::new()); |
| 143 | } |
| 144 | |
| 145 | // #[deprecated(note = "")] |
| 146 | let attr = attrs.iter().find(|attr| { |
| 147 | if let Ok(syn::Meta::List(list)) = attr.parse_meta() { |
| 148 | list.path.is_ident("deprecated" ) |
| 149 | } else { |
| 150 | false |
| 151 | } |
| 152 | })?; |
| 153 | |
| 154 | let args: syn::punctuated::Punctuated<syn::MetaNameValue, Token![,]> = |
| 155 | match attr.parse_args_with(syn::punctuated::Punctuated::parse_terminated) { |
| 156 | Ok(args) => args, |
| 157 | Err(_) => { |
| 158 | warn!("couldn't parse deprecated attribute" ); |
| 159 | return None; |
| 160 | } |
| 161 | }; |
| 162 | |
| 163 | let arg = args.iter().find(|arg| arg.path.is_ident("note" ))?; |
| 164 | if let syn::Lit::Str(ref lit) = arg.lit { |
| 165 | Some(lit.value()) |
| 166 | } else { |
| 167 | warn!("deprecated attribute must be a string" ); |
| 168 | None |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | fn is_no_mangle(&self) -> bool { |
| 173 | self.has_attr_word("no_mangle" ) |
| 174 | } |
| 175 | |
| 176 | /// Sees whether we should skip parsing a given item. |
| 177 | fn should_skip_parsing(&self) -> bool { |
| 178 | for attr in self.attrs() { |
| 179 | let meta = match attr.parse_meta() { |
| 180 | Ok(attr) => attr, |
| 181 | Err(..) => return false, |
| 182 | }; |
| 183 | if is_skip_item_attr(&meta) { |
| 184 | return true; |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | false |
| 189 | } |
| 190 | |
| 191 | fn attr_name_value_lookup(&self, name: &str) -> Option<String> { |
| 192 | self.attrs() |
| 193 | .iter() |
| 194 | .filter_map(|attr| { |
| 195 | let attr = attr.parse_meta().ok()?; |
| 196 | if let syn::Meta::NameValue(syn::MetaNameValue { |
| 197 | path, |
| 198 | lit: syn::Lit::Str(lit), |
| 199 | .. |
| 200 | }) = attr |
| 201 | { |
| 202 | if path.is_ident(name) { |
| 203 | return Some(lit.value()); |
| 204 | } |
| 205 | } |
| 206 | None |
| 207 | }) |
| 208 | .next() |
| 209 | } |
| 210 | |
| 211 | fn get_comment_lines(&self) -> Vec<String> { |
| 212 | let mut comment = Vec::new(); |
| 213 | |
| 214 | for attr in self.attrs() { |
| 215 | if attr.style == syn::AttrStyle::Outer { |
| 216 | if let Ok(syn::Meta::NameValue(syn::MetaNameValue { |
| 217 | path, |
| 218 | lit: syn::Lit::Str(content), |
| 219 | .. |
| 220 | })) = attr.parse_meta() |
| 221 | { |
| 222 | if path.is_ident("doc" ) { |
| 223 | comment.extend(split_doc_attr(&content.value())); |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | comment |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | macro_rules! syn_item_match_helper { |
| 234 | ($s:ident => has_attrs: |$i:ident| $a:block, otherwise: || $b:block) => { |
| 235 | match *$s { |
| 236 | syn::Item::Const(ref $i) => $a, |
| 237 | syn::Item::Enum(ref $i) => $a, |
| 238 | syn::Item::ExternCrate(ref $i) => $a, |
| 239 | syn::Item::Fn(ref $i) => $a, |
| 240 | syn::Item::ForeignMod(ref $i) => $a, |
| 241 | syn::Item::Impl(ref $i) => $a, |
| 242 | syn::Item::Macro(ref $i) => $a, |
| 243 | syn::Item::Macro2(ref $i) => $a, |
| 244 | syn::Item::Mod(ref $i) => $a, |
| 245 | syn::Item::Static(ref $i) => $a, |
| 246 | syn::Item::Struct(ref $i) => $a, |
| 247 | syn::Item::Trait(ref $i) => $a, |
| 248 | syn::Item::Type(ref $i) => $a, |
| 249 | syn::Item::Union(ref $i) => $a, |
| 250 | syn::Item::Use(ref $i) => $a, |
| 251 | syn::Item::TraitAlias(ref $i) => $a, |
| 252 | syn::Item::Verbatim(_) => $b, |
| 253 | _ => panic!("Unhandled syn::Item: {:?}" , $s), |
| 254 | } |
| 255 | }; |
| 256 | } |
| 257 | |
| 258 | impl SynAttributeHelpers for syn::Item { |
| 259 | fn attrs(&self) -> &[syn::Attribute] { |
| 260 | syn_item_match_helper!(self => |
| 261 | has_attrs: |item| { &item.attrs }, |
| 262 | otherwise: || { &[] } |
| 263 | ) |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | macro_rules! impl_syn_item_helper { |
| 268 | ($t:ty) => { |
| 269 | impl SynAttributeHelpers for $t { |
| 270 | fn attrs(&self) -> &[syn::Attribute] { |
| 271 | &self.attrs |
| 272 | } |
| 273 | } |
| 274 | }; |
| 275 | } |
| 276 | |
| 277 | impl_syn_item_helper!(syn::ItemExternCrate); |
| 278 | impl_syn_item_helper!(syn::ItemUse); |
| 279 | impl_syn_item_helper!(syn::ItemStatic); |
| 280 | impl_syn_item_helper!(syn::ItemConst); |
| 281 | impl_syn_item_helper!(syn::ItemFn); |
| 282 | impl_syn_item_helper!(syn::ImplItemMethod); |
| 283 | impl_syn_item_helper!(syn::ItemMod); |
| 284 | impl_syn_item_helper!(syn::ItemForeignMod); |
| 285 | impl_syn_item_helper!(syn::ItemType); |
| 286 | impl_syn_item_helper!(syn::ItemStruct); |
| 287 | impl_syn_item_helper!(syn::ItemEnum); |
| 288 | impl_syn_item_helper!(syn::ItemUnion); |
| 289 | impl_syn_item_helper!(syn::ItemTrait); |
| 290 | impl_syn_item_helper!(syn::ItemImpl); |
| 291 | impl_syn_item_helper!(syn::ItemMacro); |
| 292 | impl_syn_item_helper!(syn::ItemMacro2); |
| 293 | impl_syn_item_helper!(syn::ItemTraitAlias); |
| 294 | |
| 295 | /// Helper function for accessing Abi information |
| 296 | pub trait SynAbiHelpers { |
| 297 | fn is_c(&self) -> bool; |
| 298 | fn is_omitted(&self) -> bool; |
| 299 | } |
| 300 | |
| 301 | impl SynAbiHelpers for Option<syn::Abi> { |
| 302 | fn is_c(&self) -> bool { |
| 303 | if let Some(ref abi: &Abi) = *self { |
| 304 | if let Some(ref lit_string: &LitStr) = abi.name { |
| 305 | return matches!(lit_string.value().as_str(), "C" | "C-unwind" ); |
| 306 | } |
| 307 | } |
| 308 | false |
| 309 | } |
| 310 | fn is_omitted(&self) -> bool { |
| 311 | if let Some(ref abi: &Abi) = *self { |
| 312 | abi.name.is_none() |
| 313 | } else { |
| 314 | false |
| 315 | } |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | impl SynAbiHelpers for syn::Abi { |
| 320 | fn is_c(&self) -> bool { |
| 321 | if let Some(ref lit_string: &LitStr) = self.name { |
| 322 | matches!(lit_string.value().as_str(), "C" | "C-unwind" ) |
| 323 | } else { |
| 324 | false |
| 325 | } |
| 326 | } |
| 327 | fn is_omitted(&self) -> bool { |
| 328 | self.name.is_none() |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | impl SynAttributeHelpers for [syn::Attribute] { |
| 333 | fn attrs(&self) -> &[syn::Attribute] { |
| 334 | self |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | fn split_doc_attr(input: &str) -> Vec<String> { |
| 339 | inputimpl Iterator |
| 340 | // Convert two newline (indicate "new paragraph") into two line break. |
| 341 | .replace(from:" \n\n" , to:" \n \n" ) |
| 342 | // Convert newline after two spaces (indicate "line break") into line break. |
| 343 | .split(" \n" ) |
| 344 | // Convert single newline (indicate hard-wrapped) into space. |
| 345 | .map(|s: &str| s.replace(from:' \n' , to:" " )) |
| 346 | .map(|s: String| s.trim_end().to_string()) |
| 347 | .collect() |
| 348 | } |
| 349 | |