From 3b61c8ead5b7d74e87aedfa5e6f55bf314cc7385 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan <me@rreverser.com> Date: Wed, 15 Nov 2017 05:12:09 +0000 Subject: [PATCH] Initial support for tagged enums --- src/bindgen/ir/enumeration.rs | 189 +++++++++++++++++++++++++++------- src/bindgen/ir/structure.rs | 37 +++---- tests/expectations/enum.c | 26 ++++- tests/expectations/enum.cpp | 25 ++++- tests/rust/enum.rs | 10 +- 5 files changed, 225 insertions(+), 62 deletions(-) diff --git a/src/bindgen/ir/enumeration.rs b/src/bindgen/ir/enumeration.rs index 79b7dc8..e3a7c57 100644 --- a/src/bindgen/ir/enumeration.rs +++ b/src/bindgen/ir/enumeration.rs @@ -7,7 +7,8 @@ use std::io::Write; use syn; use bindgen::config::{Config, Language}; -use bindgen::ir::{AnnotationSet, Cfg, CfgWrite, Documentation, Item, ItemContainer, Repr}; +use bindgen::ir::{AnnotationSet, Cfg, CfgWrite, Documentation, GenericParams, GenericPath, Item, + ItemContainer, Repr, Struct, Type}; use bindgen::rename::{IdentifierType, RenameRule}; use bindgen::utilities::find_first_some; use bindgen::writer::{Source, SourceWriter}; @@ -16,7 +17,8 @@ use bindgen::writer::{Source, SourceWriter}; pub struct Enum { pub name: String, pub repr: Repr, - pub values: Vec<(String, u64, Documentation)>, + pub values: Vec<(String, u64, Option<Struct>, Documentation)>, + pub tag: Option<String>, pub cfg: Option<Cfg>, pub annotations: AnnotationSet, pub documentation: Documentation, @@ -40,38 +42,77 @@ impl Enum { let mut values = Vec::new(); let mut current = 0; + let mut is_tagged = false; for variant in variants { - match variant.data { - syn::VariantData::Unit => { - match variant.discriminant { - Some(syn::ConstExpr::Lit(syn::Lit::Int(i, _))) => { - current = i; - } - Some(_) => { - return Err("Unsupported discriminant.".to_owned()); - } - None => { /* okay, we just use current */ } - } - - values.push(( - variant.ident.to_string(), - current, - Documentation::load(&variant.attrs), - )); - current = current + 1; + match variant.discriminant { + Some(syn::ConstExpr::Lit(syn::Lit::Int(i, _))) => { + current = i; } - _ => { - return Err("Unsupported variant.".to_owned()); + Some(_) => { + return Err("Unsupported discriminant.".to_owned()); } - } + None => { /* okay, we just use current */ } + }; + let body = match variant.data { + syn::VariantData::Unit => None, + syn::VariantData::Struct(ref fields) | syn::VariantData::Tuple(ref fields) => { + is_tagged = true; + Some(Struct { + name: format!("{}_Body", variant.ident), + generic_params: GenericParams::default(), + fields: { + let mut res = vec![ + ( + "tag".to_string(), + Type::Path(GenericPath { + name: "Tag".to_string(), + generics: 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), + )); + } + } + + res + }, + is_variant: true, + tuple_struct: match variant.data { + syn::VariantData::Tuple(_) => true, + _ => false, + }, + cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), + annotations: AnnotationSet::load(attrs)?, + documentation: Documentation::none(), + }) + } + }; + values.push(( + variant.ident.to_string(), + current, + body, + Documentation::load(&variant.attrs), + )); + current = current + 1; } let annotations = AnnotationSet::load(attrs)?; if let Some(variants) = annotations.list("enum-trailing-values") { for variant in variants { - values.push((variant, current, Documentation::none())); + values.push((variant, current, None, Documentation::none())); current = current + 1; } } @@ -80,6 +121,11 @@ impl Enum { name: name, repr: repr, values: values, + tag: if is_tagged { + Some("Tag".to_string()) + } else { + None + }, cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), annotations: annotations, documentation: Documentation::load(attrs), @@ -109,16 +155,34 @@ impl Item for Enum { } fn rename_for_config(&mut self, config: &Config) { - config.export.rename(&mut self.name); + if config.language == Language::C && self.tag.is_some() { + // it makes sense to always prefix Tag with type name in C + let new_tag = format!("{}_Tag", self.name); + for value in &mut self.values { + if let Some(ref mut body) = value.2 { + body.fields[0].1 = Type::Path(GenericPath { + name: new_tag.clone(), + generics: vec![], + }); + } + } + self.tag = Some(new_tag); + } + + for value in &mut self.values { + if let Some(ref mut body) = value.2 { + body.rename_for_config(config); + } + } - if config.language == Language::C - && (config.enumeration.prefix_with_name - || self.annotations.bool("prefix-with-name").unwrap_or(false)) + if config.enumeration.prefix_with_name + || self.annotations.bool("prefix-with-name").unwrap_or(false) { - let old = ::std::mem::replace(&mut self.values, Vec::new()); - for (name, value, doc) in old { - self.values - .push((format!("{}_{}", self.name, name), value, doc)); + for value in &mut self.values { + value.0 = format!("{}_{}", self.name, value.0); + if let Some(ref mut body) = value.2 { + body.name = format!("{}_{}", self.name, body.name); + } } } @@ -135,6 +199,7 @@ impl Item for Enum { r.apply_to_pascal_case(&x.0, IdentifierType::EnumVariant(self)), x.1.clone(), x.2.clone(), + x.3.clone(), ) }) .collect(); @@ -148,6 +213,19 @@ impl Source for Enum { self.documentation.write(config, out); + let is_tagged = self.tag.is_some(); + + if is_tagged && config.language == Language::Cxx { + write!(out, "union {}", self.name); + out.open_brace(); + } + + let enum_name = if let Some(ref tag) = self.tag { + tag + } else { + &self.name + }; + let size = match self.repr { Repr::C => None, Repr::USize => Some("uintptr_t"), @@ -165,13 +243,13 @@ impl Source for Enum { if size.is_none() { out.write("typedef enum"); } else { - write!(out, "enum {}", self.name); + write!(out, "enum {}", enum_name); } } else { if let Some(prim) = size { - write!(out, "enum class {} : {}", self.name, prim); + write!(out, "enum class {} : {}", enum_name, prim); } else { - write!(out, "enum class {}", self.name); + write!(out, "enum class {}", enum_name); } } out.open_brace(); @@ -179,7 +257,7 @@ impl Source for Enum { if i != 0 { out.new_line() } - value.2.write(config, out); + value.3.write(config, out); write!(out, "{} = {},", value.0, value.1); } if config.enumeration.add_sentinel(&self.annotations) { @@ -190,7 +268,7 @@ impl Source for Enum { if config.language == Language::C && size.is_none() { out.close_brace(false); - write!(out, " {};", self.name); + write!(out, " {};", enum_name); } else { out.close_brace(true); } @@ -198,7 +276,42 @@ impl Source for Enum { if config.language == Language::C { if let Some(prim) = size { out.new_line(); - write!(out, "typedef {} {};", prim, self.name); + write!(out, "typedef {} {};", prim, enum_name); + } + } + + if is_tagged { + for value in &self.values { + if let Some(ref body) = value.2 { + out.new_line(); + out.new_line(); + + body.write(config, out); + } + } + + out.new_line(); + out.new_line(); + + if config.language == Language::C { + out.write("typedef union"); + out.open_brace(); + } + + write!(out, "{} tag;", enum_name); + + for value in &self.values { + if let Some(ref body) = value.2 { + out.new_line(); + write!(out, "{} {};", body.name, value.0); + } + } + + if config.language == Language::C { + out.close_brace(false); + write!(out, " {};", self.name); + } else { + out.close_brace(true); } } diff --git a/src/bindgen/ir/structure.rs b/src/bindgen/ir/structure.rs index eb0cd2f..12ff834 100644 --- a/src/bindgen/ir/structure.rs +++ b/src/bindgen/ir/structure.rs @@ -22,6 +22,7 @@ pub struct Struct { pub name: String, pub generic_params: GenericParams, pub fields: Vec<(String, Type, Documentation)>, + pub is_variant: bool, pub tuple_struct: bool, pub cfg: Option<Cfg>, pub annotations: AnnotationSet, @@ -63,6 +64,7 @@ impl Struct { name: name, generic_params: GenericParams::new(generics), fields: fields, + is_variant: false, tuple_struct: tuple_struct, cfg: Cfg::append(mod_cfg, Cfg::load(attrs)), annotations: AnnotationSet::load(attrs)?, @@ -131,33 +133,25 @@ impl Item for Struct { config.structure.rename_fields, ]; - if let Some(o) = self.annotations.list("field-names") { - let mut overriden_fields = Vec::new(); + let mut names = self.fields.iter_mut().map(|field| &mut field.0); - for (i, &(ref name, ref ty, ref doc)) in self.fields.iter().enumerate() { - if i >= o.len() { - overriden_fields.push((name.clone(), ty.clone(), doc.clone())); - } else { - overriden_fields.push((o[i].clone(), ty.clone(), doc.clone())); - } + if let Some(o) = self.annotations.list("field-names") { + for (dest, src) in names.zip(o) { + *dest = src; } - - self.fields = overriden_fields; } else if let Some(r) = find_first_some(&field_rules) { - self.fields = self.fields - .iter() - .map(|x| { - ( - r.apply_to_snake_case(&x.0, IdentifierType::StructMember), - x.1.clone(), - x.2.clone(), - ) - }) - .collect(); + for name in names { + *name = r.apply_to_snake_case(name, IdentifierType::StructMember); + } } else if self.tuple_struct { + // If there is a tag field, skip it + if self.is_variant { + names.next(); + } + // If we don't have any rules for a tuple struct, prefix them with // an underscore so it still compiles - for &mut (ref mut name, ..) in &mut self.fields { + for name in names { name.insert(0, '_'); } } @@ -189,6 +183,7 @@ impl Item for Struct { .iter() .map(|x| (x.0.clone(), x.1.specialize(&mappings), x.2.clone())) .collect(), + is_variant: self.is_variant, tuple_struct: self.tuple_struct, cfg: self.cfg.clone(), annotations: self.annotations.clone(), diff --git a/tests/expectations/enum.c b/tests/expectations/enum.c index 6840677..03bd490 100644 --- a/tests/expectations/enum.c +++ b/tests/expectations/enum.c @@ -42,6 +42,30 @@ enum E { }; typedef intptr_t E; +enum F_Tag { + Foo = 0, + Bar = 1, + Baz = 2, +}; +typedef uint8_t F_Tag; + +typedef struct { + F_Tag tag; + int16_t _0; +} Foo_Body; + +typedef struct { + F_Tag tag; + uint8_t x; + int16_t y; +} Bar_Body; + +typedef union { + F_Tag tag; + Foo_Body Foo; + Bar_Body Bar; +} F; + typedef struct Opaque Opaque; -void root(Opaque *o, A a, B b, C c, D d, E e); +void root(Opaque *o, A a, B b, C c, D d, E e, F f); diff --git a/tests/expectations/enum.cpp b/tests/expectations/enum.cpp index 1537c3c..cbace77 100644 --- a/tests/expectations/enum.cpp +++ b/tests/expectations/enum.cpp @@ -36,10 +36,33 @@ enum class E : intptr_t { e4 = 5, }; +union F { + enum class Tag : uint8_t { + Foo = 0, + Bar = 1, + Baz = 2, + }; + + struct Foo_Body { + Tag tag; + int16_t _0; + }; + + struct Bar_Body { + Tag tag; + uint8_t x; + int16_t y; + }; + + Tag tag; + Foo_Body Foo; + Bar_Body Bar; +}; + struct Opaque; extern "C" { -void root(Opaque *o, A a, B b, C c, D d, E e); +void root(Opaque *o, A a, B b, C c, D d, E e, F f); } // extern "C" diff --git a/tests/rust/enum.rs b/tests/rust/enum.rs index 03b0d48..6ac9078 100644 --- a/tests/rust/enum.rs +++ b/tests/rust/enum.rs @@ -43,6 +43,13 @@ enum E { e4 = 5, } +#[repr(u8)] +enum F { + Foo(i16), + Bar { x: u8, y: i16 }, + Baz +} + #[no_mangle] pub extern "C" fn root( o: *mut Opaque, @@ -50,5 +57,6 @@ pub extern "C" fn root( b: B, c: C, d: D, - e: E + e: E, + f: F ) { } -- GitLab