use std::io::Write;
use syn;
use bindgen::config::{Config, Language};
use bindgen::declarationtyperesolver::DeclarationTypeResolver;
use bindgen::dependencies::Dependencies;
use bindgen::ir::{
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, GenericPath, Item,
ItemContainer, Path, Repr, ReprStyle, ReprType, Struct, ToCondition, Type,
};
use bindgen::library::Library;
use bindgen::mangle;
use bindgen::monomorph::Monomorphs;
use bindgen::rename::{IdentifierType, RenameRule};
use bindgen::utilities::find_first_some;
use bindgen::writer::{ListType, Source, SourceWriter};
#[derive(Debug, Clone)]
pub struct EnumVariant {
pub name: String,
pub export_name: String,
pub discriminant: Option<i64>,
pub body: Option<(String, Struct)>,
pub documentation: Documentation,
}
fn value_from_expr(val: &syn::Expr) -> Option<i64> {
match *val {
syn::Expr::Lit(ref lit) => match lit.lit {
syn::Lit::Int(ref lit) => lit.base10_parse::<i64>().ok(),
_ => None,
},
syn::Expr::Unary(ref unary) => {
let v = value_from_expr(&unary.expr)?;
match unary.op {
syn::UnOp::Deref(..) => None,
syn::UnOp::Neg(..) => v.checked_mul(-1),
syn::UnOp::Not(..) => v.checked_neg(),
}
}
_ => None,
}
}
impl EnumVariant {
pub fn load(
is_tagged: bool,
variant: &syn::Variant,
generic_params: GenericParams,
mod_cfg: Option<&Cfg>,
) -> Result<Self, String> {
let discriminant = match variant.discriminant {
Some((_, ref expr)) => match value_from_expr(expr) {
Some(v) => Some(v),
None => return Err(format!("Unsupported discriminant {:?}.", expr)),
},
None => None,
};
fn parse_fields(
is_tagged: bool,
fields: &syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
) -> Result<Vec<(String, Type, Documentation)>, String> {
let mut res = Vec::new();
if is_tagged {
res.push((
"tag".to_string(),
Type::Path(GenericPath::new(Path::new("Tag"), vec![])),
Documentation::none(),
));
}
for (i, field) in fields.iter().enumerate() {
if let Some(ty) = Type::load(&field.ty)? {
res.push((
match field.ident {
Some(ref ident) => ident.to_string(),
None => i.to_string(),
},
ty,
Documentation::load(&field.attrs),
));
}
}
Ok(res)
}
let body = match variant.fields {
syn::Fields::Unit => None,
syn::Fields::Named(ref fields) => {
let path = Path::new(format!("{}_Body", variant.ident));
Some(Struct::new(
path,
generic_params,
parse_fields(is_tagged, &fields.named)?,
is_tagged,
true,
false,
false,
Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
AnnotationSet::load(&variant.attrs)?,
Documentation::none(),
))
}
syn::Fields::Unnamed(ref fields) => {
let path = Path::new(format!("{}_Body", variant.ident));
Some(Struct::new(
path,
generic_params,
parse_fields(is_tagged, &fields.unnamed)?,
is_tagged,
true,
false,
true,
Cfg::append(mod_cfg, Cfg::load(&variant.attrs)),
AnnotationSet::load(&variant.attrs)?,
Documentation::none(),
))
}
};
Ok(EnumVariant::new(
variant.ident.to_string(),
discriminant,
body.map(|body| {
(
RenameRule::SnakeCase.apply_to_pascal_case(
&format!("{}", variant.ident),
IdentifierType::StructMember,
),
body,
)
}),
Documentation::load(&variant.attrs),
))
}
pub fn new(
name: String,
discriminant: Option<i64>,
body: Option<(String, Struct)>,
documentation: Documentation,
) -> Self {
let export_name = name.clone();
Self {
name,
export_name,
discriminant,
body,
documentation,
}
}
fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
if let Some((_, ref item)) = self.body {
item.add_dependencies(library, out);
}
}
fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
if let Some((_, ref mut ty)) = self.body {
ty.resolve_declaration_types(resolver);
}
}
fn specialize(&self, generic_values: &[Type], mappings: &[(&Path, &Type)]) -> Self {
Self::new(
mangle::mangle_name(&self.name, generic_values),
self.discriminant,
self.body
.as_ref()
.map(|&(ref name, ref ty)| (name.clone(), ty.specialize(generic_values, mappings))),
self.documentation.clone(),
)
}
fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
if let Some((_, ref ty)) = self.body {
ty.add_monomorphs(library, out);
}
}
fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
if let Some((_, ref mut ty)) = self.body {
ty.mangle_paths(monomorphs);
}
}
}
impl Source for EnumVariant {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
self.documentation.write(config, out);
write!(out, "{}", self.export_name);
if let Some(discriminant) = self.discriminant {
write!(out, " = {}", discriminant);
}
out.write(",");
}
}
#[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 {
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| {
variant
.body
.as_ref()
.map_or(true, |&(_, 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>) -> Result<Enum, String> {
let repr = Repr::load(&item.attrs)?;
if repr == Repr::RUST {
return Err("Enum not marked with a valid repr(prim) or repr(C).".to_owned());
}
let generic_params = GenericParams::new(&item.generics);
let mut variants = Vec::new();
let mut is_tagged = false;
for variant in item.variants.iter() {
let variant = EnumVariant::load(
repr.style == ReprStyle::Rust,
variant,
generic_params.clone(),
mod_cfg,
)?;
is_tagged = is_tagged || variant.body.is_some();
variants.push(variant);
}
let annotations = AnnotationSet::load(&item.attrs)?;
if let Some(names) = annotations.list("enum-trailing-values") {
for name in names {
variants.push(EnumVariant::new(name, None, None, Documentation::none()));
}
}
let path = Path::new(item.ident.to_string());
let tag = if is_tagged {
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),
))
}
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() && self.repr.style == ReprStyle::C {
resolver.add_struct(&self.path);
} else if self.tag.is_some() && self.repr.style != ReprStyle::C {
resolver.add_union(&self.path);
} else if self.repr.style == ReprStyle::C {
resolver.add_enum(&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::C && self.tag.is_some() {
let new_tag = format!("{}_Tag", self.export_name);
if self.repr.style == ReprStyle::Rust {
for variant in &mut self.variants {
if let Some((_, ref mut body)) = variant.body {
let path = Path::new(new_tag.clone());
let generic_path = GenericPath::new(path, vec![]);
body.fields[0].1 = Type::Path(generic_path);
}
}
}
self.tag = Some(new_tag);
}
for variant in &mut self.variants {
if let Some((_, ref mut body)) = variant.body {
body.rename_for_config(config);
}
}
if config.enumeration.prefix_with_name
|| self.annotations.bool("prefix-with-name").unwrap_or(false)
{
for variant in &mut self.variants {
variant.export_name = format!("{}_{}", self.export_name, variant.export_name);
if let Some((_, ref mut body)) = variant.body {
body.export_name = format!("{}_{}", self.export_name, body.export_name());
}
}
}
let rules = [
self.annotations.parse_atom::<RenameRule>("rename-all"),
config.enumeration.rename_variants,
];
if let Some(r) = find_first_some(&rules) {
self.variants = self
.variants
.iter()
.map(|variant| {
EnumVariant::new(
r.apply_to_pascal_case(
&variant.export_name,
IdentifierType::EnumVariant(self),
),
variant.discriminant,
variant.body.as_ref().map(|body| {
(
r.apply_to_snake_case(&body.0, IdentifierType::StructMember),
body.1.clone(),
)
}),
variant.documentation.clone(),
)
})
.collect();
}
}
fn instantiate_monomorph(
&self,
generic_values: &[Type],
library: &Library,
out: &mut Monomorphs,
) {
assert!(
self.generic_params.len() > 0,
"{} is not generic",
self.path.name()
);
assert!(
self.generic_params.len() == generic_values.len(),
"{} has {} params but is being instantiated with {} values",
self.path.name(),
self.generic_params.len(),
generic_values.len(),
);
let mappings = self
.generic_params
.iter()
.zip(generic_values.iter())
.collect::<Vec<_>>();
for variant in &self.variants {
if let Some((_, ref body)) = variant.body {
body.instantiate_monomorph(generic_values, library, out);
}
}
let mangled_path = mangle::mangle_path(&self.path, generic_values);
let monomorph = Enum::new(
mangled_path,
GenericParams::default(),
self.repr,
self.variants
.iter()
.map(|v| v.specialize(generic_values, &mappings))
.collect(),
self.tag.clone(),
self.cfg.clone(),
self.annotations.clone(),
self.documentation.clone(),
);
monomorph.add_monomorphs(library, out);
out.insert_enum(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| match ty {
ReprType::USize => "uintptr_t",
ReprType::U32 => "uint32_t",
ReprType::U16 => "uint16_t",
ReprType::U8 => "uint8_t",
ReprType::ISize => "intptr_t",
ReprType::I32 => "int32_t",
ReprType::I16 => "int16_t",
ReprType::I8 => "int8_t",
});
let condition = (&self.cfg).to_condition(config);
condition.write_before(config, out);
self.documentation.write(config, out);
let is_tagged = self.tag.is_some();
let separate_tag = self.repr.style == ReprStyle::C;
self.generic_params.write(config, out);
if is_tagged && config.language == Language::Cxx {
out.write(if separate_tag { "struct" } else { "union" });
if self.annotations.must_use {
if let Some(ref anno) = config.structure.must_use {
write!(out, " {}", anno)
}
}
write!(out, " {}", self.export_name());
out.open_brace();
}
let enum_name = if let Some(ref tag) = self.tag {
tag
} else {
self.export_name()
};
if config.language == Language::C {
if size.is_none() && config.style.generate_typedef() {
out.write("typedef ");
}
out.write("enum");
if size.is_some() || config.style.generate_tag() {
write!(out, " {}", enum_name);
}
if config.cpp_compat {
if let Some(prim) = size {
out.new_line();
out.write("#ifdef __cplusplus");
out.new_line();
write!(out, " : {}", prim);
out.new_line();
out.write("#endif // __cplusplus");
out.new_line();
}
}
} else {
out.write("enum class");
if self.annotations.must_use {
if let Some(ref anno) = config.enumeration.must_use {
write!(out, " {}", anno)
}
}
write!(out, " {}", enum_name);
if let Some(prim) = size {
write!(out, " : {}", prim);
}
}
out.open_brace();
for (i, variant) in self.variants.iter().enumerate() {
if i != 0 {
out.new_line()
}
variant.write(config, out);
}
if config.enumeration.add_sentinel(&self.annotations) {
out.new_line();
out.new_line();
out.write("Sentinel /* this must be last for serialization purposes. */");
}
if config.language == Language::C && size.is_none() && config.style.generate_typedef() {
out.close_brace(false);
write!(out, " {};", enum_name);
} else {
out.close_brace(true);
}
if config.language == Language::C {
if let Some(prim) = size {
if config.cpp_compat {
out.new_line_if_not_start();
out.write("#ifndef __cplusplus");
}
out.new_line();
write!(out, "typedef {} {};", prim, enum_name);
if config.cpp_compat {
out.new_line_if_not_start();
out.write("#endif // __cplusplus");
}
}
}
if is_tagged {
for variant in &self.variants {
if let Some((_, ref body)) = variant.body {
out.new_line();
out.new_line();
body.write(config, out);
}
}
out.new_line();
out.new_line();
if config.language == Language::C {
if config.style.generate_typedef() {
out.write("typedef ");
}
out.write(if separate_tag { "struct" } else { "union" });
if config.style.generate_tag() {
write!(out, " {}", self.export_name());
}
out.open_brace();
}
let wrap_tag = config.language == Language::Cxx && !separate_tag;
if wrap_tag {
out.write("struct");
out.open_brace();
}
if config.language == Language::C && !config.style.generate_typedef() {
out.write("enum ");
}
write!(out, "{} tag;", enum_name);
if wrap_tag {
out.close_brace(true);
}
out.new_line();
if separate_tag {
out.write("union");
out.open_brace();
}
for (i, &(ref field_name, ref body)) in self
.variants
.iter()
.filter_map(|variant| variant.body.as_ref())
.enumerate()
{
if i != 0 {
out.new_line();
}
if config.style.generate_typedef() {
write!(out, "{} {};", body.export_name(), field_name);
} else {
write!(out, "struct {} {};", body.export_name(), field_name);
}
}
if separate_tag {
out.close_brace(true);
}
let skip_fields = if separate_tag { 0 } else { 1 };
let derive_helper_methods = config.enumeration.derive_helper_methods(&self.annotations);
if config.language == Language::Cxx && derive_helper_methods {
for variant in &self.variants {
out.new_line();
out.new_line();
let arg_renamer = |name: &str| {
config
.function
.rename_args
.as_ref()
.unwrap_or(&RenameRule::GeckoCase)
.apply_to_snake_case(name, IdentifierType::FunctionArg)
};
write!(out, "static {} {}(", self.export_name, variant.export_name);
if let Some((_, ref body)) = variant.body {
let vec: Vec<_> = body
.fields
.iter()
.skip(skip_fields)
.map(|&(ref name, ref ty, _)| {
(arg_renamer(name), Type::Ref(Box::new(ty.clone())))
})
.collect();
out.write_vertical_source_list(&vec[..], ListType::Join(","));
}
write!(out, ")");
out.open_brace();
write!(out, "{} result;", self.export_name);
if let Some((ref variant_name, ref body)) = variant.body {
for &(ref field_name, ref ty, ..) in body.fields.iter().skip(skip_fields) {
out.new_line();
match ty {
Type::Array(ref ty, ref length) => {
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 = {}::{};", enum_name, variant.export_name);
out.new_line();
write!(out, "return result;");
out.close_brace(false);
}
for variant in &self.variants {
out.new_line();
out.new_line();
write!(out, "bool Is{}() const", variant.export_name);
out.open_brace();
write!(out, "return tag == {}::{};", enum_name, variant.export_name);
out.close_brace(false);
}
}
let derive_const_casts = config.enumeration.derive_const_casts(&self.annotations);
let derive_mut_casts = config.enumeration.derive_mut_casts(&self.annotations);
if config.language == Language::Cxx
&& derive_helper_methods
&& (derive_const_casts || derive_mut_casts)
{
let assert_name = match config.enumeration.cast_assert_name {
Some(ref n) => &**n,
None => "assert",
};
for variant in &self.variants {
let (member_name, body) = match variant.body {
Some((ref member_name, ref body)) => (member_name, body),
None => continue,
};
let field_count = body.fields.len() - skip_fields;
if field_count == 0 {
continue;
}
let dig = field_count == 1 && body.tuple_struct;
let mut derive_casts = |const_casts: bool| {
out.new_line();
out.new_line();
if dig {
let field = body.fields.iter().skip(skip_fields).next().unwrap();
let return_type = field.1.clone();
let return_type = if const_casts {
Type::Ref(Box::new(return_type))
} else {
Type::MutRef(Box::new(return_type))
};
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();
if dig {
write!(out, "return {}._0;", member_name);
} else {
write!(out, "return {};", member_name);
}
out.close_brace(false);
};
if derive_const_casts {
derive_casts(true)
}
if derive_mut_casts {
derive_casts(false)
}
}
}
let other = if let Some(r) = config.function.rename_args {
r.apply_to_snake_case("other", IdentifierType::FunctionArg)
} else {
String::from("other")
};
if config.language == Language::Cxx
&& self.can_derive_eq()
&& config.structure.derive_eq(&self.annotations)
{
out.new_line();
out.new_line();
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();
for variant in &self.variants {
if let Some((ref variant_name, _)) = variant.body {
write!(
out,
"case {}::{}: return {} == {}.{};",
self.tag.as_ref().unwrap(),
variant.export_name,
variant_name,
other,
variant_name
);
out.new_line();
}
}
write!(out, "default: return true;");
out.close_brace(false);
out.close_brace(false);
if config.structure.derive_neq(&self.annotations) {
out.new_line();
out.new_line();
write!(
out,
"bool operator!=(const {}& {}) const",
self.export_name, other
);
out.open_brace();
write!(out, "return !(*this == {});", other);
out.close_brace(false);
}
}
if config.language == Language::Cxx
&& 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.language == Language::Cxx
&& config
.enumeration
.derive_tagged_enum_destructor(&self.annotations)
{
out.new_line();
out.new_line();
write!(out, "~{}()", self.export_name);
out.open_brace();
write!(out, "switch (tag)");
out.open_brace();
for variant in &self.variants {
if let Some((ref variant_name, ref item)) = variant.body {
write!(
out,
"case {}::{}: {}.~{}(); break;",
self.tag.as_ref().unwrap(),
variant.export_name,
variant_name,
item.export_name(),
);
out.new_line();
}
}
write!(out, "default: break;");
out.close_brace(false);
out.close_brace(false);
}
if config.language == Language::Cxx
&& config
.enumeration
.derive_tagged_enum_copy_constructor(&self.annotations)
{
out.new_line();
out.new_line();
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();
for variant in &self.variants {
if let Some((ref variant_name, ref item)) = variant.body {
write!(
out,
"case {}::{}: ::new (&{}) ({})({}.{}); break;",
self.tag.as_ref().unwrap(),
variant.export_name,
variant_name,
item.export_name(),
other,
variant_name,
);
out.new_line();
}
}
write!(out, "default: break;");
out.close_brace(false);
out.close_brace(false);
}
if let Some(body) = config.export.extra_body(&self.path) {
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);
}
}