summaryrefslogtreecommitdiffstats
path: root/src/bindgen/utilities.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bindgen/utilities.rs')
-rw-r--r--src/bindgen/utilities.rs348
1 files changed, 348 insertions, 0 deletions
diff --git a/src/bindgen/utilities.rs b/src/bindgen/utilities.rs
new file mode 100644
index 0000000..0e314b9
--- /dev/null
+++ b/src/bindgen/utilities.rs
@@ -0,0 +1,348 @@
+/* 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::ImplItemMethod {
+ 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;
+ }
+ 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::Lit::Str(ref content) = name_value.lit {
+ // 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()
+ .filter_map(|x| x.parse_meta().ok())
+ .any(|attr| {
+ if let syn::Meta::Path(ref path) = attr {
+ 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 Ok(syn::Meta::List(list)) = attr.parse_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::Lit::Str(ref lit) = arg.lit {
+ 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() {
+ let meta = match attr.parse_meta() {
+ Ok(attr) => attr,
+ Err(..) => return false,
+ };
+ if is_skip_item_attr(&meta) {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ fn attr_name_value_lookup(&self, name: &str) -> Option<String> {
+ self.attrs()
+ .iter()
+ .filter_map(|attr| {
+ let attr = attr.parse_meta().ok()?;
+ if let syn::Meta::NameValue(syn::MetaNameValue {
+ path,
+ lit: syn::Lit::Str(lit),
+ ..
+ }) = attr
+ {
+ 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 Ok(syn::Meta::NameValue(syn::MetaNameValue {
+ path,
+ lit: syn::Lit::Str(content),
+ ..
+ })) = attr.parse_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::Macro2(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::ImplItemMethod);
+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::ItemMacro2);
+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()
+}