| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #![allow(clippy::redundant_closure_call)] |
| |
| use syn::ext::IdentExt; |
| |
| pub trait IterHelpers: Iterator { |
| fn try_skip_map<F, T, E>(&mut self, f: F) -> Result<Vec<T>, E> |
| where |
| F: FnMut(&Self::Item) -> Result<Option<T>, E>; |
| } |
| |
| impl<I> IterHelpers for I |
| where |
| I: Iterator, |
| { |
| fn try_skip_map<F, T, E>(&mut self, mut f: F) -> Result<Vec<T>, E> |
| where |
| F: FnMut(&Self::Item) -> Result<Option<T>, E>, |
| { |
| let mut out = Vec::new(); |
| for item in self { |
| if let Some(x) = f(&item)? { |
| out.push(x); |
| } |
| } |
| Ok(out) |
| } |
| } |
| |
| pub trait SynItemHelpers: SynAttributeHelpers { |
| fn exported_name(&self) -> Option<String>; |
| } |
| |
| impl SynItemHelpers for syn::ItemFn { |
| fn exported_name(&self) -> Option<String> { |
| self.attrs |
| .attr_name_value_lookup("export_name") |
| .or_else(|| { |
| if self.is_no_mangle() { |
| Some(self.sig.ident.unraw().to_string()) |
| } else { |
| None |
| } |
| }) |
| } |
| } |
| |
| impl SynItemHelpers for syn::ImplItemFn { |
| fn exported_name(&self) -> Option<String> { |
| self.attrs |
| .attr_name_value_lookup("export_name") |
| .or_else(|| { |
| if self.is_no_mangle() { |
| Some(self.sig.ident.unraw().to_string()) |
| } else { |
| None |
| } |
| }) |
| } |
| } |
| |
| impl SynItemHelpers for syn::ItemStatic { |
| fn exported_name(&self) -> Option<String> { |
| self.attrs |
| .attr_name_value_lookup("export_name") |
| .or_else(|| { |
| if self.is_no_mangle() { |
| Some(self.ident.unraw().to_string()) |
| } else { |
| None |
| } |
| }) |
| } |
| } |
| |
| /// Returns whether this attribute causes us to skip at item. This basically |
| /// checks for `#[cfg(test)]`, `#[test]`, `/// cbindgen::ignore` and |
| /// variations thereof. |
| fn is_skip_item_attr(attr: &syn::Meta) -> bool { |
| match *attr { |
| syn::Meta::Path(ref path) => { |
| // TODO(emilio): It'd be great if rustc allowed us to use a syntax |
| // like `#[cbindgen::ignore]` or such. |
| path.is_ident("test") |
| } |
| syn::Meta::List(ref list) => { |
| if !list.path.is_ident("cfg") { |
| return false; |
| } |
| |
| // Remove commas of the question by parsing |
| let parser = syn::punctuated::Punctuated::<proc_macro2::TokenStream, syn::Token![,]>::parse_terminated; |
| let Ok(tokens) = list.parse_args_with(parser) else { |
| // cfg attr is a list separated by comma, if that fails, that is probably a malformed cfg attribute |
| return false; |
| }; |
| |
| for token in tokens { |
| let Ok(path) = syn::parse2::<syn::Path>(token) else { |
| // we are looking for `test`, that should always happen only as path |
| return false; |
| }; |
| |
| if path.is_ident("test") { |
| return true; |
| } |
| } |
| false |
| // list.nested.iter().any(|nested| match *nested { |
| // syn::NestedMeta::Meta(ref meta) => is_skip_item_attr(meta), |
| // syn::NestedMeta::Lit(..) => false, |
| // }) |
| } |
| syn::Meta::NameValue(ref name_value) => { |
| if name_value.path.is_ident("doc") { |
| if let syn::Expr::Lit(syn::ExprLit { |
| lit: syn::Lit::Str(ref content), |
| .. |
| }) = name_value.value |
| { |
| // FIXME(emilio): Maybe should use the general annotation |
| // mechanism, but it seems overkill for this. |
| if content.value().trim() == "cbindgen:ignore" { |
| return true; |
| } |
| } |
| } |
| false |
| } |
| } |
| } |
| |
| pub trait SynAttributeHelpers { |
| /// Returns the list of attributes for an item. |
| fn attrs(&self) -> &[syn::Attribute]; |
| |
| /// Searches for attributes like `#[test]`. |
| /// Example: |
| /// - `item.has_attr_word("test")` => `#[test]` |
| fn has_attr_word(&self, name: &str) -> bool { |
| self.attrs().iter().any(|attr| { |
| if let syn::Meta::Path(ref path) = &attr.meta { |
| path.is_ident(name) |
| } else { |
| false |
| } |
| }) |
| } |
| |
| fn find_deprecated_note(&self) -> Option<String> { |
| let attrs = self.attrs(); |
| // #[deprecated = ""] |
| if let Some(note) = attrs.attr_name_value_lookup("deprecated") { |
| return Some(note); |
| } |
| |
| // #[deprecated] |
| if attrs.has_attr_word("deprecated") { |
| return Some(String::new()); |
| } |
| |
| // #[deprecated(note = "")] |
| let attr = attrs.iter().find(|attr| { |
| if let syn::Meta::List(list) = &attr.meta { |
| list.path.is_ident("deprecated") |
| } else { |
| false |
| } |
| })?; |
| |
| let args: syn::punctuated::Punctuated<syn::MetaNameValue, Token![,]> = |
| match attr.parse_args_with(syn::punctuated::Punctuated::parse_terminated) { |
| Ok(args) => args, |
| Err(_) => { |
| warn!("couldn't parse deprecated attribute"); |
| return None; |
| } |
| }; |
| |
| let arg = args.iter().find(|arg| arg.path.is_ident("note"))?; |
| if let syn::Expr::Lit(syn::ExprLit { |
| lit: syn::Lit::Str(ref lit), |
| .. |
| }) = arg.value |
| { |
| Some(lit.value()) |
| } else { |
| warn!("deprecated attribute must be a string"); |
| None |
| } |
| } |
| |
| fn is_no_mangle(&self) -> bool { |
| self.has_attr_word("no_mangle") |
| } |
| |
| /// Sees whether we should skip parsing a given item. |
| fn should_skip_parsing(&self) -> bool { |
| for attr in self.attrs() { |
| if is_skip_item_attr(&attr.meta) { |
| return true; |
| } |
| } |
| |
| false |
| } |
| |
| fn attr_name_value_lookup(&self, name: &str) -> Option<String> { |
| self.attrs() |
| .iter() |
| .filter_map(|attr| { |
| if let syn::Meta::NameValue(syn::MetaNameValue { |
| path, |
| value: |
| syn::Expr::Lit(syn::ExprLit { |
| lit: syn::Lit::Str(lit), |
| .. |
| }), |
| .. |
| }) = &attr.meta |
| { |
| if path.is_ident(name) { |
| return Some(lit.value()); |
| } |
| } |
| None |
| }) |
| .next() |
| } |
| |
| fn get_comment_lines(&self) -> Vec<String> { |
| let mut comment = Vec::new(); |
| |
| for attr in self.attrs() { |
| if attr.style == syn::AttrStyle::Outer { |
| if let syn::Meta::NameValue(syn::MetaNameValue { |
| path, |
| value: |
| syn::Expr::Lit(syn::ExprLit { |
| lit: syn::Lit::Str(content), |
| .. |
| }), |
| .. |
| }) = &attr.meta |
| { |
| if path.is_ident("doc") { |
| comment.extend(split_doc_attr(&content.value())); |
| } |
| } |
| } |
| } |
| |
| comment |
| } |
| } |
| |
| macro_rules! syn_item_match_helper { |
| ($s:ident => has_attrs: |$i:ident| $a:block, otherwise: || $b:block) => { |
| match *$s { |
| syn::Item::Const(ref $i) => $a, |
| syn::Item::Enum(ref $i) => $a, |
| syn::Item::ExternCrate(ref $i) => $a, |
| syn::Item::Fn(ref $i) => $a, |
| syn::Item::ForeignMod(ref $i) => $a, |
| syn::Item::Impl(ref $i) => $a, |
| syn::Item::Macro(ref $i) => $a, |
| syn::Item::Mod(ref $i) => $a, |
| syn::Item::Static(ref $i) => $a, |
| syn::Item::Struct(ref $i) => $a, |
| syn::Item::Trait(ref $i) => $a, |
| syn::Item::Type(ref $i) => $a, |
| syn::Item::Union(ref $i) => $a, |
| syn::Item::Use(ref $i) => $a, |
| syn::Item::TraitAlias(ref $i) => $a, |
| syn::Item::Verbatim(_) => $b, |
| _ => panic!("Unhandled syn::Item: {:?}", $s), |
| } |
| }; |
| } |
| |
| impl SynAttributeHelpers for syn::Item { |
| fn attrs(&self) -> &[syn::Attribute] { |
| syn_item_match_helper!(self => |
| has_attrs: |item| { &item.attrs }, |
| otherwise: || { &[] } |
| ) |
| } |
| } |
| |
| macro_rules! impl_syn_item_helper { |
| ($t:ty) => { |
| impl SynAttributeHelpers for $t { |
| fn attrs(&self) -> &[syn::Attribute] { |
| &self.attrs |
| } |
| } |
| }; |
| } |
| |
| impl_syn_item_helper!(syn::ItemExternCrate); |
| impl_syn_item_helper!(syn::ItemUse); |
| impl_syn_item_helper!(syn::ItemStatic); |
| impl_syn_item_helper!(syn::ItemConst); |
| impl_syn_item_helper!(syn::ItemFn); |
| impl_syn_item_helper!(syn::ImplItemConst); |
| impl_syn_item_helper!(syn::ImplItemFn); |
| impl_syn_item_helper!(syn::ItemMod); |
| impl_syn_item_helper!(syn::ItemForeignMod); |
| impl_syn_item_helper!(syn::ItemType); |
| impl_syn_item_helper!(syn::ItemStruct); |
| impl_syn_item_helper!(syn::ItemEnum); |
| impl_syn_item_helper!(syn::ItemUnion); |
| impl_syn_item_helper!(syn::ItemTrait); |
| impl_syn_item_helper!(syn::ItemImpl); |
| impl_syn_item_helper!(syn::ItemMacro); |
| impl_syn_item_helper!(syn::ItemTraitAlias); |
| |
| /// Helper function for accessing Abi information |
| pub trait SynAbiHelpers { |
| fn is_c(&self) -> bool; |
| fn is_omitted(&self) -> bool; |
| } |
| |
| impl SynAbiHelpers for Option<syn::Abi> { |
| fn is_c(&self) -> bool { |
| if let Some(ref abi) = *self { |
| if let Some(ref lit_string) = abi.name { |
| return matches!(lit_string.value().as_str(), "C" | "C-unwind"); |
| } |
| } |
| false |
| } |
| fn is_omitted(&self) -> bool { |
| if let Some(ref abi) = *self { |
| abi.name.is_none() |
| } else { |
| false |
| } |
| } |
| } |
| |
| impl SynAbiHelpers for syn::Abi { |
| fn is_c(&self) -> bool { |
| if let Some(ref lit_string) = self.name { |
| matches!(lit_string.value().as_str(), "C" | "C-unwind") |
| } else { |
| false |
| } |
| } |
| fn is_omitted(&self) -> bool { |
| self.name.is_none() |
| } |
| } |
| |
| impl SynAttributeHelpers for [syn::Attribute] { |
| fn attrs(&self) -> &[syn::Attribute] { |
| self |
| } |
| } |
| |
| fn split_doc_attr(input: &str) -> Vec<String> { |
| input |
| // Convert two newline (indicate "new paragraph") into two line break. |
| .replace("\n\n", " \n \n") |
| // Convert newline after two spaces (indicate "line break") into line break. |
| .split(" \n") |
| // Convert single newline (indicate hard-wrapped) into space. |
| .map(|s| s.replace('\n', " ")) |
| .map(|s| s.trim_end().to_string()) |
| .collect() |
| } |