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/bindgen/ir | |
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/bindgen/ir')
-rw-r--r-- | src/bindgen/ir/annotation.rs | 217 | ||||
-rw-r--r-- | src/bindgen/ir/cfg.rs | 365 | ||||
-rw-r--r-- | src/bindgen/ir/constant.rs | 813 | ||||
-rw-r--r-- | src/bindgen/ir/documentation.rs | 111 | ||||
-rw-r--r-- | src/bindgen/ir/enumeration.rs | 1556 | ||||
-rw-r--r-- | src/bindgen/ir/field.rs | 80 | ||||
-rw-r--r-- | src/bindgen/ir/function.rs | 390 | ||||
-rw-r--r-- | src/bindgen/ir/generic_path.rs | 303 | ||||
-rw-r--r-- | src/bindgen/ir/global.rs | 121 | ||||
-rw-r--r-- | src/bindgen/ir/item.rs | 250 | ||||
-rw-r--r-- | src/bindgen/ir/mod.rs | 39 | ||||
-rw-r--r-- | src/bindgen/ir/opaque.rs | 176 | ||||
-rw-r--r-- | src/bindgen/ir/path.rs | 50 | ||||
-rw-r--r-- | src/bindgen/ir/repr.rs | 182 | ||||
-rw-r--r-- | src/bindgen/ir/structure.rs | 717 | ||||
-rw-r--r-- | src/bindgen/ir/ty.rs | 1017 | ||||
-rw-r--r-- | src/bindgen/ir/typedef.rs | 208 | ||||
-rw-r--r-- | src/bindgen/ir/union.rs | 336 |
18 files changed, 6931 insertions, 0 deletions
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); + } +} |