diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:39:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:39:07 +0000 |
commit | af6b8ed095f88f1df2116cdc7a9d44872cfa6074 (patch) | |
tree | 1f2df671c1f8033d5ed83f056167a0911f8d2a57 /src | |
parent | Initial commit. (diff) | |
download | rust-cbindgen-upstream/0.26.0.tar.xz rust-cbindgen-upstream/0.26.0.zip |
Adding upstream version 0.26.0.upstream/0.26.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
44 files changed, 13973 insertions, 0 deletions
diff --git a/src/bindgen/bindings.rs b/src/bindgen/bindings.rs new file mode 100644 index 0000000..410b11f --- /dev/null +++ b/src/bindgen/bindings.rs @@ -0,0 +1,537 @@ +/* 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/. */ + +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fs; +use std::fs::File; +use std::io::{Read, Write}; +use std::path; +use std::rc::Rc; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::ir::{ + Constant, Function, ItemContainer, ItemMap, Path as BindgenPath, Static, Struct, Typedef, +}; +use crate::bindgen::writer::{Source, SourceWriter}; + +/// A bindings header that can be written. +pub struct Bindings { + pub config: Config, + /// The map from path to struct, used to lookup whether a given type is a + /// transparent struct. This is needed to generate code for constants. + struct_map: ItemMap<Struct>, + typedef_map: ItemMap<Typedef>, + struct_fileds_memo: RefCell<HashMap<BindgenPath, Rc<Vec<String>>>>, + globals: Vec<Static>, + constants: Vec<Constant>, + items: Vec<ItemContainer>, + functions: Vec<Function>, + source_files: Vec<path::PathBuf>, + /// Bindings are generated by a recursive call to cbindgen + /// and shouldn't do anything when written anywhere. + noop: bool, +} + +#[derive(PartialEq, Eq)] +enum NamespaceOperation { + Open, + Close, +} + +impl Bindings { + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + config: Config, + struct_map: ItemMap<Struct>, + typedef_map: ItemMap<Typedef>, + constants: Vec<Constant>, + globals: Vec<Static>, + items: Vec<ItemContainer>, + functions: Vec<Function>, + source_files: Vec<path::PathBuf>, + noop: bool, + ) -> Bindings { + Bindings { + config, + struct_map, + typedef_map, + struct_fileds_memo: Default::default(), + globals, + constants, + items, + functions, + source_files, + noop, + } + } + + // FIXME(emilio): What to do when the configuration doesn't match? + pub fn struct_is_transparent(&self, path: &BindgenPath) -> bool { + let mut any = false; + self.struct_map.for_items(path, |s| any |= s.is_transparent); + any + } + + /// Peels through typedefs to allow resolving structs. + fn resolved_struct_path<'a>(&self, path: &'a BindgenPath) -> Cow<'a, BindgenPath> { + use crate::bindgen::ir::Type; + + let mut resolved_path = Cow::Borrowed(path); + loop { + let mut found = None; + self.typedef_map.for_items(&resolved_path, |item| { + if let Type::Path(ref p) = item.aliased { + found = Some(p.path().clone()); + } + }); + resolved_path = match found { + Some(p) => Cow::Owned(p), + None => break, + } + } + resolved_path + } + + pub fn struct_exists(&self, path: &BindgenPath) -> bool { + let mut any = false; + self.struct_map + .for_items(&self.resolved_struct_path(path), |_| any = true); + any + } + + pub fn struct_field_names(&self, path: &BindgenPath) -> Rc<Vec<String>> { + let mut memos = self.struct_fileds_memo.borrow_mut(); + if let Some(memo) = memos.get(path) { + return memo.clone(); + } + + let resolved_path = self.resolved_struct_path(path); + + let mut fields = Vec::<String>::new(); + self.struct_map.for_items(&resolved_path, |st| { + let mut pos: usize = 0; + for field in &st.fields { + if let Some(found_pos) = fields.iter().position(|v| *v == field.name) { + pos = found_pos + 1; + } else { + fields.insert(pos, field.name.clone()); + pos += 1; + } + } + }); + + let fields = Rc::new(fields); + memos.insert(path.clone(), fields.clone()); + if let Cow::Owned(p) = resolved_path { + memos.insert(p, fields.clone()); + } + fields + } + + pub fn generate_depfile<P: AsRef<path::Path>>(&self, header_path: P, depfile_path: P) { + if let Some(dir) = depfile_path.as_ref().parent() { + if !dir.exists() { + std::fs::create_dir_all(dir).unwrap() + } + } + let canon_header_path = header_path.as_ref().canonicalize().unwrap(); + let mut canon_source_files: Vec<_> = self + .source_files + .iter() + .chain(self.config.config_path.as_ref()) + .map(|p| p.canonicalize().unwrap()) + .collect(); + // Sorting makes testing easier by ensuring the output is ordered. + canon_source_files.sort_unstable(); + + // When writing the depfile we must escape whitespace in paths to avoid it being interpreted + // as a seperator. + // It is not clear how to otherwise _correctly_ replace whitespace in a non-unicode + // compliant slice, without knowing the encoding, so we lossy convert such cases, + // to avoid panics. + let mut depfile = File::create(depfile_path).unwrap(); + write!( + &mut depfile, + "{}:", + canon_header_path.to_string_lossy().replace(' ', "\\ ") + ) + .expect("Writing header name to depfile failed"); + canon_source_files.into_iter().for_each(|source_file| { + // Add line-continue and line-break and then indent with 4 spaces. + // This makes the output more human-readable. + depfile.write_all(b" \\\n ").unwrap(); + let escaped_path = source_file.to_string_lossy().replace(' ', "\\ "); + depfile.write_all(escaped_path.as_bytes()).unwrap(); + }); + + writeln!(&mut depfile).unwrap(); + + depfile.flush().unwrap(); + } + + pub fn write_to_file<P: AsRef<path::Path>>(&self, path: P) -> bool { + if self.noop { + return false; + } + + // Don't compare files if we've never written this file before + if !path.as_ref().is_file() { + if let Some(parent) = path::Path::new(path.as_ref()).parent() { + fs::create_dir_all(parent).unwrap(); + } + self.write(File::create(path).unwrap()); + return true; + } + + let mut new_file_contents = Vec::new(); + self.write(&mut new_file_contents); + + let mut old_file_contents = Vec::new(); + { + let mut old_file = File::open(&path).unwrap(); + old_file.read_to_end(&mut old_file_contents).unwrap(); + } + + if old_file_contents != new_file_contents { + let mut new_file = File::create(&path).unwrap(); + new_file.write_all(&new_file_contents).unwrap(); + true + } else { + false + } + } + + pub fn write_headers<F: Write>(&self, out: &mut SourceWriter<F>) { + if self.noop { + return; + } + + if let Some(ref f) = self.config.header { + out.new_line_if_not_start(); + write!(out, "{}", f); + out.new_line(); + } + if let Some(f) = self.config.include_guard() { + out.new_line_if_not_start(); + write!(out, "#ifndef {}", f); + out.new_line(); + write!(out, "#define {}", f); + out.new_line(); + } + if self.config.pragma_once && self.config.language != Language::Cython { + out.new_line_if_not_start(); + write!(out, "#pragma once"); + out.new_line(); + } + if self.config.include_version { + out.new_line_if_not_start(); + write!( + out, + "/* Generated with cbindgen:{} */", + crate::bindgen::config::VERSION + ); + out.new_line(); + } + if let Some(ref f) = self.config.autogen_warning { + out.new_line_if_not_start(); + write!(out, "{}", f); + out.new_line(); + } + + if self.config.no_includes + && self.config.sys_includes().is_empty() + && self.config.includes().is_empty() + && (self.config.cython.cimports.is_empty() || self.config.language != Language::Cython) + && self.config.after_includes.is_none() + { + return; + } + + out.new_line_if_not_start(); + + if !self.config.no_includes { + match self.config.language { + Language::C => { + out.write("#include <stdarg.h>"); + out.new_line(); + out.write("#include <stdbool.h>"); + out.new_line(); + if self.config.usize_is_size_t { + out.write("#include <stddef.h>"); + out.new_line(); + } + out.write("#include <stdint.h>"); + out.new_line(); + out.write("#include <stdlib.h>"); + out.new_line(); + } + Language::Cxx => { + out.write("#include <cstdarg>"); + out.new_line(); + if self.config.usize_is_size_t { + out.write("#include <cstddef>"); + out.new_line(); + } + out.write("#include <cstdint>"); + out.new_line(); + out.write("#include <cstdlib>"); + out.new_line(); + out.write("#include <ostream>"); + out.new_line(); + out.write("#include <new>"); + out.new_line(); + if self.config.enumeration.cast_assert_name.is_none() + && (self.config.enumeration.derive_mut_casts + || self.config.enumeration.derive_const_casts) + { + out.write("#include <cassert>"); + out.new_line(); + } + } + Language::Cython => { + out.write( + "from libc.stdint cimport int8_t, int16_t, int32_t, int64_t, intptr_t", + ); + out.new_line(); + out.write( + "from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, uintptr_t", + ); + out.new_line(); + out.write("cdef extern from *"); + out.open_brace(); + out.write("ctypedef bint bool"); + out.new_line(); + out.write("ctypedef struct va_list"); + out.new_line(); + out.close_brace(false); + } + } + } + + for include in self.config.sys_includes() { + write!(out, "#include <{}>", include); + out.new_line(); + } + + for include in self.config.includes() { + write!(out, "#include \"{}\"", include); + out.new_line(); + } + + if self.config.language == Language::Cython { + for (module, names) in &self.config.cython.cimports { + write!(out, "from {} cimport {}", module, names.join(", ")); + out.new_line(); + } + } + + if let Some(ref line) = self.config.after_includes { + write!(out, "{}", line); + out.new_line(); + } + } + + pub fn write<F: Write>(&self, file: F) { + if self.noop { + return; + } + + let mut out = SourceWriter::new(file, self); + + self.write_headers(&mut out); + + self.open_namespaces(&mut out); + + for constant in &self.constants { + if constant.uses_only_primitive_types() { + out.new_line_if_not_start(); + constant.write(&self.config, &mut out, None); + out.new_line(); + } + } + + for item in &self.items { + if item + .deref() + .annotations() + .bool("no-export") + .unwrap_or(false) + { + continue; + } + + out.new_line_if_not_start(); + match *item { + ItemContainer::Constant(..) => unreachable!(), + ItemContainer::Static(..) => unreachable!(), + ItemContainer::Enum(ref x) => x.write(&self.config, &mut out), + ItemContainer::Struct(ref x) => x.write(&self.config, &mut out), + ItemContainer::Union(ref x) => x.write(&self.config, &mut out), + ItemContainer::OpaqueItem(ref x) => x.write(&self.config, &mut out), + ItemContainer::Typedef(ref x) => x.write(&self.config, &mut out), + } + out.new_line(); + } + + for constant in &self.constants { + if !constant.uses_only_primitive_types() { + out.new_line_if_not_start(); + constant.write(&self.config, &mut out, None); + out.new_line(); + } + } + + if !self.functions.is_empty() || !self.globals.is_empty() { + if self.config.cpp_compatible_c() { + out.new_line_if_not_start(); + out.write("#ifdef __cplusplus"); + } + + if self.config.language == Language::Cxx { + if let Some(ref using_namespaces) = self.config.using_namespaces { + for namespace in using_namespaces { + out.new_line(); + write!(out, "using namespace {};", namespace); + } + out.new_line(); + } + } + + if self.config.language == Language::Cxx || self.config.cpp_compatible_c() { + out.new_line(); + out.write("extern \"C\" {"); + out.new_line(); + } + + if self.config.cpp_compatible_c() { + out.write("#endif // __cplusplus"); + out.new_line(); + } + + for global in &self.globals { + out.new_line_if_not_start(); + global.write(&self.config, &mut out); + out.new_line(); + } + + for function in &self.functions { + out.new_line_if_not_start(); + function.write(&self.config, &mut out); + out.new_line(); + } + + if self.config.cpp_compatible_c() { + out.new_line(); + out.write("#ifdef __cplusplus"); + } + + if self.config.language == Language::Cxx || self.config.cpp_compatible_c() { + out.new_line(); + out.write("} // extern \"C\""); + out.new_line(); + } + + if self.config.cpp_compatible_c() { + out.write("#endif // __cplusplus"); + out.new_line(); + } + } + + if self.config.language == Language::Cython + && self.globals.is_empty() + && self.constants.is_empty() + && self.items.is_empty() + && self.functions.is_empty() + { + out.write("pass"); + } + + self.close_namespaces(&mut out); + + if let Some(f) = self.config.include_guard() { + out.new_line_if_not_start(); + if self.config.language == Language::C { + write!(out, "#endif /* {} */", f); + } else { + write!(out, "#endif // {}", f); + } + out.new_line(); + } + if let Some(ref f) = self.config.trailer { + out.new_line_if_not_start(); + write!(out, "{}", f); + if !f.ends_with('\n') { + out.new_line(); + } + } + } + + fn all_namespaces(&self) -> Vec<&str> { + if self.config.language != Language::Cxx && !self.config.cpp_compatible_c() { + return vec![]; + } + let mut ret = vec![]; + if let Some(ref namespace) = self.config.namespace { + ret.push(&**namespace); + } + if let Some(ref namespaces) = self.config.namespaces { + for namespace in namespaces { + ret.push(&**namespace); + } + } + ret + } + + fn open_close_namespaces<F: Write>(&self, op: NamespaceOperation, out: &mut SourceWriter<F>) { + if self.config.language == Language::Cython { + if op == NamespaceOperation::Open { + out.new_line(); + let header = self.config.cython.header.as_deref().unwrap_or("*"); + write!(out, "cdef extern from {}", header); + out.open_brace(); + } else { + out.close_brace(false); + } + return; + } + + let mut namespaces = self.all_namespaces(); + if namespaces.is_empty() { + return; + } + + if op == NamespaceOperation::Close { + namespaces.reverse(); + } + + if self.config.cpp_compatible_c() { + out.new_line_if_not_start(); + out.write("#ifdef __cplusplus"); + } + + for namespace in namespaces { + out.new_line(); + match op { + NamespaceOperation::Open => write!(out, "namespace {} {{", namespace), + NamespaceOperation::Close => write!(out, "}} // namespace {}", namespace), + } + } + + out.new_line(); + if self.config.cpp_compatible_c() { + out.write("#endif // __cplusplus"); + out.new_line(); + } + } + + pub(crate) fn open_namespaces<F: Write>(&self, out: &mut SourceWriter<F>) { + self.open_close_namespaces(NamespaceOperation::Open, out); + } + + pub(crate) fn close_namespaces<F: Write>(&self, out: &mut SourceWriter<F>) { + self.open_close_namespaces(NamespaceOperation::Close, out); + } +} diff --git a/src/bindgen/bitflags.rs b/src/bindgen/bitflags.rs new file mode 100644 index 0000000..2ca0be6 --- /dev/null +++ b/src/bindgen/bitflags.rs @@ -0,0 +1,289 @@ +/* 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/. */ + +use proc_macro2::TokenStream; +use std::collections::HashSet; +use syn::fold::Fold; +use syn::parse::{Parse, ParseStream, Parser, Result as ParseResult}; + +// $(#[$outer:meta])* +// ($($vis:tt)*) $BitFlags:ident: $T:ty { +// $( +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +// )+ +// } +#[derive(Debug)] +pub struct BitflagsStruct { + attrs: Vec<syn::Attribute>, + vis: syn::Visibility, + #[allow(dead_code)] + struct_token: Token![struct], + name: syn::Ident, + #[allow(dead_code)] + colon_token: Token![:], + repr: syn::Type, + flags: Flags, +} + +// impl $BitFlags:ident: $T:ty { +// $( +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +// )+ +// } +#[derive(Debug)] +pub struct BitflagsImpl { + #[allow(dead_code)] + impl_token: Token![impl], + name: syn::Ident, + #[allow(dead_code)] + colon_token: Token![:], + repr: syn::Type, + flags: Flags, +} + +#[derive(Debug)] +pub enum Bitflags { + Struct(BitflagsStruct), + Impl(BitflagsImpl), +} + +impl Bitflags { + pub fn expand(&self) -> (Option<syn::ItemStruct>, syn::ItemImpl) { + match self { + Bitflags::Struct(BitflagsStruct { + attrs, + vis, + name, + repr, + flags, + .. + }) => { + let struct_ = parse_quote! { + #(#attrs)* + #vis struct #name { + bits: #repr, + } + }; + + let consts = flags.expand(name, repr, false); + let impl_ = parse_quote! { + impl #name { + #consts + } + }; + + (Some(struct_), impl_) + } + Bitflags::Impl(BitflagsImpl { + name, repr, flags, .. + }) => { + let consts = flags.expand(name, repr, true); + let impl_: syn::ItemImpl = parse_quote! { + impl #name { + #consts + } + }; + (None, impl_) + } + } + } +} + +impl Parse for Bitflags { + fn parse(input: ParseStream) -> ParseResult<Self> { + Ok(if input.peek(Token![impl]) { + Self::Impl(BitflagsImpl { + impl_token: input.parse()?, + name: input.parse()?, + colon_token: input.parse()?, + repr: input.parse()?, + flags: input.parse()?, + }) + } else { + Self::Struct(BitflagsStruct { + attrs: input.call(syn::Attribute::parse_outer)?, + vis: input.parse()?, + struct_token: input.parse()?, + name: input.parse()?, + colon_token: input.parse()?, + repr: input.parse()?, + flags: input.parse()?, + }) + }) + } +} + +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +#[derive(Debug)] +struct Flag { + attrs: Vec<syn::Attribute>, + #[allow(dead_code)] + const_token: Token![const], + name: syn::Ident, + #[allow(dead_code)] + equals_token: Token![=], + value: syn::Expr, + #[allow(dead_code)] + semicolon_token: Token![;], +} + +struct FlagValueFold<'a> { + struct_name: &'a syn::Ident, + flag_names: &'a HashSet<String>, + out_of_line: bool, +} + +impl<'a> FlagValueFold<'a> { + fn is_self(&self, ident: &syn::Ident) -> bool { + ident == self.struct_name || ident == "Self" + } +} + +impl<'a> Fold for FlagValueFold<'a> { + fn fold_expr(&mut self, node: syn::Expr) -> syn::Expr { + // bitflags 2 doesn't expose `bits` publically anymore, and the documented way to + // combine flags is using the `bits` method, e.g. + // ``` + // bitflags! { + // struct Flags: u8 { + // const A = 1; + // const B = 1 << 1; + // const AB = Flags::A.bits() | Flags::B.bits(); + // } + // } + // ``` + // As we're transforming the struct definition into `struct StructName { bits: T }` + // as far as our bindings generation is concerned, `bits` is available as a field, + // so by replacing `StructName::FLAG.bits()` with `StructName::FLAG.bits`, we make + // e.g. `Flags::AB` available in the generated bindings. + // For out-of-line definitions of the struct(*), where the struct is defined as a + // newtype, we replace it with `StructName::FLAGS.0`. + // * definitions like: + // ``` + // struct Flags(u8); + // bitflags! { + // impl Flags: u8 { + // const A = 1; + // const B = 1 << 1; + // const AB = Flags::A.bits() | Flags::B.bits(); + // } + // } + // ``` + match node { + syn::Expr::MethodCall(syn::ExprMethodCall { + attrs, + receiver, + dot_token, + method, + args, + .. + }) if method == "bits" + && args.is_empty() + && matches!(&*receiver, + syn::Expr::Path(syn::ExprPath { path, .. }) + if path.segments.len() == 2 + && self.is_self(&path.segments.first().unwrap().ident) + && self + .flag_names + .contains(&path.segments.last().unwrap().ident.to_string())) => + { + return syn::Expr::Field(syn::ExprField { + attrs, + base: receiver, + dot_token, + member: if self.out_of_line { + syn::Member::Unnamed(parse_quote! {0}) + } else { + syn::Member::Named(method) + }, + }); + } + _ => {} + } + syn::fold::fold_expr(self, node) + } +} + +impl Flag { + fn expand( + &self, + struct_name: &syn::Ident, + repr: &syn::Type, + flag_names: &HashSet<String>, + out_of_line: bool, + ) -> TokenStream { + let Flag { + ref attrs, + ref name, + ref value, + .. + } = *self; + let folded_value = FlagValueFold { + struct_name, + flag_names, + out_of_line, + } + .fold_expr(value.clone()); + let value = if out_of_line { + quote! { ((#folded_value) as #repr) } + } else { + quote! { { bits: (#folded_value) as #repr } } + }; + quote! { + #(#attrs)* + pub const #name : #struct_name = #struct_name #value; + } + } +} + +impl Parse for Flag { + fn parse(input: ParseStream) -> ParseResult<Self> { + Ok(Self { + attrs: input.call(syn::Attribute::parse_outer)?, + const_token: input.parse()?, + name: input.parse()?, + equals_token: input.parse()?, + value: input.parse()?, + semicolon_token: input.parse()?, + }) + } +} + +#[derive(Debug)] +struct Flags(Vec<Flag>); + +impl Parse for Flags { + fn parse(input: ParseStream) -> ParseResult<Self> { + let content; + let _ = braced!(content in input); + let mut flags = vec![]; + while !content.is_empty() { + flags.push(content.parse()?); + } + Ok(Flags(flags)) + } +} + +impl Flags { + fn expand(&self, struct_name: &syn::Ident, repr: &syn::Type, out_of_line: bool) -> TokenStream { + let mut ts = quote! {}; + let flag_names = self + .0 + .iter() + .map(|flag| flag.name.to_string()) + .collect::<HashSet<_>>(); + for flag in &self.0 { + ts.extend(flag.expand(struct_name, repr, &flag_names, out_of_line)); + } + ts + } +} + +pub fn parse(tokens: TokenStream) -> ParseResult<Bitflags> { + let parser = Bitflags::parse; + parser.parse2(tokens) +} diff --git a/src/bindgen/builder.rs b/src/bindgen/builder.rs new file mode 100644 index 0000000..a0328b4 --- /dev/null +++ b/src/bindgen/builder.rs @@ -0,0 +1,424 @@ +/* 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/. */ + +use std::path; + +use crate::bindgen::bindings::Bindings; +use crate::bindgen::cargo::Cargo; +use crate::bindgen::config::{Braces, Config, Language, Profile, Style}; +use crate::bindgen::error::Error; +use crate::bindgen::library::Library; +use crate::bindgen::parser::{self, Parse}; + +/// A builder for generating a bindings header. +#[derive(Debug, Clone)] +pub struct Builder { + config: Config, + srcs: Vec<path::PathBuf>, + lib: Option<(path::PathBuf, Option<String>)>, + lib_cargo: Option<Cargo>, + std_types: bool, + lockfile: Option<path::PathBuf>, +} + +impl Builder { + #[allow(clippy::new_without_default)] + pub fn new() -> Builder { + Builder { + config: Config::default(), + srcs: Vec::new(), + lib: None, + lib_cargo: None, + std_types: true, + lockfile: None, + } + } + + #[allow(unused)] + pub fn with_header<S: AsRef<str>>(mut self, header: S) -> Builder { + self.config.header = Some(String::from(header.as_ref())); + self + } + + #[allow(unused)] + pub fn with_no_includes(mut self) -> Builder { + self.config.no_includes = true; + self + } + + #[allow(unused)] + pub fn with_include<S: AsRef<str>>(mut self, include: S) -> Builder { + self.config.includes.push(String::from(include.as_ref())); + self + } + + #[allow(unused)] + pub fn with_sys_include<S: AsRef<str>>(mut self, include: S) -> Builder { + self.config + .sys_includes + .push(String::from(include.as_ref())); + self + } + + #[allow(unused)] + pub fn with_after_include<S: AsRef<str>>(mut self, line: S) -> Builder { + self.config.after_includes = Some(String::from(line.as_ref())); + self + } + + #[allow(unused)] + pub fn with_trailer<S: AsRef<str>>(mut self, trailer: S) -> Builder { + self.config.trailer = Some(String::from(trailer.as_ref())); + self + } + + #[allow(unused)] + pub fn with_include_guard<S: AsRef<str>>(mut self, include_guard: S) -> Builder { + self.config.include_guard = Some(String::from(include_guard.as_ref())); + self + } + + #[allow(unused)] + pub fn with_pragma_once(mut self, pragma_once: bool) -> Builder { + self.config.pragma_once = pragma_once; + self + } + + #[allow(unused)] + pub fn with_autogen_warning<S: AsRef<str>>(mut self, autogen_warning: S) -> Builder { + self.config.autogen_warning = Some(String::from(autogen_warning.as_ref())); + self + } + + #[allow(unused)] + pub fn with_include_version(mut self, include_version: bool) -> Builder { + self.config.include_version = include_version; + self + } + + #[allow(unused)] + pub fn with_namespace<S: AsRef<str>>(mut self, namespace: S) -> Builder { + self.config.namespace = Some(String::from(namespace.as_ref())); + self + } + + #[allow(unused)] + pub fn with_namespaces<S: AsRef<str>>(mut self, namespaces: &[S]) -> Builder { + self.config.namespaces = Some( + namespaces + .iter() + .map(|x| String::from(x.as_ref())) + .collect(), + ); + self + } + + #[allow(unused)] + pub fn with_using_namespaces<S: AsRef<str>>(mut self, namespaces: &[S]) -> Builder { + self.config.using_namespaces = Some( + namespaces + .iter() + .map(|x| String::from(x.as_ref())) + .collect(), + ); + self + } + + #[allow(unused)] + pub fn with_braces(mut self, braces: Braces) -> Builder { + self.config.braces = braces; + self + } + + #[allow(unused)] + pub fn with_line_length(mut self, line_length: usize) -> Builder { + self.config.line_length = line_length; + self + } + + #[allow(unused)] + pub fn with_tab_width(mut self, tab_width: usize) -> Builder { + self.config.tab_width = tab_width; + self + } + + #[allow(unused)] + pub fn with_language(mut self, language: Language) -> Builder { + self.config.language = language; + self + } + + #[allow(unused)] + pub fn with_cpp_compat(mut self, cpp_compat: bool) -> Builder { + self.config.cpp_compat = cpp_compat; + self + } + + #[allow(unused)] + pub fn with_style(mut self, style: Style) -> Builder { + self.config.style = style; + self + } + + #[allow(unused)] + pub fn include_item<S: AsRef<str>>(mut self, item_name: S) -> Builder { + self.config + .export + .include + .push(String::from(item_name.as_ref())); + self + } + + #[allow(unused)] + pub fn exclude_item<S: AsRef<str>>(mut self, item_name: S) -> Builder { + self.config + .export + .exclude + .push(String::from(item_name.as_ref())); + self + } + + #[allow(unused)] + pub fn rename_item<S: AsRef<str>>(mut self, from: S, to: S) -> Builder { + self.config + .export + .rename + .insert(String::from(from.as_ref()), String::from(to.as_ref())); + self + } + + #[allow(unused)] + pub fn with_item_prefix<S: AsRef<str>>(mut self, prefix: S) -> Builder { + self.config.export.prefix = Some(String::from(prefix.as_ref())); + self + } + + #[allow(unused)] + pub fn with_parse_deps(mut self, parse_deps: bool) -> Builder { + self.config.parse.parse_deps = parse_deps; + self + } + + #[allow(unused)] + pub fn with_parse_include<S: AsRef<str>>(mut self, include: &[S]) -> Builder { + self.config.parse.include = + Some(include.iter().map(|x| String::from(x.as_ref())).collect()); + self + } + + #[allow(unused)] + pub fn with_parse_exclude<S: AsRef<str>>(mut self, exclude: &[S]) -> Builder { + self.config.parse.exclude = exclude.iter().map(|x| String::from(x.as_ref())).collect(); + self + } + + #[allow(unused)] + pub fn with_parse_expand<S: AsRef<str>>(mut self, expand: &[S]) -> Builder { + self.config.parse.expand.crates = expand.iter().map(|x| String::from(x.as_ref())).collect(); + self + } + + #[allow(unused)] + pub fn with_parse_expand_all_features(mut self, expand_all_features: bool) -> Builder { + self.config.parse.expand.all_features = expand_all_features; + self + } + + #[allow(unused)] + pub fn with_parse_expand_default_features(mut self, expand_default_features: bool) -> Builder { + self.config.parse.expand.default_features = expand_default_features; + self + } + + #[allow(unused)] + pub fn with_parse_expand_features<S: AsRef<str>>(mut self, expand_features: &[S]) -> Builder { + self.config.parse.expand.features = Some( + expand_features + .iter() + .map(|x| String::from(x.as_ref())) + .collect(), + ); + self + } + + #[allow(unused)] + pub fn with_parse_expand_profile(mut self, profile: Profile) -> Builder { + self.config.parse.expand.profile = profile; + self + } + + #[allow(unused)] + pub fn with_parse_extra_bindings<S: AsRef<str>>(mut self, extra_bindings: &[S]) -> Builder { + self.config.parse.extra_bindings = extra_bindings + .iter() + .map(|x| String::from(x.as_ref())) + .collect(); + self + } + + #[allow(unused)] + pub fn with_only_target_dependencies(mut self, only_target_dependencies: bool) -> Builder { + self.config.only_target_dependencies = only_target_dependencies; + self + } + + #[allow(unused)] + pub fn with_documentation(mut self, documentation: bool) -> Builder { + self.config.documentation = documentation; + self + } + + #[allow(unused)] + pub fn with_target_os_define(mut self, platform: &str, preprocessor_define: &str) -> Builder { + self.config.defines.insert( + format!("target_os = {}", platform), + preprocessor_define.to_owned(), + ); + self + } + + #[allow(unused)] + pub fn with_define(mut self, key: &str, value: &str, preprocessor_define: &str) -> Builder { + self.config.defines.insert( + format!("{} = {}", key, value), + preprocessor_define.to_owned(), + ); + self + } + + #[allow(unused)] + pub fn with_config(mut self, config: Config) -> Builder { + self.config = config; + self + } + + #[allow(unused)] + pub fn with_std_types(mut self, std_types: bool) -> Builder { + self.std_types = std_types; + self + } + + #[allow(unused)] + pub fn with_src<P: AsRef<path::Path>>(mut self, src: P) -> Builder { + self.srcs.push(src.as_ref().to_owned()); + self + } + + #[allow(unused)] + pub fn with_crate<P: AsRef<path::Path>>(mut self, lib_dir: P) -> Builder { + debug_assert!(self.lib.is_none()); + debug_assert!(self.lib_cargo.is_none()); + self.lib = Some((path::PathBuf::from(lib_dir.as_ref()), None)); + self + } + + #[allow(unused)] + pub fn with_crate_and_name<P: AsRef<path::Path>, S: AsRef<str>>( + mut self, + lib_dir: P, + binding_lib_name: S, + ) -> Builder { + debug_assert!(self.lib.is_none()); + debug_assert!(self.lib_cargo.is_none()); + self.lib = Some(( + path::PathBuf::from(lib_dir.as_ref()), + Some(String::from(binding_lib_name.as_ref())), + )); + self + } + + #[allow(unused)] + pub(crate) fn with_cargo(mut self, lib: Cargo) -> Builder { + debug_assert!(self.lib.is_none()); + debug_assert!(self.lib_cargo.is_none()); + self.lib_cargo = Some(lib); + self + } + + #[allow(unused)] + pub fn with_lockfile<P: AsRef<path::Path>>(mut self, lockfile: P) -> Builder { + debug_assert!(self.lockfile.is_none()); + debug_assert!(self.lib_cargo.is_none()); + self.lockfile = Some(path::PathBuf::from(lockfile.as_ref())); + self + } + + pub fn generate(self) -> Result<Bindings, Error> { + // If macro expansion is enabled, then cbindgen will attempt to build the crate + // and will run its build script which may run cbindgen again. That second run may start + // infinite recursion, or overwrite previously written files with bindings. + // So if we are called recursively, we are skipping the whole generation + // and produce "noop" bindings that won't be able to overwrite anything. + if std::env::var("_CBINDGEN_IS_RUNNING").is_ok() { + return Ok(Bindings::new( + self.config, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + true, + )); + } + + let mut result = Parse::new(); + + if self.std_types { + result.add_std_types(); + } + + for x in &self.srcs { + result.extend_with(&parser::parse_src(x, &self.config)?); + } + + if let Some((lib_dir, binding_lib_name)) = self.lib.clone() { + let lockfile = self.lockfile.as_ref().and_then(|p| p.to_str()); + + let cargo = Cargo::load( + &lib_dir, + lockfile, + binding_lib_name.as_deref(), + self.config.parse.parse_deps, + self.config.parse.clean, + self.config.only_target_dependencies, + /* existing_metadata = */ None, + )?; + + result.extend_with(&parser::parse_lib(cargo, &self.config)?); + } else if let Some(cargo) = self.lib_cargo.clone() { + result.extend_with(&parser::parse_lib(cargo, &self.config)?); + } + + result.source_files.extend_from_slice(self.srcs.as_slice()); + + Library::new( + self.config, + result.constants, + result.globals, + result.enums, + result.structs, + result.unions, + result.opaque_items, + result.typedefs, + result.functions, + result.source_files, + ) + .generate() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn with_style() { + assert_eq!( + Style::Tag, + Builder::new().with_style(Style::Tag).config.style + ); + } +} diff --git a/src/bindgen/cargo/cargo.rs b/src/bindgen/cargo/cargo.rs new file mode 100644 index 0000000..69cf938 --- /dev/null +++ b/src/bindgen/cargo/cargo.rs @@ -0,0 +1,252 @@ +/* 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/. */ + +use std::path::{Path, PathBuf}; + +use crate::bindgen::cargo::cargo_expand; +use crate::bindgen::cargo::cargo_lock::{self, Lock}; +pub(crate) use crate::bindgen::cargo::cargo_metadata::PackageRef; +use crate::bindgen::cargo::cargo_metadata::{self, Metadata}; +use crate::bindgen::cargo::cargo_toml; +use crate::bindgen::config::Profile; +use crate::bindgen::error::Error; +use crate::bindgen::ir::Cfg; + +/// Parse a dependency string used in Cargo.lock +fn parse_dep_string(dep_string: &str) -> (&str, Option<&str>) { + let split: Vec<&str> = dep_string.split_whitespace().collect(); + + (split[0], split.get(1).cloned()) +} + +/// A collection of metadata for a library from cargo. +#[derive(Clone, Debug)] +pub(crate) struct Cargo { + manifest_path: PathBuf, + binding_crate_name: String, + lock: Option<Lock>, + metadata: Metadata, + clean: bool, +} + +impl Cargo { + /// Gather metadata from cargo for a specific library and binding crate + /// name. If dependency finding isn't needed then Cargo.lock files don't + /// need to be parsed. + pub(crate) fn load( + crate_dir: &Path, + lock_file: Option<&str>, + binding_crate_name: Option<&str>, + use_cargo_lock: bool, + clean: bool, + only_target_dependencies: bool, + existing_metadata_file: Option<&Path>, + ) -> Result<Cargo, Error> { + let toml_path = crate_dir.join("Cargo.toml"); + let metadata = + cargo_metadata::metadata(&toml_path, existing_metadata_file, only_target_dependencies) + .map_err(|x| Error::CargoMetadata(toml_path.to_str().unwrap().to_owned(), x))?; + let lock_path = lock_file + .map(PathBuf::from) + .unwrap_or_else(|| Path::new(&metadata.workspace_root).join("Cargo.lock")); + + let lock = if use_cargo_lock { + match cargo_lock::lock(&lock_path) { + Ok(lock) => Some(lock), + Err(x) => { + warn!("Couldn't load lock file {:?}: {:?}", lock_path, x); + None + } + } + } else { + None + }; + + // Use the specified binding crate name or infer it from the manifest + let binding_crate_name = match binding_crate_name { + Some(s) => s.to_owned(), + None => { + let manifest = cargo_toml::manifest(&toml_path) + .map_err(|x| Error::CargoToml(toml_path.to_str().unwrap().to_owned(), x))?; + manifest.package.name + } + }; + + Ok(Cargo { + manifest_path: toml_path, + binding_crate_name, + lock, + metadata, + clean, + }) + } + + pub(crate) fn binding_crate_name(&self) -> &str { + &self.binding_crate_name + } + + pub(crate) fn binding_crate_ref(&self) -> PackageRef { + match self.find_pkg_ref(&self.binding_crate_name) { + Some(pkg_ref) => pkg_ref, + None => panic!( + "Unable to find {} for {:?}", + self.binding_crate_name, self.manifest_path + ), + } + } + + pub(crate) fn dependencies(&self, package: &PackageRef) -> Vec<(PackageRef, Option<Cfg>)> { + let lock = match self.lock { + Some(ref lock) => lock, + None => return vec![], + }; + + let mut dependencies = None; + + // Find the dependencies listing in the lockfile + if let Some(ref root) = lock.root { + // If the version is not on the lockfile then it shouldn't be + // ambiguous. + if root.name == package.name + && package + .version + .as_ref() + .map_or(true, |v| *v == root.version) + { + dependencies = root.dependencies.as_ref(); + } + } + if dependencies.is_none() { + if let Some(ref lock_packages) = lock.package { + for lock_package in lock_packages { + if lock_package.name == package.name + && package + .version + .as_ref() + .map_or(true, |v| *v == lock_package.version) + { + dependencies = lock_package.dependencies.as_ref(); + break; + } + } + } + } + if dependencies.is_none() { + return vec![]; + } + + dependencies + .unwrap() + .iter() + .map(|dep| { + let (dep_name, dep_version) = parse_dep_string(dep); + + // If a version was not specified find the only package with the name of the dependency + let dep_version = dep_version.or_else(|| { + let mut versions = self.metadata.packages.iter().filter_map(|package| { + if package.name_and_version.name != dep_name { + return None; + } + package.name_and_version.version.as_deref() + }); + + // If the iterator contains more items, meaning multiple versions of the same + // package are present, warn! amd abort. + let version = versions.next(); + if versions.next().is_none() { + version + } else { + warn!("when looking for a version for package {}, multiple versions where found", dep_name); + None + } + }); + + // Try to find the cfgs in the Cargo.toml + let cfg = self + .metadata + .packages + .get(package) + .and_then(|meta_package| meta_package.dependencies.get(dep_name)) + .and_then(Cfg::load_metadata); + + let package_ref = PackageRef { + name: dep_name.to_owned(), + version: dep_version.map(|v| v.to_owned()), + }; + + (package_ref, cfg) + }) + .collect() + } + + /// Finds the package reference in `cargo metadata` that has `package_name` + /// ignoring the version. + fn find_pkg_ref(&self, package_name: &str) -> Option<PackageRef> { + for package in &self.metadata.packages { + if package.name_and_version.name == package_name { + return Some(package.name_and_version.clone()); + } + } + None + } + + /// Finds the directory for a specified package reference. + #[allow(unused)] + pub(crate) fn find_crate_dir(&self, package: &PackageRef) -> Option<PathBuf> { + self.metadata + .packages + .get(package) + .and_then(|meta_package| { + Path::new(&meta_package.manifest_path) + .parent() + .map(|x| x.to_owned()) + }) + } + + /// Finds `src/lib.rs` for a specified package reference. + pub(crate) fn find_crate_src(&self, package: &PackageRef) -> Option<PathBuf> { + let kind_lib = String::from("lib"); + let kind_staticlib = String::from("staticlib"); + let kind_rlib = String::from("rlib"); + let kind_cdylib = String::from("cdylib"); + let kind_dylib = String::from("dylib"); + + self.metadata + .packages + .get(package) + .and_then(|meta_package| { + for target in &meta_package.targets { + if target.kind.contains(&kind_lib) + || target.kind.contains(&kind_staticlib) + || target.kind.contains(&kind_rlib) + || target.kind.contains(&kind_cdylib) + || target.kind.contains(&kind_dylib) + { + return Some(PathBuf::from(&target.src_path)); + } + } + None + }) + } + + pub(crate) fn expand_crate( + &self, + package: &PackageRef, + expand_all_features: bool, + expand_default_features: bool, + expand_features: &Option<Vec<String>>, + profile: Profile, + ) -> Result<String, cargo_expand::Error> { + cargo_expand::expand( + &self.manifest_path, + &package.name, + package.version.as_deref(), + self.clean, + expand_all_features, + expand_default_features, + expand_features, + profile, + ) + } +} diff --git a/src/bindgen/cargo/cargo_expand.rs b/src/bindgen/cargo/cargo_expand.rs new file mode 100644 index 0000000..565a0d1 --- /dev/null +++ b/src/bindgen/cargo/cargo_expand.rs @@ -0,0 +1,145 @@ +/* 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/. */ + +use crate::bindgen::config::Profile; +use std::env; +use std::error; +use std::fmt; +use std::io; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::str::{from_utf8, Utf8Error}; + +extern crate tempfile; +use self::tempfile::Builder; + +#[derive(Debug)] +/// Possible errors that can occur during `rustc -Zunpretty=expanded`. +pub enum Error { + /// Error during creation of temporary directory + Io(io::Error), + /// Output of `cargo metadata` was not valid utf8 + Utf8(Utf8Error), + /// Error during execution of `cargo rustc -Zunpretty=expanded` + Compile(String), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io(err) + } +} +impl From<Utf8Error> for Error { + fn from(err: Utf8Error) -> Self { + Error::Utf8(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Io(ref err) => err.fmt(f), + Error::Utf8(ref err) => err.fmt(f), + Error::Compile(ref err) => write!(f, "{}", err), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Io(ref err) => Some(err), + Error::Utf8(ref err) => Some(err), + Error::Compile(..) => None, + } + } +} + +/// Use rustc to expand and pretty print the crate into a single file, +/// removing any macros in the process. +#[allow(clippy::too_many_arguments)] +pub fn expand( + manifest_path: &Path, + crate_name: &str, + version: Option<&str>, + use_tempdir: bool, + expand_all_features: bool, + expand_default_features: bool, + expand_features: &Option<Vec<String>>, + profile: Profile, +) -> Result<String, Error> { + let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo")); + let mut cmd = Command::new(cargo); + + let mut _temp_dir = None; // drop guard + if use_tempdir { + _temp_dir = Some(Builder::new().prefix("cbindgen-expand").tempdir()?); + cmd.env("CARGO_TARGET_DIR", _temp_dir.unwrap().path()); + } else if let Ok(ref path) = env::var("CARGO_EXPAND_TARGET_DIR") { + cmd.env("CARGO_TARGET_DIR", path); + } else if let Ok(ref path) = env::var("OUT_DIR") { + // When cbindgen was started programatically from a build.rs file, Cargo is running and + // locking the default target directory. In this case we need to use another directory, + // else we would end up in a deadlock. If Cargo is running `OUT_DIR` will be set, so we + // can use a directory relative to that. + cmd.env("CARGO_TARGET_DIR", PathBuf::from(path).join("expanded")); + } + + // Set this variable so that we don't call it recursively if we expand a crate that is using + // cbindgen + cmd.env("_CBINDGEN_IS_RUNNING", "1"); + + cmd.arg("rustc"); + cmd.arg("--lib"); + // When build with the release profile we can't choose the `check` profile. + if profile != Profile::Release { + cmd.arg("--profile=check"); + } + cmd.arg("--manifest-path"); + cmd.arg(manifest_path); + if let Some(features) = expand_features { + cmd.arg("--features"); + let mut features_str = String::new(); + for (index, feature) in features.iter().enumerate() { + if index != 0 { + features_str.push(' '); + } + features_str.push_str(feature); + } + cmd.arg(features_str); + } + if expand_all_features { + cmd.arg("--all-features"); + } + if !expand_default_features { + cmd.arg("--no-default-features"); + } + match profile { + Profile::Debug => {} + Profile::Release => { + cmd.arg("--release"); + } + } + cmd.arg("-p"); + let mut package = crate_name.to_owned(); + if let Some(version) = version { + package.push(':'); + package.push_str(version); + } + cmd.arg(&package); + cmd.arg("--verbose"); + cmd.arg("--"); + cmd.arg("-Zunpretty=expanded"); + info!("Command: {:?}", cmd); + let output = cmd.output()?; + + let src = from_utf8(&output.stdout)?.to_owned(); + let error = from_utf8(&output.stderr)?.to_owned(); + + if src.is_empty() { + Err(Error::Compile(error)) + } else { + Ok(src) + } +} diff --git a/src/bindgen/cargo/cargo_lock.rs b/src/bindgen/cargo/cargo_lock.rs new file mode 100644 index 0000000..9302082 --- /dev/null +++ b/src/bindgen/cargo/cargo_lock.rs @@ -0,0 +1,51 @@ +/* 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/. */ + +use std::fs::File; +use std::io; +use std::io::Read; +use std::path::Path; + +#[derive(Debug)] +/// Possible errors that can occur during Cargo.toml parsing. +pub enum Error { + /// Error during reading of Cargo.toml + Io(io::Error), + /// Deserialization error + Toml(toml::de::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io(err) + } +} +impl From<toml::de::Error> for Error { + fn from(err: toml::de::Error) -> Self { + Error::Toml(err) + } +} + +#[derive(Clone, Deserialize, Debug)] +pub struct Lock { + pub root: Option<Package>, + pub package: Option<Vec<Package>>, +} + +#[derive(Clone, Deserialize, Debug)] +pub struct Package { + pub name: String, + pub version: String, + /// A list of dependencies formatted like "NAME VERSION-OPT REGISTRY-OPT" + pub dependencies: Option<Vec<String>>, +} + +/// Parse the Cargo.toml for a given path +pub fn lock(manifest_path: &Path) -> Result<Lock, Error> { + let mut s = String::new(); + let mut f = File::open(manifest_path)?; + f.read_to_string(&mut s)?; + + toml::from_str::<Lock>(&s).map_err(|x| x.into()) +} diff --git a/src/bindgen/cargo/cargo_metadata.rs b/src/bindgen/cargo/cargo_metadata.rs new file mode 100644 index 0000000..01ab80f --- /dev/null +++ b/src/bindgen/cargo/cargo_metadata.rs @@ -0,0 +1,253 @@ +#![deny(missing_docs)] +#![allow(dead_code)] +//! Structured access to the output of `cargo metadata` +//! Usually used from within a `cargo-*` executable + +// Forked from `https://github.com/oli-obk/cargo_metadata` +// Modifications: +// 1. Remove `resolve` from Metadata because it was causing parse failures +// 2. Fix the `manifest-path` argument +// 3. Add `--all-features` argument +// 4. Remove the `--no-deps` argument + +use std::borrow::{Borrow, Cow}; +use std::collections::{HashMap, HashSet}; +use std::env; +use std::error; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::io; +use std::path::Path; +use std::process::{Command, Output}; +use std::str::Utf8Error; + +#[derive(Clone, Deserialize, Debug)] +/// Starting point for metadata returned by `cargo metadata` +pub struct Metadata { + /// A list of all crates referenced by this crate (and the crate itself) + pub packages: HashSet<Package>, + version: usize, + /// path to the workspace containing the `Cargo.lock` + pub workspace_root: String, +} + +/// A reference to a package including it's name and the specific version. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct PackageRef { + pub name: String, + pub version: Option<String>, +} + +#[derive(Clone, Deserialize, Debug)] +/// A crate +pub struct Package { + #[serde(flatten)] + pub name_and_version: PackageRef, + id: String, + source: Option<String>, + /// List of dependencies of this particular package + pub dependencies: HashSet<Dependency>, + /// Targets provided by the crate (lib, bin, example, test, ...) + pub targets: Vec<Target>, + features: HashMap<String, Vec<String>>, + /// path containing the `Cargo.toml` + pub manifest_path: String, +} + +#[derive(Clone, Deserialize, Debug)] +/// A dependency of the main crate +pub struct Dependency { + /// Name as given in the `Cargo.toml` + pub name: String, + source: Option<String>, + /// Whether this is required or optional + pub req: String, + kind: Option<String>, + optional: bool, + uses_default_features: bool, + features: Vec<String>, + pub target: Option<String>, +} + +#[derive(Clone, Deserialize, Debug)] +/// A single target (lib, bin, example, ...) provided by a crate +pub struct Target { + /// Name as given in the `Cargo.toml` or generated from the file name + pub name: String, + /// Kind of target ("bin", "example", "test", "bench", "lib") + pub kind: Vec<String>, + /// Almost the same as `kind`, except when an example is a library instad of an executable. + /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example` + #[serde(default)] + pub crate_types: Vec<String>, + /// Path to the main source file of the target + pub src_path: String, +} + +#[derive(Debug)] +/// Possible errors that can occur during metadata parsing. +pub enum Error { + /// Error during execution of `cargo metadata` + Io(io::Error), + /// Metadata extraction failure + Metadata(Output), + /// Output of `cargo metadata` was not valid utf8 + Utf8(Utf8Error), + /// Deserialization error (structure of json did not match expected structure) + Json(serde_json::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io(err) + } +} +impl From<Utf8Error> for Error { + fn from(err: Utf8Error) -> Self { + Error::Utf8(err) + } +} +impl From<serde_json::Error> for Error { + fn from(err: serde_json::Error) -> Self { + Error::Json(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Io(ref err) => err.fmt(f), + Error::Metadata(_) => write!(f, "Metadata error"), + Error::Utf8(ref err) => err.fmt(f), + Error::Json(ref err) => err.fmt(f), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Io(ref err) => Some(err), + Error::Metadata(_) => None, + Error::Utf8(ref err) => Some(err), + Error::Json(ref err) => Some(err), + } + } +} + +// Implementations that let us lookup Packages and Dependencies by name (string) + +impl Borrow<PackageRef> for Package { + fn borrow(&self) -> &PackageRef { + &self.name_and_version + } +} + +impl Hash for Package { + fn hash<H: Hasher>(&self, state: &mut H) { + self.name_and_version.hash(state); + } +} + +impl PartialEq for Package { + fn eq(&self, other: &Self) -> bool { + self.name_and_version == other.name_and_version + } +} + +impl Eq for Package {} + +impl Borrow<str> for Dependency { + fn borrow(&self) -> &str { + &self.name + } +} + +impl Hash for Dependency { + fn hash<H: Hasher>(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for Dependency { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Dependency {} + +fn discover_target(manifest_path: &Path) -> Option<String> { + if let Ok(target) = std::env::var("TARGET") { + return Some(target); + } + + // We must be running as a standalone script, not under cargo. + // Let's use the host platform instead. + // We figure out the host platform through rustc and use that. + // We unfortunatelly cannot go through cargo, since cargo rustc _also_ builds. + // If `rustc` fails to run, we just fall back to not passing --filter-platforms. + // + // NOTE: We set the current directory in case of rustup shenanigans. + let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc")); + debug!("Discovering host platform by {:?}", rustc); + + let rustc_output = Command::new(rustc) + .current_dir(manifest_path.parent().unwrap()) + .arg("-vV") + .output(); + let rustc_output = match rustc_output { + Ok(ref out) => String::from_utf8_lossy(&out.stdout), + Err(..) => return None, + }; + + let field = "host: "; + rustc_output + .lines() + .find_map(|l| l.strip_prefix(field).map(|stripped| stripped.to_string())) +} + +/// The main entry point to obtaining metadata +pub fn metadata( + manifest_path: &Path, + existing_metadata_file: Option<&Path>, + only_target: bool, +) -> Result<Metadata, Error> { + let output; + let metadata = match existing_metadata_file { + Some(path) => Cow::Owned(std::fs::read_to_string(path)?), + None => { + let target = if only_target { + let target = discover_target(manifest_path); + if target.is_none() { + warn!( + "Failed to discover host platform for cargo metadata; \ + will fetch dependencies for all platforms." + ); + } + target + } else { + None + }; + + let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo")); + let mut cmd = Command::new(cargo); + cmd.arg("metadata"); + cmd.arg("--all-features"); + cmd.arg("--format-version").arg("1"); + if let Some(target) = target { + cmd.arg("--filter-platform").arg(target); + } + cmd.arg("--manifest-path"); + cmd.arg(manifest_path); + output = cmd.output()?; + if !output.status.success() { + return Err(Error::Metadata(output)); + } + Cow::Borrowed(std::str::from_utf8(&output.stdout)?) + } + }; + + let meta: Metadata = serde_json::from_str(&metadata)?; + Ok(meta) +} diff --git a/src/bindgen/cargo/cargo_toml.rs b/src/bindgen/cargo/cargo_toml.rs new file mode 100644 index 0000000..998176e --- /dev/null +++ b/src/bindgen/cargo/cargo_toml.rs @@ -0,0 +1,67 @@ +/* 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/. */ + +use std::error; +use std::fmt; +use std::fs::File; +use std::io; +use std::io::Read; +use std::path::Path; + +#[derive(Debug)] +/// Possible errors that can occur during Cargo.toml parsing. +pub enum Error { + /// Error during reading of Cargo.toml + Io(io::Error), + /// Deserialization error + Toml(toml::de::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io(err) + } +} +impl From<toml::de::Error> for Error { + fn from(err: toml::de::Error) -> Self { + Error::Toml(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Io(ref err) => err.fmt(f), + Error::Toml(ref err) => err.fmt(f), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Io(ref err) => Some(err), + Error::Toml(ref err) => Some(err), + } + } +} + +#[derive(Clone, Deserialize, Debug)] +pub struct Manifest { + pub package: Package, +} + +#[derive(Clone, Deserialize, Debug)] +pub struct Package { + pub name: String, +} + +/// Parse the Cargo.toml for a given path +pub fn manifest(manifest_path: &Path) -> Result<Manifest, Error> { + let mut s = String::new(); + let mut f = File::open(manifest_path)?; + f.read_to_string(&mut s)?; + + toml::from_str::<Manifest>(&s).map_err(|x| x.into()) +} diff --git a/src/bindgen/cargo/mod.rs b/src/bindgen/cargo/mod.rs new file mode 100644 index 0000000..19fef54 --- /dev/null +++ b/src/bindgen/cargo/mod.rs @@ -0,0 +1,12 @@ +/* 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::module_inception)] +mod cargo; +pub(crate) mod cargo_expand; +pub(crate) mod cargo_lock; +pub(crate) mod cargo_metadata; +pub(crate) mod cargo_toml; + +pub(crate) use self::cargo::*; diff --git a/src/bindgen/cdecl.rs b/src/bindgen/cdecl.rs new file mode 100644 index 0000000..a9c5a0f --- /dev/null +++ b/src/bindgen/cdecl.rs @@ -0,0 +1,372 @@ +/* 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/. */ + +use std::io::Write; + +use crate::bindgen::config::Layout; +use crate::bindgen::declarationtyperesolver::DeclarationType; +use crate::bindgen::ir::{ConstExpr, Function, GenericArgument, Type}; +use crate::bindgen::writer::{ListType, SourceWriter}; +use crate::bindgen::{Config, Language}; + +// This code is for translating Rust types into C declarations. +// See Section 6.7, Declarations, in the C standard for background. +// http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf + +enum CDeclarator { + Ptr { + is_const: bool, + is_nullable: bool, + is_ref: bool, + }, + Array(String), + Func { + args: Vec<(Option<String>, CDecl)>, + layout: Layout, + never_return: bool, + }, +} + +impl CDeclarator { + fn is_ptr(&self) -> bool { + matches!(self, CDeclarator::Ptr { .. } | CDeclarator::Func { .. }) + } +} + +struct CDecl { + type_qualifers: String, + type_name: String, + type_generic_args: Vec<GenericArgument>, + declarators: Vec<CDeclarator>, + type_ctype: Option<DeclarationType>, + deprecated: Option<String>, +} + +impl CDecl { + fn new() -> CDecl { + CDecl { + type_qualifers: String::new(), + type_name: String::new(), + type_generic_args: Vec::new(), + declarators: Vec::new(), + type_ctype: None, + deprecated: None, + } + } + + fn from_type(t: &Type, config: &Config) -> CDecl { + let mut cdecl = CDecl::new(); + cdecl.build_type(t, false, config); + cdecl + } + + fn from_func_arg(t: &Type, array_length: Option<&str>, config: &Config) -> CDecl { + let mut cdecl = CDecl::new(); + let length = match array_length { + Some(l) => l, + None => return CDecl::from_type(t, config), + }; + let (ty, is_const) = match t { + Type::Ptr { ty, is_const, .. } => (ty, is_const), + _ => unreachable!( + "Should never have an array length for a non pointer type {:?}", + t + ), + }; + let ptr_as_array = Type::Array(ty.clone(), ConstExpr::Value(length.to_string())); + cdecl.build_type(&ptr_as_array, *is_const, config); + cdecl + } + + fn from_func(f: &Function, layout: Layout, config: &Config) -> CDecl { + let mut cdecl = CDecl::new(); + cdecl.build_func(f, layout, config); + cdecl + } + + fn build_func(&mut self, f: &Function, layout: Layout, config: &Config) { + let args = f + .args + .iter() + .map(|arg| { + ( + arg.name.clone(), + CDecl::from_func_arg(&arg.ty, arg.array_length.as_deref(), config), + ) + }) + .collect(); + self.declarators.push(CDeclarator::Func { + args, + layout, + never_return: f.never_return, + }); + self.deprecated = f.annotations.deprecated.clone(); + self.build_type(&f.ret, false, config); + } + + fn build_type(&mut self, t: &Type, is_const: bool, config: &Config) { + match t { + Type::Path(ref generic) => { + if is_const { + assert!( + self.type_qualifers.is_empty(), + "error generating cdecl for {:?}", + t + ); + self.type_qualifers = "const".to_owned(); + } + + assert!( + self.type_name.is_empty(), + "error generating cdecl for {:?}", + t + ); + self.type_name = generic.export_name().to_owned(); + assert!( + self.type_generic_args.is_empty(), + "error generating cdecl for {:?}", + t + ); + self.type_generic_args = generic.generics().to_owned(); + self.type_ctype = generic.ctype().cloned(); + } + Type::Primitive(ref p) => { + if is_const { + assert!( + self.type_qualifers.is_empty(), + "error generating cdecl for {:?}", + t + ); + self.type_qualifers = "const".to_owned(); + } + + assert!( + self.type_name.is_empty(), + "error generating cdecl for {:?}", + t + ); + self.type_name = p.to_repr_c(config).to_string(); + } + Type::Ptr { + ref ty, + is_nullable, + is_const: ptr_is_const, + is_ref, + } => { + self.declarators.push(CDeclarator::Ptr { + is_const, + is_nullable: *is_nullable, + is_ref: *is_ref, + }); + self.build_type(ty, *ptr_is_const, config); + } + Type::Array(ref t, ref constant) => { + let len = constant.as_str().to_owned(); + self.declarators.push(CDeclarator::Array(len)); + self.build_type(t, is_const, config); + } + Type::FuncPtr { + ref ret, + ref args, + is_nullable: _, + never_return, + } => { + let args = args + .iter() + .map(|(ref name, ref ty)| (name.clone(), CDecl::from_type(ty, config))) + .collect(); + self.declarators.push(CDeclarator::Ptr { + is_const: false, + is_nullable: true, + is_ref: false, + }); + self.declarators.push(CDeclarator::Func { + args, + layout: config.function.args.clone(), + never_return: *never_return, + }); + self.build_type(ret, false, config); + } + } + } + + fn write<F: Write>(&self, out: &mut SourceWriter<F>, ident: Option<&str>, config: &Config) { + // Write the type-specifier and type-qualifier first + if !self.type_qualifers.is_empty() { + write!(out, "{} ", self.type_qualifers); + } + + if config.language != Language::Cython { + if let Some(ref ctype) = self.type_ctype { + write!(out, "{} ", ctype.to_str()); + } + } + + write!(out, "{}", self.type_name); + + if !self.type_generic_args.is_empty() { + out.write("<"); + out.write_horizontal_source_list(&self.type_generic_args, ListType::Join(", ")); + out.write(">"); + } + + // When we have an identifier, put a space between the type and the declarators + if ident.is_some() { + out.write(" "); + } + + // Write the left part of declarators before the identifier + let mut iter_rev = self.declarators.iter().rev().peekable(); + + #[allow(clippy::while_let_on_iterator)] + while let Some(declarator) = iter_rev.next() { + let next_is_pointer = iter_rev.peek().map_or(false, |x| x.is_ptr()); + + match *declarator { + CDeclarator::Ptr { + is_const, + is_nullable, + is_ref, + } => { + out.write(if is_ref { "&" } else { "*" }); + if is_const { + out.write("const "); + } + if !is_nullable && !is_ref && config.language != Language::Cython { + if let Some(attr) = &config.pointer.non_null_attribute { + write!(out, "{} ", attr); + } + } + } + CDeclarator::Array(..) => { + if next_is_pointer { + out.write("("); + } + } + CDeclarator::Func { .. } => { + if next_is_pointer { + out.write("("); + } + } + } + } + + // Write the identifier + if let Some(ident) = ident { + write!(out, "{}", ident); + } + + // Write the right part of declarators after the identifier + let mut iter = self.declarators.iter(); + let mut last_was_pointer = false; + + #[allow(clippy::while_let_on_iterator)] + while let Some(declarator) = iter.next() { + match *declarator { + CDeclarator::Ptr { .. } => { + last_was_pointer = true; + } + CDeclarator::Array(ref constant) => { + if last_was_pointer { + out.write(")"); + } + write!(out, "[{}]", constant); + + last_was_pointer = false; + } + CDeclarator::Func { + ref args, + ref layout, + never_return, + } => { + if last_was_pointer { + out.write(")"); + } + + out.write("("); + if args.is_empty() && config.language == Language::C { + out.write("void"); + } + + fn write_vertical<F: Write>( + out: &mut SourceWriter<F>, + config: &Config, + args: &[(Option<String>, CDecl)], + ) { + let align_length = out.line_length_for_align(); + out.push_set_spaces(align_length); + for (i, (arg_ident, arg_ty)) in args.iter().enumerate() { + if i != 0 { + out.write(","); + out.new_line(); + } + + // Convert &Option<String> to Option<&str> + let arg_ident = arg_ident.as_ref().map(|x| x.as_ref()); + + arg_ty.write(out, arg_ident, config); + } + out.pop_tab(); + } + + fn write_horizontal<F: Write>( + out: &mut SourceWriter<F>, + config: &Config, + args: &[(Option<String>, CDecl)], + ) { + for (i, (arg_ident, arg_ty)) in args.iter().enumerate() { + if i != 0 { + out.write(", "); + } + + // Convert &Option<String> to Option<&str> + let arg_ident = arg_ident.as_ref().map(|x| x.as_ref()); + + arg_ty.write(out, arg_ident, config); + } + } + + match layout { + Layout::Vertical => write_vertical(out, config, args), + Layout::Horizontal => write_horizontal(out, config, args), + Layout::Auto => { + if !out.try_write( + |out| write_horizontal(out, config, args), + config.line_length, + ) { + write_vertical(out, config, args) + } + } + } + out.write(")"); + + if never_return && config.language != Language::Cython { + if let Some(ref no_return_attr) = config.function.no_return { + out.write_fmt(format_args!(" {}", no_return_attr)); + } + } + + last_was_pointer = true; + } + } + } + } +} + +pub fn write_func<F: Write>( + out: &mut SourceWriter<F>, + f: &Function, + layout: Layout, + config: &Config, +) { + CDecl::from_func(f, layout, config).write(out, Some(f.path().name()), config); +} + +pub fn write_field<F: Write>(out: &mut SourceWriter<F>, t: &Type, ident: &str, config: &Config) { + CDecl::from_type(t, config).write(out, Some(ident), config); +} + +pub fn write_type<F: Write>(out: &mut SourceWriter<F>, t: &Type, config: &Config) { + CDecl::from_type(t, config).write(out, None, config); +} diff --git a/src/bindgen/config.rs b/src/bindgen/config.rs new file mode 100644 index 0000000..5012414 --- /dev/null +++ b/src/bindgen/config.rs @@ -0,0 +1,1114 @@ +/* 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/. */ + +use std::collections::{BTreeMap, HashMap}; +use std::default::Default; +use std::str::FromStr; +use std::{fmt, fs, path::Path as StdPath, path::PathBuf as StdPathBuf}; + +use serde::de::value::{MapAccessDeserializer, SeqAccessDeserializer}; +use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; + +use crate::bindgen::ir::annotation::AnnotationSet; +use crate::bindgen::ir::path::Path; +use crate::bindgen::ir::repr::ReprAlign; +pub use crate::bindgen::rename::RenameRule; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// A language type to generate bindings for. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Language { + Cxx, + C, + Cython, +} + +impl FromStr for Language { + type Err = String; + + fn from_str(s: &str) -> Result<Language, Self::Err> { + match s { + "cxx" => Ok(Language::Cxx), + "Cxx" => Ok(Language::Cxx), + "CXX" => Ok(Language::Cxx), + "cpp" => Ok(Language::Cxx), + "Cpp" => Ok(Language::Cxx), + "CPP" => Ok(Language::Cxx), + "c++" => Ok(Language::Cxx), + "C++" => Ok(Language::Cxx), + "c" => Ok(Language::C), + "C" => Ok(Language::C), + "cython" => Ok(Language::Cython), + "Cython" => Ok(Language::Cython), + _ => Err(format!("Unrecognized Language: '{}'.", s)), + } + } +} + +deserialize_enum_str!(Language); + +impl Language { + pub(crate) fn typedef(self) -> &'static str { + match self { + Language::Cxx | Language::C => "typedef", + Language::Cython => "ctypedef", + } + } +} + +/// Controls what type of line endings are used in the generated code. +#[derive(Debug, Clone, Copy)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Default)] +pub enum LineEndingStyle { + /// Use Unix-style linefeed characters + #[default] + LF, + /// Use classic Mac-style carriage-return characters + CR, + /// Use Windows-style carriage-return and linefeed characters + CRLF, + /// Use the native mode for the platform: CRLF on Windows, LF everywhere else. + Native, +} + +impl LineEndingStyle { + pub fn as_str(&self) -> &'static str { + match self { + Self::LF => "\n", + Self::CR => "\r", + Self::CRLF => "\r\n", + Self::Native => { + #[cfg(target_os = "windows")] + { + Self::CRLF.as_str() + } + #[cfg(not(target_os = "windows"))] + { + Self::LF.as_str() + } + } + } + } +} + +impl FromStr for LineEndingStyle { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.to_lowercase().as_ref() { + "native" => Ok(Self::Native), + "lf" => Ok(Self::LF), + "crlf" => Ok(Self::CRLF), + "cr" => Ok(Self::CR), + _ => Err(format!("Unrecognized line ending style: '{}'.", s)), + } + } +} + +deserialize_enum_str!(LineEndingStyle); + +/// A style of braces to use for generating code. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Braces { + SameLine, + NextLine, +} + +impl FromStr for Braces { + type Err = String; + + fn from_str(s: &str) -> Result<Braces, Self::Err> { + match s { + "SameLine" => Ok(Braces::SameLine), + "same_line" => Ok(Braces::SameLine), + "NextLine" => Ok(Braces::NextLine), + "next_line" => Ok(Braces::NextLine), + _ => Err(format!("Unrecognized Braces: '{}'.", s)), + } + } +} + +deserialize_enum_str!(Braces); + +/// A type of layout to use when generating long lines of code. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Layout { + Horizontal, + Vertical, + Auto, +} + +impl FromStr for Layout { + type Err = String; + + fn from_str(s: &str) -> Result<Layout, Self::Err> { + match s { + "Horizontal" => Ok(Layout::Horizontal), + "horizontal" => Ok(Layout::Horizontal), + "Vertical" => Ok(Layout::Vertical), + "vertical" => Ok(Layout::Vertical), + "Auto" => Ok(Layout::Auto), + "auto" => Ok(Layout::Auto), + _ => Err(format!("Unrecognized Layout: '{}'.", s)), + } + } +} + +deserialize_enum_str!(Layout); + +/// How the comments containing documentation should be styled. +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum DocumentationStyle { + C, + C99, + Doxy, + Cxx, + Auto, +} + +impl FromStr for DocumentationStyle { + type Err = String; + + fn from_str(s: &str) -> Result<DocumentationStyle, Self::Err> { + match s.to_lowercase().as_ref() { + "c" => Ok(DocumentationStyle::C), + "c99" => Ok(DocumentationStyle::C99), + "cxx" => Ok(DocumentationStyle::Cxx), + "c++" => Ok(DocumentationStyle::Cxx), + "doxy" => Ok(DocumentationStyle::Doxy), + "auto" => Ok(DocumentationStyle::Auto), + _ => Err(format!("Unrecognized documentation style: '{}'.", s)), + } + } +} + +deserialize_enum_str!(DocumentationStyle); + +/// How much of the documentation to include in the header file. +#[derive(Debug, Clone, Copy)] +pub enum DocumentationLength { + Short, + Full, +} + +impl FromStr for DocumentationLength { + type Err = String; + + fn from_str(s: &str) -> Result<DocumentationLength, Self::Err> { + match s.to_lowercase().as_ref() { + "short" => Ok(DocumentationLength::Short), + "full" => Ok(DocumentationLength::Full), + _ => Err(format!("Unrecognized documentation style: '{}'.", s)), + } + } +} + +deserialize_enum_str!(DocumentationLength); + +/// A style of Style to use when generating structs and enums. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum Style { + #[default] + Both, + Tag, + Type, +} + +impl Style { + pub fn generate_tag(self) -> bool { + match self { + Style::Both | Style::Tag => true, + Style::Type => false, + } + } + + pub fn generate_typedef(self) -> bool { + match self { + Style::Both | Style::Type => true, + Style::Tag => false, + } + } + + // https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#styles-of-struct-union-and-enum-declaration + pub fn cython_def(self) -> &'static str { + if self.generate_tag() { + "cdef " + } else { + "ctypedef " + } + } +} + +impl FromStr for Style { + type Err = String; + + fn from_str(s: &str) -> Result<Style, Self::Err> { + match s { + "Both" => Ok(Style::Both), + "both" => Ok(Style::Both), + "Tag" => Ok(Style::Tag), + "tag" => Ok(Style::Tag), + "Type" => Ok(Style::Type), + "type" => Ok(Style::Type), + _ => Err(format!("Unrecognized Style: '{}'.", s)), + } + } +} + +deserialize_enum_str!(Style); + +/// Different item types that we can generate and filter. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ItemType { + Constants, + Globals, + Enums, + Structs, + Unions, + Typedefs, + OpaqueItems, + Functions, +} + +impl FromStr for ItemType { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::ItemType::*; + Ok(match &*s.to_lowercase() { + "constants" => Constants, + "globals" => Globals, + "enums" => Enums, + "structs" => Structs, + "unions" => Unions, + "typedefs" => Typedefs, + "opaque" => OpaqueItems, + "functions" => Functions, + _ => return Err(format!("Unrecognized Style: '{}'.", s)), + }) + } +} + +deserialize_enum_str!(ItemType); + +/// Type which specifies the sort order of functions +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SortKey { + Name, + None, +} + +impl FromStr for SortKey { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + use self::SortKey::*; + Ok(match &*s.to_lowercase() { + "name" => Name, + "none" => None, + _ => return Err(format!("Unrecognized sort option: '{}'.", s)), + }) + } +} + +deserialize_enum_str!(SortKey); + +/// Settings to apply when exporting items. +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct ExportConfig { + /// A list of additional items not used by exported functions to include in + /// the generated bindings + pub include: Vec<String>, + /// A list of items to not include in the generated bindings + pub exclude: Vec<String>, + /// Table of name conversions to apply to item names + pub rename: HashMap<String, String>, + /// Table of raw strings to prepend to the body of items. + pub pre_body: HashMap<String, String>, + /// Table of raw strings to append to the body of items. + pub body: HashMap<String, String>, + /// A prefix to add before the name of every item + pub prefix: Option<String>, + /// Types of items to generate. + pub item_types: Vec<ItemType>, + /// Whether renaming overrides or extends prefixing. + pub renaming_overrides_prefixing: bool, + /// Mangling configuration. + pub mangle: MangleConfig, +} + +/// Mangling-specific configuration. +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct MangleConfig { + /// The rename rule to apply to the type names mangled. + pub rename_types: RenameRule, + /// Remove the underscores used for name mangling. + pub remove_underscores: bool, +} + +impl ExportConfig { + pub(crate) fn should_generate(&self, item_type: ItemType) -> bool { + self.item_types.is_empty() || self.item_types.contains(&item_type) + } + + pub(crate) fn pre_body(&self, path: &Path) -> Option<&str> { + self.pre_body.get(path.name()).map(|s| s.trim_matches('\n')) + } + + pub(crate) fn post_body(&self, path: &Path) -> Option<&str> { + self.body.get(path.name()).map(|s| s.trim_matches('\n')) + } + + pub(crate) fn rename(&self, item_name: &mut String) { + if let Some(name) = self.rename.get(item_name) { + *item_name = name.clone(); + if self.renaming_overrides_prefixing { + return; + } + } + if let Some(ref prefix) = self.prefix { + item_name.insert_str(0, prefix); + } + } +} + +/// Settings to apply to generated types with layout modifiers. +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct LayoutConfig { + /// The way to annotate C types as #[repr(packed)]. + pub packed: Option<String>, + /// The way to annotate C types as #[repr(align(...))]. This is assumed to be a functional + /// macro which takes a single argument (the alignment). + pub aligned_n: Option<String>, +} + +impl LayoutConfig { + pub(crate) fn ensure_safe_to_represent(&self, align: &ReprAlign) -> Result<(), String> { + match (align, &self.packed, &self.aligned_n) { + (ReprAlign::Packed, None, _) => Err("Cannot safely represent #[repr(packed)] type without configured 'packed' annotation.".to_string()), + (ReprAlign::Align(_), _, None) => Err("Cannot safely represent #[repr(aligned(...))] type without configured 'aligned_n' annotation.".to_string()), + _ => Ok(()), + } + } +} + +/// Settings to apply to generated functions. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct FunctionConfig { + /// Optional text to output before each function declaration + pub prefix: Option<String>, + /// Optional text to output after each function declaration + pub postfix: Option<String>, + /// The way to annotation this function as #[must_use] + pub must_use: Option<String>, + /// The way to annotation this function as #[deprecated] without notes + pub deprecated: Option<String>, + /// The way to annotation this function as #[deprecated] with notes + pub deprecated_with_note: Option<String>, + /// The style to layout the args + pub args: Layout, + /// The rename rule to apply to function args + pub rename_args: RenameRule, + /// An optional macro to use when generating Swift function name attributes + pub swift_name_macro: Option<String>, + /// Sort key for functions + pub sort_by: Option<SortKey>, + /// Optional text to output after functions which return `!`. + pub no_return: Option<String>, +} + +impl Default for FunctionConfig { + fn default() -> FunctionConfig { + FunctionConfig { + prefix: None, + postfix: None, + must_use: None, + deprecated: None, + deprecated_with_note: None, + args: Layout::Auto, + rename_args: RenameRule::None, + swift_name_macro: None, + sort_by: None, + no_return: None, + } + } +} + +impl FunctionConfig { + pub(crate) fn prefix(&self, annotations: &AnnotationSet) -> Option<String> { + if let Some(x) = annotations.atom("prefix") { + return x; + } + self.prefix.clone() + } + + pub(crate) fn postfix(&self, annotations: &AnnotationSet) -> Option<String> { + if let Some(x) = annotations.atom("postfix") { + return x; + } + self.postfix.clone() + } +} + +/// Settings to apply to generated structs. +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct StructConfig { + /// The rename rule to apply to the name of struct fields + pub rename_fields: RenameRule, + /// Whether to generate a constructor for the struct (which takes + /// arguments to initialize all the members) + pub derive_constructor: bool, + /// Whether to generate a piecewise equality operator + pub derive_eq: bool, + /// Whether to generate a piecewise inequality operator + pub derive_neq: bool, + /// Whether to generate a less than operator on structs with one field + pub derive_lt: bool, + /// Whether to generate a less than or equal to operator on structs with one field + pub derive_lte: bool, + /// Whether to generate a greater than operator on structs with one field + pub derive_gt: bool, + /// Whether to generate a greater than or equal to operator on structs with one field + pub derive_gte: bool, + /// Whether to generate a ostream serializer for the struct + pub derive_ostream: bool, + /// Whether associated constants should be in the body. Only applicable to + /// non-transparent structs, and in C++-only. + pub associated_constants_in_body: bool, + /// The way to annotate this struct as #[must_use]. + pub must_use: Option<String>, + /// The way to annotation this function as #[deprecated] without notes + pub deprecated: Option<String>, + /// The way to annotation this function as #[deprecated] with notes + pub deprecated_with_note: Option<String>, +} + +impl StructConfig { + pub(crate) fn derive_constructor(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-constructor") { + return x; + } + self.derive_constructor + } + pub(crate) fn derive_eq(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-eq") { + return x; + } + self.derive_eq + } + pub(crate) fn derive_neq(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-neq") { + return x; + } + self.derive_neq + } + pub(crate) fn derive_lt(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-lt") { + return x; + } + self.derive_lt + } + pub(crate) fn derive_lte(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-lte") { + return x; + } + self.derive_lte + } + pub(crate) fn derive_gt(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-gt") { + return x; + } + self.derive_gt + } + pub(crate) fn derive_gte(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-gte") { + return x; + } + self.derive_gte + } + pub(crate) fn derive_ostream(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-ostream") { + return x; + } + self.derive_ostream + } +} + +/// Settings to apply to generated enums. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct EnumConfig { + /// The rename rule to apply to the name of enum variants + pub rename_variants: RenameRule, + /// The rename rule to apply to the names of the union fields in C/C++ + /// generated from the Rust enum. Applied before rename_variants + /// rename rule. Defaults to SnakeCase. + pub rename_variant_name_fields: RenameRule, + /// Whether to add a `Sentinel` value at the end of every enum + /// This is useful in Gecko for IPC serialization + pub add_sentinel: bool, + /// Whether the enum variants should be prefixed with the enum name + pub prefix_with_name: bool, + /// Whether to generate static `::X(..)` constructors and `IsX()` + /// methods for tagged enums. + pub derive_helper_methods: bool, + /// Whether to generate `AsX() const` methods for tagged enums. + pub derive_const_casts: bool, + /// Whether to generate `AsX()` methods for tagged enums. + pub derive_mut_casts: bool, + /// The name of the macro to use for `derive_{const,mut}casts`. If custom, you're + /// responsible to provide the necessary header, otherwise `assert` will be + /// used, and `<cassert>` will be included. + pub cast_assert_name: Option<String>, + /// The way to annotation this enum as #[must_use]. + pub must_use: Option<String>, + /// The way to annotation this function as #[deprecated] without notes + pub deprecated: Option<String>, + /// The way to annotation this function as #[deprecated] with notes + pub deprecated_with_note: Option<String>, + /// Whether to generate destructors of tagged enums. + pub derive_tagged_enum_destructor: bool, + /// Whether to generate copy-constructors of tagged enums. + pub derive_tagged_enum_copy_constructor: bool, + /// Whether to generate copy-assignment operators of tagged enums. + /// + /// This is only generated if a copy constructor for the same tagged enum is + /// generated as well. + pub derive_tagged_enum_copy_assignment: bool, + /// Whether to generate a ostream serializer for the struct + pub derive_ostream: bool, + /// Declare the enum as an enum class. + /// Only relevant when targeting C++. + pub enum_class: bool, + /// Whether to generate empty, private default-constructors for tagged + /// enums. + pub private_default_tagged_enum_constructor: bool, +} + +impl Default for EnumConfig { + fn default() -> EnumConfig { + EnumConfig { + rename_variants: RenameRule::None, + rename_variant_name_fields: RenameRule::SnakeCase, + add_sentinel: false, + prefix_with_name: false, + derive_helper_methods: false, + derive_const_casts: false, + derive_mut_casts: false, + cast_assert_name: None, + must_use: None, + deprecated: None, + deprecated_with_note: None, + derive_tagged_enum_destructor: false, + derive_tagged_enum_copy_constructor: false, + derive_tagged_enum_copy_assignment: false, + derive_ostream: false, + enum_class: true, + private_default_tagged_enum_constructor: false, + } + } +} + +impl EnumConfig { + pub(crate) fn add_sentinel(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("add-sentinel") { + return x; + } + self.add_sentinel + } + pub(crate) fn derive_helper_methods(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-helper-methods") { + return x; + } + self.derive_helper_methods + } + pub(crate) fn derive_const_casts(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-const-casts") { + return x; + } + self.derive_const_casts + } + pub(crate) fn derive_mut_casts(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-mut-casts") { + return x; + } + self.derive_mut_casts + } + pub(crate) fn derive_tagged_enum_destructor(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-tagged-enum-destructor") { + return x; + } + self.derive_tagged_enum_destructor + } + pub(crate) fn derive_tagged_enum_copy_constructor(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-tagged-enum-copy-constructor") { + return x; + } + self.derive_tagged_enum_copy_constructor + } + pub(crate) fn derive_tagged_enum_copy_assignment(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-tagged-enum-copy-assignment") { + return x; + } + self.derive_tagged_enum_copy_assignment + } + pub(crate) fn derive_ostream(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("derive-ostream") { + return x; + } + self.derive_ostream + } + pub(crate) fn enum_class(&self, annotations: &AnnotationSet) -> bool { + if let Some(x) = annotations.bool("enum-class") { + return x; + } + self.enum_class + } + pub(crate) fn private_default_tagged_enum_constructor( + &self, + annotations: &AnnotationSet, + ) -> bool { + if let Some(x) = annotations.bool("private-default-tagged-enum-constructor") { + return x; + } + self.private_default_tagged_enum_constructor + } +} + +/// Settings to apply to generated constants. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct ConstantConfig { + /// Whether a generated constant can be a static const in C++ mode. + pub allow_static_const: bool, + /// Whether a generated constant should be constexpr in C++ mode. + pub allow_constexpr: bool, + /// Sort key for constants + pub sort_by: Option<SortKey>, +} + +impl Default for ConstantConfig { + fn default() -> ConstantConfig { + ConstantConfig { + allow_static_const: true, + allow_constexpr: true, + sort_by: None, + } + } +} + +/// Settings for custom macro expansion. +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct MacroExpansionConfig { + /// Whether the `bitflags` macro should be expanded. + pub bitflags: bool, +} + +/// Controls which Cargo profile is used for macro expansion. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Profile { + Debug, + Release, +} + +impl FromStr for Profile { + type Err = String; + + fn from_str(s: &str) -> Result<Profile, Self::Err> { + match s { + "debug" | "Debug" => Ok(Profile::Debug), + "release" | "Release" => Ok(Profile::Release), + _ => Err(format!("Unrecognized Profile: '{}'.", s)), + } + } +} + +deserialize_enum_str!(Profile); + +/// Settings to apply when running `rustc -Zunpretty=expanded` +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct ParseExpandConfig { + /// The names of crates to parse with `rustc -Zunpretty=expanded` + pub crates: Vec<String>, + /// Whether to enable all the features when expanding. + pub all_features: bool, + /// Whether to use the default feature set when expanding. + pub default_features: bool, + /// List of features to use when expanding. Combines with `default_features` like in + /// `Cargo.toml`. + pub features: Option<Vec<String>>, + /// Controls whether or not to pass `--release` when expanding. + pub profile: Profile, +} + +impl Default for ParseExpandConfig { + fn default() -> ParseExpandConfig { + ParseExpandConfig { + crates: Vec::new(), + all_features: false, + default_features: true, + features: None, + profile: Profile::Debug, + } + } +} + +// Backwards-compatibility deserializer for ParseExpandConfig. This allows accepting both the +// simple `expand = ["crate"]` and the more complex `expand = {"crates": ["crate"], +// "default_features": false}` format for the `expand` key. +// +// Note that one (major) difference between the two forms is that, for backwards-compatibility +// reasons, the `expand = ["crate"]` form will enable the `--all-features` flag by default while +// the `expand = {"crates": ["crate"]}` form will use the default feature set by default. +fn retrocomp_parse_expand_config_deserialize<'de, D: Deserializer<'de>>( + deserializer: D, +) -> Result<ParseExpandConfig, D::Error> { + struct ParseExpandVisitor; + + impl<'de> Visitor<'de> for ParseExpandVisitor { + type Value = ParseExpandConfig; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map or sequence of string") + } + + fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> { + let crates = + <Vec<String> as Deserialize>::deserialize(SeqAccessDeserializer::new(seq))?; + Ok(ParseExpandConfig { + crates, + all_features: true, + default_features: true, + features: None, + profile: Profile::Debug, + }) + } + + fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> { + <ParseExpandConfig as Deserialize>::deserialize(MapAccessDeserializer::new(map)) + } + } + + deserializer.deserialize_any(ParseExpandVisitor) +} + +/// Settings to apply when parsing. +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct ParseConfig { + /// Whether to parse dependencies when generating bindings. When this is true, + /// each dependent crate is found using a combination of `cargo metadata` and + /// `Cargo.lock`. To further control this behavior, crates can be whitelisted or + /// blacklisted using `include` and `exclude` respectively. Additionally in cases + /// where crates have types to expose in bindings hidden in macros, a crate can + /// be marked in `expand` and `cargo expand` will be used to expand the macros + /// before parsing. A crate marked in `expand` doesn't need to be added to any + /// whitelist. + pub parse_deps: bool, + /// An optional whitelist of names of crates to parse + pub include: Option<Vec<String>>, + /// The names of crates to not parse + pub exclude: Vec<String>, + /// The configuration options for `rustc -Zunpretty=expanded` + #[serde(deserialize_with = "retrocomp_parse_expand_config_deserialize")] + pub expand: ParseExpandConfig, + /// Whether to use a new temporary target directory when running `rustc -Zunpretty=expanded`. + /// This may be required for some build processes. + pub clean: bool, + /// List of crate names which generate consts, statics, and fns. By default + /// no dependent crates generate them. + pub extra_bindings: Vec<String>, +} + +impl ParseConfig { + pub(crate) fn should_generate_top_level_item( + &self, + crate_name: &str, + binding_crate_name: &str, + ) -> bool { + if crate_name == binding_crate_name { + // Always generate items for the binding crate. + return true; + } + + self.extra_bindings.iter().any(|dep| dep == crate_name) + } +} + +/// Settings to apply to pointers +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct PtrConfig { + /// Optional attribute to apply to pointers that are required to not be null + pub non_null_attribute: Option<String>, +} + +/// Settings specific to Cython bindings. +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct CythonConfig { + /// Header specified in the top level `cdef extern from header:` declaration. + pub header: Option<String>, + /// `from module cimport name1, name2, ...` declarations added in the same place + /// where you'd get includes in C. + pub cimports: BTreeMap<String, Vec<String>>, +} + +/// A collection of settings to customize the generated bindings. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +#[serde(default)] +pub struct Config { + /// Optional text to output at the beginning of the file + pub header: Option<String>, + /// A list of additional includes to put at the beginning of the generated header + pub includes: Vec<String>, + /// A list of additional system includes to put at the beginning of the generated header + pub sys_includes: Vec<String>, + /// Optional verbatim code added after the include blocks + pub after_includes: Option<String>, + /// Optional text to output at the end of the file + pub trailer: Option<String>, + /// Optional name to use for an include guard + pub include_guard: Option<String>, + /// Add a `#pragma once` guard + pub pragma_once: bool, + /// Generates no includes at all. Overrides all other include options + /// + /// This option is useful when using cbindgen with tools such as python's cffi which + /// doesn't understand include directives + pub no_includes: bool, + /// Optional text to output at major sections to deter manual editing + pub autogen_warning: Option<String>, + /// Include a comment with the version of cbindgen used to generate the file + pub include_version: bool, + /// An optional name for the root namespace. Only applicable when language="C++" + pub namespace: Option<String>, + /// An optional list of namespaces. Only applicable when language="C++" + pub namespaces: Option<Vec<String>>, + /// An optional list of namespaces to declare as using. Only applicable when language="C++" + pub using_namespaces: Option<Vec<String>>, + /// The style to use for braces + pub braces: Braces, + /// The preferred length of a line, used for auto breaking function arguments + pub line_length: usize, + /// The amount of spaces in a tab + pub tab_width: usize, + /// The type of line endings to generate + pub line_endings: LineEndingStyle, + /// The language to output bindings for + pub language: Language, + /// Include preprocessor defines in C bindings to ensure C++ compatibility + pub cpp_compat: bool, + /// The style to declare structs, enums and unions in for C + pub style: Style, + /// Default sort key for functions and constants. + pub sort_by: SortKey, + /// If this option is true `usize` and `isize` will be converted into `size_t` and `ptrdiff_t` + /// instead of `uintptr_t` and `intptr_t` respectively. + pub usize_is_size_t: bool, + /// The configuration options for parsing + pub parse: ParseConfig, + /// The configuration options for exporting + pub export: ExportConfig, + /// The configuration options for macros. + pub macro_expansion: MacroExpansionConfig, + /// The configuration options for type layouts. + pub layout: LayoutConfig, + /// The configuration options for functions + #[serde(rename = "fn")] + pub function: FunctionConfig, + /// The configuration options for structs + #[serde(rename = "struct")] + pub structure: StructConfig, + /// The configuration options for enums + #[serde(rename = "enum")] + pub enumeration: EnumConfig, + /// The configuration options for constants + #[serde(rename = "const")] + pub constant: ConstantConfig, + /// Preprocessor defines to use when generating #ifdef's for #[cfg] + pub defines: HashMap<String, String>, + /// Include doc comments from Rust as documentation + pub documentation: bool, + /// How documentation comments should be styled. + pub documentation_style: DocumentationStyle, + /// How much of the documentation should be output for each item. + pub documentation_length: DocumentationLength, + /// Configuration options for pointers + #[serde(rename = "ptr")] + pub pointer: PtrConfig, + /// Only download sources for dependencies needed for the target platform. + /// + /// By default, cbindgen will fetch sources for dependencies used on any platform so that if a + /// type is defined in terms of a type from a dependency on another target (probably behind a + /// `#[cfg]`), cbindgen will be able to generate the appropriate binding as it can see the + /// nested type's definition. However, this makes calling cbindgen slower, as it may have to + /// download a number of additional dependencies. + /// + /// As an example, consider this Cargo.toml: + /// + /// ```toml + /// [target.'cfg(windows)'.dependencies] + /// windows = "0.7" + /// ``` + /// + /// with this declaration in one of the `.rs` files that cbindgen is asked to generate bindings + /// for: + /// + /// ```rust,ignore + /// #[cfg(windows)] + /// pub struct Error(windows::ErrorCode); + /// ``` + /// + /// With the default value (`false`), cbindgen will download the `windows` dependency even when + /// not compiling for Windows, and will thus be able to generate the binding for `Error` + /// (behind a `#define`). + /// + /// If this value is instead to `true`, cbindgen will _not_ download the `windows` dependency + /// if it's not compiling for Windows, but will also fail to generate a Windows binding for + /// `Error` as it does not know the definition for `ErrorCode`. + /// + /// The target can be chosen via the `TARGET` environment variable (if used + /// via the CLI, when ran from a build script cargo sets this variable + /// appropriately). + pub only_target_dependencies: bool, + /// Configuration options specific to Cython. + pub cython: CythonConfig, + #[serde(skip)] + pub(crate) config_path: Option<StdPathBuf>, +} + +impl Default for Config { + fn default() -> Config { + Config { + header: None, + includes: Vec::new(), + sys_includes: Vec::new(), + after_includes: None, + trailer: None, + include_guard: None, + pragma_once: false, + autogen_warning: None, + include_version: false, + no_includes: false, + namespace: None, + namespaces: None, + using_namespaces: None, + braces: Braces::SameLine, + line_length: 100, + tab_width: 2, + line_endings: LineEndingStyle::default(), + language: Language::Cxx, + cpp_compat: false, + style: Style::default(), + usize_is_size_t: false, + sort_by: SortKey::None, + macro_expansion: Default::default(), + parse: ParseConfig::default(), + export: ExportConfig::default(), + layout: LayoutConfig::default(), + function: FunctionConfig::default(), + structure: StructConfig::default(), + enumeration: EnumConfig::default(), + constant: ConstantConfig::default(), + defines: HashMap::new(), + documentation: true, + documentation_style: DocumentationStyle::Auto, + documentation_length: DocumentationLength::Full, + pointer: PtrConfig::default(), + only_target_dependencies: false, + cython: CythonConfig::default(), + config_path: None, + } + } +} + +impl Config { + pub(crate) fn cpp_compatible_c(&self) -> bool { + self.language == Language::C && self.cpp_compat + } + + pub(crate) fn include_guard(&self) -> Option<&str> { + if self.language == Language::Cython { + None + } else { + self.include_guard.as_deref() + } + } + + pub(crate) fn includes(&self) -> &[String] { + if self.language == Language::Cython { + &[] + } else { + &self.includes + } + } + + pub(crate) fn sys_includes(&self) -> &[String] { + if self.language == Language::Cython { + &[] + } else { + &self.sys_includes + } + } + + pub fn from_file<P: AsRef<StdPath>>(file_name: P) -> Result<Config, String> { + let config_text = fs::read_to_string(file_name.as_ref()).map_err(|_| { + format!( + "Couldn't open config file: {}.", + file_name.as_ref().display() + ) + })?; + + let mut config = toml::from_str::<Config>(&config_text) + .map_err(|e| format!("Couldn't parse config file: {}.", e))?; + config.config_path = Some(StdPathBuf::from(file_name.as_ref())); + Ok(config) + } + + pub fn from_root_or_default<P: AsRef<StdPath>>(root: P) -> Config { + let c = root.as_ref().join("cbindgen.toml"); + + if c.exists() { + Config::from_file(c).unwrap() + } else { + Config::default() + } + } +} diff --git a/src/bindgen/declarationtyperesolver.rs b/src/bindgen/declarationtyperesolver.rs new file mode 100644 index 0000000..e72761e --- /dev/null +++ b/src/bindgen/declarationtyperesolver.rs @@ -0,0 +1,57 @@ +/* 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/. */ + +use crate::bindgen::ir::Path; +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +impl DeclarationType { + pub fn to_str(self) -> &'static str { + match self { + DeclarationType::Struct => "struct", + DeclarationType::Enum => "enum", + DeclarationType::Union => "union", + } + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum DeclarationType { + Struct, + Enum, + Union, +} + +#[derive(Default)] +pub struct DeclarationTypeResolver { + types: HashMap<Path, Option<DeclarationType>>, +} + +impl DeclarationTypeResolver { + fn insert(&mut self, path: &Path, ty: Option<DeclarationType>) { + if let Entry::Vacant(vacant_entry) = self.types.entry(path.clone()) { + vacant_entry.insert(ty); + } + } + + pub fn add_enum(&mut self, path: &Path) { + self.insert(path, Some(DeclarationType::Enum)); + } + + pub fn add_struct(&mut self, path: &Path) { + self.insert(path, Some(DeclarationType::Struct)); + } + + pub fn add_union(&mut self, path: &Path) { + self.insert(path, Some(DeclarationType::Union)); + } + + pub fn add_none(&mut self, path: &Path) { + self.insert(path, None); + } + + pub fn type_for(&self, path: &Path) -> Option<DeclarationType> { + *self.types.get(path)? + } +} diff --git a/src/bindgen/dependencies.rs b/src/bindgen/dependencies.rs new file mode 100644 index 0000000..6a98738 --- /dev/null +++ b/src/bindgen/dependencies.rs @@ -0,0 +1,46 @@ +/* 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/. */ + +use std::cmp::Ordering; +use std::collections::HashSet; + +use crate::bindgen::ir::{ItemContainer, Path}; + +/// A dependency list is used for gathering what order to output the types. +#[derive(Default)] +pub struct Dependencies { + pub order: Vec<ItemContainer>, + pub items: HashSet<Path>, +} + +impl Dependencies { + pub fn new() -> Dependencies { + Dependencies { + order: Vec::new(), + items: HashSet::new(), + } + } + + pub fn sort(&mut self) { + // Sort untagged enums and opaque structs into their own layers because they don't + // depend on each other or anything else. + let ordering = |a: &ItemContainer, b: &ItemContainer| match (a, b) { + (ItemContainer::Enum(x), ItemContainer::Enum(y)) + if x.tag.is_none() && y.tag.is_none() => + { + x.path.cmp(&y.path) + } + (ItemContainer::Enum(x), _) if x.tag.is_none() => Ordering::Less, + (_, ItemContainer::Enum(x)) if x.tag.is_none() => Ordering::Greater, + + (ItemContainer::OpaqueItem(x), ItemContainer::OpaqueItem(y)) => x.path.cmp(&y.path), + (&ItemContainer::OpaqueItem(_), _) => Ordering::Less, + (_, &ItemContainer::OpaqueItem(_)) => Ordering::Greater, + + _ => Ordering::Equal, + }; + + self.order.sort_by(ordering); + } +} diff --git a/src/bindgen/error.rs b/src/bindgen/error.rs new file mode 100644 index 0000000..e4002f6 --- /dev/null +++ b/src/bindgen/error.rs @@ -0,0 +1,88 @@ +/* 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/. */ + +use std::error; +use std::fmt; + +pub use crate::bindgen::cargo::cargo_expand::Error as CargoExpandError; +pub use crate::bindgen::cargo::cargo_metadata::Error as CargoMetadataError; +pub use crate::bindgen::cargo::cargo_toml::Error as CargoTomlError; +pub use syn::parse::Error as ParseError; + +#[derive(Debug)] +#[allow(clippy::enum_variant_names)] +pub enum Error { + CargoMetadata(String, CargoMetadataError), + CargoToml(String, CargoTomlError), + CargoExpand(String, CargoExpandError), + ParseSyntaxError { + crate_name: String, + src_path: String, + error: ParseError, + }, + ParseCannotOpenFile { + crate_name: String, + src_path: String, + }, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::CargoMetadata(ref path, ref error) => write!( + f, + "Couldn't execute `cargo metadata` with manifest {:?}: {:?}", + path, error + ), + Error::CargoToml(ref path, ref error) => { + write!(f, "Couldn't load manifest file {:?}: {:?}", path, error) + } + Error::CargoExpand(ref crate_name, ref error) => write!( + f, + "Parsing crate `{}`: couldn't run `cargo rustc -Zunpretty=expanded`: {:?}", + crate_name, error + ), + Error::ParseSyntaxError { + ref crate_name, + ref src_path, + ref error, + } => { + write!( + f, + "Parsing crate `{}`:`{}`:\n{:?}", + crate_name, src_path, error + )?; + + if !src_path.is_empty() { + write!( + f, + "\nTry running `rustc -Z parse-only {}` to see a nicer error message", + src_path, + )? + } + Ok(()) + } + Error::ParseCannotOpenFile { + ref crate_name, + ref src_path, + } => write!( + f, + "Parsing crate `{}`: cannot open file `{}`.", + crate_name, src_path + ), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::CargoMetadata(_, ref error) => Some(error), + Error::CargoToml(_, ref error) => Some(error), + Error::CargoExpand(_, ref error) => Some(error), + Error::ParseSyntaxError { ref error, .. } => Some(error), + Error::ParseCannotOpenFile { .. } => None, + } + } +} diff --git a/src/bindgen/ir/annotation.rs b/src/bindgen/ir/annotation.rs new file mode 100644 index 0000000..48e3e4b --- /dev/null +++ b/src/bindgen/ir/annotation.rs @@ -0,0 +1,217 @@ +/* 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/. */ + +use std::borrow::Cow; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::str::FromStr; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::utilities::SynAttributeHelpers; + +// A system for specifying properties on items. Annotations are +// given through document comments and parsed by this code. +// +// An annotation is in the form cbindgen:PROPERTY=VALUE +// Where PROPERTY depends on the item +// Where VALUE can be +// * list - [Item1, Item2, Item3, ...] +// * atom - Foo +// * bool - true,false +// Examples: +// * cbindgen:field-names=[mHandle, mNamespace] +// * cbindgen:function-postfix=WR_DESTRUCTOR_SAFE + +/// A value specified by an annotation. +#[derive(Debug, Clone)] +pub enum AnnotationValue { + List(Vec<String>), + Atom(Option<String>), + Bool(bool), +} + +/// A set of annotations specified by a document comment. +#[derive(Debug, Default, Clone)] +pub struct AnnotationSet { + annotations: HashMap<String, AnnotationValue>, + pub must_use: bool, + pub deprecated: Option<String>, +} + +pub enum DeprecatedNoteKind { + Function, + Struct, + Enum, +} + +impl AnnotationSet { + pub fn new() -> AnnotationSet { + AnnotationSet { + annotations: HashMap::new(), + must_use: false, + deprecated: None, + } + } + + pub fn is_empty(&self) -> bool { + self.annotations.is_empty() && !self.must_use + } + + pub(crate) fn must_use(&self, config: &Config) -> bool { + self.must_use && config.language != Language::Cython + } + + pub(crate) fn deprecated_note<'c>( + &self, + config: &'c Config, + kind: DeprecatedNoteKind, + ) -> Option<Cow<'c, str>> { + let note = self.deprecated.as_deref()?; + + if config.language == Language::Cython { + return None; + } + + if note.is_empty() { + return Some(Cow::Borrowed(match kind { + DeprecatedNoteKind::Enum => config.enumeration.deprecated.as_deref()?, + DeprecatedNoteKind::Function => config.function.deprecated.as_deref()?, + DeprecatedNoteKind::Struct => config.structure.deprecated.as_deref()?, + })); + } + + let format = match kind { + DeprecatedNoteKind::Enum => &config.enumeration.deprecated_with_note, + DeprecatedNoteKind::Function => &config.function.deprecated_with_note, + DeprecatedNoteKind::Struct => &config.structure.deprecated_with_note, + } + .as_ref()?; + Some(Cow::Owned(format.replace("{}", &format!("{:?}", note)))) + } + + pub fn load(attrs: &[syn::Attribute]) -> Result<AnnotationSet, String> { + let lines = attrs.get_comment_lines(); + let lines: Vec<&str> = lines + .iter() + .filter_map(|line| { + let line = line.trim_start(); + if !line.starts_with("cbindgen:") { + return None; + } + + Some(line) + }) + .collect(); + + let must_use = attrs.has_attr_word("must_use"); + let deprecated = attrs.find_deprecated_note(); + let mut annotations = HashMap::new(); + + // Look at each line for an annotation + for line in lines { + debug_assert!(line.starts_with("cbindgen:")); + + // Remove the "cbindgen:" prefix + let annotation = &line[9..]; + + // Split the annotation in two + let parts: Vec<&str> = annotation.split('=').map(|x| x.trim()).collect(); + + if parts.len() > 2 { + return Err(format!("Couldn't parse {}.", line)); + } + + // Grab the name that this annotation is modifying + let name = parts[0]; + + // If the annotation only has a name, assume it's setting a bool flag + if parts.len() == 1 { + annotations.insert(name.to_string(), AnnotationValue::Bool(true)); + continue; + } + + // Parse the value we're setting the name to + let value = parts[1]; + + if let Some(x) = parse_list(value) { + annotations.insert(name.to_string(), AnnotationValue::List(x)); + continue; + } + if let Ok(x) = value.parse::<bool>() { + annotations.insert(name.to_string(), AnnotationValue::Bool(x)); + continue; + } + annotations.insert( + name.to_string(), + if value.is_empty() { + AnnotationValue::Atom(None) + } else { + AnnotationValue::Atom(Some(value.to_string())) + }, + ); + } + + Ok(AnnotationSet { + annotations, + must_use, + deprecated, + }) + } + + /// Adds an annotation value if none is specified. + pub fn add_default(&mut self, name: &str, value: AnnotationValue) { + if let Entry::Vacant(e) = self.annotations.entry(name.to_string()) { + e.insert(value); + } + } + + pub fn list(&self, name: &str) -> Option<Vec<String>> { + match self.annotations.get(name) { + Some(AnnotationValue::List(x)) => Some(x.clone()), + _ => None, + } + } + pub fn atom(&self, name: &str) -> Option<Option<String>> { + match self.annotations.get(name) { + Some(AnnotationValue::Atom(x)) => Some(x.clone()), + _ => None, + } + } + pub fn bool(&self, name: &str) -> Option<bool> { + match self.annotations.get(name) { + Some(AnnotationValue::Bool(x)) => Some(*x), + _ => None, + } + } + + pub fn parse_atom<T>(&self, name: &str) -> Option<T> + where + T: Default + FromStr, + { + match self.annotations.get(name) { + Some(AnnotationValue::Atom(x)) => Some( + x.as_ref() + .map_or(T::default(), |y| y.parse::<T>().ok().unwrap()), + ), + _ => None, + } + } +} + +/// Parse lists like "[x, y, z]". This is not implemented efficiently or well. +fn parse_list(list: &str) -> Option<Vec<String>> { + if list.len() < 2 { + return None; + } + + match (list.chars().next(), list.chars().last()) { + (Some('['), Some(']')) => Some( + list[1..list.len() - 1] + .split(',') + .map(|x| x.trim().to_string()) + .collect(), + ), + _ => None, + } +} diff --git a/src/bindgen/ir/cfg.rs b/src/bindgen/ir/cfg.rs new file mode 100644 index 0000000..e8aa412 --- /dev/null +++ b/src/bindgen/ir/cfg.rs @@ -0,0 +1,365 @@ +/* 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/. */ + +use std::fmt; +use std::io::Write; + +use crate::bindgen::cargo::cargo_metadata::Dependency; +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::writer::SourceWriter; + +#[derive(PartialEq, Eq)] +enum DefineKey<'a> { + Boolean(&'a str), + Named(&'a str, &'a str), +} + +impl<'a> DefineKey<'a> { + fn load(key: &str) -> DefineKey { + // TODO: dirty parser + if !key.contains('=') { + return DefineKey::Boolean(key); + } + + let mut splits = key.trim().split('='); + + let name = match splits.next() { + Some(n) => n.trim(), + None => return DefineKey::Boolean(key), + }; + + let value = match splits.next() { + Some(v) => v.trim(), + None => return DefineKey::Boolean(key), + }; + + if splits.next().is_some() { + return DefineKey::Boolean(key); + } + + DefineKey::Named(name, value) + } +} + +#[derive(Debug, Clone)] +pub enum Cfg { + Boolean(String), + Named(String, String), + Any(Vec<Cfg>), + All(Vec<Cfg>), + Not(Box<Cfg>), +} + +impl fmt::Display for Cfg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Cfg::Boolean(key) => write!(f, "{}", key), + Cfg::Named(key, value) => write!(f, "{} = {:?}", key, value), + Cfg::Any(cfgs) => { + write!(f, "any(")?; + for (index, cfg) in cfgs.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", cfg)?; + } + write!(f, ")") + } + Cfg::All(cfgs) => { + write!(f, "all(")?; + for (index, cfg) in cfgs.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + write!(f, "{}", cfg)?; + } + write!(f, ")") + } + Cfg::Not(cfg) => write!(f, "not({})", cfg), + } + } +} + +impl Cfg { + pub fn join(cfgs: &[Cfg]) -> Option<Cfg> { + if cfgs.is_empty() { + None + } else { + Some(Cfg::All(cfgs.to_owned())) + } + } + + pub fn append(parent: Option<&Cfg>, child: Option<Cfg>) -> Option<Cfg> { + match (parent, child) { + (None, None) => None, + (None, Some(child)) => Some(child), + (Some(parent), None) => Some(parent.clone()), + (Some(parent), Some(child)) => Some(Cfg::All(vec![parent.clone(), child])), + } + } + + pub fn load(attrs: &[syn::Attribute]) -> Option<Cfg> { + let mut configs = Vec::new(); + + for attr in attrs { + if let Ok(syn::Meta::List(syn::MetaList { path, nested, .. })) = attr.parse_meta() { + if !path.is_ident("cfg") || nested.len() != 1 { + continue; + } + + if let Some(config) = Cfg::load_single(nested.first().unwrap()) { + configs.push(config); + } + } + } + + match configs.len() { + 0 => None, + 1 => Some(configs.pop().unwrap()), + _ => Some(Cfg::All(configs)), + } + } + + pub fn load_metadata(dependency: &Dependency) -> Option<Cfg> { + let target = dependency.target.as_ref()?; + match syn::parse_str::<syn::Meta>(target) { + Ok(target) => { + // Parsing succeeded using the #[cfg] syntax + if let syn::Meta::List(syn::MetaList { path, nested, .. }) = target { + if !path.is_ident("cfg") || nested.len() != 1 { + return None; + } + Cfg::load_single(nested.first().unwrap()) + } else { + None + } + } + Err(_) => { + // Parsing failed using #[cfg], this may be a literal target + // name + Cfg::load_single(&syn::NestedMeta::Lit(syn::Lit::Str(syn::LitStr::new( + target, + proc_macro2::Span::call_site(), + )))) + } + } + } + + fn load_single(item: &syn::NestedMeta) -> Option<Cfg> { + Some(match *item { + syn::NestedMeta::Meta(syn::Meta::Path(ref path)) => { + Cfg::Boolean(format!("{}", path.segments.first().unwrap().ident)) + } + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + ref path, + lit: syn::Lit::Str(ref value), + .. + })) => Cfg::Named( + format!("{}", path.segments.first().unwrap().ident), + value.value(), + ), + syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { + ref path, + ref nested, + .. + })) => { + if path.is_ident("any") { + Cfg::Any(Cfg::load_list(nested.iter())?) + } else if path.is_ident("all") { + Cfg::All(Cfg::load_list(nested.iter())?) + } else if path.is_ident("not") { + if nested.len() != 1 { + return None; + } + + Cfg::Not(Box::new(Cfg::load_single(&nested[0])?)) + } else { + return None; + } + } + _ => return None, + }) + } + + fn load_list<'a, I: Iterator<Item = &'a syn::NestedMeta>>(attrs: I) -> Option<Vec<Cfg>> { + let mut configs = Vec::new(); + + for attr in attrs { + configs.push(Cfg::load_single(attr)?); + } + + if configs.is_empty() { + None + } else { + Some(configs) + } + } +} + +pub trait ToCondition: Sized { + fn to_condition(&self, config: &Config) -> Option<Condition>; +} + +impl ToCondition for Option<Cfg> { + fn to_condition(&self, config: &Config) -> Option<Condition> { + self.as_ref()?.to_condition(config) + } +} + +impl ToCondition for Cfg { + fn to_condition(&self, config: &Config) -> Option<Condition> { + match *self { + Cfg::Boolean(ref cfg_name) => { + let define = config + .defines + .iter() + .find(|(key, ..)| DefineKey::Boolean(cfg_name) == DefineKey::load(key)); + if let Some((_, define)) = define { + Some(Condition::Define(define.to_owned())) + } else { + warn!( + "Missing `[defines]` entry for `{}` in cbindgen config.", + self, + ); + None + } + } + Cfg::Named(ref cfg_name, ref cfg_value) => { + let define = config.defines.iter().find(|(key, ..)| { + DefineKey::Named(cfg_name, cfg_value) == DefineKey::load(key) + }); + if let Some((_, define)) = define { + Some(Condition::Define(define.to_owned())) + } else { + warn!( + "Missing `[defines]` entry for `{}` in cbindgen config.", + self, + ); + None + } + } + Cfg::Any(ref children) => { + let conditions: Vec<_> = children + .iter() + .filter_map(|x| x.to_condition(config)) + .collect(); + match conditions.len() { + 0 => None, + 1 => conditions.into_iter().next(), + _ => Some(Condition::Any(conditions)), + } + } + Cfg::All(ref children) => { + let cfgs: Vec<_> = children + .iter() + .filter_map(|x| x.to_condition(config)) + .collect(); + match cfgs.len() { + 0 => None, + 1 => cfgs.into_iter().next(), + _ => Some(Condition::All(cfgs)), + } + } + Cfg::Not(ref child) => child + .to_condition(config) + .map(|cfg| Condition::Not(Box::new(cfg))), + } + } +} + +#[derive(Debug, Clone)] +pub enum Condition { + Define(String), + Any(Vec<Condition>), + All(Vec<Condition>), + Not(Box<Condition>), +} + +impl Condition { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + match *self { + Condition::Define(ref define) => { + if config.language == Language::Cython { + write!(out, "{}", define); + } else { + out.write("defined("); + write!(out, "{}", define); + out.write(")"); + } + } + Condition::Any(ref conditions) => { + out.write("("); + for (i, condition) in conditions.iter().enumerate() { + if i != 0 { + out.write(if config.language == Language::Cython { + " or " + } else { + " || " + }); + } + condition.write(config, out); + } + out.write(")"); + } + Condition::All(ref conditions) => { + out.write("("); + for (i, condition) in conditions.iter().enumerate() { + if i != 0 { + out.write(if config.language == Language::Cython { + " and " + } else { + " && " + }); + } + condition.write(config, out); + } + out.write(")"); + } + Condition::Not(ref condition) => { + out.write(if config.language == Language::Cython { + "not " + } else { + "!" + }); + condition.write(config, out); + } + } + } +} + +pub trait ConditionWrite { + fn write_before<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>); + fn write_after<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>); +} + +impl ConditionWrite for Option<Condition> { + fn write_before<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + if let Some(ref cfg) = *self { + if config.language == Language::Cython { + out.write("IF "); + cfg.write(config, out); + out.open_brace(); + } else { + out.push_set_spaces(0); + out.write("#if "); + cfg.write(config, out); + out.pop_set_spaces(); + out.new_line(); + } + } + } + + fn write_after<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + if self.is_some() { + if config.language == Language::Cython { + out.close_brace(false); + } else { + out.new_line(); + out.push_set_spaces(0); + out.write("#endif"); + out.pop_set_spaces(); + } + } + } +} diff --git a/src/bindgen/ir/constant.rs b/src/bindgen/ir/constant.rs new file mode 100644 index 0000000..d3b9bd4 --- /dev/null +++ b/src/bindgen/ir/constant.rs @@ -0,0 +1,813 @@ +/* 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/. */ + +use std::borrow::Cow; +use std::collections::HashMap; +use std::io::Write; + +use syn::ext::IdentExt; +use syn::{self, UnOp}; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path, + Struct, ToCondition, Type, +}; +use crate::bindgen::library::Library; +use crate::bindgen::writer::{Source, SourceWriter}; +use crate::bindgen::Bindings; + +fn member_to_ident(member: &syn::Member) -> String { + match member { + syn::Member::Named(ref name) => name.unraw().to_string(), + syn::Member::Unnamed(ref index) => format!("_{}", index.index), + } +} + +// TODO: Maybe add support to more std associated constants. +fn to_known_assoc_constant(associated_to: &Path, name: &str) -> Option<String> { + use crate::bindgen::ir::{IntKind, PrimitiveType}; + + if name != "MAX" && name != "MIN" { + return None; + } + + let prim = PrimitiveType::maybe(associated_to.name())?; + let prefix = match prim { + PrimitiveType::Integer { + kind, + signed, + zeroable: _, + } => match kind { + IntKind::B8 => { + if signed { + "INT8" + } else { + "UINT8" + } + } + IntKind::B16 => { + if signed { + "INT16" + } else { + "UINT16" + } + } + IntKind::B32 => { + if signed { + "INT32" + } else { + "UINT32" + } + } + IntKind::B64 => { + if signed { + "INT64" + } else { + "UINT64" + } + } + _ => return None, + }, + _ => return None, + }; + Some(format!("{}_{}", prefix, name)) +} + +#[derive(Debug, Clone)] +pub enum Literal { + Expr(String), + Path { + associated_to: Option<(Path, String)>, + name: String, + }, + PostfixUnaryOp { + op: &'static str, + value: Box<Literal>, + }, + BinOp { + left: Box<Literal>, + op: &'static str, + right: Box<Literal>, + }, + FieldAccess { + base: Box<Literal>, + field: String, + }, + Struct { + path: Path, + export_name: String, + fields: HashMap<String, Literal>, + }, + Cast { + ty: Type, + value: Box<Literal>, + }, +} + +impl Literal { + fn replace_self_with(&mut self, self_ty: &Path) { + match *self { + Literal::PostfixUnaryOp { ref mut value, .. } => { + value.replace_self_with(self_ty); + } + Literal::BinOp { + ref mut left, + ref mut right, + .. + } => { + left.replace_self_with(self_ty); + right.replace_self_with(self_ty); + } + Literal::FieldAccess { ref mut base, .. } => { + base.replace_self_with(self_ty); + } + Literal::Struct { + ref mut path, + ref mut export_name, + ref mut fields, + } => { + if path.replace_self_with(self_ty) { + *export_name = self_ty.name().to_owned(); + } + for ref mut expr in fields.values_mut() { + expr.replace_self_with(self_ty); + } + } + Literal::Cast { + ref mut ty, + ref mut value, + } => { + ty.replace_self_with(self_ty); + value.replace_self_with(self_ty); + } + Literal::Path { + ref mut associated_to, + .. + } => { + if let Some((ref mut path, ref mut export_name)) = *associated_to { + if path.replace_self_with(self_ty) { + *export_name = self_ty.name().to_owned(); + } + } + } + Literal::Expr(..) => {} + } + } + + fn is_valid(&self, bindings: &Bindings) -> bool { + match *self { + Literal::Expr(..) => true, + Literal::Path { + ref associated_to, + ref name, + } => { + if let Some((ref path, _export_name)) = associated_to { + return bindings.struct_exists(path) + || to_known_assoc_constant(path, name).is_some(); + } + true + } + Literal::PostfixUnaryOp { ref value, .. } => value.is_valid(bindings), + Literal::BinOp { + ref left, + ref right, + .. + } => left.is_valid(bindings) && right.is_valid(bindings), + Literal::FieldAccess { ref base, .. } => base.is_valid(bindings), + Literal::Struct { ref path, .. } => bindings.struct_exists(path), + Literal::Cast { ref value, .. } => value.is_valid(bindings), + } + } + + fn can_be_constexpr(&self) -> bool { + !self.has_pointer_casts() + } + + fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool { + if !visitor(self) { + return false; + } + match self { + Literal::Expr(..) | Literal::Path { .. } => true, + Literal::PostfixUnaryOp { ref value, .. } => value.visit(visitor), + Literal::BinOp { + ref left, + ref right, + .. + } => left.visit(visitor) && right.visit(visitor), + Literal::FieldAccess { ref base, .. } => base.visit(visitor), + Literal::Struct { ref fields, .. } => { + for (_name, field) in fields.iter() { + if !field.visit(visitor) { + return false; + } + } + true + } + Literal::Cast { ref value, .. } => value.visit(visitor), + } + } + + fn has_pointer_casts(&self) -> bool { + let mut has_pointer_casts = false; + self.visit(&mut |lit| { + if let Literal::Cast { ref ty, .. } = *lit { + has_pointer_casts = has_pointer_casts || ty.is_ptr(); + } + !has_pointer_casts + }); + has_pointer_casts + } + + pub fn uses_only_primitive_types(&self) -> bool { + let mut uses_only_primitive_types = true; + self.visit(&mut |lit| { + // XXX This is a bit sketchy, but alas. + uses_only_primitive_types = uses_only_primitive_types + && match *lit { + Literal::Struct { .. } => false, + Literal::Cast { ref ty, .. } => ty.is_primitive_or_ptr_primitive(), + _ => true, + }; + uses_only_primitive_types + }); + uses_only_primitive_types + } +} + +impl Literal { + pub fn rename_for_config(&mut self, config: &Config) { + match self { + Literal::Struct { + ref mut export_name, + fields, + .. + } => { + config.export.rename(export_name); + for lit in fields.values_mut() { + lit.rename_for_config(config); + } + } + Literal::FieldAccess { ref mut base, .. } => { + base.rename_for_config(config); + } + Literal::Path { + ref mut associated_to, + ref mut name, + } => { + if let Some((_path, ref mut export_name)) = associated_to { + config.export.rename(export_name); + } else { + config.export.rename(name); + } + } + Literal::PostfixUnaryOp { ref mut value, .. } => { + value.rename_for_config(config); + } + Literal::BinOp { + ref mut left, + ref mut right, + .. + } => { + left.rename_for_config(config); + right.rename_for_config(config); + } + Literal::Expr(_) => {} + Literal::Cast { + ref mut ty, + ref mut value, + } => { + ty.rename_for_config(config, &GenericParams::default()); + value.rename_for_config(config); + } + } + } + + // Translate from full blown `syn::Expr` into a simpler `Literal` type + pub fn load(expr: &syn::Expr) -> Result<Literal, String> { + match *expr { + // Match binary expressions of the form `a * b` + syn::Expr::Binary(ref bin_expr) => { + let l = Self::load(&bin_expr.left)?; + let r = Self::load(&bin_expr.right)?; + let op = match bin_expr.op { + syn::BinOp::Add(..) => "+", + syn::BinOp::Sub(..) => "-", + syn::BinOp::Mul(..) => "*", + syn::BinOp::Div(..) => "/", + syn::BinOp::Rem(..) => "%", + syn::BinOp::And(..) => "&&", + syn::BinOp::Or(..) => "||", + syn::BinOp::BitXor(..) => "^", + syn::BinOp::BitAnd(..) => "&", + syn::BinOp::BitOr(..) => "|", + syn::BinOp::Shl(..) => "<<", + syn::BinOp::Shr(..) => ">>", + syn::BinOp::Eq(..) => "==", + syn::BinOp::Lt(..) => "<", + syn::BinOp::Le(..) => "<=", + syn::BinOp::Ne(..) => "!=", + syn::BinOp::Ge(..) => ">=", + syn::BinOp::Gt(..) => ">", + syn::BinOp::AddEq(..) => "+=", + syn::BinOp::SubEq(..) => "-=", + syn::BinOp::MulEq(..) => "*=", + syn::BinOp::DivEq(..) => "/=", + syn::BinOp::RemEq(..) => "%=", + syn::BinOp::BitXorEq(..) => "^=", + syn::BinOp::BitAndEq(..) => "&=", + syn::BinOp::BitOrEq(..) => "|=", + syn::BinOp::ShlEq(..) => "<<=", + syn::BinOp::ShrEq(..) => ">>=", + }; + Ok(Literal::BinOp { + left: Box::new(l), + op, + right: Box::new(r), + }) + } + + // Match literals like true, 'a', 32 etc + syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { + match lit { + syn::Lit::Byte(ref value) => Ok(Literal::Expr(format!("{}", value.value()))), + syn::Lit::Char(ref value) => Ok(Literal::Expr(match value.value() as u32 { + 0..=255 => format!("'{}'", value.value().escape_default()), + other_code => format!(r"U'\U{:08X}'", other_code), + })), + syn::Lit::Int(ref value) => { + let suffix = match value.suffix() { + "u64" => "ull", + "i64" => "ll", + "u32" => "u", + _ if value.base10_parse::<i64>().is_err() => "ull", + _ => "", + }; + Ok(Literal::Expr(format!( + "{}{}", + value.base10_digits(), + suffix + ))) + } + syn::Lit::Float(ref value) => { + Ok(Literal::Expr(value.base10_digits().to_string())) + } + syn::Lit::Bool(ref value) => Ok(Literal::Expr(format!("{}", value.value))), + // TODO: Add support for byte string and Verbatim + _ => Err(format!("Unsupported literal expression. {:?}", *lit)), + } + } + + syn::Expr::Field(syn::ExprField { + ref base, + ref member, + .. + }) => Ok(Literal::FieldAccess { + base: Box::new(Literal::load(base)?), + field: member_to_ident(member), + }), + + syn::Expr::Call(syn::ExprCall { + ref func, ref args, .. + }) => { + let struct_name = match Literal::load(func)? { + Literal::Path { + associated_to: None, + name, + } => name, + _ => return Err(format!("Unsupported call expression. {:?}", *expr)), + }; + let mut fields = HashMap::<String, Literal>::default(); + for (index, arg) in args.iter().enumerate() { + let ident = + member_to_ident(&syn::Member::Unnamed(syn::Index::from(index))).to_string(); + let value = Literal::load(arg)?; + fields.insert(ident, value); + } + Ok(Literal::Struct { + path: Path::new(struct_name.clone()), + export_name: struct_name, + fields, + }) + } + + syn::Expr::Struct(syn::ExprStruct { + ref path, + ref fields, + .. + }) => { + let struct_name = path.segments[0].ident.unraw().to_string(); + let mut field_map = HashMap::<String, Literal>::default(); + for field in fields { + let ident = member_to_ident(&field.member).to_string(); + let value = Literal::load(&field.expr)?; + field_map.insert(ident, value); + } + Ok(Literal::Struct { + path: Path::new(struct_name.clone()), + export_name: struct_name, + fields: field_map, + }) + } + + syn::Expr::Unary(syn::ExprUnary { + ref op, ref expr, .. + }) => match *op { + UnOp::Not(_) => { + let val = Self::load(expr)?; + Ok(Literal::PostfixUnaryOp { + op: "~", + value: Box::new(val), + }) + } + UnOp::Neg(_) => { + let val = Self::load(expr)?; + Ok(Literal::PostfixUnaryOp { + op: "-", + value: Box::new(val), + }) + } + _ => Err(format!("Unsupported Unary expression. {:?}", *op)), + }, + + // Match identifiers, like `5 << SHIFT` + syn::Expr::Path(syn::ExprPath { ref path, .. }) => { + // Handle only the simplest identifiers and Associated::IDENT + // kind of syntax. + Ok(match path.segments.len() { + 1 => Literal::Path { + associated_to: None, + name: path.segments[0].ident.to_string(), + }, + 2 => { + let struct_name = path.segments[0].ident.to_string(); + Literal::Path { + associated_to: Some((Path::new(&struct_name), struct_name)), + name: path.segments[1].ident.to_string(), + } + } + _ => return Err(format!("Unsupported path expression. {:?}", path)), + }) + } + + syn::Expr::Paren(syn::ExprParen { ref expr, .. }) => Self::load(expr), + + syn::Expr::Cast(syn::ExprCast { + ref expr, ref ty, .. + }) => { + let val = Self::load(expr)?; + match Type::load(ty)? { + Some(ty) => Ok(Literal::Cast { + ty, + value: Box::new(val), + }), + None => Err("Cannot cast to zero sized type.".to_owned()), + } + } + + _ => Err(format!("Unsupported expression. {:?}", *expr)), + } + } + + pub(crate) fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + match self { + Literal::Expr(v) => match (&**v, config.language) { + ("true", Language::Cython) => write!(out, "True"), + ("false", Language::Cython) => write!(out, "False"), + (v, _) => write!(out, "{}", v), + }, + Literal::Path { + ref associated_to, + ref name, + } => { + if let Some((ref path, ref export_name)) = associated_to { + if let Some(known) = to_known_assoc_constant(path, name) { + return write!(out, "{}", known); + } + let path_separator = match config.language { + Language::Cython | Language::C => "_", + Language::Cxx => { + if config.structure.associated_constants_in_body { + "::" + } else { + "_" + } + } + }; + write!(out, "{}{}", export_name, path_separator) + } + write!(out, "{}", name) + } + Literal::FieldAccess { + ref base, + ref field, + } => { + write!(out, "("); + base.write(config, out); + write!(out, ").{}", field); + } + Literal::PostfixUnaryOp { op, ref value } => { + write!(out, "{}", op); + value.write(config, out); + } + Literal::BinOp { + ref left, + op, + ref right, + } => { + write!(out, "("); + left.write(config, out); + write!(out, " {} ", op); + right.write(config, out); + write!(out, ")"); + } + Literal::Cast { ref ty, ref value } => { + out.write(if config.language == Language::Cython { + "<" + } else { + "(" + }); + ty.write(config, out); + out.write(if config.language == Language::Cython { + ">" + } else { + ")" + }); + value.write(config, out); + } + Literal::Struct { + export_name, + fields, + path, + } => { + match config.language { + Language::C => write!(out, "({})", export_name), + Language::Cxx => write!(out, "{}", export_name), + Language::Cython => write!(out, "<{}>", export_name), + } + + write!(out, "{{ "); + let mut is_first_field = true; + // In C++, same order as defined is required. + let ordered_fields = out.bindings().struct_field_names(path); + for ordered_key in ordered_fields.iter() { + if let Some(lit) = fields.get(ordered_key) { + if !is_first_field { + write!(out, ", "); + } else { + is_first_field = false; + } + match config.language { + Language::Cxx => write!(out, "/* .{} = */ ", ordered_key), + Language::C => write!(out, ".{} = ", ordered_key), + Language::Cython => {} + } + lit.write(config, out); + } + } + write!(out, " }}"); + } + } + } +} + +#[derive(Debug, Clone)] +pub struct Constant { + pub path: Path, + pub export_name: String, + pub ty: Type, + pub value: Literal, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, + pub associated_to: Option<Path>, +} + +impl Constant { + pub fn load( + path: Path, + mod_cfg: Option<&Cfg>, + ty: &syn::Type, + expr: &syn::Expr, + attrs: &[syn::Attribute], + associated_to: Option<Path>, + ) -> Result<Constant, String> { + let ty = Type::load(ty)?; + let mut ty = match ty { + Some(ty) => ty, + None => { + return Err("Cannot have a zero sized const definition.".to_owned()); + } + }; + + let mut lit = Literal::load(expr)?; + + if let Some(ref associated_to) = associated_to { + ty.replace_self_with(associated_to); + lit.replace_self_with(associated_to); + } + + Ok(Constant::new( + path, + ty, + lit, + Cfg::append(mod_cfg, Cfg::load(attrs)), + AnnotationSet::load(attrs)?, + Documentation::load(attrs), + associated_to, + )) + } + + pub fn new( + path: Path, + ty: Type, + value: Literal, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + associated_to: Option<Path>, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + ty, + value, + cfg, + annotations, + documentation, + associated_to, + } + } + + pub fn uses_only_primitive_types(&self) -> bool { + self.value.uses_only_primitive_types() && self.ty.is_primitive_or_ptr_primitive() + } +} + +impl Item for Constant { + fn path(&self) -> &Path { + &self.path + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.ty.add_dependencies(library, out); + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Constant(self.clone()) + } + + fn rename_for_config(&mut self, config: &Config) { + if self.associated_to.is_none() { + config.export.rename(&mut self.export_name); + } + self.value.rename_for_config(config); + self.ty.rename_for_config(config, &GenericParams::default()); // FIXME: should probably propagate something here + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.ty.resolve_declaration_types(resolver); + } +} + +impl Constant { + pub fn write_declaration<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + associated_to_struct: &Struct, + ) { + debug_assert!(self.associated_to.is_some()); + debug_assert!(config.language == Language::Cxx); + debug_assert!(!associated_to_struct.is_transparent); + debug_assert!(config.structure.associated_constants_in_body); + debug_assert!(config.constant.allow_static_const); + + if let Type::Ptr { is_const: true, .. } = self.ty { + out.write("static "); + } else { + out.write("static const "); + } + self.ty.write(config, out); + write!(out, " {};", self.export_name()) + } + + pub fn write<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + associated_to_struct: Option<&Struct>, + ) { + if let Some(assoc) = associated_to_struct { + if assoc.is_generic() { + return; // Not tested / implemented yet, so bail out. + } + } + + if !self.value.is_valid(out.bindings()) { + return; + } + + let associated_to_transparent = associated_to_struct.map_or(false, |s| s.is_transparent); + + let in_body = associated_to_struct.is_some() + && config.language == Language::Cxx + && config.structure.associated_constants_in_body + && config.constant.allow_static_const + && !associated_to_transparent; + + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + let name = if in_body { + Cow::Owned(format!( + "{}::{}", + associated_to_struct.unwrap().export_name(), + self.export_name(), + )) + } else if self.associated_to.is_none() { + Cow::Borrowed(self.export_name()) + } else { + let associated_name = match associated_to_struct { + Some(s) => Cow::Borrowed(s.export_name()), + None => { + let mut name = self.associated_to.as_ref().unwrap().name().to_owned(); + config.export.rename(&mut name); + Cow::Owned(name) + } + }; + + Cow::Owned(format!("{}_{}", associated_name, self.export_name())) + }; + + let value = match self.value { + Literal::Struct { + ref fields, + ref path, + .. + } if out.bindings().struct_is_transparent(path) => fields.iter().next().unwrap().1, + _ => &self.value, + }; + + self.documentation.write(config, out); + + let allow_constexpr = config.constant.allow_constexpr && self.value.can_be_constexpr(); + match config.language { + Language::Cxx if config.constant.allow_static_const || allow_constexpr => { + if allow_constexpr { + out.write("constexpr ") + } + + if config.constant.allow_static_const { + out.write(if in_body { "inline " } else { "static " }); + } + + if let Type::Ptr { is_const: true, .. } = self.ty { + // Nothing. + } else { + out.write("const "); + } + + self.ty.write(config, out); + write!(out, " {} = ", name); + value.write(config, out); + write!(out, ";"); + } + Language::Cxx | Language::C => { + write!(out, "#define {} ", name); + value.write(config, out); + } + Language::Cython => { + out.write("const "); + self.ty.write(config, out); + // For extern Cython declarations the initializer is ignored, + // but still useful as documentation, so we write it as a comment. + write!(out, " {} # = ", name); + value.write(config, out); + } + } + + condition.write_after(config, out); + } +} diff --git a/src/bindgen/ir/documentation.rs b/src/bindgen/ir/documentation.rs new file mode 100644 index 0000000..6822c0e --- /dev/null +++ b/src/bindgen/ir/documentation.rs @@ -0,0 +1,111 @@ +/* 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/. */ + +use std::io::Write; + +use crate::bindgen::config::{Config, DocumentationLength, DocumentationStyle, Language}; +use crate::bindgen::utilities::SynAttributeHelpers; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct Documentation { + pub doc_comment: Vec<String>, +} + +impl Documentation { + pub fn load(attrs: &[syn::Attribute]) -> Self { + let doc = attrs + .get_comment_lines() + .into_iter() + .filter(|x| !x.trim_start().starts_with("cbindgen:")) + .collect(); + + Documentation { doc_comment: doc } + } + + pub fn simple(line: &str) -> Self { + Documentation { + doc_comment: vec![line.to_owned()], + } + } + + pub fn none() -> Self { + Documentation { + doc_comment: Vec::new(), + } + } +} + +impl Source for Documentation { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + if self.doc_comment.is_empty() || !config.documentation { + return; + } + + let end = match config.documentation_length { + DocumentationLength::Short => 1, + DocumentationLength::Full => self.doc_comment.len(), + }; + + // Cython uses Python-style comments, so `documentation_style` is not relevant. + if config.language == Language::Cython { + for line in &self.doc_comment[..end] { + write!(out, "#{}", line); + out.new_line(); + } + return; + } + + let style = match config.documentation_style { + DocumentationStyle::Auto if config.language == Language::C => DocumentationStyle::Doxy, + DocumentationStyle::Auto if config.language == Language::Cxx => DocumentationStyle::Cxx, + DocumentationStyle::Auto => DocumentationStyle::C, // Fallback if `Language` gets extended. + other => other, + }; + + // Following these documents for style conventions: + // https://en.wikibooks.org/wiki/C++_Programming/Code/Style_Conventions/Comments + // https://www.cs.cmu.edu/~410/doc/doxygen.html + match style { + DocumentationStyle::C => { + out.write("/*"); + out.new_line(); + } + + DocumentationStyle::Doxy => { + out.write("/**"); + out.new_line(); + } + + _ => (), + } + + for line in &self.doc_comment[..end] { + match style { + DocumentationStyle::C => out.write(""), + DocumentationStyle::Doxy => out.write(" *"), + DocumentationStyle::C99 => out.write("//"), + DocumentationStyle::Cxx => out.write("///"), + DocumentationStyle::Auto => unreachable!(), // Auto case should always be covered + } + + write!(out, "{}", line); + out.new_line(); + } + + match style { + DocumentationStyle::C => { + out.write(" */"); + out.new_line(); + } + + DocumentationStyle::Doxy => { + out.write(" */"); + out.new_line(); + } + + _ => (), + } + } +} diff --git a/src/bindgen/ir/enumeration.rs b/src/bindgen/ir/enumeration.rs new file mode 100644 index 0000000..a456b76 --- /dev/null +++ b/src/bindgen/ir/enumeration.rs @@ -0,0 +1,1556 @@ +/* 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/. */ + +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, AnnotationValue, Cfg, ConditionWrite, DeprecatedNoteKind, Documentation, Field, + GenericArgument, GenericParams, GenericPath, Item, ItemContainer, Literal, Path, Repr, + ReprStyle, Struct, ToCondition, Type, +}; +use crate::bindgen::library::Library; +use crate::bindgen::mangle; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::rename::{IdentifierType, RenameRule}; +use crate::bindgen::reserved; +use crate::bindgen::writer::{ListType, Source, SourceWriter}; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone)] +pub enum VariantBody { + Empty(AnnotationSet), + Body { + /// The variant field / export name. + name: String, + /// The struct with all the items. + body: Struct, + /// A separate named struct is not created for this variant, + /// an unnamed struct is inlined at the point of use instead. + /// This is a reasonable thing to do only for tuple variants with a single field. + inline: bool, + /// Generated cast methods return the variant's only field instead of the variant itself. + /// For backward compatibility casts are inlined in a slightly + /// larger set of cases than whole variants. + inline_casts: bool, + }, +} + +impl VariantBody { + fn empty() -> Self { + Self::Empty(AnnotationSet::new()) + } + + fn annotations(&self) -> &AnnotationSet { + match *self { + Self::Empty(ref anno) => anno, + Self::Body { ref body, .. } => &body.annotations, + } + } + + fn is_empty(&self) -> bool { + match *self { + Self::Empty(..) => true, + Self::Body { .. } => false, + } + } + + fn specialize( + &self, + generic_values: &[GenericArgument], + mappings: &[(&Path, &GenericArgument)], + config: &Config, + ) -> Self { + match *self { + Self::Empty(ref annos) => Self::Empty(annos.clone()), + Self::Body { + ref name, + ref body, + inline, + inline_casts, + } => Self::Body { + name: name.clone(), + body: body.specialize(generic_values, mappings, config), + inline, + inline_casts, + }, + } + } +} + +#[derive(Debug, Clone)] +pub struct EnumVariant { + pub name: String, + pub export_name: String, + pub discriminant: Option<Literal>, + pub body: VariantBody, + pub cfg: Option<Cfg>, + pub documentation: Documentation, +} + +impl EnumVariant { + fn load( + inline_tag_field: bool, + variant: &syn::Variant, + generic_params: GenericParams, + mod_cfg: Option<&Cfg>, + self_path: &Path, + enum_annotations: &AnnotationSet, + config: &Config, + ) -> Result<Self, String> { + let discriminant = match variant.discriminant { + Some((_, ref expr)) => Some(Literal::load(expr)?), + None => None, + }; + + fn parse_fields( + inline_tag_field: bool, + fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>, + self_path: &Path, + inline_name: Option<&str>, + ) -> Result<Vec<Field>, String> { + let mut res = Vec::new(); + + if inline_tag_field { + res.push(Field::from_name_and_type( + inline_name.map_or_else(|| "tag".to_string(), |name| format!("{}_tag", name)), + Type::Path(GenericPath::new(Path::new("Tag"), vec![])), + )); + } + + for (i, field) in fields.iter().enumerate() { + if let Some(mut ty) = Type::load(&field.ty)? { + ty.replace_self_with(self_path); + res.push(Field { + name: inline_name.map_or_else( + || match field.ident { + Some(ref ident) => ident.unraw().to_string(), + None => i.to_string(), + }, + |name| name.to_string(), + ), + ty, + cfg: Cfg::load(&field.attrs), + annotations: AnnotationSet::load(&field.attrs)?, + documentation: Documentation::load(&field.attrs), + }); + } + } + + Ok(res) + } + + let variant_cfg = Cfg::append(mod_cfg, Cfg::load(&variant.attrs)); + let mut annotations = AnnotationSet::load(&variant.attrs)?; + if let Some(b) = enum_annotations.bool("derive-ostream") { + annotations.add_default("derive-ostream", AnnotationValue::Bool(b)); + } + + let body_rule = enum_annotations + .parse_atom::<RenameRule>("rename-variant-name-fields") + .unwrap_or(config.enumeration.rename_variant_name_fields); + + let body = match variant.fields { + syn::Fields::Unit => VariantBody::Empty(annotations), + syn::Fields::Named(ref fields) => { + let path = Path::new(format!("{}_Body", variant.ident)); + let name = body_rule + .apply( + &variant.ident.unraw().to_string(), + IdentifierType::StructMember, + ) + .into_owned(); + VariantBody::Body { + body: Struct::new( + path, + generic_params, + parse_fields(inline_tag_field, &fields.named, self_path, None)?, + inline_tag_field, + true, + None, + false, + None, + annotations, + Documentation::none(), + ), + name, + inline: false, + inline_casts: false, + } + } + syn::Fields::Unnamed(ref fields) => { + let path = Path::new(format!("{}_Body", variant.ident)); + let name = body_rule + .apply( + &variant.ident.unraw().to_string(), + IdentifierType::StructMember, + ) + .into_owned(); + let inline_casts = fields.unnamed.len() == 1; + // In C++ types with destructors cannot be put into unnamed structs like the + // inlining requires, and it's hard to detect such types. + // Besides that for C++ we generate casts/getters that can be used instead of + // direct field accesses and also have a benefit of being checked. + // As a result we don't currently inline variant definitions in C++ mode at all. + let inline = inline_casts && config.language != Language::Cxx; + let inline_name = if inline { Some(&*name) } else { None }; + VariantBody::Body { + body: Struct::new( + path, + generic_params, + parse_fields(inline_tag_field, &fields.unnamed, self_path, inline_name)?, + inline_tag_field, + true, + None, + false, + None, + annotations, + Documentation::none(), + ), + name, + inline, + inline_casts, + } + } + }; + + Ok(EnumVariant::new( + variant.ident.unraw().to_string(), + discriminant, + body, + variant_cfg, + Documentation::load(&variant.attrs), + )) + } + + pub fn new( + name: String, + discriminant: Option<Literal>, + body: VariantBody, + cfg: Option<Cfg>, + documentation: Documentation, + ) -> Self { + let export_name = name.clone(); + Self { + name, + export_name, + discriminant, + body, + cfg, + documentation, + } + } + + fn simplify_standard_types(&mut self, config: &Config) { + if let VariantBody::Body { ref mut body, .. } = self.body { + body.simplify_standard_types(config); + } + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + if let VariantBody::Body { ref body, .. } = self.body { + body.add_dependencies(library, out); + } + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + if let VariantBody::Body { ref mut body, .. } = self.body { + body.resolve_declaration_types(resolver); + } + } + + fn specialize( + &self, + generic_values: &[GenericArgument], + mappings: &[(&Path, &GenericArgument)], + config: &Config, + ) -> Self { + Self::new( + mangle::mangle_name(&self.name, generic_values, &config.export.mangle), + self.discriminant.clone(), + self.body.specialize(generic_values, mappings, config), + self.cfg.clone(), + self.documentation.clone(), + ) + } + + fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + if let VariantBody::Body { ref body, .. } = self.body { + body.add_monomorphs(library, out); + } + } + + fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + if let VariantBody::Body { ref mut body, .. } = self.body { + body.mangle_paths(monomorphs); + } + } +} + +impl Source for EnumVariant { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + let condition = self.cfg.to_condition(config); + // Cython doesn't support conditional enum variants. + if config.language != Language::Cython { + condition.write_before(config, out); + } + self.documentation.write(config, out); + write!(out, "{}", self.export_name); + if let Some(discriminant) = &self.discriminant { + if config.language == Language::Cython { + // For extern Cython declarations the enumerator value is ignored, + // but still useful as documentation, so we write it as a comment. + out.write(" #") + } + out.write(" = "); + discriminant.write(config, out); + } + out.write(","); + if config.language != Language::Cython { + condition.write_after(config, out); + } + } +} + +#[derive(Debug, Clone)] +pub struct Enum { + pub path: Path, + pub export_name: String, + pub generic_params: GenericParams, + pub repr: Repr, + pub variants: Vec<EnumVariant>, + pub tag: Option<String>, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl Enum { + /// Name of the generated tag enum. + fn tag_name(&self) -> &str { + self.tag.as_deref().unwrap_or_else(|| self.export_name()) + } + + /// Enum with data turns into a union of structs with each struct having its own tag field. + fn inline_tag_field(repr: &Repr) -> bool { + repr.style != ReprStyle::C + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + if self.generic_params.len() > 0 { + return; + } + + for v in &self.variants { + v.add_monomorphs(library, out); + } + } + + fn can_derive_eq(&self) -> bool { + if self.tag.is_none() { + return false; + } + + self.variants.iter().all(|variant| match variant.body { + VariantBody::Empty(..) => true, + VariantBody::Body { ref body, .. } => body.can_derive_eq(), + }) + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + for variant in &mut self.variants { + variant.mangle_paths(monomorphs); + } + } + + pub fn load( + item: &syn::ItemEnum, + mod_cfg: Option<&Cfg>, + config: &Config, + ) -> Result<Enum, String> { + let repr = Repr::load(&item.attrs)?; + if repr.style == ReprStyle::Rust && repr.ty.is_none() { + return Err("Enum is not marked with a valid #[repr(prim)] or #[repr(C)].".to_owned()); + } + // TODO: Implement translation of aligned enums. + if repr.align.is_some() { + return Err("Enum is marked with #[repr(align(...))] or #[repr(packed)].".to_owned()); + } + + let path = Path::new(item.ident.unraw().to_string()); + let generic_params = GenericParams::load(&item.generics)?; + + let mut variants = Vec::new(); + let mut has_data = false; + + let annotations = AnnotationSet::load(&item.attrs)?; + + for variant in item.variants.iter() { + let variant = EnumVariant::load( + Self::inline_tag_field(&repr), + variant, + generic_params.clone(), + mod_cfg, + &path, + &annotations, + config, + )?; + has_data = has_data || !variant.body.is_empty(); + variants.push(variant); + } + + if let Some(names) = annotations.list("enum-trailing-values") { + for name in names { + variants.push(EnumVariant::new( + name, + None, + VariantBody::empty(), + None, + Documentation::none(), + )); + } + } + + if config.enumeration.add_sentinel(&annotations) { + variants.push(EnumVariant::new( + "Sentinel".to_owned(), + None, + VariantBody::empty(), + None, + Documentation::simple(" Must be last for serialization purposes"), + )); + } + + let tag = if has_data { + Some("Tag".to_string()) + } else { + None + }; + + Ok(Enum::new( + path, + generic_params, + repr, + variants, + tag, + Cfg::append(mod_cfg, Cfg::load(&item.attrs)), + annotations, + Documentation::load(&item.attrs), + )) + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + path: Path, + generic_params: GenericParams, + repr: Repr, + variants: Vec<EnumVariant>, + tag: Option<String>, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generic_params, + repr, + variants, + tag, + cfg, + annotations, + documentation, + } + } +} + +impl Item for Enum { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Enum(self.clone()) + } + + fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) { + if self.tag.is_some() { + if self.repr.style == ReprStyle::C { + resolver.add_struct(&self.path); + } else { + resolver.add_union(&self.path); + } + } else if self.repr.style == ReprStyle::C { + resolver.add_enum(&self.path); + } else { + // This is important to handle conflicting names with opaque items. + resolver.add_none(&self.path); + } + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + for &mut ref mut var in &mut self.variants { + var.resolve_declaration_types(resolver); + } + } + + fn rename_for_config(&mut self, config: &Config) { + config.export.rename(&mut self.export_name); + + if config.language != Language::Cxx && self.tag.is_some() { + // it makes sense to always prefix Tag with type name in C + let new_tag = format!("{}_Tag", self.export_name); + if self.repr.style == ReprStyle::Rust { + for variant in &mut self.variants { + if let VariantBody::Body { ref mut body, .. } = variant.body { + let path = Path::new(new_tag.clone()); + let generic_path = GenericPath::new(path, vec![]); + body.fields[0].ty = Type::Path(generic_path); + } + } + } + self.tag = Some(new_tag); + } + + for variant in &mut self.variants { + reserved::escape(&mut variant.export_name); + if let Some(discriminant) = &mut variant.discriminant { + discriminant.rename_for_config(config); + } + if let VariantBody::Body { + ref mut name, + ref mut body, + .. + } = variant.body + { + body.rename_for_config(config); + reserved::escape(name); + } + } + + if config.enumeration.prefix_with_name + || self.annotations.bool("prefix-with-name").unwrap_or(false) + { + let separator = if config.export.mangle.remove_underscores { + "" + } else { + "_" + }; + + for variant in &mut self.variants { + variant.export_name = + format!("{}{}{}", self.export_name, separator, variant.export_name); + if let VariantBody::Body { ref mut body, .. } = variant.body { + body.export_name = + format!("{}{}{}", self.export_name, separator, body.export_name()); + } + } + } + + let rules = self + .annotations + .parse_atom::<RenameRule>("rename-all") + .unwrap_or(config.enumeration.rename_variants); + + if let Some(r) = rules.not_none() { + self.variants = self + .variants + .iter() + .map(|variant| { + EnumVariant::new( + r.apply( + &variant.export_name, + IdentifierType::EnumVariant { + prefix: &self.export_name, + }, + ) + .into_owned(), + variant.discriminant.clone(), + match variant.body { + VariantBody::Empty(..) => variant.body.clone(), + VariantBody::Body { + ref name, + ref body, + inline, + inline_casts, + } => VariantBody::Body { + name: r.apply(name, IdentifierType::StructMember).into_owned(), + body: body.clone(), + inline, + inline_casts, + }, + }, + variant.cfg.clone(), + variant.documentation.clone(), + ) + }) + .collect(); + } + } + + fn instantiate_monomorph( + &self, + generic_values: &[GenericArgument], + library: &Library, + out: &mut Monomorphs, + ) { + let mappings = self.generic_params.call(self.path.name(), generic_values); + + for variant in &self.variants { + if let VariantBody::Body { ref body, .. } = variant.body { + body.instantiate_monomorph(generic_values, library, out); + } + } + + let mangled_path = mangle::mangle_path( + &self.path, + generic_values, + &library.get_config().export.mangle, + ); + + let monomorph = Enum::new( + mangled_path, + GenericParams::default(), + self.repr, + self.variants + .iter() + .map(|v| v.specialize(generic_values, &mappings, library.get_config())) + .collect(), + self.tag.clone(), + self.cfg.clone(), + self.annotations.clone(), + self.documentation.clone(), + ); + + out.insert_enum(library, self, monomorph, generic_values.to_owned()); + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + for variant in &self.variants { + variant.add_dependencies(library, out); + } + } +} + +impl Source for Enum { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + let size = self.repr.ty.map(|ty| ty.to_primitive().to_repr_c(config)); + let has_data = self.tag.is_some(); + let inline_tag_field = Self::inline_tag_field(&self.repr); + let tag_name = self.tag_name(); + + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + self.documentation.write(config, out); + self.generic_params.write(config, out); + + // If the enum has data, we need to emit a struct or union for the data + // and enum for the tag. C++ supports nested type definitions, so we open + // the struct or union here and define the tag enum inside it (*). + if has_data && config.language == Language::Cxx { + self.open_struct_or_union(config, out, inline_tag_field); + } + + // Emit the tag enum and everything related to it. + self.write_tag_enum(config, out, size, has_data, tag_name); + + // If the enum has data, we need to emit structs for the variants and gather them together. + if has_data { + self.write_variant_defs(config, out); + out.new_line(); + out.new_line(); + + // Open the struct or union for the data (**), gathering all the variants with data + // together, unless it's C++, then we have already opened that struct/union at (*) and + // are currently inside it. + if config.language != Language::Cxx { + self.open_struct_or_union(config, out, inline_tag_field); + } + + // Emit tag field that is separate from all variants. + self.write_tag_field(config, out, size, inline_tag_field, tag_name); + out.new_line(); + + // Open union of all variants with data, only in the non-inline tag scenario. + // Cython extern declarations don't manage layouts, layouts are defined entierly by the + // corresponding C code. So we can inline the unnamed union into the struct and get the + // same observable result. Moreother we have to do it because Cython doesn't support + // unnamed unions. + if !inline_tag_field && config.language != Language::Cython { + out.write("union"); + out.open_brace(); + } + + // Emit fields for all variants with data. + self.write_variant_fields(config, out, inline_tag_field); + + // Close union of all variants with data, only in the non-inline tag scenario. + // See the comment about Cython on `open_brace`. + if !inline_tag_field && config.language != Language::Cython { + out.close_brace(true); + } + + // Emit convenience methods for the struct or enum for the data. + self.write_derived_functions_data(config, out, tag_name); + + // Emit the post_body section, if relevant. + if let Some(body) = config.export.post_body(&self.path) { + out.new_line(); + out.write_raw_block(body); + } + + // Close the struct or union opened either at (*) or at (**). + if config.language == Language::C && config.style.generate_typedef() { + out.close_brace(false); + write!(out, " {};", self.export_name); + } else { + out.close_brace(true); + } + } + + condition.write_after(config, out); + } +} + +impl Enum { + /// Emit the tag enum and convenience methods for it. + /// For enums with data this is only a part of the output, + /// but for enums without data it's the whole output (modulo doc comments etc.). + fn write_tag_enum<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + size: Option<&str>, + has_data: bool, + tag_name: &str, + ) { + // Open the tag enum. + match config.language { + Language::C => { + if let Some(prim) = size { + // If we need to specify size, then we have no choice but to create a typedef, + // so `config.style` is not respected. + write!(out, "enum"); + if let Some(note) = self + .annotations + .deprecated_note(config, DeprecatedNoteKind::Enum) + { + write!(out, " {}", note); + } + write!(out, " {}", tag_name); + + if config.cpp_compatible_c() { + out.new_line(); + out.write("#ifdef __cplusplus"); + out.new_line(); + write!(out, " : {}", prim); + out.new_line(); + out.write("#endif // __cplusplus"); + out.new_line(); + } + } else { + if config.style.generate_typedef() { + out.write("typedef "); + } + out.write("enum"); + if let Some(note) = self + .annotations + .deprecated_note(config, DeprecatedNoteKind::Enum) + { + write!(out, " {}", note); + } + if config.style.generate_tag() { + write!(out, " {}", tag_name); + } + } + } + Language::Cxx => { + if config.enumeration.enum_class(&self.annotations) { + out.write("enum class"); + } else { + out.write("enum"); + } + + if self.annotations.must_use(config) { + if let Some(ref anno) = config.enumeration.must_use { + write!(out, " {}", anno) + } + } + + if let Some(note) = self + .annotations + .deprecated_note(config, DeprecatedNoteKind::Enum) + { + write!(out, " {}", note); + } + + write!(out, " {}", tag_name); + if let Some(prim) = size { + write!(out, " : {}", prim); + } + } + Language::Cython => { + if size.is_some() { + // If we need to specify size, then we have no choice but to create a typedef, + // so `config.style` is not respected. + write!(out, "cdef enum"); + } else { + write!(out, "{}enum {}", config.style.cython_def(), tag_name); + } + } + } + out.open_brace(); + + // Emit enumerators for the tag enum. + for (i, variant) in self.variants.iter().enumerate() { + if i != 0 { + out.new_line() + } + variant.write(config, out); + } + + // Close the tag enum. + if config.language == Language::C && size.is_none() && config.style.generate_typedef() { + out.close_brace(false); + write!(out, " {};", tag_name); + } else { + out.close_brace(true); + } + + // Emit typedef specifying the tag enum's size if necessary. + // In C++ enums can "inherit" from numeric types (`enum E: uint8_t { ... }`), + // but in C `typedef uint8_t E` is the only way to give a fixed size to `E`. + if let Some(prim) = size { + if config.cpp_compatible_c() { + out.new_line_if_not_start(); + out.write("#ifndef __cplusplus"); + } + + if config.language != Language::Cxx { + out.new_line(); + write!(out, "{} {} {};", config.language.typedef(), prim, tag_name); + } + + if config.cpp_compatible_c() { + out.new_line_if_not_start(); + out.write("#endif // __cplusplus"); + } + } + + // Emit convenience methods for the tag enum. + self.write_derived_functions_enum(config, out, has_data, tag_name); + } + + /// The code here mirrors the beginning of `Struct::write` and `Union::write`. + fn open_struct_or_union<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + inline_tag_field: bool, + ) { + match config.language { + Language::C if config.style.generate_typedef() => out.write("typedef "), + Language::C | Language::Cxx => {} + Language::Cython => out.write(config.style.cython_def()), + } + + out.write(if inline_tag_field { "union" } else { "struct" }); + + if self.annotations.must_use(config) { + if let Some(ref anno) = config.structure.must_use { + write!(out, " {}", anno); + } + } + + if let Some(note) = self + .annotations + .deprecated_note(config, DeprecatedNoteKind::Struct) + { + write!(out, " {} ", note); + } + + if config.language != Language::C || config.style.generate_tag() { + write!(out, " {}", self.export_name()); + } + + out.open_brace(); + + // Emit the pre_body section, if relevant. + if let Some(body) = config.export.pre_body(&self.path) { + out.write_raw_block(body); + out.new_line(); + } + } + + /// Emit struct definitions for variants having data. + fn write_variant_defs<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + for variant in &self.variants { + if let VariantBody::Body { + ref body, + inline: false, + .. + } = variant.body + { + out.new_line(); + out.new_line(); + let condition = variant.cfg.to_condition(config); + // Cython doesn't support conditional enum variants. + if config.language != Language::Cython { + condition.write_before(config, out); + } + body.write(config, out); + if config.language != Language::Cython { + condition.write_after(config, out); + } + } + } + } + + /// Emit tag field that is separate from all variants. + /// For non-inline tag scenario this is *the* tag field, and it does not exist in the variants. + /// For the inline tag scenario this is just a convenience and another way + /// to refer to the same tag that exist in all the variants. + fn write_tag_field<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + size: Option<&str>, + inline_tag_field: bool, + tag_name: &str, + ) { + // C++ allows accessing only common initial sequence of union + // fields so we have to wrap the tag field into an anonymous struct. + let wrap_tag = inline_tag_field && config.language == Language::Cxx; + + if wrap_tag { + out.write("struct"); + out.open_brace(); + } + + if config.language == Language::C && size.is_none() && !config.style.generate_typedef() { + out.write("enum "); + } + + write!(out, "{} tag;", tag_name); + + if wrap_tag { + out.close_brace(true); + } + } + + /// Emit fields for all variants with data. + fn write_variant_fields<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + inline_tag_field: bool, + ) { + let mut first = true; + for variant in &self.variants { + if let VariantBody::Body { + name, body, inline, .. + } = &variant.body + { + if !first { + out.new_line(); + } + first = false; + let condition = variant.cfg.to_condition(config); + // Cython doesn't support conditional enum variants. + if config.language != Language::Cython { + condition.write_before(config, out); + } + if *inline { + // Write definition of an inlined variant with data. + // Cython extern declarations don't manage layouts, layouts are defined entierly + // by the corresponding C code. So we can inline the unnamed struct and get the + // same observable result. Moreother we have to do it because Cython doesn't + // support unnamed structs. + // For the same reason with Cython we can omit per-variant tags (the first + // field) to avoid extra noise, the main `tag` is enough in this case. + if config.language != Language::Cython { + out.write("struct"); + out.open_brace(); + } + let start_field = + usize::from(inline_tag_field && config.language == Language::Cython); + out.write_vertical_source_list(&body.fields[start_field..], ListType::Cap(";")); + if config.language != Language::Cython { + out.close_brace(true); + } + } else if config.style.generate_typedef() || config.language == Language::Cython { + write!(out, "{} {};", body.export_name(), name); + } else { + write!(out, "struct {} {};", body.export_name(), name); + } + if config.language != Language::Cython { + condition.write_after(config, out); + } + } + } + } + + // Emit convenience methods for enums themselves. + fn write_derived_functions_enum<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + has_data: bool, + tag_name: &str, + ) { + if config.language != Language::Cxx { + return; + } + + // Emit an ostream function if required. + if config.enumeration.derive_ostream(&self.annotations) { + // For enums without data, this emits the serializer function for the + // enum. For enums with data, this emits the serializer function for + // the tag enum. In the latter case we need a couple of minor changes + // due to the function living inside the top-level struct or enum. + let stream = config + .function + .rename_args + .apply("stream", IdentifierType::FunctionArg); + let instance = config + .function + .rename_args + .apply("instance", IdentifierType::FunctionArg); + + out.new_line(); + out.new_line(); + // For enums without data, we mark the function inline because the + // header might get included into multiple compilation units that + // get linked together, and not marking it inline would result in + // multiply-defined symbol errors. For enums with data we don't have + // the same problem, but mark it as a friend function of the + // containing union/struct. + // Note also that for enums with data, the case labels for switch + // statements apparently need to be qualified to the top-level + // generated struct or union. This is why the generated case labels + // below use the A::B::C format for enums with data, with A being + // self.export_name(). Failure to have that qualification results + // in a surprising compilation failure for the generated header. + write!( + out, + "{} std::ostream& operator<<(std::ostream& {}, const {}& {})", + if has_data { "friend" } else { "inline" }, + stream, + tag_name, + instance, + ); + + out.open_brace(); + if has_data { + // C++ name resolution rules are weird. + write!( + out, + "using {} = {}::{};", + tag_name, + self.export_name(), + tag_name + ); + out.new_line(); + } + write!(out, "switch ({})", instance); + out.open_brace(); + let vec: Vec<_> = self + .variants + .iter() + .map(|x| { + format!( + "case {}::{}: {} << \"{}\"; break;", + tag_name, x.export_name, stream, x.export_name + ) + }) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join("")); + out.close_brace(false); + out.new_line(); + + write!(out, "return {};", stream); + out.close_brace(false); + + if has_data { + // For enums with data, this emits the serializer function for + // the top-level union or struct. + out.new_line(); + out.new_line(); + write!( + out, + "friend std::ostream& operator<<(std::ostream& {}, const {}& {})", + stream, + self.export_name(), + instance, + ); + + out.open_brace(); + + // C++ name resolution rules are weird. + write!( + out, + "using {} = {}::{};", + tag_name, + self.export_name(), + tag_name + ); + out.new_line(); + + write!(out, "switch ({}.tag)", instance); + out.open_brace(); + let vec: Vec<_> = self + .variants + .iter() + .map(|x| { + let tag_str = format!("\"{}\"", x.export_name); + if let VariantBody::Body { + ref name, ref body, .. + } = x.body + { + format!( + "case {}::{}: {} << {}{}{}.{}; break;", + tag_name, + x.export_name, + stream, + if body.has_tag_field { "" } else { &tag_str }, + if body.has_tag_field { "" } else { " << " }, + instance, + name, + ) + } else { + format!( + "case {}::{}: {} << {}; break;", + tag_name, x.export_name, stream, tag_str, + ) + } + }) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join("")); + out.close_brace(false); + out.new_line(); + + write!(out, "return {};", stream); + out.close_brace(false); + } + } + } + + // Emit convenience methods for structs or unions produced for enums with data. + fn write_derived_functions_data<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + tag_name: &str, + ) { + if config.language != Language::Cxx { + return; + } + + if config.enumeration.derive_helper_methods(&self.annotations) { + for variant in &self.variants { + out.new_line(); + out.new_line(); + + let condition = variant.cfg.to_condition(config); + condition.write_before(config, out); + + let arg_renamer = |name: &str| { + config + .function + .rename_args + .apply(name, IdentifierType::FunctionArg) + .into_owned() + }; + + macro_rules! write_attrs { + ($op:expr) => {{ + if let Some(Some(attrs)) = + variant + .body + .annotations() + .atom(concat!("variant-", $op, "-attributes")) + { + write!(out, "{} ", attrs); + } + }}; + } + + write_attrs!("constructor"); + write!(out, "static {} {}(", self.export_name, variant.export_name); + + if let VariantBody::Body { ref body, .. } = variant.body { + let skip_fields = body.has_tag_field as usize; + let vec: Vec<_> = body + .fields + .iter() + .skip(skip_fields) + .map(|field| { + Field::from_name_and_type( + // const-ref args to constructor + arg_renamer(&field.name), + Type::const_ref_to(&field.ty), + ) + }) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join(",")); + } + + write!(out, ")"); + out.open_brace(); + + write!(out, "{} result;", self.export_name); + + if let VariantBody::Body { + name: ref variant_name, + ref body, + .. + } = variant.body + { + let skip_fields = body.has_tag_field as usize; + for field in body.fields.iter().skip(skip_fields) { + out.new_line(); + match field.ty { + Type::Array(ref ty, ref length) => { + // arrays are not assignable in C++ so we + // need to manually copy the elements + write!(out, "for (int i = 0; i < {}; i++)", length.as_str()); + out.open_brace(); + write!(out, "::new (&result.{}.{}[i]) (", variant_name, field.name); + ty.write(config, out); + write!(out, ")({}[i]);", arg_renamer(&field.name)); + out.close_brace(false); + } + ref ty => { + write!(out, "::new (&result.{}.{}) (", variant_name, field.name); + ty.write(config, out); + write!(out, ")({});", arg_renamer(&field.name)); + } + } + } + } + + out.new_line(); + write!(out, "result.tag = {}::{};", tag_name, variant.export_name); + out.new_line(); + write!(out, "return result;"); + out.close_brace(false); + + out.new_line(); + out.new_line(); + + write_attrs!("is"); + // FIXME: create a config for method case + write!(out, "bool Is{}() const", variant.export_name); + out.open_brace(); + write!(out, "return tag == {}::{};", tag_name, variant.export_name); + out.close_brace(false); + + let assert_name = match config.enumeration.cast_assert_name { + Some(ref n) => &**n, + None => "assert", + }; + + let mut derive_casts = |const_casts: bool| { + let (member_name, body, inline_casts) = match variant.body { + VariantBody::Body { + ref name, + ref body, + inline_casts, + .. + } => (name, body, inline_casts), + VariantBody::Empty(..) => return, + }; + + let skip_fields = body.has_tag_field as usize; + let field_count = body.fields.len() - skip_fields; + if field_count == 0 { + return; + } + + out.new_line(); + out.new_line(); + + if const_casts { + write_attrs!("const-cast"); + } else { + write_attrs!("mut-cast"); + } + if inline_casts { + let field = body.fields.last().unwrap(); + let return_type = field.ty.clone(); + let return_type = Type::Ptr { + ty: Box::new(return_type), + is_const: const_casts, + is_ref: true, + is_nullable: false, + }; + return_type.write(config, out); + } else if const_casts { + write!(out, "const {}&", body.export_name()); + } else { + write!(out, "{}&", body.export_name()); + } + + write!(out, " As{}()", variant.export_name); + if const_casts { + write!(out, " const"); + } + out.open_brace(); + write!(out, "{}(Is{}());", assert_name, variant.export_name); + out.new_line(); + write!(out, "return {}", member_name); + if inline_casts { + write!(out, "._0"); + } + write!(out, ";"); + out.close_brace(false); + }; + + if config.enumeration.derive_const_casts(&self.annotations) { + derive_casts(true) + } + + if config.enumeration.derive_mut_casts(&self.annotations) { + derive_casts(false) + } + + condition.write_after(config, out); + } + } + + let other = config + .function + .rename_args + .apply("other", IdentifierType::FunctionArg); + + macro_rules! write_attrs { + ($op:expr) => {{ + if let Some(Some(attrs)) = self.annotations.atom(concat!($op, "-attributes")) { + write!(out, "{} ", attrs); + } + }}; + } + + if self.can_derive_eq() && config.structure.derive_eq(&self.annotations) { + out.new_line(); + out.new_line(); + write_attrs!("eq"); + write!( + out, + "bool operator==(const {}& {}) const", + self.export_name, other + ); + out.open_brace(); + write!(out, "if (tag != {}.tag)", other); + out.open_brace(); + write!(out, "return false;"); + out.close_brace(false); + out.new_line(); + write!(out, "switch (tag)"); + out.open_brace(); + let mut exhaustive = true; + for variant in &self.variants { + if let VariantBody::Body { + name: ref variant_name, + .. + } = variant.body + { + let condition = variant.cfg.to_condition(config); + condition.write_before(config, out); + write!( + out, + "case {}::{}: return {} == {}.{};", + self.tag.as_ref().unwrap(), + variant.export_name, + variant_name, + other, + variant_name + ); + condition.write_after(config, out); + out.new_line(); + } else { + exhaustive = false; + } + } + if !exhaustive { + write!(out, "default: break;"); + } + out.close_brace(false); + + out.new_line(); + write!(out, "return true;"); + + out.close_brace(false); + + if config.structure.derive_neq(&self.annotations) { + out.new_line(); + out.new_line(); + write_attrs!("neq"); + write!( + out, + "bool operator!=(const {}& {}) const", + self.export_name, other + ); + out.open_brace(); + write!(out, "return !(*this == {});", other); + out.close_brace(false); + } + } + + if config + .enumeration + .private_default_tagged_enum_constructor(&self.annotations) + { + out.new_line(); + out.new_line(); + write!(out, "private:"); + out.new_line(); + write!(out, "{}()", self.export_name); + out.open_brace(); + out.close_brace(false); + out.new_line(); + write!(out, "public:"); + out.new_line(); + } + + if config + .enumeration + .derive_tagged_enum_destructor(&self.annotations) + { + out.new_line(); + out.new_line(); + write_attrs!("destructor"); + write!(out, "~{}()", self.export_name); + out.open_brace(); + write!(out, "switch (tag)"); + out.open_brace(); + let mut exhaustive = true; + for variant in &self.variants { + if let VariantBody::Body { + ref name, ref body, .. + } = variant.body + { + let condition = variant.cfg.to_condition(config); + condition.write_before(config, out); + write!( + out, + "case {}::{}: {}.~{}(); break;", + self.tag.as_ref().unwrap(), + variant.export_name, + name, + body.export_name(), + ); + condition.write_after(config, out); + out.new_line(); + } else { + exhaustive = false; + } + } + if !exhaustive { + write!(out, "default: break;"); + } + out.close_brace(false); + out.close_brace(false); + } + + if config + .enumeration + .derive_tagged_enum_copy_constructor(&self.annotations) + { + out.new_line(); + out.new_line(); + write_attrs!("copy-constructor"); + write!( + out, + "{}(const {}& {})", + self.export_name, self.export_name, other + ); + out.new_line(); + write!(out, " : tag({}.tag)", other); + out.open_brace(); + write!(out, "switch (tag)"); + out.open_brace(); + let mut exhaustive = true; + for variant in &self.variants { + if let VariantBody::Body { + ref name, ref body, .. + } = variant.body + { + let condition = variant.cfg.to_condition(config); + condition.write_before(config, out); + write!( + out, + "case {}::{}: ::new (&{}) ({})({}.{}); break;", + self.tag.as_ref().unwrap(), + variant.export_name, + name, + body.export_name(), + other, + name, + ); + condition.write_after(config, out); + out.new_line(); + } else { + exhaustive = false; + } + } + if !exhaustive { + write!(out, "default: break;"); + } + out.close_brace(false); + out.close_brace(false); + + if config + .enumeration + .derive_tagged_enum_copy_assignment(&self.annotations) + { + out.new_line(); + write_attrs!("copy-assignment"); + write!( + out, + "{}& operator=(const {}& {})", + self.export_name, self.export_name, other + ); + out.open_brace(); + write!(out, "if (this != &{})", other); + out.open_brace(); + write!(out, "this->~{}();", self.export_name); + out.new_line(); + write!(out, "new (this) {}({});", self.export_name, other); + out.close_brace(false); + out.new_line(); + write!(out, "return *this;"); + out.close_brace(false); + } + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + for variant in &mut self.variants { + variant.simplify_standard_types(config); + } + } +} diff --git a/src/bindgen/ir/field.rs b/src/bindgen/ir/field.rs new file mode 100644 index 0000000..6e132bf --- /dev/null +++ b/src/bindgen/ir/field.rs @@ -0,0 +1,80 @@ +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::cdecl; +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::ir::{AnnotationSet, Cfg, ConditionWrite}; +use crate::bindgen::ir::{Documentation, Path, ToCondition, Type}; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct Field { + pub name: String, + pub ty: Type, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl Field { + pub fn from_name_and_type(name: String, ty: Type) -> Field { + Field { + name, + ty, + cfg: None, + annotations: AnnotationSet::new(), + documentation: Documentation::none(), + } + } + + pub fn load(field: &syn::Field, self_path: &Path) -> Result<Option<Field>, String> { + Ok(if let Some(mut ty) = Type::load(&field.ty)? { + ty.replace_self_with(self_path); + Some(Field { + name: field + .ident + .as_ref() + .ok_or_else(|| "field is missing identifier".to_string())? + .unraw() + .to_string(), + ty, + cfg: Cfg::load(&field.attrs), + annotations: AnnotationSet::load(&field.attrs)?, + documentation: Documentation::load(&field.attrs), + }) + } else { + None + }) + } +} + +impl Source for Field { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + // Cython doesn't support conditional fields. + let condition = self.cfg.to_condition(config); + if config.language != Language::Cython { + condition.write_before(config, out); + } + + self.documentation.write(config, out); + cdecl::write_field(out, &self.ty, &self.name, config); + // Cython extern declarations don't manage layouts, layouts are defined entierly by the + // corresponding C code. So we can omit bitfield sizes which are not supported by Cython. + if config.language != Language::Cython { + if let Some(bitfield) = self.annotations.atom("bitfield") { + write!(out, ": {}", bitfield.unwrap_or_default()); + } + } + + if config.language != Language::Cython { + condition.write_after(config, out); + // FIXME(#634): `write_vertical_source_list` should support + // configuring list elements natively. For now we print a newline + // here to avoid printing `#endif;` with semicolon. + if condition.is_some() { + out.new_line(); + } + } + } +} diff --git a/src/bindgen/ir/function.rs b/src/bindgen/ir/function.rs new file mode 100644 index 0000000..8c65f2f --- /dev/null +++ b/src/bindgen/ir/function.rs @@ -0,0 +1,390 @@ +/* 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/. */ + +use std::collections::HashMap; +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::cdecl; +use crate::bindgen::config::{Config, Language, Layout}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, DeprecatedNoteKind, Documentation, GenericPath, Path, + ToCondition, Type, +}; +use crate::bindgen::library::Library; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::rename::{IdentifierType, RenameRule}; +use crate::bindgen::reserved; +use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct FunctionArgument { + pub name: Option<String>, + pub ty: Type, + pub array_length: Option<String>, +} + +#[derive(Debug, Clone)] +pub struct Function { + pub path: Path, + /// Path to the self-type of the function + /// If the function is a method, this will contain the path of the type in the impl block + pub self_type_path: Option<Path>, + pub ret: Type, + pub args: Vec<FunctionArgument>, + pub extern_decl: bool, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, + pub never_return: bool, +} + +impl Function { + pub fn load( + path: Path, + self_type_path: Option<&Path>, + sig: &syn::Signature, + extern_decl: bool, + attrs: &[syn::Attribute], + mod_cfg: Option<&Cfg>, + ) -> Result<Function, String> { + let mut args = sig.inputs.iter().try_skip_map(|x| x.as_argument())?; + + let (mut ret, never_return) = Type::load_from_output(&sig.output)?; + + if let Some(self_path) = self_type_path { + for arg in &mut args { + arg.ty.replace_self_with(self_path); + } + ret.replace_self_with(self_path); + } + + Ok(Function { + path, + self_type_path: self_type_path.cloned(), + ret, + args, + extern_decl, + cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), + annotations: AnnotationSet::load(attrs)?, + documentation: Documentation::load(attrs), + never_return, + }) + } + + pub fn swift_name(&self, config: &Config) -> Option<String> { + if config.language == Language::Cython { + return None; + } + // If the symbol name starts with the type name, separate the two components with '.' + // so that Swift recognises the association between the method and the type + let (ref type_prefix, ref type_name) = match self.self_type_path { + Some(ref type_name) => { + let type_name = type_name.to_string(); + if !self.path.name().starts_with(&type_name) { + return Some(self.path.to_string()); + } + (format!("{}.", type_name), type_name) + } + None => ("".to_string(), "".to_string()), + }; + + let item_name = self + .path + .name() + .trim_start_matches(type_name) + .trim_start_matches('_'); + + let item_args = { + let mut items = Vec::with_capacity(self.args.len()); + for arg in self.args.iter() { + items.push(format!("{}:", arg.name.as_ref()?.as_str())); + } + items.join("") + }; + Some(format!("{}{}({})", type_prefix, item_name, item_args)) + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + self.ret.simplify_standard_types(config); + for arg in &mut self.args { + arg.ty.simplify_standard_types(config); + } + } + + pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.ret.add_dependencies(library, out); + for arg in &self.args { + arg.ty.add_dependencies(library, out); + } + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + self.ret.add_monomorphs(library, out); + for arg in &self.args { + arg.ty.add_monomorphs(library, out); + } + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + self.ret.mangle_paths(monomorphs); + for arg in &mut self.args { + arg.ty.mangle_paths(monomorphs); + } + } + + pub fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.ret.resolve_declaration_types(resolver); + for arg in &mut self.args { + arg.ty.resolve_declaration_types(resolver); + } + } + + pub fn rename_for_config(&mut self, config: &Config) { + // Rename the types used in arguments + let generic_params = Default::default(); + self.ret.rename_for_config(config, &generic_params); + + // Apply rename rules to argument names + let rules = self + .annotations + .parse_atom::<RenameRule>("rename-all") + .unwrap_or(config.function.rename_args); + + if let Some(r) = rules.not_none() { + let args = std::mem::take(&mut self.args); + self.args = args + .into_iter() + .map(|arg| { + let name = arg + .name + .map(|n| r.apply(&n, IdentifierType::FunctionArg).into_owned()); + FunctionArgument { + name, + ty: arg.ty, + array_length: None, + } + }) + .collect() + } + + // Escape C/C++ reserved keywords used in argument names, and + // recursively rename argument types. + for arg in &mut self.args { + arg.ty.rename_for_config(config, &generic_params); + if let Some(ref mut name) = arg.name { + reserved::escape(name); + } + } + + // Save the array length of the pointer arguments which need to use + // the C-array notation + if let Some(tuples) = self.annotations.list("ptrs-as-arrays") { + let mut ptrs_as_arrays: HashMap<String, String> = HashMap::new(); + for str_tuple in tuples { + let parts: Vec<&str> = str_tuple[1..str_tuple.len() - 1] + .split(';') + .map(|x| x.trim()) + .collect(); + if parts.len() != 2 { + warn!( + "{:?} does not follow the correct syntax, so the annotation is being ignored", + parts + ); + continue; + } + ptrs_as_arrays.insert(parts[0].to_string(), parts[1].to_string()); + } + + for arg in &mut self.args { + match arg.ty { + Type::Ptr { .. } => {} + _ => continue, + } + let name = match arg.name { + Some(ref name) => name, + None => continue, + }; + arg.array_length = ptrs_as_arrays.get(name).cloned(); + } + } + } +} + +impl Source for Function { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + fn write_1<W: Write>(func: &Function, config: &Config, out: &mut SourceWriter<W>) { + let prefix = config.function.prefix(&func.annotations); + let postfix = config.function.postfix(&func.annotations); + + let condition = func.cfg.to_condition(config); + condition.write_before(config, out); + + func.documentation.write(config, out); + + if func.extern_decl { + out.write("extern "); + } else { + if let Some(ref prefix) = prefix { + write!(out, "{} ", prefix); + } + if func.annotations.must_use(config) { + if let Some(ref anno) = config.function.must_use { + write!(out, "{} ", anno); + } + } + if let Some(note) = func + .annotations + .deprecated_note(config, DeprecatedNoteKind::Function) + { + write!(out, "{} ", note); + } + } + cdecl::write_func(out, func, Layout::Horizontal, config); + + if !func.extern_decl { + if let Some(ref postfix) = postfix { + write!(out, " {}", postfix); + } + } + + if let Some(ref swift_name_macro) = config.function.swift_name_macro { + if let Some(swift_name) = func.swift_name(config) { + write!(out, " {}({})", swift_name_macro, swift_name); + } + } + + out.write(";"); + + condition.write_after(config, out); + } + + fn write_2<W: Write>(func: &Function, config: &Config, out: &mut SourceWriter<W>) { + let prefix = config.function.prefix(&func.annotations); + let postfix = config.function.postfix(&func.annotations); + + let condition = func.cfg.to_condition(config); + + condition.write_before(config, out); + + func.documentation.write(config, out); + + if func.extern_decl { + out.write("extern "); + } else { + if let Some(ref prefix) = prefix { + write!(out, "{}", prefix); + out.new_line(); + } + if func.annotations.must_use(config) { + if let Some(ref anno) = config.function.must_use { + write!(out, "{}", anno); + out.new_line(); + } + } + if let Some(note) = func + .annotations + .deprecated_note(config, DeprecatedNoteKind::Function) + { + write!(out, "{}", note); + out.new_line(); + } + } + cdecl::write_func(out, func, Layout::Vertical, config); + if !func.extern_decl { + if let Some(ref postfix) = postfix { + out.new_line(); + write!(out, "{}", postfix); + } + } + + if let Some(ref swift_name_macro) = config.function.swift_name_macro { + if let Some(swift_name) = func.swift_name(config) { + write!(out, " {}({})", swift_name_macro, swift_name); + } + } + + out.write(";"); + + condition.write_after(config, out); + } + + match config.function.args { + Layout::Horizontal => write_1(self, config, out), + Layout::Vertical => write_2(self, config, out), + Layout::Auto => { + if !out.try_write(|out| write_1(self, config, out), config.line_length) { + write_2(self, config, out) + } + } + } + } +} + +trait SynFnArgHelpers { + fn as_argument(&self) -> Result<Option<FunctionArgument>, String>; +} + +fn gen_self_type(receiver: &syn::Receiver) -> Type { + let self_ty = Type::Path(GenericPath::self_path()); + if receiver.reference.is_none() { + return self_ty; + } + + let is_const = receiver.mutability.is_none(); + Type::Ptr { + ty: Box::new(self_ty), + is_const, + is_nullable: false, + is_ref: false, + } +} + +impl SynFnArgHelpers for syn::FnArg { + fn as_argument(&self) -> Result<Option<FunctionArgument>, String> { + match *self { + syn::FnArg::Typed(syn::PatType { + ref pat, ref ty, .. + }) => { + let name = match **pat { + syn::Pat::Wild(..) => None, + syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => { + Some(ident.unraw().to_string()) + } + _ => { + return Err(format!( + "Parameter has an unsupported argument name: {:?}", + pat + )) + } + }; + let ty = match Type::load(ty)? { + Some(x) => x, + None => return Ok(None), + }; + if let Type::Array(..) = ty { + return Err("Array as function arguments are not supported".to_owned()); + } + Ok(Some(FunctionArgument { + name, + ty, + array_length: None, + })) + } + syn::FnArg::Receiver(ref receiver) => Ok(Some(FunctionArgument { + name: Some("self".to_string()), + ty: gen_self_type(receiver), + array_length: None, + })), + } + } +} diff --git a/src/bindgen/ir/generic_path.rs b/src/bindgen/ir/generic_path.rs new file mode 100644 index 0000000..ef14890 --- /dev/null +++ b/src/bindgen/ir/generic_path.rs @@ -0,0 +1,303 @@ +use std::io::Write; +use std::ops::Deref; + +use syn::ext::IdentExt; + +use crate::bindgen::cdecl; +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::{DeclarationType, DeclarationTypeResolver}; +use crate::bindgen::ir::{ConstExpr, Path, Type}; +use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub enum GenericParamType { + Type, + Const(Type), +} + +#[derive(Debug, Clone)] +pub struct GenericParam { + name: Path, + ty: GenericParamType, +} + +impl GenericParam { + pub fn new_type_param(name: &str) -> Self { + GenericParam { + name: Path::new(name), + ty: GenericParamType::Type, + } + } + + pub fn load(param: &syn::GenericParam) -> Result<Option<Self>, String> { + match *param { + syn::GenericParam::Type(syn::TypeParam { ref ident, .. }) => Ok(Some(GenericParam { + name: Path::new(ident.unraw().to_string()), + ty: GenericParamType::Type, + })), + + syn::GenericParam::Lifetime(_) => Ok(None), + + syn::GenericParam::Const(syn::ConstParam { + ref ident, ref ty, .. + }) => match Type::load(ty)? { + None => { + // A type that evaporates, like PhantomData. + Err(format!("unsupported const generic type: {:?}", ty)) + } + Some(ty) => Ok(Some(GenericParam { + name: Path::new(ident.unraw().to_string()), + ty: GenericParamType::Const(ty), + })), + }, + } + } + + pub fn name(&self) -> &Path { + &self.name + } +} + +#[derive(Default, Debug, Clone)] +pub struct GenericParams(pub Vec<GenericParam>); + +impl GenericParams { + pub fn load(generics: &syn::Generics) -> Result<Self, String> { + let mut params = vec![]; + for param in &generics.params { + if let Some(p) = GenericParam::load(param)? { + params.push(p); + } + } + + Ok(GenericParams(params)) + } + + /// Associate each parameter with an argument. + pub fn call<'out>( + &'out self, + item_name: &str, + arguments: &'out [GenericArgument], + ) -> Vec<(&'out Path, &'out GenericArgument)> { + assert!(self.len() > 0, "{} is not generic", item_name); + assert!( + self.len() == arguments.len(), + "{} has {} params but is being instantiated with {} values", + item_name, + self.len(), + arguments.len(), + ); + self.iter() + .map(|param| param.name()) + .zip(arguments.iter()) + .collect() + } + + fn write_internal<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + with_default: bool, + ) { + if !self.0.is_empty() && config.language == Language::Cxx { + out.write("template<"); + for (i, item) in self.0.iter().enumerate() { + if i != 0 { + out.write(", "); + } + match item.ty { + GenericParamType::Type => { + write!(out, "typename {}", item.name); + if with_default { + write!(out, " = void"); + } + } + GenericParamType::Const(ref ty) => { + cdecl::write_field(out, ty, item.name.name(), config); + if with_default { + write!(out, " = 0"); + } + } + } + } + out.write(">"); + out.new_line(); + } + } + + pub fn write_with_default<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + self.write_internal(config, out, true); + } +} + +impl Deref for GenericParams { + type Target = [GenericParam]; + + fn deref(&self) -> &[GenericParam] { + &self.0 + } +} + +impl Source for GenericParams { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + self.write_internal(config, out, false); + } +} + +/// A (non-lifetime) argument passed to a generic, either a type or a constant expression. +/// +/// Note: Both arguments in a type like `Array<T, N>` are represented as +/// `GenericArgument::Type`s, even if `N` is actually the name of a const. This +/// is a consequence of `syn::GenericArgument` doing the same thing. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GenericArgument { + Type(Type), + Const(ConstExpr), +} + +impl GenericArgument { + pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> GenericArgument { + match *self { + GenericArgument::Type(ref ty) => { + if let Type::Path(ref path) = *ty { + if path.is_single_identifier() { + // See note on `GenericArgument` above: `ty` may + // actually be the name of a const. Check for that now. + for &(name, value) in mappings { + if *name == path.path { + return value.clone(); + } + } + } + } + GenericArgument::Type(ty.specialize(mappings)) + } + GenericArgument::Const(ref expr) => GenericArgument::Const(expr.clone()), + } + } + + pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) { + match *self { + GenericArgument::Type(ref mut ty) => ty.rename_for_config(config, generic_params), + GenericArgument::Const(ref mut expr) => expr.rename_for_config(config), + } + } +} + +impl Source for GenericArgument { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + match *self { + GenericArgument::Type(ref ty) => ty.write(config, out), + GenericArgument::Const(ref expr) => expr.write(config, out), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct GenericPath { + path: Path, + export_name: String, + generics: Vec<GenericArgument>, + ctype: Option<DeclarationType>, +} + +impl GenericPath { + pub fn new(path: Path, generics: Vec<GenericArgument>) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generics, + ctype: None, + } + } + + pub fn self_path() -> Self { + Self::new(Path::new("Self"), vec![]) + } + + pub fn replace_self_with(&mut self, self_ty: &Path) { + if self.path.replace_self_with(self_ty) { + self.export_name = self_ty.name().to_owned(); + } + // Caller deals with generics. + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn generics(&self) -> &[GenericArgument] { + &self.generics + } + + pub fn generics_mut(&mut self) -> &mut [GenericArgument] { + &mut self.generics + } + + pub fn ctype(&self) -> Option<&DeclarationType> { + self.ctype.as_ref() + } + + pub fn name(&self) -> &str { + self.path.name() + } + + pub fn export_name(&self) -> &str { + &self.export_name + } + + pub fn is_single_identifier(&self) -> bool { + self.generics.is_empty() + } + + pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) { + for generic in &mut self.generics { + generic.rename_for_config(config, generic_params); + } + if !generic_params.iter().any(|param| param.name == self.path) { + config.export.rename(&mut self.export_name); + } + } + + pub fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.ctype = resolver.type_for(&self.path); + } + + pub fn load(path: &syn::Path) -> Result<Self, String> { + assert!( + !path.segments.is_empty(), + "{:?} doesn't have any segments", + path + ); + let last_segment = path.segments.last().unwrap(); + let name = last_segment.ident.unraw().to_string(); + + let path = Path::new(name); + let phantom_data_path = Path::new("PhantomData"); + if path == phantom_data_path { + return Ok(Self::new(path, Vec::new())); + } + + let generics = match last_segment.arguments { + syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + ref args, + .. + }) => args.iter().try_skip_map(|x| match *x { + syn::GenericArgument::Type(ref x) => Ok(Type::load(x)?.map(GenericArgument::Type)), + syn::GenericArgument::Lifetime(_) => Ok(None), + syn::GenericArgument::Const(ref x) => { + Ok(Some(GenericArgument::Const(ConstExpr::load(x)?))) + } + _ => Err(format!("can't handle generic argument {:?}", x)), + })?, + syn::PathArguments::Parenthesized(_) => { + return Err("Path contains parentheses.".to_owned()); + } + _ => Vec::new(), + }; + + Ok(Self::new(path, generics)) + } +} diff --git a/src/bindgen/ir/global.rs b/src/bindgen/ir/global.rs new file mode 100644 index 0000000..82a1756 --- /dev/null +++ b/src/bindgen/ir/global.rs @@ -0,0 +1,121 @@ +/* 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/. */ + +use std::io::Write; + +use crate::bindgen::cdecl; +use crate::bindgen::config::Config; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{AnnotationSet, Cfg, Documentation, Item, ItemContainer, Path, Type}; +use crate::bindgen::library::Library; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct Static { + pub path: Path, + pub export_name: String, + pub ty: Type, + pub mutable: bool, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl Static { + pub fn load( + path: Path, + item: &syn::ItemStatic, + mod_cfg: Option<&Cfg>, + ) -> Result<Static, String> { + let ty = Type::load(&item.ty)?; + + if ty.is_none() { + return Err("Cannot have a zero sized static definition.".to_owned()); + } + + Ok(Static::new( + path, + ty.unwrap(), + item.mutability.is_some(), + Cfg::append(mod_cfg, Cfg::load(&item.attrs)), + AnnotationSet::load(&item.attrs)?, + Documentation::load(&item.attrs), + )) + } + + pub fn new( + path: Path, + ty: Type, + mutable: bool, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + ty, + mutable, + cfg, + annotations, + documentation, + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + self.ty.simplify_standard_types(config); + } +} + +impl Item for Static { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Static(self.clone()) + } + + fn rename_for_config(&mut self, config: &Config) { + self.ty.rename_for_config(config, &Default::default()); + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.ty.resolve_declaration_types(resolver); + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.ty.add_dependencies(library, out); + } +} + +impl Source for Static { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + out.write("extern "); + if let Type::Ptr { is_const: true, .. } = self.ty { + } else if !self.mutable { + out.write("const "); + } + cdecl::write_field(out, &self.ty, &self.export_name, config); + out.write(";"); + } +} diff --git a/src/bindgen/ir/item.rs b/src/bindgen/ir/item.rs new file mode 100644 index 0000000..16d98f5 --- /dev/null +++ b/src/bindgen/ir/item.rs @@ -0,0 +1,250 @@ +/* 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/. */ + +use indexmap::IndexMap; +use std::mem; + +use crate::bindgen::config::Config; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, Constant, Enum, GenericArgument, OpaqueItem, Path, Static, Struct, Typedef, + Union, +}; +use crate::bindgen::library::Library; +use crate::bindgen::monomorph::Monomorphs; + +/// An item is any type of rust item besides a function +pub trait Item { + fn path(&self) -> &Path; + fn name(&self) -> &str { + self.path().name() + } + fn export_name(&self) -> &str { + self.name() + } + fn cfg(&self) -> Option<&Cfg>; + fn annotations(&self) -> &AnnotationSet; + fn annotations_mut(&mut self) -> &mut AnnotationSet; + + fn container(&self) -> ItemContainer; + + fn collect_declaration_types(&self, _resolver: &mut DeclarationTypeResolver) { + unimplemented!() + } + fn resolve_declaration_types(&mut self, _resolver: &DeclarationTypeResolver) { + unimplemented!() + } + fn rename_for_config(&mut self, _config: &Config) {} + fn add_dependencies(&self, _library: &Library, _out: &mut Dependencies) {} + fn instantiate_monomorph( + &self, + _generics: &[GenericArgument], + _library: &Library, + _out: &mut Monomorphs, + ) { + unreachable!("Cannot instantiate {} as a generic.", self.name()) + } +} + +#[derive(Debug, Clone)] +pub enum ItemContainer { + Constant(Constant), + Static(Static), + OpaqueItem(OpaqueItem), + Struct(Struct), + Union(Union), + Enum(Enum), + Typedef(Typedef), +} + +impl ItemContainer { + pub fn deref(&self) -> &dyn Item { + match *self { + ItemContainer::Constant(ref x) => x, + ItemContainer::Static(ref x) => x, + ItemContainer::OpaqueItem(ref x) => x, + ItemContainer::Struct(ref x) => x, + ItemContainer::Union(ref x) => x, + ItemContainer::Enum(ref x) => x, + ItemContainer::Typedef(ref x) => x, + } + } +} + +#[derive(Debug, Clone)] +pub enum ItemValue<T: Item> { + Cfg(Vec<T>), + Single(T), +} + +#[derive(Debug, Clone)] +pub struct ItemMap<T: Item> { + data: IndexMap<Path, ItemValue<T>>, +} + +impl<T: Item> Default for ItemMap<T> { + fn default() -> ItemMap<T> { + ItemMap { + data: Default::default(), + } + } +} + +impl<T: Item + Clone> ItemMap<T> { + pub fn rebuild(&mut self) { + let old = mem::take(self); + old.for_all_items(|x| { + self.try_insert(x.clone()); + }); + } + + pub fn try_insert(&mut self, item: T) -> bool { + match (item.cfg().is_some(), self.data.get_mut(item.path())) { + (true, Some(&mut ItemValue::Cfg(ref mut items))) => { + items.push(item); + return true; + } + (false, Some(&mut ItemValue::Cfg(_))) => { + return false; + } + (true, Some(&mut ItemValue::Single(_))) => { + return false; + } + (false, Some(&mut ItemValue::Single(_))) => { + return false; + } + _ => {} + } + + let path = item.path().clone(); + if item.cfg().is_some() { + self.data.insert(path, ItemValue::Cfg(vec![item])); + } else { + self.data.insert(path, ItemValue::Single(item)); + } + + true + } + + pub fn extend_with(&mut self, other: &ItemMap<T>) { + other.for_all_items(|x| { + self.try_insert(x.clone()); + }); + } + + pub fn to_vec(&self) -> Vec<T> { + let mut result = Vec::with_capacity(self.data.len()); + for container in self.data.values() { + match *container { + ItemValue::Cfg(ref items) => result.extend_from_slice(items), + ItemValue::Single(ref item) => { + result.push(item.clone()); + } + } + } + result + } + + pub fn get_items(&self, path: &Path) -> Option<Vec<ItemContainer>> { + Some(match *self.data.get(path)? { + ItemValue::Cfg(ref items) => items.iter().map(|x| x.container()).collect(), + ItemValue::Single(ref item) => vec![item.container()], + }) + } + + pub fn filter<F>(&mut self, callback: F) + where + F: Fn(&T) -> bool, + { + let data = mem::take(&mut self.data); + + for (name, container) in data { + match container { + ItemValue::Cfg(items) => { + let mut new_items = Vec::new(); + for item in items { + if !callback(&item) { + new_items.push(item); + } + } + if !new_items.is_empty() { + self.data.insert(name, ItemValue::Cfg(new_items)); + } + } + ItemValue::Single(item) => { + if !callback(&item) { + self.data.insert(name, ItemValue::Single(item)); + } + } + } + } + } + + pub fn for_all_items<F>(&self, mut callback: F) + where + F: FnMut(&T), + { + for container in self.data.values() { + match *container { + ItemValue::Cfg(ref items) => { + for item in items { + callback(item); + } + } + ItemValue::Single(ref item) => callback(item), + } + } + } + + pub fn for_all_items_mut<F>(&mut self, mut callback: F) + where + F: FnMut(&mut T), + { + for container in self.data.values_mut() { + match *container { + ItemValue::Cfg(ref mut items) => { + for item in items { + callback(item); + } + } + ItemValue::Single(ref mut item) => callback(item), + } + } + } + + pub fn for_items<F>(&self, path: &Path, mut callback: F) + where + F: FnMut(&T), + { + match self.data.get(path) { + Some(ItemValue::Cfg(items)) => { + for item in items { + callback(item); + } + } + Some(ItemValue::Single(item)) => { + callback(item); + } + None => {} + } + } + + pub fn for_items_mut<F>(&mut self, path: &Path, mut callback: F) + where + F: FnMut(&mut T), + { + match self.data.get_mut(path) { + Some(&mut ItemValue::Cfg(ref mut items)) => { + for item in items { + callback(item); + } + } + Some(&mut ItemValue::Single(ref mut item)) => { + callback(item); + } + None => {} + } + } +} diff --git a/src/bindgen/ir/mod.rs b/src/bindgen/ir/mod.rs new file mode 100644 index 0000000..3566f0d --- /dev/null +++ b/src/bindgen/ir/mod.rs @@ -0,0 +1,39 @@ +/* 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/. */ + +pub mod annotation; +pub mod cfg; +pub mod constant; +pub mod documentation; +pub mod enumeration; +pub mod field; +pub mod function; +pub mod generic_path; +pub mod global; +pub mod item; +pub mod opaque; +pub mod path; +pub mod repr; +pub mod structure; +pub mod ty; +pub mod typedef; +pub mod union; + +pub use self::annotation::{AnnotationSet, AnnotationValue, DeprecatedNoteKind}; +pub use self::cfg::*; +pub use self::constant::*; +pub use self::documentation::Documentation; +pub use self::enumeration::*; +pub use self::field::*; +pub use self::function::*; +pub use self::generic_path::*; +pub use self::global::*; +pub use self::item::*; +pub use self::opaque::*; +pub use self::path::*; +pub use self::repr::*; +pub use self::structure::*; +pub use self::ty::*; +pub use self::typedef::*; +pub use self::union::*; diff --git a/src/bindgen/ir/opaque.rs b/src/bindgen/ir/opaque.rs new file mode 100644 index 0000000..4451d4a --- /dev/null +++ b/src/bindgen/ir/opaque.rs @@ -0,0 +1,176 @@ +/* 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/. */ + +use std::io::Write; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Documentation, GenericArgument, GenericParams, Item, + ItemContainer, Path, ToCondition, +}; +use crate::bindgen::library::Library; +use crate::bindgen::mangle; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct OpaqueItem { + pub path: Path, + pub export_name: String, + pub generic_params: GenericParams, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl OpaqueItem { + pub fn load( + path: Path, + generics: &syn::Generics, + attrs: &[syn::Attribute], + mod_cfg: Option<&Cfg>, + ) -> Result<OpaqueItem, String> { + Ok(Self::new( + path, + GenericParams::load(generics)?, + Cfg::append(mod_cfg, Cfg::load(attrs)), + AnnotationSet::load(attrs).unwrap_or_else(|_| AnnotationSet::new()), + Documentation::load(attrs), + )) + } + + pub fn new( + path: Path, + generic_params: GenericParams, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> OpaqueItem { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generic_params, + cfg, + annotations, + documentation, + } + } +} + +impl Item for OpaqueItem { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::OpaqueItem(self.clone()) + } + + fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) { + resolver.add_struct(&self.path); + } + + fn rename_for_config(&mut self, config: &Config) { + config.export.rename(&mut self.export_name); + } + + fn add_dependencies(&self, _: &Library, _: &mut Dependencies) {} + + fn instantiate_monomorph( + &self, + generic_values: &[GenericArgument], + library: &Library, + out: &mut Monomorphs, + ) { + assert!( + !self.generic_params.is_empty(), + "{} is not generic", + self.path + ); + + // We can be instantiated with less generic params because of default + // template parameters, or because of empty types that we remove during + // parsing (`()`). + assert!( + self.generic_params.len() >= generic_values.len(), + "{} has {} params but is being instantiated with {} values", + self.path, + self.generic_params.len(), + generic_values.len(), + ); + + let mangled_path = mangle::mangle_path( + &self.path, + generic_values, + &library.get_config().export.mangle, + ); + + let monomorph = OpaqueItem::new( + mangled_path, + GenericParams::default(), + self.cfg.clone(), + self.annotations.clone(), + self.documentation.clone(), + ); + + out.insert_opaque(self, monomorph, generic_values.to_owned()); + } +} + +impl Source for OpaqueItem { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + self.documentation.write(config, out); + + self.generic_params.write_with_default(config, out); + + match config.language { + Language::C if config.style.generate_typedef() => { + write!( + out, + "typedef struct {} {};", + self.export_name(), + self.export_name() + ); + } + Language::C | Language::Cxx => { + write!(out, "struct {};", self.export_name()); + } + Language::Cython => { + write!( + out, + "{}struct {}", + config.style.cython_def(), + self.export_name() + ); + out.open_brace(); + out.write("pass"); + out.close_brace(false); + } + } + + condition.write_after(config, out); + } +} diff --git a/src/bindgen/ir/path.rs b/src/bindgen/ir/path.rs new file mode 100644 index 0000000..480c31b --- /dev/null +++ b/src/bindgen/ir/path.rs @@ -0,0 +1,50 @@ +/* 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/. */ + +use std::cmp::Ordering; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Path { + name: String, +} + +impl Path { + pub fn new<T>(name: T) -> Self + where + String: From<T>, + { + Self { name: name.into() } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn replace_self_with(&mut self, path: &Self) -> bool { + if self.name() != "Self" { + return false; + } + *self = path.clone(); + true + } +} + +impl PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Path { + fn cmp(&self, other: &Path) -> Ordering { + self.name.cmp(&other.name) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/src/bindgen/ir/repr.rs b/src/bindgen/ir/repr.rs new file mode 100644 index 0000000..c40cd7f --- /dev/null +++ b/src/bindgen/ir/repr.rs @@ -0,0 +1,182 @@ +/* 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/. */ + +use syn::ext::IdentExt; + +use crate::bindgen::ir::ty::{IntKind, PrimitiveType}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum ReprStyle { + #[default] + Rust, + C, + Transparent, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ReprType { + kind: IntKind, + signed: bool, +} + +impl ReprType { + pub(crate) fn to_primitive(self) -> PrimitiveType { + PrimitiveType::Integer { + kind: self.kind, + signed: self.signed, + zeroable: true, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ReprAlign { + Packed, + Align(u64), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Repr { + pub style: ReprStyle, + pub ty: Option<ReprType>, + pub align: Option<ReprAlign>, +} + +impl Repr { + pub fn load(attrs: &[syn::Attribute]) -> Result<Repr, String> { + let ids = attrs + .iter() + .filter_map(|attr| { + if let syn::Meta::List(syn::MetaList { path, nested, .. }) = + attr.parse_meta().ok()? + { + if path.is_ident("repr") { + return Some(nested.into_iter().collect::<Vec<_>>()); + } + } + None + }) + .flatten() + .filter_map(|meta| match meta { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => Some(( + path.segments.first().unwrap().ident.unraw().to_string(), + None, + )), + syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { path, nested, .. })) => { + Some(( + path.segments.first().unwrap().ident.unraw().to_string(), + Some( + nested + .iter() + .filter_map(|meta| match meta { + // Only used for #[repr(align(...))]. + syn::NestedMeta::Lit(syn::Lit::Int(literal)) => { + Some(literal.base10_digits().to_string()) + } + // Only single levels of nesting supported at the moment. + _ => None, + }) + .collect::<Vec<_>>(), + ), + )) + } + _ => None, + }); + + let mut repr = Repr::default(); + for id in ids { + let (int_kind, signed) = match (id.0.as_ref(), id.1) { + ("u8", None) => (IntKind::B8, false), + ("u16", None) => (IntKind::B16, false), + ("u32", None) => (IntKind::B32, false), + ("u64", None) => (IntKind::B64, false), + ("usize", None) => (IntKind::Size, false), + ("i8", None) => (IntKind::B8, true), + ("i16", None) => (IntKind::B16, true), + ("i32", None) => (IntKind::B32, true), + ("i64", None) => (IntKind::B64, true), + ("isize", None) => (IntKind::Size, true), + ("C", None) => { + repr.style = ReprStyle::C; + continue; + } + ("transparent", None) => { + repr.style = ReprStyle::Transparent; + continue; + } + ("packed", args) => { + // #[repr(packed(n))] not supported because of some open questions about how + // to calculate the native alignment of types. See mozilla/cbindgen#433. + if args.is_some() { + return Err( + "Not-yet-implemented #[repr(packed(...))] encountered.".to_string() + ); + } + let align = ReprAlign::Packed; + // Only permit a single alignment-setting repr. + if let Some(old_align) = repr.align { + return Err(format!( + "Conflicting #[repr(align(...))] type hints {:?} and {:?}.", + old_align, align + )); + } + repr.align = Some(align); + continue; + } + ("align", Some(args)) => { + // #[repr(align(...))] only allows a single argument. + if args.len() != 1 { + return Err(format!( + "Unsupported #[repr(align({}))], align must have exactly one argument.", + args.join(", ") + )); + } + // Must be a positive integer. + let align = match args.first().unwrap().parse::<u64>() { + Ok(align) => align, + Err(_) => { + return Err(format!("Non-numeric #[repr(align({}))].", args.join(", "))) + } + }; + // Must be a power of 2. + if !align.is_power_of_two() || align == 0 { + return Err(format!("Invalid alignment to #[repr(align({}))].", align)); + } + // Only permit a single alignment-setting repr. + if let Some(old_align) = repr.align { + return Err(format!( + "Conflicting #[repr(align(...))] type hints {:?} and {:?}.", + old_align, + ReprAlign::Align(align) + )); + } + repr.align = Some(ReprAlign::Align(align)); + continue; + } + (path, args) => match args { + None => return Err(format!("Unsupported #[repr({})].", path)), + Some(args) => { + return Err(format!( + "Unsupported #[repr({}({}))].", + path, + args.join(", ") + )); + } + }, + }; + let ty = ReprType { + kind: int_kind, + signed, + }; + if let Some(old_ty) = repr.ty { + return Err(format!( + "Conflicting #[repr(...)] type hints {:?} and {:?}.", + old_ty, ty + )); + } + repr.ty = Some(ty); + } + Ok(repr) + } +} diff --git a/src/bindgen/ir/structure.rs b/src/bindgen/ir/structure.rs new file mode 100644 index 0000000..2eefa95 --- /dev/null +++ b/src/bindgen/ir/structure.rs @@ -0,0 +1,717 @@ +/* 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/. */ + +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::config::{Config, Language, LayoutConfig}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Constant, DeprecatedNoteKind, Documentation, Field, + GenericArgument, GenericParams, Item, ItemContainer, Path, Repr, ReprAlign, ReprStyle, + ToCondition, Type, Typedef, +}; +use crate::bindgen::library::Library; +use crate::bindgen::mangle; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::rename::{IdentifierType, RenameRule}; +use crate::bindgen::reserved; +use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::writer::{ListType, Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct Struct { + pub path: Path, + pub export_name: String, + pub generic_params: GenericParams, + pub fields: Vec<Field>, + /// Whether there's a tag field on the body of this struct. When this is + /// true, is_enum_variant_body is also guaranteed to be true. + pub has_tag_field: bool, + /// Whether this is an enum variant body. + pub is_enum_variant_body: bool, + pub alignment: Option<ReprAlign>, + pub is_transparent: bool, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, + pub associated_constants: Vec<Constant>, +} + +impl Struct { + /// Whether this struct can derive operator== / operator!=. + pub fn can_derive_eq(&self) -> bool { + !self.fields.is_empty() && self.fields.iter().all(|x| x.ty.can_cmp_eq()) + } + + pub fn add_associated_constant(&mut self, c: Constant) { + self.associated_constants.push(c); + } + + pub fn load( + layout_config: &LayoutConfig, + item: &syn::ItemStruct, + mod_cfg: Option<&Cfg>, + ) -> Result<Self, String> { + let repr = Repr::load(&item.attrs)?; + let is_transparent = match repr.style { + ReprStyle::C => false, + ReprStyle::Transparent => true, + _ => { + return Err("Struct is not marked #[repr(C)] or #[repr(transparent)].".to_owned()); + } + }; + + let path = Path::new(item.ident.unraw().to_string()); + + // Ensure we can safely represent the struct given the configuration. + if let Some(align) = repr.align { + layout_config.ensure_safe_to_represent(&align)?; + } + + let fields = match item.fields { + syn::Fields::Unit => Vec::new(), + syn::Fields::Named(ref fields) => fields + .named + .iter() + .try_skip_map(|field| Field::load(field, &path))?, + syn::Fields::Unnamed(ref fields) => { + let mut out = Vec::new(); + let mut current = 0; + for field in fields.unnamed.iter() { + if let Some(mut ty) = Type::load(&field.ty)? { + ty.replace_self_with(&path); + out.push(Field { + name: format!("{}", current), + ty, + cfg: Cfg::load(&field.attrs), + annotations: AnnotationSet::load(&field.attrs)?, + documentation: Documentation::load(&field.attrs), + }); + current += 1; + } + } + out + } + }; + + let has_tag_field = false; + let is_enum_variant_body = false; + + Ok(Struct::new( + path, + GenericParams::load(&item.generics)?, + fields, + has_tag_field, + is_enum_variant_body, + repr.align, + is_transparent, + Cfg::append(mod_cfg, Cfg::load(&item.attrs)), + AnnotationSet::load(&item.attrs)?, + Documentation::load(&item.attrs), + )) + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + path: Path, + generic_params: GenericParams, + fields: Vec<Field>, + has_tag_field: bool, + is_enum_variant_body: bool, + alignment: Option<ReprAlign>, + is_transparent: bool, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generic_params, + fields, + has_tag_field, + is_enum_variant_body, + alignment, + is_transparent, + cfg, + annotations, + documentation, + associated_constants: vec![], + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + for field in &mut self.fields { + field.ty.simplify_standard_types(config); + } + } + + pub fn is_generic(&self) -> bool { + self.generic_params.len() > 0 + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + // Generic structs can instantiate monomorphs only once they've been + // instantiated. See `instantiate_monomorph` for more details. + if self.is_generic() { + return; + } + + for field in &self.fields { + field.ty.add_monomorphs(library, out); + } + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + for field in &mut self.fields { + field.ty.mangle_paths(monomorphs); + } + } + + pub fn specialize( + &self, + generic_values: &[GenericArgument], + mappings: &[(&Path, &GenericArgument)], + config: &Config, + ) -> Self { + let mangled_path = mangle::mangle_path(&self.path, generic_values, &config.export.mangle); + Struct::new( + mangled_path, + GenericParams::default(), + self.fields + .iter() + .map(|field| Field { + name: field.name.clone(), + ty: field.ty.specialize(mappings), + cfg: field.cfg.clone(), + annotations: field.annotations.clone(), + documentation: field.documentation.clone(), + }) + .collect(), + self.has_tag_field, + self.is_enum_variant_body, + self.alignment, + self.is_transparent, + self.cfg.clone(), + self.annotations.clone(), + self.documentation.clone(), + ) + } + + fn emit_bitflags_binop<F: Write>( + &self, + constexpr_prefix: &str, + operator: char, + other: &str, + out: &mut SourceWriter<F>, + ) { + let bits = &self.fields[0].name; + out.new_line(); + write!( + out, + "{}{} operator{}(const {}& {}) const", + constexpr_prefix, + self.export_name(), + operator, + self.export_name(), + other + ); + out.open_brace(); + write!( + out, + "return {} {{ static_cast<decltype({bits})>(this->{bits} {operator} {other}.{bits}) }};", + self.export_name() + ); + out.close_brace(false); + + out.new_line(); + write!( + out, + "{}& operator{}=(const {}& {})", + self.export_name(), + operator, + self.export_name(), + other + ); + out.open_brace(); + write!(out, "*this = (*this {} {});", operator, other); + out.new_line(); + write!(out, "return *this;"); + out.close_brace(false); + } +} + +impl Item for Struct { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Struct(self.clone()) + } + + fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) { + if self.is_transparent { + resolver.add_none(&self.path); + } else { + resolver.add_struct(&self.path); + } + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + for field in &mut self.fields { + field.ty.resolve_declaration_types(resolver); + } + } + + fn rename_for_config(&mut self, config: &Config) { + // Rename the name of the struct + if !(self.has_tag_field && config.language == Language::Cxx) { + config.export.rename(&mut self.export_name); + } + + // Rename the types used in fields + { + let fields = self.fields.iter_mut().skip(self.has_tag_field as usize); + for field in fields { + field.ty.rename_for_config(config, &self.generic_params); + } + } + + // Apply renaming rules to fields in the following order + // 1. `cbindgen::field-names` annotation + // 2. `cbindgen::rename-all` annotation + // 3. config struct rename rule + // If the struct is a tuple struct and we have not renamed the + // fields, then prefix each of them with an underscore. + // If any field is a reserved keyword, then postfix it with an + // underscore. + + // Scope for mutable borrow of fields + { + let names = self.fields.iter_mut().map(|field| &mut field.name); + + let field_rules = self + .annotations + .parse_atom::<RenameRule>("rename-all") + .unwrap_or(config.structure.rename_fields); + + if let Some(o) = self.annotations.list("field-names") { + for (dest, src) in names.zip(o) { + *dest = src; + } + } else if let Some(r) = field_rules.not_none() { + for name in names { + *name = r.apply(name, IdentifierType::StructMember).into_owned(); + } + } else { + // If we don't have any rules for a tuple struct, prefix them with + // an underscore so it still compiles. + for name in names { + if name.starts_with(|c: char| c.is_ascii_digit()) { + name.insert(0, '_'); + } + } + } + } + + for field in &mut self.fields { + reserved::escape(&mut field.name); + } + + for c in self.associated_constants.iter_mut() { + c.rename_for_config(config); + } + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + let mut fields = self.fields.iter(); + + // If there is a tag field, skip it + if self.has_tag_field { + fields.next(); + } + + for field in fields { + field + .ty + .add_dependencies_ignoring_generics(&self.generic_params, library, out); + } + + for c in &self.associated_constants { + c.add_dependencies(library, out); + } + } + + fn instantiate_monomorph( + &self, + generic_values: &[GenericArgument], + library: &Library, + out: &mut Monomorphs, + ) { + let mappings = self.generic_params.call(self.path.name(), generic_values); + let monomorph = self.specialize(generic_values, &mappings, library.get_config()); + out.insert_struct(library, self, monomorph, generic_values.to_owned()); + } +} + +impl Source for Struct { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + if self.is_transparent { + let typedef = Typedef { + path: self.path.clone(), + export_name: self.export_name.to_owned(), + generic_params: self.generic_params.clone(), + aliased: self.fields[0].ty.clone(), + cfg: self.cfg.clone(), + annotations: self.annotations.clone(), + documentation: self.documentation.clone(), + }; + typedef.write(config, out); + for constant in &self.associated_constants { + out.new_line(); + constant.write(config, out, Some(self)); + } + return; + } + + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + self.documentation.write(config, out); + + if !self.is_enum_variant_body { + self.generic_params.write(config, out); + } + + // The following results in + // C++ or C with Tag as style: + // struct Name { + // C with Type only style: + // typedef struct { + // C with Both as style: + // typedef struct Name { + match config.language { + Language::C if config.style.generate_typedef() => out.write("typedef "), + Language::C | Language::Cxx => {} + Language::Cython => out.write(config.style.cython_def()), + } + + // Cython extern declarations don't manage layouts, layouts are defined entierly by the + // corresponding C code. So this `packed` is only for documentation, and missing + // `aligned(n)` is also not a problem. + if config.language == Language::Cython { + if let Some(align) = self.alignment { + match align { + ReprAlign::Packed => out.write("packed "), + ReprAlign::Align(_) => {} // Not supported + } + } + } + + out.write("struct"); + + if config.language != Language::Cython { + if let Some(align) = self.alignment { + match align { + ReprAlign::Packed => { + if let Some(ref anno) = config.layout.packed { + write!(out, " {}", anno); + } + } + ReprAlign::Align(n) => { + if let Some(ref anno) = config.layout.aligned_n { + write!(out, " {}({})", anno, n); + } + } + } + } + } + + if self.annotations.must_use(config) { + if let Some(ref anno) = config.structure.must_use { + write!(out, " {}", anno); + } + } + if let Some(note) = self + .annotations + .deprecated_note(config, DeprecatedNoteKind::Struct) + { + write!(out, " {}", note); + } + + if config.language != Language::C || config.style.generate_tag() { + write!(out, " {}", self.export_name()); + } + + out.open_brace(); + + // Emit the pre_body section, if relevant + if let Some(body) = config.export.pre_body(&self.path) { + out.write_raw_block(body); + out.new_line(); + } + + out.write_vertical_source_list(&self.fields, ListType::Cap(";")); + if config.language == Language::Cython && self.fields.is_empty() { + out.write("pass"); + } + + if config.language == Language::Cxx { + let mut wrote_start_newline = false; + + if config.structure.derive_constructor(&self.annotations) && !self.fields.is_empty() { + if !wrote_start_newline { + wrote_start_newline = true; + out.new_line(); + } + + out.new_line(); + + let arg_renamer = |name: &str| { + config + .function + .rename_args + .apply(name, IdentifierType::FunctionArg) + .into_owned() + }; + write!(out, "{}(", self.export_name()); + let vec: Vec<_> = self + .fields + .iter() + .map(|field| { + Field::from_name_and_type( + // const-ref args to constructor + format!("const& {}", arg_renamer(&field.name)), + field.ty.clone(), + ) + }) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join(",")); + write!(out, ")"); + out.new_line(); + write!(out, " : "); + let vec: Vec<_> = self + .fields + .iter() + .map(|field| format!("{}({})", field.name, arg_renamer(&field.name))) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join(",")); + out.new_line(); + write!(out, "{{}}"); + out.new_line(); + } + + let other = config + .function + .rename_args + .apply("other", IdentifierType::FunctionArg); + + if self + .annotations + .bool("internal-derive-bitflags") + .unwrap_or(false) + { + assert_eq!(self.fields.len(), 1); + let bits = &self.fields[0].name; + if !wrote_start_newline { + wrote_start_newline = true; + out.new_line(); + } + let constexpr_prefix = if config.constant.allow_constexpr { + "constexpr " + } else { + "" + }; + + out.new_line(); + write!(out, "{}explicit operator bool() const", constexpr_prefix); + out.open_brace(); + write!(out, "return !!{bits};"); + out.close_brace(false); + + out.new_line(); + write!( + out, + "{}{} operator~() const", + constexpr_prefix, + self.export_name() + ); + out.open_brace(); + write!( + out, + "return {} {{ static_cast<decltype({bits})>(~{bits}) }};", + self.export_name() + ); + out.close_brace(false); + self.emit_bitflags_binop(constexpr_prefix, '|', &other, out); + self.emit_bitflags_binop(constexpr_prefix, '&', &other, out); + self.emit_bitflags_binop(constexpr_prefix, '^', &other, out); + } + + // Generate a serializer function that allows dumping this struct + // to an std::ostream. It's defined as a friend function inside the + // struct definition, and doesn't need the `inline` keyword even + // though it's implemented right in the generated header file. + if config.structure.derive_ostream(&self.annotations) { + if !wrote_start_newline { + wrote_start_newline = true; + out.new_line(); + } + + out.new_line(); + let stream = config + .function + .rename_args + .apply("stream", IdentifierType::FunctionArg); + let instance = config + .function + .rename_args + .apply("instance", IdentifierType::FunctionArg); + write!( + out, + "friend std::ostream& operator<<(std::ostream& {}, const {}& {})", + stream, + self.export_name(), + instance, + ); + out.open_brace(); + write!(out, "return {} << \"{{ \"", stream); + let vec: Vec<_> = self + .fields + .iter() + .map(|x| format!(" << \"{}=\" << {}.{}", x.name, instance, x.name)) + .collect(); + out.write_vertical_source_list(&vec[..], ListType::Join(" << \", \"")); + out.write(" << \" }\";"); + out.close_brace(false); + } + + let skip_fields = self.has_tag_field as usize; + + macro_rules! emit_op { + ($op_name:expr, $op:expr, $conjuc:expr) => {{ + if !wrote_start_newline { + #[allow(unused_assignments)] + { + wrote_start_newline = true; + } + out.new_line(); + } + + out.new_line(); + + if let Some(Some(attrs)) = + self.annotations.atom(concat!($op_name, "-attributes")) + { + write!(out, "{} ", attrs); + } + + write!( + out, + "bool operator{}(const {}& {}) const", + $op, + self.export_name(), + other + ); + out.open_brace(); + out.write("return "); + let vec: Vec<_> = self + .fields + .iter() + .skip(skip_fields) + .map(|field| format!("{} {} {}.{}", field.name, $op, other, field.name)) + .collect(); + out.write_vertical_source_list( + &vec[..], + ListType::Join(&format!(" {}", $conjuc)), + ); + out.write(";"); + out.close_brace(false); + }}; + } + + if config.structure.derive_eq(&self.annotations) && self.can_derive_eq() { + emit_op!("eq", "==", "&&"); + } + if config.structure.derive_neq(&self.annotations) && self.can_derive_eq() { + emit_op!("neq", "!=", "||"); + } + if config.structure.derive_lt(&self.annotations) + && self.fields.len() == 1 + && self.fields[0].ty.can_cmp_order() + { + emit_op!("lt", "<", "&&"); + } + if config.structure.derive_lte(&self.annotations) + && self.fields.len() == 1 + && self.fields[0].ty.can_cmp_order() + { + emit_op!("lte", "<=", "&&"); + } + if config.structure.derive_gt(&self.annotations) + && self.fields.len() == 1 + && self.fields[0].ty.can_cmp_order() + { + emit_op!("gt", ">", "&&"); + } + if config.structure.derive_gte(&self.annotations) + && self.fields.len() == 1 + && self.fields[0].ty.can_cmp_order() + { + emit_op!("gte", ">=", "&&"); + } + } + + // Emit the post_body section, if relevant + if let Some(body) = config.export.post_body(&self.path) { + out.new_line(); + out.write_raw_block(body); + } + + if config.language == Language::Cxx + && config.structure.associated_constants_in_body + && config.constant.allow_static_const + { + for constant in &self.associated_constants { + out.new_line(); + constant.write_declaration(config, out, self); + } + } + + if config.language == Language::C && config.style.generate_typedef() { + out.close_brace(false); + write!(out, " {};", self.export_name()); + } else { + out.close_brace(true); + } + + for constant in &self.associated_constants { + out.new_line(); + constant.write(config, out, Some(self)); + } + + condition.write_after(config, out); + } +} diff --git a/src/bindgen/ir/ty.rs b/src/bindgen/ir/ty.rs new file mode 100644 index 0000000..5a31fb6 --- /dev/null +++ b/src/bindgen/ir/ty.rs @@ -0,0 +1,1017 @@ +/* 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/. */ + +use std::borrow::Cow; +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::cdecl; +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{GenericArgument, GenericParams, GenericPath, Path}; +use crate::bindgen::library::Library; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::writer::{Source, SourceWriter}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum PrimitiveType { + Void, + Bool, + Char, + SChar, + UChar, + Char32, + Float, + Double, + VaList, + PtrDiffT, + Integer { + zeroable: bool, + signed: bool, + kind: IntKind, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum IntKind { + Short, + Int, + Long, + LongLong, + SizeT, + Size, + B8, + B16, + B32, + B64, +} + +impl PrimitiveType { + pub fn maybe(path: &str) -> Option<PrimitiveType> { + Some(match path { + "c_void" => PrimitiveType::Void, + "c_char" => PrimitiveType::Char, + "c_schar" => PrimitiveType::SChar, + "c_uchar" => PrimitiveType::UChar, + "c_float" => PrimitiveType::Float, + "c_double" => PrimitiveType::Double, + "ptrdiff_t" => PrimitiveType::PtrDiffT, + "VaList" => PrimitiveType::VaList, + "bool" => PrimitiveType::Bool, + "char" => PrimitiveType::Char32, + + "f32" => PrimitiveType::Float, + "f64" => PrimitiveType::Double, + + _ => { + let (kind, signed) = match path { + "c_short" => (IntKind::Short, true), + "c_int" => (IntKind::Int, true), + "c_long" => (IntKind::Long, true), + "c_longlong" => (IntKind::LongLong, true), + "ssize_t" => (IntKind::SizeT, true), + "c_ushort" => (IntKind::Short, false), + "c_uint" => (IntKind::Int, false), + "c_ulong" => (IntKind::Long, false), + "c_ulonglong" => (IntKind::LongLong, false), + "size_t" => (IntKind::SizeT, false), + "RawFd" => (IntKind::Int, true), + + "isize" | "intptr_t" => (IntKind::Size, true), + "usize" | "uintptr_t" => (IntKind::Size, false), + + "u8" | "uint8_t" => (IntKind::B8, false), + "u16" | "uint16_t" => (IntKind::B16, false), + "u32" | "uint32_t" => (IntKind::B32, false), + "u64" | "uint64_t" => (IntKind::B64, false), + "i8" | "int8_t" => (IntKind::B8, true), + "i16" | "int16_t" => (IntKind::B16, true), + "i32" | "int32_t" => (IntKind::B32, true), + "i64" | "int64_t" => (IntKind::B64, true), + _ => return None, + }; + PrimitiveType::Integer { + zeroable: true, + signed, + kind, + } + } + }) + } + + pub fn to_repr_rust(&self) -> &'static str { + match *self { + PrimitiveType::Bool => "bool", + PrimitiveType::Void => "c_void", + PrimitiveType::Char => "c_char", + PrimitiveType::SChar => "c_schar", + PrimitiveType::UChar => "c_uchar", + PrimitiveType::Char32 => "char", + PrimitiveType::Integer { + kind, + signed, + zeroable: _, + } => match kind { + IntKind::Short => { + if signed { + "c_short" + } else { + "c_ushort" + } + } + IntKind::Int => { + if signed { + "c_int" + } else { + "c_uint" + } + } + IntKind::Long => { + if signed { + "c_long" + } else { + "c_ulong" + } + } + IntKind::LongLong => { + if signed { + "c_longlong" + } else { + "c_ulonglong" + } + } + IntKind::SizeT => { + if signed { + "ssize_t" + } else { + "size_t" + } + } + IntKind::Size => { + if signed { + "isize" + } else { + "usize" + } + } + IntKind::B8 => { + if signed { + "i8" + } else { + "u8" + } + } + IntKind::B16 => { + if signed { + "i16" + } else { + "u16" + } + } + IntKind::B32 => { + if signed { + "i32" + } else { + "u32" + } + } + IntKind::B64 => { + if signed { + "i64" + } else { + "u64" + } + } + }, + PrimitiveType::Float => "f32", + PrimitiveType::Double => "f64", + PrimitiveType::PtrDiffT => "ptrdiff_t", + PrimitiveType::VaList => "va_list", + } + } + + pub fn to_repr_c(&self, config: &Config) -> &'static str { + match *self { + PrimitiveType::Void => "void", + PrimitiveType::Bool => "bool", + PrimitiveType::Char => "char", + PrimitiveType::SChar => "signed char", + PrimitiveType::UChar => "unsigned char", + // NOTE: It'd be nice to use a char32_t, but: + // + // * uchar.h is not present on mac (see #423). + // + // * char32_t isn't required to be compatible with Rust's char, as + // the C++ spec only requires it to be the same size as + // uint_least32_t, which is _not_ guaranteed to be 4-bytes. + // + PrimitiveType::Char32 => "uint32_t", + PrimitiveType::Integer { + kind, + signed, + zeroable: _, + } => match kind { + IntKind::Short => { + if signed { + "short" + } else { + "unsigned short" + } + } + IntKind::Int => { + if signed { + "int" + } else { + "unsigned int" + } + } + IntKind::Long => { + if signed { + "long" + } else { + "unsigned long" + } + } + IntKind::LongLong => { + if signed { + "long long" + } else { + "unsigned long long" + } + } + IntKind::SizeT => { + if signed { + "ssize_t" + } else { + "size_t" + } + } + IntKind::Size => { + if config.usize_is_size_t { + if signed { + "ptrdiff_t" + } else { + "size_t" + } + } else if signed { + "intptr_t" + } else { + "uintptr_t" + } + } + IntKind::B8 => { + if signed { + "int8_t" + } else { + "uint8_t" + } + } + IntKind::B16 => { + if signed { + "int16_t" + } else { + "uint16_t" + } + } + IntKind::B32 => { + if signed { + "int32_t" + } else { + "uint32_t" + } + } + IntKind::B64 => { + if signed { + "int64_t" + } else { + "uint64_t" + } + } + }, + PrimitiveType::Float => "float", + PrimitiveType::Double => "double", + PrimitiveType::PtrDiffT => "ptrdiff_t", + PrimitiveType::VaList => "va_list", + } + } + + fn can_cmp_order(&self) -> bool { + !matches!(*self, PrimitiveType::Bool) + } + + fn can_cmp_eq(&self) -> bool { + true + } +} + +/// Constant expressions. +/// +/// Used for the `U` part of `[T; U]` and const generics. We support a very +/// limited vocabulary here: only identifiers and literals. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ConstExpr { + Name(String), + Value(String), +} + +impl ConstExpr { + pub fn as_str(&self) -> &str { + match *self { + ConstExpr::Name(ref string) | ConstExpr::Value(ref string) => string, + } + } + + pub fn rename_for_config(&mut self, config: &Config) { + if let ConstExpr::Name(ref mut name) = self { + config.export.rename(name); + } + } + + pub fn load(expr: &syn::Expr) -> Result<Self, String> { + match *expr { + syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { + let val = match *lit { + syn::Lit::Bool(syn::LitBool { value, .. }) => value.to_string(), + syn::Lit::Int(ref len) => len.base10_digits().to_string(), + syn::Lit::Byte(ref byte) => u8::to_string(&byte.value()), + syn::Lit::Char(ref ch) => u32::to_string(&ch.value().into()), + _ => return Err(format!("can't handle const expression {:?}", lit)), + }; + Ok(ConstExpr::Value(val)) + } + syn::Expr::Path(ref path) => { + let generic_path = GenericPath::load(&path.path)?; + Ok(ConstExpr::Name(generic_path.export_name().to_owned())) + } + _ => Err(format!("can't handle const expression {:?}", expr)), + } + } + + pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> ConstExpr { + match *self { + ConstExpr::Name(ref name) => { + let path = Path::new(name); + for &(param, value) in mappings { + if path == *param { + match *value { + GenericArgument::Type(Type::Path(ref path)) + if path.is_single_identifier() => + { + // This happens when the generic argument is a path. + return ConstExpr::Name(path.name().to_string()); + } + GenericArgument::Const(ref expr) => { + return expr.clone(); + } + _ => { + // unsupported argument type - really should be an error + } + } + } + } + } + ConstExpr::Value(_) => {} + } + self.clone() + } +} + +impl Source for ConstExpr { + fn write<F: Write>(&self, _config: &Config, out: &mut SourceWriter<F>) { + write!(out, "{}", self.as_str()); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Type { + Ptr { + ty: Box<Type>, + is_const: bool, + is_nullable: bool, + // FIXME: This is a bit of a hack, this is only to get us to codegen + // `T&` / `const T&`, but we should probably pass that down as an option + // to code generation or something. + is_ref: bool, + }, + Path(GenericPath), + Primitive(PrimitiveType), + Array(Box<Type>, ConstExpr), + FuncPtr { + ret: Box<Type>, + args: Vec<(Option<String>, Type)>, + is_nullable: bool, + never_return: bool, + }, +} + +impl Type { + pub fn const_ref_to(ty: &Self) -> Self { + Type::Ptr { + ty: Box::new(ty.clone()), + is_const: true, + is_nullable: false, + is_ref: true, + } + } + + pub fn load_from_output(output: &syn::ReturnType) -> Result<(Type, bool), String> { + let mut never_return = false; + let ty = match output { + syn::ReturnType::Default => Type::Primitive(PrimitiveType::Void), + syn::ReturnType::Type(_, ref ty) => { + if let syn::Type::Never(_) = ty.as_ref() { + never_return = true; + Type::Primitive(PrimitiveType::Void) + } else { + Type::load(ty)?.unwrap_or(Type::Primitive(PrimitiveType::Void)) + } + } + }; + Ok((ty, never_return)) + } + + pub fn load(ty: &syn::Type) -> Result<Option<Type>, String> { + let converted = match *ty { + syn::Type::Reference(ref reference) => { + let converted = Type::load(&reference.elem)?; + + let converted = match converted { + Some(converted) => converted, + None => Type::Primitive(PrimitiveType::Void), + }; + + // TODO(emilio): we could make these use is_ref: true. + let is_const = reference.mutability.is_none(); + Type::Ptr { + ty: Box::new(converted), + is_const, + is_nullable: false, + is_ref: false, + } + } + syn::Type::Ptr(ref pointer) => { + let converted = Type::load(&pointer.elem)?; + + let converted = match converted { + Some(converted) => converted, + None => Type::Primitive(PrimitiveType::Void), + }; + + let is_const = pointer.mutability.is_none(); + Type::Ptr { + ty: Box::new(converted), + is_const, + is_nullable: true, + is_ref: false, + } + } + syn::Type::Path(ref path) => { + let generic_path = GenericPath::load(&path.path)?; + + if generic_path.name() == "PhantomData" || generic_path.name() == "PhantomPinned" { + return Ok(None); + } + + if let Some(prim) = PrimitiveType::maybe(generic_path.name()) { + if !generic_path.generics().is_empty() { + return Err("Primitive has generics.".to_owned()); + } + Type::Primitive(prim) + } else { + Type::Path(generic_path) + } + } + syn::Type::Array(syn::TypeArray { + ref elem, ref len, .. + }) => { + let converted = Type::load(elem)?; + + let converted = match converted { + Some(converted) => converted, + None => return Err("Cannot have an array of zero sized types.".to_owned()), + }; + + let len = ConstExpr::load(len)?; + Type::Array(Box::new(converted), len) + } + syn::Type::BareFn(ref function) => { + let mut wildcard_counter = 0; + let args = function.inputs.iter().try_skip_map(|x| { + Type::load(&x.ty).map(|opt_ty| { + opt_ty.map(|ty| { + ( + x.name.as_ref().map(|(ref ident, _)| { + if ident == "_" { + wildcard_counter += 1; + if wildcard_counter == 1 { + "_".to_owned() + } else { + format!("_{}", wildcard_counter - 1) + } + } else { + ident.unraw().to_string() + } + }), + ty, + ) + }) + }) + })?; + let (ret, never_return) = Type::load_from_output(&function.output)?; + Type::FuncPtr { + ret: Box::new(ret), + args, + is_nullable: false, + never_return, + } + } + syn::Type::Tuple(ref tuple) => { + if tuple.elems.is_empty() { + return Ok(None); + } + return Err("Tuples are not supported types.".to_owned()); + } + syn::Type::Verbatim(ref tokens) if tokens.to_string() == "..." => { + Type::Primitive(PrimitiveType::VaList) + } + _ => return Err(format!("Unsupported type: {:?}", ty)), + }; + + Ok(Some(converted)) + } + + pub fn is_ptr(&self) -> bool { + matches!(*self, Type::Ptr { .. } | Type::FuncPtr { .. }) + } + + pub fn is_primitive_or_ptr_primitive(&self) -> bool { + match *self { + Type::Primitive(..) => true, + Type::Ptr { ref ty, .. } => matches!(ty.as_ref(), Type::Primitive(..)), + _ => false, + } + } + + pub fn make_zeroable(&self) -> Option<Self> { + let (kind, signed) = match *self { + Type::Primitive(PrimitiveType::Integer { + zeroable: false, + kind, + signed, + }) => (kind, signed), + _ => return None, + }; + + Some(Type::Primitive(PrimitiveType::Integer { + kind, + signed, + zeroable: true, + })) + } + + pub fn make_nullable(&self) -> Option<Self> { + match *self { + Type::Ptr { + ref ty, + is_const, + is_ref, + is_nullable: false, + } => Some(Type::Ptr { + ty: ty.clone(), + is_const, + is_ref, + is_nullable: true, + }), + Type::FuncPtr { + ref ret, + ref args, + is_nullable: false, + never_return, + } => Some(Type::FuncPtr { + ret: ret.clone(), + args: args.clone(), + is_nullable: true, + never_return, + }), + _ => None, + } + } + + fn nonzero_to_primitive(&self) -> Option<Self> { + let path = match *self { + Type::Path(ref p) => p, + _ => return None, + }; + + if !path.generics().is_empty() { + return None; + } + + let name = path.name(); + if !name.starts_with("NonZero") { + return None; + } + + let (kind, signed) = match path.name() { + "NonZeroU8" => (IntKind::B8, false), + "NonZeroU16" => (IntKind::B16, false), + "NonZeroU32" => (IntKind::B32, false), + "NonZeroU64" => (IntKind::B64, false), + "NonZeroUSize" => (IntKind::Size, false), + "NonZeroI8" => (IntKind::B8, true), + "NonZeroI16" => (IntKind::B16, true), + "NonZeroI32" => (IntKind::B32, true), + "NonZeroI64" => (IntKind::B64, true), + "NonZeroISize" => (IntKind::Size, true), + _ => return None, + }; + + Some(Type::Primitive(PrimitiveType::Integer { + zeroable: false, + signed, + kind, + })) + } + + fn simplified_type(&self, config: &Config) -> Option<Self> { + let path = match *self { + Type::Path(ref p) => p, + _ => return None, + }; + + if path.generics().is_empty() { + return self.nonzero_to_primitive(); + } + + if path.generics().len() != 1 { + return None; + } + + let unsimplified_generic = match path.generics()[0] { + GenericArgument::Type(ref ty) => ty, + GenericArgument::Const(_) => return None, + }; + + let generic = match unsimplified_generic.simplified_type(config) { + Some(generic) => Cow::Owned(generic), + None => Cow::Borrowed(unsimplified_generic), + }; + match path.name() { + "Option" => { + if let Some(nullable) = generic.make_nullable() { + return Some(nullable); + } + if let Some(zeroable) = generic.make_zeroable() { + return Some(zeroable); + } + None + } + "NonNull" => Some(Type::Ptr { + ty: Box::new(generic.into_owned()), + is_const: false, + is_nullable: false, + is_ref: false, + }), + "Box" if config.language != Language::Cxx => Some(Type::Ptr { + ty: Box::new(generic.into_owned()), + is_const: false, + is_nullable: false, + is_ref: false, + }), + "Cell" => Some(generic.into_owned()), + "ManuallyDrop" | "MaybeUninit" | "Pin" if config.language != Language::Cxx => { + Some(generic.into_owned()) + } + _ => None, + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + self.visit_types(|ty| ty.simplify_standard_types(config)); + if let Some(ty) = self.simplified_type(config) { + *self = ty; + } + } + + pub fn replace_self_with(&mut self, self_ty: &Path) { + if let Type::Path(ref mut generic_path) = *self { + generic_path.replace_self_with(self_ty); + } + self.visit_types(|ty| ty.replace_self_with(self_ty)) + } + + fn visit_types(&mut self, mut visitor: impl FnMut(&mut Type)) { + match *self { + Type::Array(ref mut ty, ..) | Type::Ptr { ref mut ty, .. } => visitor(ty), + Type::Path(ref mut path) => { + for generic in path.generics_mut() { + match *generic { + GenericArgument::Type(ref mut ty) => visitor(ty), + GenericArgument::Const(_) => {} + } + } + } + Type::Primitive(..) => {} + Type::FuncPtr { + ref mut ret, + ref mut args, + .. + } => { + visitor(ret); + for arg in args { + visitor(&mut arg.1) + } + } + } + } + + pub fn get_root_path(&self) -> Option<Path> { + let mut current = self; + loop { + match *current { + Type::Ptr { ref ty, .. } => current = ty, + Type::Path(ref generic) => { + return Some(generic.path().clone()); + } + Type::Primitive(..) => { + return None; + } + Type::Array(..) => { + return None; + } + Type::FuncPtr { .. } => { + return None; + } + }; + } + } + + pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> Type { + match *self { + Type::Ptr { + ref ty, + is_const, + is_nullable, + is_ref, + } => Type::Ptr { + ty: Box::new(ty.specialize(mappings)), + is_const, + is_nullable, + is_ref, + }, + Type::Path(ref generic_path) => { + for &(param, value) in mappings { + if generic_path.path() == param { + if let GenericArgument::Type(ref ty) = *value { + return ty.clone(); + } + } + } + + let specialized = GenericPath::new( + generic_path.path().clone(), + generic_path + .generics() + .iter() + .map(|x| x.specialize(mappings)) + .collect(), + ); + Type::Path(specialized) + } + Type::Primitive(ref primitive) => Type::Primitive(primitive.clone()), + Type::Array(ref ty, ref constant) => Type::Array( + Box::new(ty.specialize(mappings)), + constant.specialize(mappings), + ), + Type::FuncPtr { + ref ret, + ref args, + is_nullable, + never_return, + } => Type::FuncPtr { + ret: Box::new(ret.specialize(mappings)), + args: args + .iter() + .cloned() + .map(|(name, ty)| (name, ty.specialize(mappings))) + .collect(), + is_nullable, + never_return, + }, + } + } + + pub fn add_dependencies_ignoring_generics( + &self, + generic_params: &GenericParams, + library: &Library, + out: &mut Dependencies, + ) { + match *self { + Type::Ptr { ref ty, .. } => { + ty.add_dependencies_ignoring_generics(generic_params, library, out); + } + Type::Path(ref generic) => { + for generic_value in generic.generics() { + if let GenericArgument::Type(ref ty) = *generic_value { + ty.add_dependencies_ignoring_generics(generic_params, library, out); + } + } + let path = generic.path(); + if !generic_params.iter().any(|param| param.name() == path) { + if let Some(items) = library.get_items(path) { + if !out.items.contains(path) { + out.items.insert(path.clone()); + + for item in &items { + item.deref().add_dependencies(library, out); + } + for item in items { + out.order.push(item); + } + } + } else { + warn!( + "Can't find {}. This usually means that this type was incompatible or \ + not found.", + path + ); + } + } + } + Type::Primitive(_) => {} + Type::Array(ref ty, _) => { + ty.add_dependencies_ignoring_generics(generic_params, library, out); + } + Type::FuncPtr { + ref ret, ref args, .. + } => { + ret.add_dependencies_ignoring_generics(generic_params, library, out); + for (_, ref arg) in args { + arg.add_dependencies_ignoring_generics(generic_params, library, out); + } + } + } + } + + pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.add_dependencies_ignoring_generics(&GenericParams::default(), library, out) + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + match *self { + Type::Ptr { ref ty, .. } => { + ty.add_monomorphs(library, out); + } + Type::Path(ref generic) => { + if generic.generics().is_empty() || out.contains(generic) { + return; + } + let path = generic.path(); + if let Some(items) = library.get_items(path) { + for item in items { + item.deref() + .instantiate_monomorph(generic.generics(), library, out); + } + } + } + Type::Primitive(_) => {} + Type::Array(ref ty, _) => { + ty.add_monomorphs(library, out); + } + Type::FuncPtr { + ref ret, ref args, .. + } => { + ret.add_monomorphs(library, out); + for (_, ref arg) in args { + arg.add_monomorphs(library, out); + } + } + } + } + + pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) { + match *self { + Type::Ptr { ref mut ty, .. } => { + ty.rename_for_config(config, generic_params); + } + Type::Path(ref mut ty) => { + ty.rename_for_config(config, generic_params); + } + Type::Primitive(_) => {} + Type::Array(ref mut ty, ref mut len) => { + ty.rename_for_config(config, generic_params); + len.rename_for_config(config); + } + Type::FuncPtr { + ref mut ret, + ref mut args, + .. + } => { + ret.rename_for_config(config, generic_params); + for (_, arg) in args { + arg.rename_for_config(config, generic_params); + } + } + } + } + + pub fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + match *self { + Type::Ptr { ref mut ty, .. } => { + ty.resolve_declaration_types(resolver); + } + Type::Path(ref mut generic_path) => { + generic_path.resolve_declaration_types(resolver); + } + Type::Primitive(_) => {} + Type::Array(ref mut ty, _) => { + ty.resolve_declaration_types(resolver); + } + Type::FuncPtr { + ref mut ret, + ref mut args, + .. + } => { + ret.resolve_declaration_types(resolver); + for (_, ref mut arg) in args { + arg.resolve_declaration_types(resolver); + } + } + } + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + match *self { + Type::Ptr { ref mut ty, .. } => { + ty.mangle_paths(monomorphs); + } + Type::Path(ref mut generic_path) => { + if generic_path.generics().is_empty() { + return; + } + + if let Some(mangled_path) = monomorphs.mangle_path(generic_path) { + *generic_path = GenericPath::new(mangled_path.clone(), vec![]); + } else { + warn!( + "Cannot find a mangling for generic path {:?}. This usually means that a \ + type referenced by this generic was incompatible or not found.", + generic_path + ); + } + } + Type::Primitive(_) => {} + Type::Array(ref mut ty, _) => { + ty.mangle_paths(monomorphs); + } + Type::FuncPtr { + ref mut ret, + ref mut args, + .. + } => { + ret.mangle_paths(monomorphs); + for (_, ref mut arg) in args { + arg.mangle_paths(monomorphs); + } + } + } + } + + pub fn can_cmp_order(&self) -> bool { + match *self { + // FIXME: Shouldn't this look at ty.can_cmp_order() as well? + Type::Ptr { is_ref, .. } => !is_ref, + Type::Path(..) => true, + Type::Primitive(ref p) => p.can_cmp_order(), + Type::Array(..) => false, + Type::FuncPtr { .. } => false, + } + } + + pub fn can_cmp_eq(&self) -> bool { + match *self { + Type::Ptr { ref ty, is_ref, .. } => !is_ref || ty.can_cmp_eq(), + Type::Path(..) => true, + Type::Primitive(ref p) => p.can_cmp_eq(), + Type::Array(..) => false, + Type::FuncPtr { .. } => true, + } + } +} + +impl Source for String { + fn write<F: Write>(&self, _config: &Config, out: &mut SourceWriter<F>) { + write!(out, "{}", self); + } +} + +impl Source for Type { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + cdecl::write_type(out, self, config); + } +} diff --git a/src/bindgen/ir/typedef.rs b/src/bindgen/ir/typedef.rs new file mode 100644 index 0000000..626732e --- /dev/null +++ b/src/bindgen/ir/typedef.rs @@ -0,0 +1,208 @@ +/* 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/. */ + +use std::collections::HashMap; +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Documentation, Field, GenericArgument, GenericParams, Item, + ItemContainer, Path, ToCondition, Type, +}; +use crate::bindgen::library::Library; +use crate::bindgen::mangle; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::writer::{Source, SourceWriter}; + +/// A type alias that is represented as a C typedef +#[derive(Debug, Clone)] +pub struct Typedef { + pub path: Path, + pub export_name: String, + pub generic_params: GenericParams, + pub aliased: Type, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl Typedef { + pub fn load(item: &syn::ItemType, mod_cfg: Option<&Cfg>) -> Result<Typedef, String> { + if let Some(x) = Type::load(&item.ty)? { + let path = Path::new(item.ident.unraw().to_string()); + Ok(Typedef::new( + path, + GenericParams::load(&item.generics)?, + x, + Cfg::append(mod_cfg, Cfg::load(&item.attrs)), + AnnotationSet::load(&item.attrs)?, + Documentation::load(&item.attrs), + )) + } else { + Err("Cannot have a typedef of a zero sized type.".to_owned()) + } + } + + pub fn new( + path: Path, + generic_params: GenericParams, + aliased: Type, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generic_params, + aliased, + cfg, + annotations, + documentation, + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + self.aliased.simplify_standard_types(config); + } + + pub fn transfer_annotations(&mut self, out: &mut HashMap<Path, AnnotationSet>) { + if self.annotations.is_empty() { + return; + } + + if let Some(alias_path) = self.aliased.get_root_path() { + if out.contains_key(&alias_path) { + warn!( + "Multiple typedef's with annotations for {}. Ignoring annotations from {}.", + alias_path, self.path + ); + return; + } + + out.insert(alias_path, self.annotations.clone()); + self.annotations = AnnotationSet::new(); + } + } + + pub fn is_generic(&self) -> bool { + self.generic_params.len() > 0 + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + // Generic structs can instantiate monomorphs only once they've been + // instantiated. See `instantiate_monomorph` for more details. + if self.is_generic() { + return; + } + + self.aliased.add_monomorphs(library, out); + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + self.aliased.mangle_paths(monomorphs); + } +} + +impl Item for Typedef { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Typedef(self.clone()) + } + + fn rename_for_config(&mut self, config: &Config) { + config.export.rename(&mut self.export_name); + self.aliased.rename_for_config(config, &self.generic_params); + } + + fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) { + resolver.add_none(&self.path); + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.aliased.resolve_declaration_types(resolver); + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.aliased + .add_dependencies_ignoring_generics(&self.generic_params, library, out); + } + + fn instantiate_monomorph( + &self, + generic_values: &[GenericArgument], + library: &Library, + out: &mut Monomorphs, + ) { + let mappings = self.generic_params.call(self.path.name(), generic_values); + + let mangled_path = mangle::mangle_path( + &self.path, + generic_values, + &library.get_config().export.mangle, + ); + + let monomorph = Typedef::new( + mangled_path, + GenericParams::default(), + self.aliased.specialize(&mappings), + self.cfg.clone(), + self.annotations.clone(), + self.documentation.clone(), + ); + + out.insert_typedef(library, self, monomorph, generic_values.to_owned()); + } +} + +impl Source for Typedef { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + self.documentation.write(config, out); + + self.generic_params.write(config, out); + + match config.language { + Language::Cxx => { + write!(out, "using {} = ", self.export_name()); + self.aliased.write(config, out); + } + Language::C | Language::Cython => { + write!(out, "{} ", config.language.typedef()); + Field::from_name_and_type(self.export_name().to_owned(), self.aliased.clone()) + .write(config, out); + } + } + + out.write(";"); + + condition.write_after(config, out); + } +} diff --git a/src/bindgen/ir/union.rs b/src/bindgen/ir/union.rs new file mode 100644 index 0000000..07bd16c --- /dev/null +++ b/src/bindgen/ir/union.rs @@ -0,0 +1,336 @@ +/* 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/. */ + +use std::io::Write; + +use syn::ext::IdentExt; + +use crate::bindgen::config::{Config, Language, LayoutConfig}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Documentation, Field, GenericArgument, GenericParams, Item, + ItemContainer, Path, Repr, ReprAlign, ReprStyle, ToCondition, +}; +use crate::bindgen::library::Library; +use crate::bindgen::mangle; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::rename::{IdentifierType, RenameRule}; +use crate::bindgen::utilities::IterHelpers; +use crate::bindgen::writer::{ListType, Source, SourceWriter}; + +#[derive(Debug, Clone)] +pub struct Union { + pub path: Path, + pub export_name: String, + pub generic_params: GenericParams, + pub fields: Vec<Field>, + pub tuple_union: bool, + pub alignment: Option<ReprAlign>, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, +} + +impl Union { + pub fn load( + layout_config: &LayoutConfig, + item: &syn::ItemUnion, + mod_cfg: Option<&Cfg>, + ) -> Result<Union, String> { + let repr = Repr::load(&item.attrs)?; + if repr.style != ReprStyle::C { + return Err("Union is not marked #[repr(C)].".to_owned()); + } + + // Ensure we can safely represent the union given the configuration. + if let Some(align) = repr.align { + layout_config.ensure_safe_to_represent(&align)?; + } + + let path = Path::new(item.ident.unraw().to_string()); + + let (fields, tuple_union) = { + let out = item + .fields + .named + .iter() + .try_skip_map(|field| Field::load(field, &path))?; + (out, false) + }; + + Ok(Union::new( + path, + GenericParams::load(&item.generics)?, + fields, + repr.align, + tuple_union, + Cfg::append(mod_cfg, Cfg::load(&item.attrs)), + AnnotationSet::load(&item.attrs)?, + Documentation::load(&item.attrs), + )) + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + path: Path, + generic_params: GenericParams, + fields: Vec<Field>, + alignment: Option<ReprAlign>, + tuple_union: bool, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + generic_params, + fields, + tuple_union, + alignment, + cfg, + annotations, + documentation, + } + } + + pub fn simplify_standard_types(&mut self, config: &Config) { + for field in &mut self.fields { + field.ty.simplify_standard_types(config); + } + } + + pub fn is_generic(&self) -> bool { + self.generic_params.len() > 0 + } + + pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) { + // Generic unions can instantiate monomorphs only once they've been + // instantiated. See `instantiate_monomorph` for more details. + if self.is_generic() { + return; + } + + for field in &self.fields { + field.ty.add_monomorphs(library, out); + } + } + + pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) { + for field in &mut self.fields { + field.ty.mangle_paths(monomorphs); + } + } +} + +impl Item for Union { + fn path(&self) -> &Path { + &self.path + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Union(self.clone()) + } + + fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) { + resolver.add_union(&self.path); + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + for field in &mut self.fields { + field.ty.resolve_declaration_types(resolver); + } + } + + fn rename_for_config(&mut self, config: &Config) { + config.export.rename(&mut self.export_name); + for field in &mut self.fields { + field.ty.rename_for_config(config, &self.generic_params); + } + + let rules = self + .annotations + .parse_atom::<RenameRule>("rename-all") + .unwrap_or(config.structure.rename_fields); + + if let Some(o) = self.annotations.list("field-names") { + let mut overriden_fields = Vec::new(); + + for (i, field) in self.fields.iter().enumerate() { + if i >= o.len() { + overriden_fields.push(field.clone()); + } else { + overriden_fields.push(Field { + name: o[i].clone(), + ty: field.ty.clone(), + cfg: field.cfg.clone(), + annotations: field.annotations.clone(), + documentation: field.documentation.clone(), + }); + } + } + + self.fields = overriden_fields; + } else if let Some(r) = rules.not_none() { + self.fields = self + .fields + .iter() + .map(|field| Field { + name: r + .apply(&field.name, IdentifierType::StructMember) + .into_owned(), + ty: field.ty.clone(), + cfg: field.cfg.clone(), + annotations: field.annotations.clone(), + documentation: field.documentation.clone(), + }) + .collect(); + } else if self.tuple_union { + // If we don't have any rules for a tuple union, prefix them with + // an underscore so it still compiles + for field in &mut self.fields { + field.name.insert(0, '_'); + } + } + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + for field in &self.fields { + field + .ty + .add_dependencies_ignoring_generics(&self.generic_params, library, out); + } + } + + fn instantiate_monomorph( + &self, + generic_values: &[GenericArgument], + library: &Library, + out: &mut Monomorphs, + ) { + let mappings = self.generic_params.call(self.path.name(), generic_values); + + let mangled_path = mangle::mangle_path( + &self.path, + generic_values, + &library.get_config().export.mangle, + ); + + let monomorph = Union::new( + mangled_path, + GenericParams::default(), + self.fields + .iter() + .map(|field| Field { + name: field.name.clone(), + ty: field.ty.specialize(&mappings), + cfg: field.cfg.clone(), + annotations: field.annotations.clone(), + documentation: field.documentation.clone(), + }) + .collect(), + self.alignment, + self.tuple_union, + self.cfg.clone(), + self.annotations.clone(), + self.documentation.clone(), + ); + + out.insert_union(library, self, monomorph, generic_values.to_owned()); + } +} + +impl Source for Union { + fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + self.documentation.write(config, out); + + self.generic_params.write(config, out); + + // The following results in + // C++ or C with Tag as style: + // union Name { + // C with Type only style: + // typedef union { + // C with Both as style: + // typedef union Name { + match config.language { + Language::C if config.style.generate_typedef() => out.write("typedef "), + Language::C | Language::Cxx => {} + Language::Cython => out.write(config.style.cython_def()), + } + + out.write("union"); + + // Cython supports `packed` on structs (see comments there), but not on unions. + if config.language != Language::Cython { + if let Some(align) = self.alignment { + match align { + ReprAlign::Packed => { + if let Some(ref anno) = config.layout.packed { + write!(out, " {}", anno); + } + } + ReprAlign::Align(n) => { + if let Some(ref anno) = config.layout.aligned_n { + write!(out, " {}({})", anno, n); + } + } + } + } + } + + if config.language != Language::C || config.style.generate_tag() { + write!(out, " {}", self.export_name); + } + + out.open_brace(); + + // Emit the pre_body section, if relevant + if let Some(body) = config.export.pre_body(&self.path) { + out.write_raw_block(body); + out.new_line(); + } + + out.write_vertical_source_list(&self.fields, ListType::Cap(";")); + if config.language == Language::Cython && self.fields.is_empty() { + out.write("pass"); + } + + // Emit the post_body section, if relevant + if let Some(body) = config.export.post_body(&self.path) { + out.new_line(); + out.write_raw_block(body); + } + + if config.language == Language::C && config.style.generate_typedef() { + out.close_brace(false); + write!(out, " {};", self.export_name); + } else { + out.close_brace(true); + } + + condition.write_after(config, out); + } +} diff --git a/src/bindgen/library.rs b/src/bindgen/library.rs new file mode 100644 index 0000000..cb4cfbd --- /dev/null +++ b/src/bindgen/library.rs @@ -0,0 +1,446 @@ +/* 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/. */ + +use std::collections::HashMap; +use std::path::PathBuf; + +use crate::bindgen::bindings::Bindings; +use crate::bindgen::config::{Config, Language, SortKey}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::error::Error; +use crate::bindgen::ir::{Constant, Enum, Function, Item, ItemContainer, ItemMap}; +use crate::bindgen::ir::{OpaqueItem, Path, Static, Struct, Typedef, Union}; +use crate::bindgen::monomorph::Monomorphs; +use crate::bindgen::ItemType; + +#[derive(Debug, Clone)] +pub struct Library { + config: Config, + constants: ItemMap<Constant>, + globals: ItemMap<Static>, + enums: ItemMap<Enum>, + structs: ItemMap<Struct>, + unions: ItemMap<Union>, + opaque_items: ItemMap<OpaqueItem>, + typedefs: ItemMap<Typedef>, + functions: Vec<Function>, + source_files: Vec<PathBuf>, +} + +impl Library { + #[allow(clippy::too_many_arguments)] + pub fn new( + config: Config, + constants: ItemMap<Constant>, + globals: ItemMap<Static>, + enums: ItemMap<Enum>, + structs: ItemMap<Struct>, + unions: ItemMap<Union>, + opaque_items: ItemMap<OpaqueItem>, + typedefs: ItemMap<Typedef>, + functions: Vec<Function>, + source_files: Vec<PathBuf>, + ) -> Library { + Library { + config, + constants, + globals, + enums, + structs, + unions, + opaque_items, + typedefs, + functions, + source_files, + } + } + + pub fn generate(mut self) -> Result<Bindings, Error> { + self.transfer_annotations(); + self.simplify_standard_types(); + + match self.config.function.sort_by.unwrap_or(self.config.sort_by) { + SortKey::Name => self.functions.sort_by(|x, y| x.path.cmp(&y.path)), + SortKey::None => { /* keep input order */ } + } + + if self.config.language != Language::Cxx { + self.instantiate_monomorphs(); + } + self.remove_excluded(); + if self.config.language == Language::C { + self.resolve_declaration_types(); + } + + self.rename_items(); + + let mut dependencies = Dependencies::new(); + + for function in &self.functions { + function.add_dependencies(&self, &mut dependencies); + } + self.globals.for_all_items(|global| { + global.add_dependencies(&self, &mut dependencies); + }); + self.constants.for_all_items(|constant| { + constant.add_dependencies(&self, &mut dependencies); + }); + for name in &self.config.export.include { + let path = Path::new(name.clone()); + if let Some(items) = self.get_items(&path) { + if dependencies.items.insert(path) { + for item in &items { + item.deref().add_dependencies(&self, &mut dependencies); + } + for item in items { + dependencies.order.push(item); + } + } + } + } + + dependencies.sort(); + + let items = dependencies.order; + let constants = if self.config.export.should_generate(ItemType::Constants) { + let mut constants = self.constants.to_vec(); + match self.config.constant.sort_by.unwrap_or(self.config.sort_by) { + SortKey::Name => constants.sort_by(|x, y| x.path.cmp(&y.path)), + SortKey::None => { /* keep input order */ } + } + constants + } else { + vec![] + }; + + let globals = if self.config.export.should_generate(ItemType::Globals) { + let mut globals = self.globals.to_vec(); + match self.config.constant.sort_by.unwrap_or(self.config.sort_by) { + SortKey::Name => globals.sort_by(|x, y| x.path.cmp(&y.path)), + SortKey::None => { /* keep input order */ } + } + globals + } else { + vec![] + }; + let functions = if self.config.export.should_generate(ItemType::Functions) { + self.functions + } else { + vec![] + }; + + Ok(Bindings::new( + self.config, + self.structs, + self.typedefs, + constants, + globals, + items, + functions, + self.source_files, + false, + )) + } + + pub fn get_items(&self, p: &Path) -> Option<Vec<ItemContainer>> { + macro_rules! find { + ($field:ident, $kind:ident) => { + if self.config.export.should_generate(ItemType::$kind) { + if let Some(x) = self.$field.get_items(p) { + return Some(x); + } + } + }; + } + + find!(enums, Enums); + find!(structs, Structs); + find!(unions, Unions); + find!(opaque_items, OpaqueItems); + find!(typedefs, Typedefs); + + None + } + + pub fn get_config(&self) -> &Config { + &self.config + } + + fn remove_excluded(&mut self) { + let config = &self.config; + // FIXME: interpret `config.export.exclude` as `Path`s. + self.functions + .retain(|x| !config.export.exclude.iter().any(|y| y == x.path().name())); + self.enums + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.structs + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.unions + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.opaque_items + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.typedefs + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.globals + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + self.constants + .filter(|x| config.export.exclude.iter().any(|y| y == x.path().name())); + } + + fn transfer_annotations(&mut self) { + let mut annotations = HashMap::new(); + + self.typedefs.for_all_items_mut(|x| { + x.transfer_annotations(&mut annotations); + }); + + for (alias_path, annotations) in annotations { + // TODO + let mut transferred = false; + + self.enums.for_items_mut(&alias_path, |x| { + if x.annotations().is_empty() { + *x.annotations_mut() = annotations.clone(); + transferred = true; + } else { + warn!( + "Can't transfer annotations from typedef to alias ({}) \ + that already has annotations.", + alias_path + ); + } + }); + if transferred { + continue; + } + self.structs.for_items_mut(&alias_path, |x| { + if x.annotations().is_empty() { + *x.annotations_mut() = annotations.clone(); + transferred = true; + } else { + warn!( + "Can't transfer annotations from typedef to alias ({}) \ + that already has annotations.", + alias_path + ); + } + }); + if transferred { + continue; + } + self.unions.for_items_mut(&alias_path, |x| { + if x.annotations().is_empty() { + *x.annotations_mut() = annotations.clone(); + transferred = true; + } else { + warn!( + "Can't transfer annotations from typedef to alias ({}) \ + that already has annotations.", + alias_path + ); + } + }); + if transferred { + continue; + } + self.opaque_items.for_items_mut(&alias_path, |x| { + if x.annotations().is_empty() { + *x.annotations_mut() = annotations.clone(); + transferred = true; + } else { + warn!( + "Can't transfer annotations from typedef to alias ({}) \ + that already has annotations.", + alias_path + ); + } + }); + if transferred { + continue; + } + self.typedefs.for_items_mut(&alias_path, |x| { + if x.annotations().is_empty() { + *x.annotations_mut() = annotations.clone(); + transferred = true; + } else { + warn!( + "Can't transfer annotations from typedef to alias ({}) \ + that already has annotations.", + alias_path + ); + } + }); + if transferred { + continue; + } + } + } + + fn rename_items(&mut self) { + let config = &self.config; + + self.globals + .for_all_items_mut(|x| x.rename_for_config(config)); + self.globals.rebuild(); + + self.constants + .for_all_items_mut(|x| x.rename_for_config(config)); + self.constants.rebuild(); + + self.structs + .for_all_items_mut(|x| x.rename_for_config(config)); + self.structs.rebuild(); + + self.unions + .for_all_items_mut(|x| x.rename_for_config(config)); + self.unions.rebuild(); + + self.enums + .for_all_items_mut(|x| x.rename_for_config(config)); + self.enums.rebuild(); + + self.opaque_items + .for_all_items_mut(|x| x.rename_for_config(config)); + self.opaque_items.rebuild(); + + self.typedefs + .for_all_items_mut(|x| x.rename_for_config(config)); + self.typedefs.rebuild(); + + for item in &mut self.functions { + item.rename_for_config(&self.config); + } + } + + fn resolve_declaration_types(&mut self) { + if !self.config.style.generate_tag() { + return; + } + + let mut resolver = DeclarationTypeResolver::default(); + + self.structs.for_all_items(|x| { + x.collect_declaration_types(&mut resolver); + }); + + self.enums.for_all_items(|x| { + x.collect_declaration_types(&mut resolver); + }); + + self.unions.for_all_items(|x| { + x.collect_declaration_types(&mut resolver); + }); + + self.typedefs.for_all_items(|x| { + x.collect_declaration_types(&mut resolver); + }); + + // NOTE: Intentionally last, so that in case there's an opaque type + // which is conflicting with a non-opaque one, the later wins. + self.opaque_items.for_all_items(|x| { + x.collect_declaration_types(&mut resolver); + }); + + self.enums + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + + self.structs + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + + self.unions + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + + self.typedefs + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + + self.globals + .for_all_items_mut(|x| x.resolve_declaration_types(&resolver)); + + for item in &mut self.functions { + item.resolve_declaration_types(&resolver); + } + } + + fn simplify_standard_types(&mut self) { + let config = &self.config; + + self.structs.for_all_items_mut(|x| { + x.simplify_standard_types(config); + }); + self.enums.for_all_items_mut(|x| { + x.simplify_standard_types(config); + }); + self.unions.for_all_items_mut(|x| { + x.simplify_standard_types(config); + }); + self.globals.for_all_items_mut(|x| { + x.simplify_standard_types(config); + }); + self.typedefs.for_all_items_mut(|x| { + x.simplify_standard_types(config); + }); + for x in &mut self.functions { + x.simplify_standard_types(config); + } + } + + fn instantiate_monomorphs(&mut self) { + // Collect a list of monomorphs + let mut monomorphs = Monomorphs::default(); + + self.structs.for_all_items(|x| { + x.add_monomorphs(self, &mut monomorphs); + }); + self.unions.for_all_items(|x| { + x.add_monomorphs(self, &mut monomorphs); + }); + self.enums.for_all_items(|x| { + x.add_monomorphs(self, &mut monomorphs); + }); + self.typedefs.for_all_items(|x| { + x.add_monomorphs(self, &mut monomorphs); + }); + for x in &self.functions { + x.add_monomorphs(self, &mut monomorphs); + } + + // Insert the monomorphs into self + for monomorph in monomorphs.drain_structs() { + self.structs.try_insert(monomorph); + } + for monomorph in monomorphs.drain_unions() { + self.unions.try_insert(monomorph); + } + for monomorph in monomorphs.drain_opaques() { + self.opaque_items.try_insert(monomorph); + } + for monomorph in monomorphs.drain_typedefs() { + self.typedefs.try_insert(monomorph); + } + for monomorph in monomorphs.drain_enums() { + self.enums.try_insert(monomorph); + } + + // Remove structs and opaque items that are generic + self.opaque_items.filter(|x| x.generic_params.len() > 0); + self.structs.filter(|x| x.generic_params.len() > 0); + self.unions.filter(|x| x.generic_params.len() > 0); + self.enums.filter(|x| x.generic_params.len() > 0); + self.typedefs.filter(|x| x.generic_params.len() > 0); + + // Mangle the paths that remain + self.unions + .for_all_items_mut(|x| x.mangle_paths(&monomorphs)); + self.structs + .for_all_items_mut(|x| x.mangle_paths(&monomorphs)); + self.enums + .for_all_items_mut(|x| x.mangle_paths(&monomorphs)); + self.typedefs + .for_all_items_mut(|x| x.mangle_paths(&monomorphs)); + for x in &mut self.functions { + x.mangle_paths(&monomorphs); + } + } +} diff --git a/src/bindgen/mangle.rs b/src/bindgen/mangle.rs new file mode 100644 index 0000000..c8769f8 --- /dev/null +++ b/src/bindgen/mangle.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/. */ + +use crate::bindgen::config::MangleConfig; +use crate::bindgen::ir::{ConstExpr, GenericArgument, GenericPath, Path, Type}; +use crate::bindgen::rename::IdentifierType; + +pub fn mangle_path(path: &Path, generic_values: &[GenericArgument], config: &MangleConfig) -> Path { + Path::new(mangle_name(path.name(), generic_values, config)) +} + +pub fn mangle_name( + name: &str, + generic_values: &[GenericArgument], + config: &MangleConfig, +) -> String { + Mangler::new(name, generic_values, /* last = */ true, config).mangle() +} + +enum Separator { + OpeningAngleBracket = 1, + Comma, + ClosingAngleBracket, + BeginMutPtr, + BeginConstPtr, + BeginFn, + BetweenFnArg, + EndFn, +} + +struct Mangler<'a> { + input: &'a str, + generic_values: &'a [GenericArgument], + output: String, + last: bool, + config: &'a MangleConfig, +} + +impl<'a> Mangler<'a> { + fn new( + input: &'a str, + generic_values: &'a [GenericArgument], + last: bool, + config: &'a MangleConfig, + ) -> Self { + Self { + input, + generic_values, + output: String::new(), + last, + config, + } + } + + fn mangle(mut self) -> String { + self.mangle_internal(); + self.output + } + + fn push(&mut self, id: Separator) { + let count = id as usize; + let separator = if self.config.remove_underscores { + "" + } else { + "_" + }; + self.output.extend(std::iter::repeat(separator).take(count)); + } + + fn append_mangled_argument(&mut self, arg: &GenericArgument, last: bool) { + match *arg { + GenericArgument::Type(ref ty) => self.append_mangled_type(ty, last), + GenericArgument::Const(ConstExpr::Name(ref name)) => { + // This must behave the same as a GenericArgument::Type, + // because const arguments are commonly represented as Types; + // see the comment on `enum GenericArgument`. + let fake_ty = Type::Path(GenericPath::new(Path::new(name), vec![])); + self.append_mangled_type(&fake_ty, last); + } + GenericArgument::Const(ConstExpr::Value(ref val)) => self.output.push_str(val), + } + } + + fn append_mangled_type(&mut self, ty: &Type, last: bool) { + match *ty { + Type::Path(ref generic) => { + let sub_path = + Mangler::new(generic.export_name(), generic.generics(), last, self.config) + .mangle(); + + self.output.push_str( + &self + .config + .rename_types + .apply(&sub_path, IdentifierType::Type), + ); + } + Type::Primitive(ref primitive) => { + self.output.push_str( + &self + .config + .rename_types + .apply(primitive.to_repr_rust(), IdentifierType::Type), + ); + } + Type::Ptr { + ref ty, is_const, .. + } => { + self.push(if is_const { + Separator::BeginConstPtr + } else { + Separator::BeginMutPtr + }); + self.append_mangled_type(ty, last); + } + Type::FuncPtr { + ref ret, ref args, .. + } => { + self.push(Separator::BeginFn); + self.append_mangled_type(ret, args.is_empty()); + for (i, arg) in args.iter().enumerate() { + self.push(Separator::BetweenFnArg); + let last = last && i == args.len() - 1; + self.append_mangled_type(&arg.1, last); + } + if !self.last { + self.push(Separator::EndFn); + } + } + Type::Array(..) => { + unimplemented!( + "Unable to mangle generic parameter {:?} for '{}'", + ty, + self.input + ); + } + } + } + + fn mangle_internal(&mut self) { + debug_assert!(self.output.is_empty()); + self.output = self.input.to_owned(); + if self.generic_values.is_empty() { + return; + } + + self.push(Separator::OpeningAngleBracket); + for (i, arg) in self.generic_values.iter().enumerate() { + if i != 0 { + self.push(Separator::Comma); + } + let last = self.last && i == self.generic_values.len() - 1; + self.append_mangled_argument(arg, last); + } + + // Skip writing the trailing '>' mangling when possible + if !self.last { + self.push(Separator::ClosingAngleBracket) + } + } +} + +#[test] +fn generics() { + use crate::bindgen::ir::{GenericPath, PrimitiveType}; + use crate::bindgen::rename::RenameRule::{self, PascalCase}; + + fn float() -> GenericArgument { + GenericArgument::Type(Type::Primitive(PrimitiveType::Float)) + } + + fn c_char() -> GenericArgument { + GenericArgument::Type(Type::Primitive(PrimitiveType::Char)) + } + + fn path(path: &str) -> GenericArgument { + generic_path(path, &[]) + } + + fn generic_path(path: &str, arguments: &[GenericArgument]) -> GenericArgument { + let path = Path::new(path); + let generic_path = GenericPath::new(path, arguments.to_owned()); + GenericArgument::Type(Type::Path(generic_path)) + } + + // Foo<f32> => Foo_f32 + assert_eq!( + mangle_path(&Path::new("Foo"), &[float()], &MangleConfig::default()), + Path::new("Foo_f32") + ); + + // Foo<Bar<f32>> => Foo_Bar_f32 + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[float()])], + &MangleConfig::default(), + ), + Path::new("Foo_Bar_f32") + ); + + // Foo<Bar> => Foo_Bar + assert_eq!( + mangle_path(&Path::new("Foo"), &[path("Bar")], &MangleConfig::default()), + Path::new("Foo_Bar") + ); + + // Foo<Bar> => FooBar + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[path("Bar")], + &MangleConfig { + remove_underscores: true, + rename_types: RenameRule::None, + } + ), + Path::new("FooBar") + ); + + // Foo<Bar<f32>> => FooBarF32 + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[float()])], + &MangleConfig { + remove_underscores: true, + rename_types: PascalCase, + }, + ), + Path::new("FooBarF32") + ); + + // Foo<Bar<c_char>> => FooBarCChar + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[c_char()])], + &MangleConfig { + remove_underscores: true, + rename_types: PascalCase, + }, + ), + Path::new("FooBarCChar") + ); + + // Foo<Bar<T>> => Foo_Bar_T + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[path("T")])], + &MangleConfig::default(), + ), + Path::new("Foo_Bar_T") + ); + + // Foo<Bar<T>, E> => Foo_Bar_T_____E + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[path("T")]), path("E")], + &MangleConfig::default(), + ), + Path::new("Foo_Bar_T_____E") + ); + + // Foo<Bar<T>, Bar<E>> => Foo_Bar_T_____Bar_E + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[ + generic_path("Bar", &[path("T")]), + generic_path("Bar", &[path("E")]), + ], + &MangleConfig::default(), + ), + Path::new("Foo_Bar_T_____Bar_E") + ); + + // Foo<Bar<T>, E> => FooBarTE + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[generic_path("Bar", &[path("T")]), path("E")], + &MangleConfig { + remove_underscores: true, + rename_types: PascalCase, + }, + ), + Path::new("FooBarTE") + ); + + // Foo<Bar<T>, Bar<E>> => FooBarTBarE + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[ + generic_path("Bar", &[path("T")]), + generic_path("Bar", &[path("E")]), + ], + &MangleConfig { + remove_underscores: true, + rename_types: PascalCase, + }, + ), + Path::new("FooBarTBarE") + ); + + assert_eq!( + mangle_path( + &Path::new("Top"), + &[GenericArgument::Const(ConstExpr::Value("40".to_string()))], + &MangleConfig::default(), + ), + Path::new("Top_40") + ); + + assert_eq!( + mangle_path( + &Path::new("Top"), + &[GenericArgument::Const(ConstExpr::Name("N".to_string()))], + &MangleConfig::default(), + ), + Path::new("Top_N") + ); + + assert_eq!( + mangle_path( + &Path::new("Top"), + &[generic_path("N", &[])], + &MangleConfig::default(), + ), + Path::new("Top_N") + ); + + assert_eq!( + mangle_path( + &Path::new("Foo"), + &[ + float(), + GenericArgument::Const(ConstExpr::Value("40".to_string())) + ], + &MangleConfig::default(), + ), + Path::new("Foo_f32__40") + ); +} diff --git a/src/bindgen/mod.rs b/src/bindgen/mod.rs new file mode 100644 index 0000000..d0789da --- /dev/null +++ b/src/bindgen/mod.rs @@ -0,0 +1,65 @@ +/* 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/. */ + +/// A helper macro for deriving deserialize for an enum to be used in toml-rs. +/// This macro works be relying on an existing FromStr implementation for the +/// desired type. +macro_rules! deserialize_enum_str { + ($name:ident) => { + impl<'de> ::serde::Deserialize<'de> for $name { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: ::serde::Deserializer<'de>, + { + struct Visitor; + impl<'de> ::serde::de::Visitor<'de> for Visitor { + type Value = $name; + + fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str("$name") + } + + fn visit_str<E>(self, v: &str) -> Result<$name, E> + where + E: ::serde::de::Error, + { + match v.parse::<$name>() { + Ok(v) => Ok(v), + Err(m) => Err(E::custom(m)), + } + } + } + deserializer.deserialize_str(Visitor) + } + } + }; +} + +mod bindings; +mod bitflags; +mod builder; +mod cargo; +mod cdecl; +mod config; +mod declarationtyperesolver; +mod dependencies; +mod error; +mod ir; +mod library; +mod mangle; +mod monomorph; +mod parser; +mod rename; +mod reserved; +mod utilities; +mod writer; + +#[allow(unused)] +pub(crate) use self::cargo::*; + +pub use self::bindings::Bindings; +pub use self::builder::Builder; +pub use self::config::Profile; // disambiguate with cargo::Profile +pub use self::config::*; +pub use self::error::Error; diff --git a/src/bindgen/monomorph.rs b/src/bindgen/monomorph.rs new file mode 100644 index 0000000..db6dce6 --- /dev/null +++ b/src/bindgen/monomorph.rs @@ -0,0 +1,147 @@ +/* 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/. */ + +use std::collections::HashMap; +use std::mem; + +use crate::bindgen::ir::{ + Enum, GenericArgument, GenericPath, OpaqueItem, Path, Struct, Typedef, Union, +}; +use crate::bindgen::library::Library; + +#[derive(Default, Clone, Debug)] +pub struct Monomorphs { + replacements: HashMap<GenericPath, Path>, + opaques: Vec<OpaqueItem>, + structs: Vec<Struct>, + unions: Vec<Union>, + typedefs: Vec<Typedef>, + enums: Vec<Enum>, +} + +impl Monomorphs { + pub fn contains(&self, path: &GenericPath) -> bool { + self.replacements.contains_key(path) + } + + pub fn insert_struct( + &mut self, + library: &Library, + generic: &Struct, + monomorph: Struct, + arguments: Vec<GenericArgument>, + ) { + let replacement_path = GenericPath::new(generic.path.clone(), arguments); + + debug_assert!(generic.generic_params.len() > 0); + debug_assert!(!self.contains(&replacement_path)); + + self.replacements + .insert(replacement_path, monomorph.path.clone()); + + monomorph.add_monomorphs(library, self); + + self.structs.push(monomorph); + } + + pub fn insert_enum( + &mut self, + library: &Library, + generic: &Enum, + monomorph: Enum, + arguments: Vec<GenericArgument>, + ) { + let replacement_path = GenericPath::new(generic.path.clone(), arguments); + + debug_assert!(generic.generic_params.len() > 0); + debug_assert!(!self.contains(&replacement_path)); + + self.replacements + .insert(replacement_path, monomorph.path.clone()); + + monomorph.add_monomorphs(library, self); + + self.enums.push(monomorph); + } + + pub fn insert_union( + &mut self, + library: &Library, + generic: &Union, + monomorph: Union, + arguments: Vec<GenericArgument>, + ) { + let replacement_path = GenericPath::new(generic.path.clone(), arguments); + + debug_assert!(generic.generic_params.len() > 0); + debug_assert!(!self.contains(&replacement_path)); + + self.replacements + .insert(replacement_path, monomorph.path.clone()); + + monomorph.add_monomorphs(library, self); + + self.unions.push(monomorph); + } + + pub fn insert_opaque( + &mut self, + generic: &OpaqueItem, + monomorph: OpaqueItem, + arguments: Vec<GenericArgument>, + ) { + let replacement_path = GenericPath::new(generic.path.clone(), arguments); + + debug_assert!(generic.generic_params.len() > 0); + debug_assert!(!self.contains(&replacement_path)); + + self.replacements + .insert(replacement_path, monomorph.path.clone()); + self.opaques.push(monomorph); + } + + pub fn insert_typedef( + &mut self, + library: &Library, + generic: &Typedef, + monomorph: Typedef, + arguments: Vec<GenericArgument>, + ) { + let replacement_path = GenericPath::new(generic.path.clone(), arguments); + + debug_assert!(generic.generic_params.len() > 0); + debug_assert!(!self.contains(&replacement_path)); + + self.replacements + .insert(replacement_path, monomorph.path.clone()); + + monomorph.add_monomorphs(library, self); + + self.typedefs.push(monomorph); + } + + pub fn mangle_path(&self, path: &GenericPath) -> Option<&Path> { + self.replacements.get(path) + } + + pub fn drain_opaques(&mut self) -> Vec<OpaqueItem> { + mem::take(&mut self.opaques) + } + + pub fn drain_structs(&mut self) -> Vec<Struct> { + mem::take(&mut self.structs) + } + + pub fn drain_unions(&mut self) -> Vec<Union> { + mem::take(&mut self.unions) + } + + pub fn drain_typedefs(&mut self) -> Vec<Typedef> { + mem::take(&mut self.typedefs) + } + + pub fn drain_enums(&mut self) -> Vec<Enum> { + mem::take(&mut self.enums) + } +} diff --git a/src/bindgen/parser.rs b/src/bindgen/parser.rs new file mode 100644 index 0000000..b54c7fb --- /dev/null +++ b/src/bindgen/parser.rs @@ -0,0 +1,1011 @@ +/* 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/. */ + +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::io::Read; +use std::path::{Path as FilePath, PathBuf as FilePathBuf}; + +use syn::ext::IdentExt; + +use crate::bindgen::bitflags; +use crate::bindgen::cargo::{Cargo, PackageRef}; +use crate::bindgen::config::{Config, ParseConfig}; +use crate::bindgen::error::Error; +use crate::bindgen::ir::{ + AnnotationSet, AnnotationValue, Cfg, Constant, Documentation, Enum, Function, GenericParam, + GenericParams, ItemMap, OpaqueItem, Path, Static, Struct, Type, Typedef, Union, +}; +use crate::bindgen::utilities::{SynAbiHelpers, SynAttributeHelpers, SynItemHelpers}; + +const STD_CRATES: &[&str] = &[ + "std", + "std_unicode", + "alloc", + "collections", + "core", + "proc_macro", +]; + +type ParseResult = Result<Parse, Error>; + +/// Parses a single rust source file, not following `mod` or `extern crate`. +pub fn parse_src(src_file: &FilePath, config: &Config) -> ParseResult { + let mod_name = src_file.file_stem().unwrap().to_str().unwrap(); + let mut config = config.clone(); + config.parse = ParseConfig { + parse_deps: true, + ..ParseConfig::default() + }; + + let mut context = Parser { + binding_crate_name: mod_name.to_owned(), + config: &config, + lib: None, + parsed_crates: HashSet::new(), + cache_src: HashMap::new(), + cache_expanded_crate: HashMap::new(), + cfg_stack: Vec::new(), + out: Parse::new(), + }; + + let pkg_ref = PackageRef { + name: mod_name.to_owned(), + version: None, + }; + + context.parse_mod(&pkg_ref, src_file, 0)?; + context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect(); + Ok(context.out) +} + +/// Recursively parses a rust library starting at the root crate's directory. +/// +/// Inside a crate, `mod` and `extern crate` declarations are followed +/// and parsed. To find an external crate, the parser uses the `cargo metadata` +/// command to find the location of dependencies. +pub(crate) fn parse_lib(lib: Cargo, config: &Config) -> ParseResult { + let mut context = Parser { + binding_crate_name: lib.binding_crate_name().to_owned(), + config, + lib: Some(lib), + parsed_crates: HashSet::new(), + cache_src: HashMap::new(), + cache_expanded_crate: HashMap::new(), + cfg_stack: Vec::new(), + out: Parse::new(), + }; + + let binding_crate = context.lib.as_ref().unwrap().binding_crate_ref(); + context.parse_crate(&binding_crate)?; + context.out.source_files = context.cache_src.keys().map(|k| k.to_owned()).collect(); + Ok(context.out) +} + +#[derive(Debug, Clone)] +struct Parser<'a> { + binding_crate_name: String, + lib: Option<Cargo>, + config: &'a Config, + + parsed_crates: HashSet<String>, + cache_src: HashMap<FilePathBuf, Vec<syn::Item>>, + cache_expanded_crate: HashMap<String, Vec<syn::Item>>, + + cfg_stack: Vec<Cfg>, + + out: Parse, +} + +impl<'a> Parser<'a> { + fn should_parse_dependency(&self, pkg_name: &str) -> bool { + if self.parsed_crates.contains(pkg_name) { + return false; + } + + if !self.config.parse.parse_deps { + return false; + } + + // Skip any whitelist or blacklist for expand + if self + .config + .parse + .expand + .crates + .iter() + .any(|name| name == pkg_name) + { + return true; + } + + // If we have a whitelist, check it + if let Some(ref include) = self.config.parse.include { + if !include.iter().any(|name| name == pkg_name) { + debug!("Excluding crate {}", pkg_name); + return false; + } + } + + // Check the blacklist + !STD_CRATES.contains(&pkg_name) + && !self + .config + .parse + .exclude + .iter() + .any(|name| name == pkg_name) + } + + fn parse_crate(&mut self, pkg: &PackageRef) -> Result<(), Error> { + assert!(self.lib.is_some()); + debug!("Parsing crate {}", pkg.name); + self.parsed_crates.insert(pkg.name.clone()); + + // Check if we should use cargo expand for this crate + if self.config.parse.expand.crates.contains(&pkg.name) { + self.parse_expand_crate(pkg)?; + } else { + // Parse the crate before the dependencies otherwise the same-named idents we + // want to generate bindings for would be replaced by the ones provided + // by the first dependency containing it. + let crate_src = self.lib.as_ref().unwrap().find_crate_src(pkg); + + match crate_src { + Some(crate_src) => self.parse_mod(pkg, crate_src.as_path(), 0)?, + None => { + // This should be an error, but is common enough to just elicit a warning + warn!( + "Parsing crate `{}`: can't find lib.rs with `cargo metadata`. \ + The crate may be available only on a particular platform, \ + so consider setting `fetch_all_dependencies` in your cbindgen configuration.", + pkg.name + ); + } + } + } + + for (dep_pkg, cfg) in self.lib.as_ref().unwrap().dependencies(pkg) { + if !self.should_parse_dependency(&dep_pkg.name) { + continue; + } + + if let Some(ref cfg) = cfg { + self.cfg_stack.push(cfg.clone()); + } + + self.parse_crate(&dep_pkg)?; + + if cfg.is_some() { + self.cfg_stack.pop(); + } + } + + Ok(()) + } + + fn parse_expand_crate(&mut self, pkg: &PackageRef) -> Result<(), Error> { + assert!(self.lib.is_some()); + + let mod_items = { + if !self.cache_expanded_crate.contains_key(&pkg.name) { + let s = self + .lib + .as_ref() + .unwrap() + .expand_crate( + pkg, + self.config.parse.expand.all_features, + self.config.parse.expand.default_features, + &self.config.parse.expand.features, + self.config.parse.expand.profile, + ) + .map_err(|x| Error::CargoExpand(pkg.name.clone(), x))?; + let i = syn::parse_file(&s).map_err(|x| Error::ParseSyntaxError { + crate_name: pkg.name.clone(), + src_path: "".to_owned(), + error: x, + })?; + self.cache_expanded_crate.insert(pkg.name.clone(), i.items); + } + + self.cache_expanded_crate.get(&pkg.name).unwrap().clone() + }; + + self.process_mod( + pkg, None, None, &mod_items, 0, /* is_mod_rs = */ true, + /* is_inline = */ false, + ) + } + + fn parse_mod( + &mut self, + pkg: &PackageRef, + mod_path: &FilePath, + depth: usize, + ) -> Result<(), Error> { + let mod_items = match self.cache_src.entry(mod_path.to_path_buf()) { + Entry::Vacant(vacant_entry) => { + let mut s = String::new(); + let mut f = File::open(mod_path).map_err(|_| Error::ParseCannotOpenFile { + crate_name: pkg.name.clone(), + src_path: mod_path.to_str().unwrap().to_owned(), + })?; + + f.read_to_string(&mut s) + .map_err(|_| Error::ParseCannotOpenFile { + crate_name: pkg.name.clone(), + src_path: mod_path.to_str().unwrap().to_owned(), + })?; + + let i = syn::parse_file(&s).map_err(|x| Error::ParseSyntaxError { + crate_name: pkg.name.clone(), + src_path: mod_path.to_string_lossy().into(), + error: x, + })?; + + vacant_entry.insert(i.items).clone() + } + Entry::Occupied(occupied_entry) => occupied_entry.get().clone(), + }; + + // Compute module directory according to Rust 2018 rules + let submod_dir_2018; + + let mod_dir = mod_path.parent().unwrap(); + + let is_mod_rs = depth == 0 || mod_path.ends_with("mod.rs"); + let submod_dir = if is_mod_rs { + mod_dir + } else { + submod_dir_2018 = mod_path + .parent() + .unwrap() + .join(mod_path.file_stem().unwrap()); + &submod_dir_2018 + }; + + self.process_mod( + pkg, + Some(mod_dir), + Some(submod_dir), + &mod_items, + depth, + /* is_inline = */ false, + is_mod_rs, + ) + } + + /// `mod_dir` is the path to the current directory of the module. It may be + /// `None` for pre-expanded modules. + /// + /// `submod_dir` is the path to search submodules in by default, which might + /// be different for rust 2018 for example. + #[allow(clippy::too_many_arguments)] + fn process_mod( + &mut self, + pkg: &PackageRef, + mod_dir: Option<&FilePath>, + submod_dir: Option<&FilePath>, + items: &[syn::Item], + depth: usize, + is_inline: bool, + is_in_mod_rs: bool, + ) -> Result<(), Error> { + debug_assert_eq!(mod_dir.is_some(), submod_dir.is_some()); + // We process the items first then the nested modules. + let nested_modules = self.out.load_syn_crate_mod( + self.config, + &self.binding_crate_name, + &pkg.name, + Cfg::join(&self.cfg_stack).as_ref(), + items, + ); + + for item in nested_modules { + let next_mod_name = item.ident.unraw().to_string(); + let cfg = Cfg::load(&item.attrs); + if let Some(ref cfg) = cfg { + self.cfg_stack.push(cfg.clone()); + } + + if let Some((_, ref inline_items)) = item.content { + // TODO(emilio): This should use #[path] attribute if present, + // rather than next_mod_name. + let next_submod_dir = submod_dir.map(|dir| dir.join(&next_mod_name)); + let next_mod_dir = mod_dir.map(|dir| dir.join(&next_mod_name)); + self.process_mod( + pkg, + next_mod_dir.as_deref(), + next_submod_dir.as_deref(), + inline_items, + depth, + /* is_inline = */ true, + is_in_mod_rs, + )?; + } else if let Some(mod_dir) = mod_dir { + let submod_dir = submod_dir.unwrap(); + let next_mod_path1 = submod_dir.join(next_mod_name.clone() + ".rs"); + let next_mod_path2 = submod_dir.join(next_mod_name.clone()).join("mod.rs"); + + if next_mod_path1.exists() { + self.parse_mod(pkg, next_mod_path1.as_path(), depth + 1)?; + } else if next_mod_path2.exists() { + self.parse_mod(pkg, next_mod_path2.as_path(), depth + 1)?; + } else { + // Last chance to find a module path + let mut path_attr_found = false; + for attr in &item.attrs { + if let Ok(syn::Meta::NameValue(syn::MetaNameValue { path, lit, .. })) = + attr.parse_meta() + { + match lit { + syn::Lit::Str(ref path_lit) if path.is_ident("path") => { + path_attr_found = true; + // https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute + // + // For path attributes on modules not inside inline module blocks, the file path + // is relative to the directory the source file is located. + // + // For path attributes inside inline module blocks, the relative location of the + // file path depends on the kind of source file the path attribute is located + // in. "mod-rs" source files are root modules (such as lib.rs or main.rs) and + // modules with files named mod.rs. "non-mod-rs" source files are all other + // module files. + // + // Paths for path attributes inside inline module blocks in a mod-rs file are + // relative to the directory of the mod-rs file including the inline module + // components as directories. For non-mod-rs files, it is the same except the + // path starts with a directory with the name of the non-mod-rs module. + // + let base = if is_inline && !is_in_mod_rs { + submod_dir + } else { + mod_dir + }; + self.parse_mod(pkg, &base.join(path_lit.value()), depth + 1)?; + break; + } + _ => (), + } + } + } + + // This should be an error, but it's common enough to + // just elicit a warning + if !path_attr_found { + warn!( + "Parsing crate `{}`: can't find mod {}`.", + pkg.name, next_mod_name + ); + } + } + } else { + warn!( + "Parsing expanded crate `{}`: can't find mod {}`.", + pkg.name, next_mod_name + ); + } + + if cfg.is_some() { + self.cfg_stack.pop(); + } + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct Parse { + pub constants: ItemMap<Constant>, + pub globals: ItemMap<Static>, + pub enums: ItemMap<Enum>, + pub structs: ItemMap<Struct>, + pub unions: ItemMap<Union>, + pub opaque_items: ItemMap<OpaqueItem>, + pub typedefs: ItemMap<Typedef>, + pub functions: Vec<Function>, + pub source_files: Vec<FilePathBuf>, +} + +impl Parse { + pub fn new() -> Parse { + Parse { + constants: ItemMap::default(), + globals: ItemMap::default(), + enums: ItemMap::default(), + structs: ItemMap::default(), + unions: ItemMap::default(), + opaque_items: ItemMap::default(), + typedefs: ItemMap::default(), + functions: Vec::new(), + source_files: Vec::new(), + } + } + + pub fn add_std_types(&mut self) { + let mut add_opaque = |path: &str, generic_params: Vec<&str>| { + let path = Path::new(path); + let generic_params: Vec<_> = generic_params + .into_iter() + .map(GenericParam::new_type_param) + .collect(); + self.opaque_items.try_insert(OpaqueItem::new( + path, + GenericParams(generic_params), + None, + AnnotationSet::new(), + Documentation::none(), + )) + }; + + add_opaque("String", vec![]); + add_opaque("Box", vec!["T"]); + add_opaque("RefCell", vec!["T"]); + add_opaque("Rc", vec!["T"]); + add_opaque("Arc", vec!["T"]); + add_opaque("Result", vec!["T", "E"]); + add_opaque("Option", vec!["T"]); + add_opaque("NonNull", vec!["T"]); + add_opaque("Vec", vec!["T"]); + add_opaque("HashMap", vec!["K", "V", "Hasher"]); + add_opaque("BTreeMap", vec!["K", "V"]); + add_opaque("HashSet", vec!["T"]); + add_opaque("BTreeSet", vec!["T"]); + add_opaque("LinkedList", vec!["T"]); + add_opaque("VecDeque", vec!["T"]); + add_opaque("ManuallyDrop", vec!["T"]); + add_opaque("MaybeUninit", vec!["T"]); + } + + pub fn extend_with(&mut self, other: &Parse) { + self.constants.extend_with(&other.constants); + self.globals.extend_with(&other.globals); + self.enums.extend_with(&other.enums); + self.structs.extend_with(&other.structs); + self.unions.extend_with(&other.unions); + self.opaque_items.extend_with(&other.opaque_items); + self.typedefs.extend_with(&other.typedefs); + self.functions.extend_from_slice(&other.functions); + self.source_files.extend_from_slice(&other.source_files); + } + + fn load_syn_crate_mod<'a>( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + items: &'a [syn::Item], + ) -> Vec<&'a syn::ItemMod> { + let mut impls_with_assoc_consts = Vec::new(); + let mut nested_modules = Vec::new(); + + for item in items { + if item.should_skip_parsing() { + continue; + } + match item { + syn::Item::ForeignMod(ref item) => { + self.load_syn_foreign_mod( + config, + binding_crate_name, + crate_name, + mod_cfg, + item, + ); + } + syn::Item::Fn(ref item) => { + self.load_syn_fn(config, binding_crate_name, crate_name, mod_cfg, item); + } + syn::Item::Const(ref item) => { + self.load_syn_const(config, binding_crate_name, crate_name, mod_cfg, item); + } + syn::Item::Static(ref item) => { + self.load_syn_static(config, binding_crate_name, crate_name, mod_cfg, item); + } + syn::Item::Struct(ref item) => { + self.load_syn_struct(config, crate_name, mod_cfg, item); + } + syn::Item::Union(ref item) => { + self.load_syn_union(config, crate_name, mod_cfg, item); + } + syn::Item::Enum(ref item) => { + self.load_syn_enum(config, crate_name, mod_cfg, item); + } + syn::Item::Type(ref item) => { + self.load_syn_ty(crate_name, mod_cfg, item); + } + syn::Item::Impl(ref item_impl) => { + let has_assoc_const = item_impl + .items + .iter() + .any(|item| matches!(item, syn::ImplItem::Const(_))); + if has_assoc_const { + impls_with_assoc_consts.push(item_impl); + } + + if let syn::Type::Path(ref path) = *item_impl.self_ty { + if let Some(type_name) = path.path.get_ident() { + for method in item_impl.items.iter().filter_map(|item| match item { + syn::ImplItem::Method(method) => Some(method), + _ => None, + }) { + self.load_syn_method( + config, + binding_crate_name, + crate_name, + mod_cfg, + &Path::new(type_name.unraw().to_string()), + method, + ) + } + } + } + } + syn::Item::Macro(ref item) => { + self.load_builtin_macro(config, crate_name, mod_cfg, item); + } + syn::Item::Mod(ref item) => { + nested_modules.push(item); + } + _ => {} + } + } + + for item_impl in impls_with_assoc_consts { + self.load_syn_assoc_consts_from_impl(crate_name, mod_cfg, item_impl) + } + + nested_modules + } + + fn load_syn_assoc_consts_from_impl( + &mut self, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item_impl: &syn::ItemImpl, + ) { + let associated_constants = item_impl.items.iter().filter_map(|item| match item { + syn::ImplItem::Const(ref associated_constant) => Some(associated_constant), + _ => None, + }); + self.load_syn_assoc_consts( + crate_name, + mod_cfg, + &item_impl.self_ty, + associated_constants, + ); + } + + /// Enters a `extern "C" { }` declaration and loads function declarations. + fn load_syn_foreign_mod( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemForeignMod, + ) { + if !item.abi.is_c() { + info!("Skip {} - (extern block must be extern C).", crate_name); + return; + } + + for foreign_item in &item.items { + if let syn::ForeignItem::Fn(ref function) = *foreign_item { + if !config + .parse + .should_generate_top_level_item(crate_name, binding_crate_name) + { + info!( + "Skip {}::{} - (fn's outside of the binding crate are not used).", + crate_name, &function.sig.ident + ); + return; + } + let path = Path::new(function.sig.ident.unraw().to_string()); + match Function::load(path, None, &function.sig, true, &function.attrs, mod_cfg) { + Ok(func) => { + info!("Take {}::{}.", crate_name, &function.sig.ident); + + self.functions.push(func); + } + Err(msg) => { + error!( + "Cannot use fn {}::{} ({}).", + crate_name, &function.sig.ident, msg + ); + } + } + } + } + } + + /// Loads a `fn` declaration inside an `impl` block, if the type is a simple identifier + fn load_syn_method( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + self_type: &Path, + item: &syn::ImplItemMethod, + ) { + self.load_fn_declaration( + config, + binding_crate_name, + crate_name, + mod_cfg, + item, + Some(self_type), + &item.sig, + &item.attrs, + ) + } + + /// Loads a `fn` declaration + fn load_syn_fn( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemFn, + ) { + self.load_fn_declaration( + config, + binding_crate_name, + crate_name, + mod_cfg, + item, + None, + &item.sig, + &item.attrs, + ); + } + + #[allow(clippy::too_many_arguments)] + fn load_fn_declaration( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + named_symbol: &dyn SynItemHelpers, + self_type: Option<&Path>, + sig: &syn::Signature, + attrs: &[syn::Attribute], + ) { + if !config + .parse + .should_generate_top_level_item(crate_name, binding_crate_name) + { + info!( + "Skip {}::{} - (fn's outside of the binding crate are not used).", + crate_name, &sig.ident + ); + return; + } + + let loggable_item_name = || { + let mut items = Vec::with_capacity(3); + items.push(crate_name.to_owned()); + if let Some(ref self_type) = self_type { + items.push(self_type.to_string()); + } + items.push(sig.ident.unraw().to_string()); + items.join("::") + }; + + let is_extern_c = sig.abi.is_omitted() || sig.abi.is_c(); + let exported_name = named_symbol.exported_name(); + + match (is_extern_c, exported_name) { + (true, Some(exported_name)) => { + let path = Path::new(exported_name); + match Function::load(path, self_type, sig, false, attrs, mod_cfg) { + Ok(func) => { + info!("Take {}.", loggable_item_name()); + self.functions.push(func); + } + Err(msg) => { + error!("Cannot use fn {} ({}).", loggable_item_name(), msg); + } + } + } + (true, None) => { + warn!( + "Skipping {} - (not `no_mangle`, and has no `export_name` attribute)", + loggable_item_name() + ); + } + (false, Some(_exported_name)) => { + warn!("Skipping {} - (not `extern \"C\"`", loggable_item_name()); + } + (false, None) => {} + } + } + + /// Loads associated `const` declarations + fn load_syn_assoc_consts<'a, I>( + &mut self, + crate_name: &str, + mod_cfg: Option<&Cfg>, + impl_ty: &syn::Type, + items: I, + ) where + I: IntoIterator<Item = &'a syn::ImplItemConst>, + { + let ty = match Type::load(impl_ty) { + Ok(ty) => ty, + Err(e) => { + warn!("Skipping associated constants for {:?}: {:?}", impl_ty, e); + return; + } + }; + + let ty = match ty { + Some(ty) => ty, + None => return, + }; + + let impl_path = match ty.get_root_path() { + Some(p) => p, + None => { + warn!( + "Couldn't find path for {:?}, skipping associated constants", + ty + ); + return; + } + }; + + for item in items.into_iter() { + if let syn::Visibility::Public(_) = item.vis { + } else { + warn!("Skip {}::{} - (not `pub`).", crate_name, &item.ident); + return; + } + + let path = Path::new(item.ident.unraw().to_string()); + match Constant::load( + path, + mod_cfg, + &item.ty, + &item.expr, + &item.attrs, + Some(impl_path.clone()), + ) { + Ok(constant) => { + info!("Take {}::{}::{}.", crate_name, impl_path, &item.ident); + let mut any = false; + self.structs.for_items_mut(&impl_path, |item| { + any = true; + item.add_associated_constant(constant.clone()); + }); + // Handle associated constants to other item types that are + // not structs like enums or such as regular constants. + if !any && !self.constants.try_insert(constant) { + error!( + "Conflicting name for constant {}::{}::{}.", + crate_name, impl_path, &item.ident, + ); + } + } + Err(msg) => { + warn!("Skip {}::{} - ({})", crate_name, &item.ident, msg); + } + } + } + } + + /// Loads a `const` declaration + fn load_syn_const( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemConst, + ) { + if !config + .parse + .should_generate_top_level_item(crate_name, binding_crate_name) + { + info!( + "Skip {}::{} - (const's outside of the binding crate are not used).", + crate_name, &item.ident + ); + return; + } + + if let syn::Visibility::Public(_) = item.vis { + } else { + warn!("Skip {}::{} - (not `pub`).", crate_name, &item.ident); + return; + } + + let path = Path::new(item.ident.unraw().to_string()); + match Constant::load(path, mod_cfg, &item.ty, &item.expr, &item.attrs, None) { + Ok(constant) => { + info!("Take {}::{}.", crate_name, &item.ident); + + let full_name = constant.path.clone(); + if !self.constants.try_insert(constant) { + error!("Conflicting name for constant {}", full_name); + } + } + Err(msg) => { + warn!("Skip {}::{} - ({})", crate_name, &item.ident, msg); + } + } + } + + /// Loads a `static` declaration + fn load_syn_static( + &mut self, + config: &Config, + binding_crate_name: &str, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemStatic, + ) { + if !config + .parse + .should_generate_top_level_item(crate_name, binding_crate_name) + { + info!( + "Skip {}::{} - (static's outside of the binding crate are not used).", + crate_name, &item.ident + ); + return; + } + + if let Some(exported_name) = item.exported_name() { + let path = Path::new(exported_name); + match Static::load(path, item, mod_cfg) { + Ok(constant) => { + info!("Take {}::{}.", crate_name, &item.ident); + self.globals.try_insert(constant); + } + Err(msg) => { + warn!("Skip {}::{} - ({})", crate_name, &item.ident, msg); + } + } + } else { + warn!("Skip {}::{} - (not `no_mangle`).", crate_name, &item.ident); + } + } + + /// Loads a `struct` declaration + fn load_syn_struct( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemStruct, + ) { + match Struct::load(&config.layout, item, mod_cfg) { + Ok(st) => { + info!("Take {}::{}.", crate_name, &item.ident); + self.structs.try_insert(st); + } + Err(msg) => { + info!("Take {}::{} - opaque ({}).", crate_name, &item.ident, msg); + let path = Path::new(item.ident.unraw().to_string()); + self.opaque_items.try_insert( + OpaqueItem::load(path, &item.generics, &item.attrs, mod_cfg).unwrap(), + ); + } + } + } + + /// Loads a `union` declaration + fn load_syn_union( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemUnion, + ) { + match Union::load(&config.layout, item, mod_cfg) { + Ok(st) => { + info!("Take {}::{}.", crate_name, &item.ident); + + self.unions.try_insert(st); + } + Err(msg) => { + info!("Take {}::{} - opaque ({}).", crate_name, &item.ident, msg); + let path = Path::new(item.ident.unraw().to_string()); + self.opaque_items.try_insert( + OpaqueItem::load(path, &item.generics, &item.attrs, mod_cfg).unwrap(), + ); + } + } + } + + /// Loads a `enum` declaration + fn load_syn_enum( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemEnum, + ) { + match Enum::load(item, mod_cfg, config) { + Ok(en) => { + info!("Take {}::{}.", crate_name, &item.ident); + self.enums.try_insert(en); + } + Err(msg) => { + info!("Take {}::{} - opaque ({}).", crate_name, &item.ident, msg); + let path = Path::new(item.ident.unraw().to_string()); + self.opaque_items.try_insert( + OpaqueItem::load(path, &item.generics, &item.attrs, mod_cfg).unwrap(), + ); + } + } + } + + /// Loads a `type` declaration + fn load_syn_ty(&mut self, crate_name: &str, mod_cfg: Option<&Cfg>, item: &syn::ItemType) { + match Typedef::load(item, mod_cfg) { + Ok(st) => { + info!("Take {}::{}.", crate_name, &item.ident); + + self.typedefs.try_insert(st); + } + Err(msg) => { + info!("Take {}::{} - opaque ({}).", crate_name, &item.ident, msg); + let path = Path::new(item.ident.unraw().to_string()); + self.opaque_items.try_insert( + OpaqueItem::load(path, &item.generics, &item.attrs, mod_cfg).unwrap(), + ); + } + } + } + + fn load_builtin_macro( + &mut self, + config: &Config, + crate_name: &str, + mod_cfg: Option<&Cfg>, + item: &syn::ItemMacro, + ) { + let name = match item.mac.path.segments.last() { + Some(n) => n.ident.unraw().to_string(), + None => return, + }; + + if name != "bitflags" || !config.macro_expansion.bitflags { + return; + } + + let bitflags = match bitflags::parse(item.mac.tokens.clone()) { + Ok(bf) => bf, + Err(e) => { + warn!("Failed to parse bitflags invocation: {:?}", e); + return; + } + }; + + let (struct_, impl_) = bitflags.expand(); + if let Some(struct_) = struct_ { + self.load_syn_struct(config, crate_name, mod_cfg, &struct_); + } + if let syn::Type::Path(ref path) = *impl_.self_ty { + if let Some(type_name) = path.path.get_ident() { + self.structs + .for_items_mut(&Path::new(type_name.unraw().to_string()), |item| { + item.annotations + .add_default("internal-derive-bitflags", AnnotationValue::Bool(true)); + }); + } + } + self.load_syn_assoc_consts_from_impl(crate_name, mod_cfg, &impl_) + } +} diff --git a/src/bindgen/rename.rs b/src/bindgen/rename.rs new file mode 100644 index 0000000..f594939 --- /dev/null +++ b/src/bindgen/rename.rs @@ -0,0 +1,140 @@ +/* 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/. */ + +use std::borrow::Cow; +use std::str::FromStr; + +/// The type of identifier to be renamed. +#[derive(Debug, Clone, Copy)] +pub enum IdentifierType<'a> { + StructMember, + EnumVariant { prefix: &'a str }, + FunctionArg, + Type, + Enum, +} + +impl<'a> IdentifierType<'a> { + fn to_str(self) -> &'static str { + match self { + IdentifierType::StructMember => "m", + IdentifierType::EnumVariant { .. } => "", + IdentifierType::FunctionArg => "a", + IdentifierType::Type => "", + IdentifierType::Enum => "", + } + } +} + +/// A rule to apply to an identifier when generating bindings. +#[derive(Debug, Clone, Copy, Default)] +pub enum RenameRule { + /// Do not apply any renaming. The default. + #[default] + None, + /// Converts the identifier to PascalCase and adds a context dependent prefix + GeckoCase, + /// Converts the identifier to lower case. + LowerCase, + /// Converts the identifier to upper case. + UpperCase, + /// Converts the identifier to PascalCase. + PascalCase, + /// Converts the identifier to camelCase. + CamelCase, + /// Converts the identifier to snake_case. + SnakeCase, + /// Converts the identifier to SCREAMING_SNAKE_CASE. + ScreamingSnakeCase, + /// Converts the identifier to SCREAMING_SNAKE_CASE and prefixes enum variants + /// with the enum name. + QualifiedScreamingSnakeCase, +} + +impl RenameRule { + pub(crate) fn not_none(self) -> Option<Self> { + match self { + RenameRule::None => None, + other => Some(other), + } + } + + /// Applies the rename rule to a string + pub fn apply<'a>(self, text: &'a str, context: IdentifierType) -> Cow<'a, str> { + use heck::*; + + if text.is_empty() { + return Cow::Borrowed(text); + } + + Cow::Owned(match self { + RenameRule::None => return Cow::Borrowed(text), + RenameRule::GeckoCase => context.to_str().to_owned() + &text.to_upper_camel_case(), + RenameRule::LowerCase => text.to_lowercase(), + RenameRule::UpperCase => text.to_uppercase(), + RenameRule::PascalCase => text.to_pascal_case(), + RenameRule::CamelCase => text.to_lower_camel_case(), + RenameRule::SnakeCase => text.to_snake_case(), + RenameRule::ScreamingSnakeCase => text.to_shouty_snake_case(), + RenameRule::QualifiedScreamingSnakeCase => { + let mut result = String::new(); + + if let IdentifierType::EnumVariant { prefix } = context { + result.push_str( + &RenameRule::ScreamingSnakeCase.apply(prefix, IdentifierType::Enum), + ); + result.push('_'); + } + + result.push_str(&RenameRule::ScreamingSnakeCase.apply(text, context)); + result + } + }) + } +} + +impl FromStr for RenameRule { + type Err = String; + + fn from_str(s: &str) -> Result<RenameRule, Self::Err> { + match s { + "none" => Ok(RenameRule::None), + "None" => Ok(RenameRule::None), + + "mGeckoCase" => Ok(RenameRule::GeckoCase), + "GeckoCase" => Ok(RenameRule::GeckoCase), + "gecko_case" => Ok(RenameRule::GeckoCase), + + "lowercase" => Ok(RenameRule::LowerCase), + "LowerCase" => Ok(RenameRule::LowerCase), + "lower_case" => Ok(RenameRule::LowerCase), + + "UPPERCASE" => Ok(RenameRule::UpperCase), + "UpperCase" => Ok(RenameRule::UpperCase), + "upper_case" => Ok(RenameRule::UpperCase), + + "PascalCase" => Ok(RenameRule::PascalCase), + "pascal_case" => Ok(RenameRule::PascalCase), + + "camelCase" => Ok(RenameRule::CamelCase), + "CamelCase" => Ok(RenameRule::CamelCase), + "camel_case" => Ok(RenameRule::CamelCase), + + "snake_case" => Ok(RenameRule::SnakeCase), + "SnakeCase" => Ok(RenameRule::SnakeCase), + + "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase), + "ScreamingSnakeCase" => Ok(RenameRule::ScreamingSnakeCase), + "screaming_snake_case" => Ok(RenameRule::ScreamingSnakeCase), + + "QUALIFIED_SCREAMING_SNAKE_CASE" => Ok(RenameRule::QualifiedScreamingSnakeCase), + "QualifiedScreamingSnakeCase" => Ok(RenameRule::QualifiedScreamingSnakeCase), + "qualified_screaming_snake_case" => Ok(RenameRule::QualifiedScreamingSnakeCase), + + _ => Err(format!("Unrecognized RenameRule: '{}'.", s)), + } + } +} + +deserialize_enum_str!(RenameRule); diff --git a/src/bindgen/reserved.rs b/src/bindgen/reserved.rs new file mode 100644 index 0000000..4983f99 --- /dev/null +++ b/src/bindgen/reserved.rs @@ -0,0 +1,91 @@ +/* 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/. */ + +/// Taken from `https://en.cppreference.com/w/cpp/keyword` +/// Some experimental keywords were filtered out and the resulting list was +/// sorted using a rust program. +const RESERVED_KEYWORDS: &[&str] = &[ + "alignas", + "alignof", + "auto", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "char8_t", + "class", + "const", + "const_cast", + "consteval", + "constexpr", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "noexcept", + "nullptr", + "operator", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "return", + "short", + "signed", + "sizeof", + "static", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", +]; + +pub fn escape(rust_identifier: &mut String) { + if RESERVED_KEYWORDS + .binary_search(&rust_identifier.as_ref()) + .is_ok() + { + rust_identifier.push('_'); + } +} 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() +} diff --git a/src/bindgen/writer.rs b/src/bindgen/writer.rs new file mode 100644 index 0000000..eed6917 --- /dev/null +++ b/src/bindgen/writer.rs @@ -0,0 +1,258 @@ +/* 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/. */ + +use std::cmp; +use std::io; +use std::io::Write; + +use crate::bindgen::config::{Braces, Config, Language}; +use crate::bindgen::Bindings; + +/// A type of way to format a list. +pub enum ListType<'a> { + /// Join each adjacent item with a str. + Join(&'a str), + /// End each item with a str. + Cap(&'a str), +} + +/// A utility wrapper to write unbuffered data and correctly adjust positions. +struct InnerWriter<'a, 'b: 'a, F: 'a + Write>(&'a mut SourceWriter<'b, F>); + +impl<'a, 'b, F: Write> Write for InnerWriter<'a, 'b, F> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let writer = &mut self.0; + + if !writer.line_started { + for _ in 0..writer.spaces() { + write!(writer.out, " ").unwrap(); + } + writer.line_started = true; + writer.line_length += writer.spaces(); + } + + let written = writer.out.write(buf)?; + writer.line_length += written; + writer.max_line_length = cmp::max(writer.max_line_length, writer.line_length); + Ok(written) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.out.flush() + } +} + +/// A utility writer for generating code easier. +pub struct SourceWriter<'a, F: Write> { + out: F, + bindings: &'a Bindings, + spaces: Vec<usize>, + line_started: bool, + line_length: usize, + line_number: usize, + max_line_length: usize, +} + +pub type MeasureWriter<'a> = SourceWriter<'a, &'a mut Vec<u8>>; + +impl<'a, F: Write> SourceWriter<'a, F> { + pub fn new(out: F, bindings: &'a Bindings) -> Self { + SourceWriter { + out, + bindings, + spaces: vec![0], + line_started: false, + line_length: 0, + line_number: 1, + max_line_length: 0, + } + } + + pub fn bindings(&self) -> &Bindings { + self.bindings + } + + /// Takes a function that writes source and returns the maximum line length + /// written. + pub fn try_write<T>(&mut self, func: T, max_line_length: usize) -> bool + where + T: Fn(&mut MeasureWriter), + { + if self.line_length > max_line_length { + return false; + } + + let mut buffer = Vec::new(); + let line_length = { + let mut measurer = SourceWriter { + out: &mut buffer, + bindings: self.bindings, + spaces: self.spaces.clone(), + line_started: self.line_started, + line_length: self.line_length, + line_number: self.line_number, + max_line_length: self.line_length, + }; + + func(&mut measurer); + + measurer.max_line_length + }; + + if line_length > max_line_length { + return false; + } + // We don't want the extra alignment, it's already accounted for by the + // measurer. + self.line_started = true; + InnerWriter(self).write_all(&buffer).unwrap(); + true + } + + fn spaces(&self) -> usize { + *self.spaces.last().unwrap() + } + + pub fn push_set_spaces(&mut self, spaces: usize) { + self.spaces.push(spaces); + } + + pub fn pop_set_spaces(&mut self) { + self.pop_tab() + } + + pub fn line_length_for_align(&self) -> usize { + if self.line_started { + self.line_length + } else { + self.line_length + self.spaces() + } + } + + pub fn push_tab(&mut self) { + let spaces = self.spaces() - (self.spaces() % self.bindings.config.tab_width) + + self.bindings.config.tab_width; + self.spaces.push(spaces); + } + + pub fn pop_tab(&mut self) { + assert!(!self.spaces.is_empty()); + self.spaces.pop(); + } + + pub fn new_line(&mut self) { + self.out + .write_all(self.bindings.config.line_endings.as_str().as_bytes()) + .unwrap(); + self.line_started = false; + self.line_length = 0; + self.line_number += 1; + } + + pub fn new_line_if_not_start(&mut self) { + if self.line_number != 1 { + self.new_line(); + } + } + + pub fn open_brace(&mut self) { + match self.bindings.config.language { + Language::Cxx | Language::C => match self.bindings.config.braces { + Braces::SameLine => { + self.write(" {"); + self.push_tab(); + self.new_line(); + } + Braces::NextLine => { + self.new_line(); + self.write("{"); + self.push_tab(); + self.new_line(); + } + }, + Language::Cython => { + self.write(":"); + self.new_line(); + self.push_tab(); + } + } + } + + pub fn close_brace(&mut self, semicolon: bool) { + self.pop_tab(); + match self.bindings.config.language { + Language::Cxx | Language::C => { + self.new_line(); + if semicolon { + self.write("};"); + } else { + self.write("}"); + } + } + Language::Cython => {} + } + } + + pub fn write(&mut self, text: &'static str) { + write!(self, "{}", text); + } + + pub fn write_raw_block(&mut self, block: &str) { + self.line_started = true; + write!(self, "{}", block); + } + + pub fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) { + InnerWriter(self).write_fmt(fmt).unwrap(); + } + + pub fn write_horizontal_source_list<S: Source>( + &mut self, + items: &[S], + list_type: ListType<'_>, + ) { + for (i, item) in items.iter().enumerate() { + item.write(&self.bindings.config, self); + + match list_type { + ListType::Join(text) => { + if i != items.len() - 1 { + write!(self, "{}", text); + } + } + ListType::Cap(text) => { + write!(self, "{}", text); + } + } + } + } + + pub fn write_vertical_source_list<S: Source>(&mut self, items: &[S], list_type: ListType<'_>) { + let align_length = self.line_length_for_align(); + self.push_set_spaces(align_length); + for (i, item) in items.iter().enumerate() { + item.write(&self.bindings.config, self); + + match list_type { + ListType::Join(text) => { + if i != items.len() - 1 { + write!(self, "{}", text); + } + } + ListType::Cap(text) => { + write!(self, "{}", text); + } + } + + if i != items.len() - 1 { + self.new_line(); + } + } + self.pop_tab(); + } +} + +pub trait Source { + fn write<F: Write>(&self, config: &Config, _: &mut SourceWriter<F>); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f98f4fc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,41 @@ +/* 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/. */ + +#[macro_use] +extern crate log; +extern crate proc_macro2; +#[macro_use] +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate quote; +#[macro_use] +extern crate syn; +extern crate toml; + +mod bindgen; + +pub use crate::bindgen::*; + +use std::path::Path; + +/// A utility function for build scripts to generate bindings for a crate, using +/// a `cbindgen.toml` if it exists. +pub fn generate<P: AsRef<Path>>(crate_dir: P) -> Result<Bindings, Error> { + let config = Config::from_root_or_default(crate_dir.as_ref()); + + generate_with_config(crate_dir, config) +} + +/// A utility function for build scripts to generate bindings for a crate with a +/// custom config. +pub fn generate_with_config<P: AsRef<Path>>( + crate_dir: P, + config: Config, +) -> Result<Bindings, Error> { + Builder::new() + .with_config(config) + .with_crate(crate_dir) + .generate() +} diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..0b49ce2 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,105 @@ +/* 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/. */ + +use std::io; +use std::io::Write; + +use log::*; + +pub struct TraceLogger; +pub struct WarnLogger; +pub struct InfoLogger; +pub struct ErrorLogger; + +impl TraceLogger { + pub fn init() -> Result<(), SetLoggerError> { + log::set_logger(&TraceLogger)?; + log::set_max_level(LevelFilter::Trace); + Ok(()) + } +} +impl log::Log for TraceLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Trace + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + eprintln!("{}: {}", record.level(), record.args()); + } + } + + fn flush(&self) { + io::stderr().flush().unwrap(); + } +} + +impl WarnLogger { + pub fn init() -> Result<(), SetLoggerError> { + log::set_logger(&WarnLogger)?; + log::set_max_level(LevelFilter::Warn); + Ok(()) + } +} +impl log::Log for WarnLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Warn + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + eprintln!("{}: {}", record.level(), record.args()); + } + } + + fn flush(&self) { + io::stderr().flush().unwrap(); + } +} + +impl ErrorLogger { + pub fn init() -> Result<(), SetLoggerError> { + log::set_logger(&ErrorLogger)?; + log::set_max_level(LevelFilter::Error); + Ok(()) + } +} +impl log::Log for ErrorLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Error + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + eprintln!("{}: {}", record.level(), record.args()); + } + } + + fn flush(&self) { + io::stderr().flush().unwrap(); + } +} + +impl InfoLogger { + pub fn init() -> Result<(), SetLoggerError> { + log::set_logger(&InfoLogger)?; + log::set_max_level(LevelFilter::Info); + Ok(()) + } +} +impl log::Log for InfoLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + eprintln!("{}: {}", record.level(), record.args()); + } + } + + fn flush(&self) { + io::stderr().flush().unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb78a08 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,335 @@ +/* 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/. */ + +use std::env; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +extern crate clap; +#[macro_use] +extern crate log; +extern crate proc_macro2; +#[macro_use] +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate quote; +#[macro_use] +extern crate syn; +extern crate toml; + +use clap::{Arg, ArgMatches, Command}; + +mod bindgen; +mod logging; + +use crate::bindgen::{Bindings, Builder, Cargo, Config, Error, Profile, Style}; + +fn apply_config_overrides(config: &mut Config, matches: &ArgMatches) { + // We allow specifying a language to override the config default. This is + // used by compile-tests. + if let Some(lang) = matches.value_of("lang") { + config.language = match lang.parse() { + Ok(lang) => lang, + Err(reason) => { + error!("{}", reason); + return; + } + } + } + + if matches.is_present("cpp-compat") { + config.cpp_compat = true; + } + + if matches.is_present("only-target-dependencies") { + config.only_target_dependencies = true; + } + + if let Some(style) = matches.value_of("style") { + config.style = match style { + "Both" => Style::Both, + "both" => Style::Both, + "Tag" => Style::Tag, + "tag" => Style::Tag, + "Type" => Style::Type, + "type" => Style::Type, + _ => { + error!("Unknown style specified."); + return; + } + } + } + + if let Some(profile) = matches.value_of("profile") { + config.parse.expand.profile = match Profile::from_str(profile) { + Ok(p) => p, + Err(e) => { + error!("{}", e); + return; + } + } + } + + if matches.is_present("d") { + config.parse.parse_deps = true; + } +} + +fn load_bindings(input: &Path, matches: &ArgMatches) -> Result<Bindings, Error> { + // If a file is specified then we load it as a single source + if !input.is_dir() { + // Load any config specified or search in the input directory + let mut config = match matches.value_of("config") { + Some(c) => Config::from_file(c).unwrap(), + None => Config::from_root_or_default( + input + .parent() + .expect("All files should have a parent directory"), + ), + }; + + apply_config_overrides(&mut config, matches); + + return Builder::new() + .with_config(config) + .with_src(input) + .generate(); + } + + // We have to load a whole crate, so we use cargo to gather metadata + let lib = Cargo::load( + input, + matches.value_of("lockfile"), + matches.value_of("crate"), + true, + matches.is_present("clean"), + matches.is_present("only-target-dependencies"), + matches.value_of("metadata").map(Path::new), + )?; + + // Load any config specified or search in the binding crate directory + let mut config = match matches.value_of("config") { + Some(c) => Config::from_file(c).unwrap(), + None => { + let binding_crate_dir = lib.find_crate_dir(&lib.binding_crate_ref()); + + if let Some(binding_crate_dir) = binding_crate_dir { + Config::from_root_or_default(binding_crate_dir) + } else { + // This shouldn't happen + Config::from_root_or_default(input) + } + } + }; + + apply_config_overrides(&mut config, matches); + + Builder::new() + .with_config(config) + .with_cargo(lib) + .generate() +} + +fn main() { + let matches = Command::new("cbindgen") + .version(bindgen::VERSION) + .about("Generate C bindings for a Rust library") + .arg( + Arg::new("v") + .short('v') + .multiple_occurrences(true) + .help("Enable verbose logging"), + ) + .arg( + Arg::new("verify") + .long("verify") + .help("Generate bindings and compare it to the existing bindings file and error if they are different"), + ) + .arg( + Arg::new("config") + .short('c') + .long("config") + .value_name("PATH") + .help("Specify path to a `cbindgen.toml` config to use"), + ) + .arg( + Arg::new("lang") + .short('l') + .long("lang") + .value_name("LANGUAGE") + .help("Specify the language to output bindings in") + .possible_values(["c++", "C++", "c", "C", "cython", "Cython"]), + ) + .arg( + Arg::new("cpp-compat") + .long("cpp-compat") + .help("Whether to add C++ compatibility to generated C bindings") + ) + .arg( + Arg::new("only-target-dependencies") + .long("only-target-dependencies") + .help("Only fetch dependencies needed by the target platform. \ + The target platform defaults to the host platform; set TARGET to override.") + ) + .arg( + Arg::new("style") + .short('s') + .long("style") + .value_name("STYLE") + .help("Specify the declaration style to use for bindings") + .possible_values(["Both", "both", "Tag", "tag", "Type", "type"]), + ) + .arg( + Arg::new("d") + .short('d') + .long("parse-dependencies") + .help("Whether to parse dependencies when generating bindings"), + ) + .arg( + Arg::new("clean") + .long("clean") + .help( + "Whether to use a new temporary directory for expanding macros. \ + Affects performance, but might be required in certain build processes.") + .required(false) + ) + .arg( + Arg::new("INPUT") + .help( + "A crate directory or source file to generate bindings for. \ + In general this is the folder where the Cargo.toml file of \ + source Rust library resides.") + .required(false) + .index(1), + ) + .arg( + Arg::new("crate") + .long("crate") + .value_name("CRATE_NAME") + .help( + "If generating bindings for a crate, \ + the specific crate to generate bindings for", + ) + .required(false), + ) + .arg( + Arg::new("out") + .short('o') + .long("output") + .value_name("PATH") + .help("The file to output the bindings to") + .required(false), + ) + .arg( + Arg::new("lockfile") + .long("lockfile") + .value_name("PATH") + .help( + "Specify the path to the Cargo.lock file explicitly. If this \ + is not specified, the Cargo.lock file is searched for in the \ + same folder as the Cargo.toml file. This option is useful for \ + projects that use workspaces.") + .required(false), + ) + .arg( + Arg::new("metadata") + .long("metadata") + .value_name("PATH") + .help( + "Specify the path to the output of a `cargo metadata` \ + command that allows to get dependency information. \ + This is useful because cargo metadata may be the longest \ + part of cbindgen runtime, and you may want to share it \ + across cbindgen invocations. By default cbindgen will run \ + `cargo metadata --all-features --format-version 1 \ + --manifest-path <path/to/crate/Cargo.toml>" + ) + .required(false), + ) + .arg( + Arg::new("profile") + .long("profile") + .value_name("PROFILE") + .help( + "Specify the profile to use when expanding macros. \ + Has no effect otherwise." + ) + .possible_values(["Debug", "debug", "Release", "release"]), + ) + .arg( + Arg::new("quiet") + .short('q') + .long("quiet") + .help("Report errors only (overrides verbosity options).") + .required(false), + ) + .arg( + Arg::new("depfile") + .value_name("PATH") + .long("depfile") + .takes_value(true) + .min_values(1) + .max_values(1) + .required(false) + .help("Generate a depfile at the given Path listing the source files \ + cbindgen traversed when generating the bindings. Useful when \ + integrating cbindgen into 3rd party build-systems. \ + This option is ignored if `--out` is missing." + ) + ) + .get_matches(); + + if !matches.is_present("out") && matches.is_present("verify") { + error!( + "Cannot verify bindings against `stdout`, please specify a file to compare against." + ); + std::process::exit(2); + } + + // Initialize logging + if matches.is_present("quiet") { + logging::ErrorLogger::init().unwrap(); + } else { + match matches.occurrences_of("v") { + 0 => logging::WarnLogger::init().unwrap(), + 1 => logging::InfoLogger::init().unwrap(), + _ => logging::TraceLogger::init().unwrap(), + } + } + + // Find the input directory + let input = match matches.value_of("INPUT") { + Some(input) => PathBuf::from(input), + None => env::current_dir().unwrap(), + }; + + let bindings = match load_bindings(&input, &matches) { + Ok(bindings) => bindings, + Err(msg) => { + error!("{}", msg); + error!("Couldn't generate bindings for {}.", input.display()); + std::process::exit(1); + } + }; + + // Write the bindings file + match matches.value_of("out") { + Some(file) => { + let changed = bindings.write_to_file(file); + + if matches.is_present("verify") && changed { + error!("Bindings changed: {}", file); + std::process::exit(2); + } + if let Some(depfile) = matches.value_of("depfile") { + bindings.generate_depfile(file, depfile) + } + } + _ => { + bindings.write(io::stdout()); + } + } +} |