diff --git a/src/bindgen/ir/enumeration.rs b/src/bindgen/ir/enumeration.rs
index 79b7dc8205d84555528ffe8f48c51264e0d171b0..e3a7c574bba36649c5a4472100fd11c409078095 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 eb0cd2f817b19d459894e6d920faebaa0dffb085..12ff8342a08bae1f21600bf28513f107e31b26e3 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 6840677278a3b8fa32631287b5580018a61bcd3a..03bd490d6a5d2765e02b5e4f63c32dc7f036e567 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 1537c3c359a93be164614ee751cc050f287a2e0e..cbace7747bc59ee09819649f9078af3b59350d04 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 03b0d487164275e2dc0d9579adfa8b78c3e4aa42..6ac907819dce9d028ccc00fbea36943e094e48ae 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
 ) { }