Repository: dtolnay/cxx (language: rust)

Issue:
Represent enums using the smallest sufficient integer type
The C-style enum support added in #170 unconditionally uses `u32` / `uint32_t` as the representation. However, this means a negative discriminant like `-1` or a large discriminant like `0xfffffffff` won't compile. Additionally, an explicitly typed discriminant literal like `0u8` just ignores the type.

It would be nice to infer the intended discriminant type from the values. For example:

- If the discriminants are `0`, `1`, `2` -> `u8`
- If they are `-1`, `0`, `1` -> `i8`
- If they are `0u16`, `1`, `2` -> `u16`


Discussion / hints:
I would add to this that we should be able to specify the integer type so that we can match an existing C++ enum.
Specifying the integer type is shown in my third bullet, where the discriminant is written as `0u16` so the representation is decided as `u16`.

Interop with an existing C++ enum is tracked separately by #177.
I was thinking more along the lines of allowing users to annotate the enum with "#[repr(u8)]" or whatever and use that when available.  This seems to me to be an easier way to force the type to be something specific than forcing users to set discriminant values.

This does overlap with #177, but I'm mentioning it here because I feel like it should override what you're suggesting here.
:+1: I am on board with recognizing #\[repr(u8)\].

Fix patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 8f54bd7c6..0849d908a 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -365,7 +365,9 @@ fn write_enum(out: &mut OutFile, enm: &Enum) {
     for line in enm.doc.to_string().lines() {
         writeln!(out, "//{}", line);
     }
-    writeln!(out, "enum class {} : uint32_t {{", enm.ident);
+    write!(out, "enum class {} : ", enm.ident);
+    write_atom(out, enm.repr);
+    writeln!(out, " {{");
     for variant in &enm.variants {
         writeln!(out, "  {} = {},", variant.ident, variant.discriminant);
     }
@@ -373,16 +375,15 @@ fn write_enum(out: &mut OutFile, enm: &Enum) {
 }
 
 fn check_enum(out: &mut OutFile, enm: &Enum) {
-    writeln!(
-        out,
-        "static_assert(sizeof({}) == sizeof(uint32_t), \"incorrect size\");",
-        enm.ident
-    );
+    write!(out, "static_assert(sizeof({}) == sizeof(", enm.ident);
+    write_atom(out, enm.repr);
+    writeln!(out, "), \"incorrect size\");");
     for variant in &enm.variants {
+        write!(out, "static_assert(static_cast<");
+        write_atom(out, enm.repr);
         writeln!(
             out,
-            "static_assert(static_cast<uint32_t>({}::{}) == {},
-              \"disagrees with the value in #[cxx::bridge]\");",
+            ">({}::{}) == {}, \"disagrees with the value in #[cxx::bridge]\");",
             enm.ident, variant.ident, variant.discriminant,
         );
     }
@@ -849,21 +850,7 @@ fn write_extern_arg(out: &mut OutFile, arg: &Var, types: &Types) {
 fn write_type(out: &mut OutFile, ty: &Type) {
     match ty {
         Type::Ident(ident) => match Atom::from(ident) {
-            Some(Bool) => write!(out, "bool"),
-            Some(U8) => write!(out, "uint8_t"),
-            Some(U16) => write!(out, "uint16_t"),
-            Some(U32) => write!(out, "uint32_t"),
-            Some(U64) => write!(out, "uint64_t"),
-            Some(Usize) => write!(out, "size_t"),
-            Some(I8) => write!(out, "int8_t"),
-            Some(I16) => write!(out, "int16_t"),
-            Some(I32) => write!(out, "int32_t"),
-            Some(I64) => write!(out, "int64_t"),
-            Some(Isize) => write!(out, "::rust::isize"),
-            Some(F32) => write!(out, "float"),
-            Some(F64) => write!(out, "double"),
-            Some(CxxString) => write!(out, "::std::string"),
-            Some(RustString) => write!(out, "::rust::String"),
+            Some(atom) => write_atom(out, atom),
             None => write!(out, "{}", ident),
         },
         Type::RustBox(ty) => {
@@ -922,6 +909,26 @@ fn write_type(out: &mut OutFile, ty: &Type) {
     }
 }
 
+fn write_atom(out: &mut OutFile, atom: Atom) {
+    match atom {
+        Bool => write!(out, "bool"),
+        U8 => write!(out, "uint8_t"),
+        U16 => write!(out, "uint16_t"),
+        U32 => write!(out, "uint32_t"),
+        U64 => write!(out, "uint64_t"),
+        Usize => write!(out, "size_t"),
+        I8 => write!(out, "int8_t"),
+        I16 => write!(out, "int16_t"),
+        I32 => write!(out, "int32_t"),
+        I64 => write!(out, "int64_t"),
+        Isize => write!(out, "::rust::isize"),
+        F32 => write!(out, "float"),
+        F64 => write!(out, "double"),
+        CxxString => write!(out, "::std::string"),
+        RustString => write!(out, "::rust::String"),
+    }
+}
+
 fn write_type_space(out: &mut OutFile, ty: &Type) {
     write_type(out, ty);
     write_space_after_type(out, ty);
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index fc129c6d4..f28d955f1 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -143,6 +143,7 @@ fn expand_struct(strct: &Struct) -> TokenStream {
 fn expand_enum(enm: &Enum) -> TokenStream {
     let ident = &enm.ident;
     let doc = &enm.doc;
+    let repr = enm.repr;
     let variants = enm.variants.iter().map(|variant| {
         let variant_ident = &variant.ident;
         let discriminant = &variant.discriminant;
@@ -155,7 +156,7 @@ fn expand_enum(enm: &Enum) -> TokenStream {
         #[derive(Copy, Clone, PartialEq, Eq)]
         #[repr(transparent)]
         pub struct #ident {
-            pub repr: u32,
+            pub repr: #repr,
         }
 
         #[allow(non_upper_case_globals)]
diff --git a/syntax/atom.rs b/syntax/atom.rs
index a2ff7b7f0..6e5fa8801 100644
--- a/syntax/atom.rs
+++ b/syntax/atom.rs
@@ -23,8 +23,12 @@ pub enum Atom {
 
 impl Atom {
     pub fn from(ident: &Ident) -> Option<Self> {
+        Self::from_str(ident.to_string().as_str())
+    }
+
+    pub fn from_str(s: &str) -> Option<Self> {
         use self::Atom::*;
-        match ident.to_string().as_str() {
+        match s {
             "bool" => Some(Bool),
             "u8" => Some(U8),
             "u16" => Some(U16),
diff --git a/syntax/attrs.rs b/syntax/attrs.rs
index f48a210e1..16541a3f6 100644
--- a/syntax/attrs.rs
+++ b/syntax/attrs.rs
@@ -1,5 +1,7 @@
 use crate::syntax::report::Errors;
+use crate::syntax::Atom::{self, *};
 use crate::syntax::{Derive, Doc};
+use proc_macro2::Ident;
 use syn::parse::{ParseStream, Parser as _};
 use syn::{Attribute, Error, LitStr, Path, Result, Token};
 
@@ -7,6 +9,7 @@ use syn::{Attribute, Error, LitStr, Path, Result, Token};
 pub struct Parser<'a> {
     pub doc: Option<&'a mut Doc>,
     pub derives: Option<&'a mut Vec<Derive>>,
+    pub repr: Option<&'a mut Option<Atom>>,
 }
 
 pub(super) fn parse_doc(cx: &mut Errors, attrs: &[Attribute]) -> Doc {
@@ -44,6 +47,16 @@ pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) {
                 }
                 Err(err) => return cx.push(err),
             }
+        } else if attr.path.is_ident("repr") {
+            match attr.parse_args_with(parse_repr_attribute) {
+                Ok(attr) => {
+                    if let Some(repr) = &mut parser.repr {
+                        **repr = Some(attr);
+                        continue;
+                    }
+                }
+                Err(err) => return cx.push(err),
+            }
         }
         return cx.error(attr, "unsupported attribute");
     }
@@ -69,3 +82,18 @@ fn parse_derive_attribute(input: ParseStream) -> Result<Vec<Derive>> {
         })
         .collect()
 }
+
+fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
+    let begin = input.cursor();
+    let ident: Ident = input.parse()?;
+    if let Some(atom) = Atom::from(&ident) {
+        match atom {
+            U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(atom),
+            _ => {}
+        }
+    }
+    Err(Error::new_spanned(
+        begin.token_stream(),
+        "unrecognized repr",
+    ))
+}
diff --git a/syntax/discriminant.rs b/syntax/discriminant.rs
new file mode 100644
index 000000000..67fa14d88
--- /dev/null
+++ b/syntax/discriminant.rs
@@ -0,0 +1,280 @@
+use crate::syntax::Atom::{self, *};
+use proc_macro2::{Literal, Span, TokenStream};
+use quote::ToTokens;
+use std::cmp::Ordering;
+use std::collections::BTreeSet;
+use std::fmt::{self, Display};
+use std::str::FromStr;
+use std::u64;
+use syn::{Error, Expr, Lit, Result, Token, UnOp};
+
+pub struct DiscriminantSet {
+    repr: Option<Atom>,
+    values: BTreeSet<Discriminant>,
+    previous: Option<Discriminant>,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Discriminant {
+    negative: bool,
+    magnitude: u64,
+}
+
+impl DiscriminantSet {
+    pub fn new(repr: Option<Atom>) -> Self {
+        DiscriminantSet {
+            repr,
+            values: BTreeSet::new(),
+            previous: None,
+        }
+    }
+
+    pub fn insert(&mut self, expr: &Expr) -> Result<Discriminant> {
+        let (discriminant, repr) = expr_to_discriminant(expr)?;
+        match (self.repr, repr) {
+            (None, _) => self.repr = repr,
+            (Some(prev), Some(repr)) if prev != repr => {
+                let msg = format!("expected {}, found {}", prev, repr);
+                return Err(Error::new(Span::call_site(), msg));
+            }
+            _ => {}
+        }
+        insert(self, discriminant)
+    }
+
+    pub fn insert_next(&mut self) -> Result<Discriminant> {
+        let discriminant = match self.previous {
+            None => Discriminant::zero(),
+            Some(mut discriminant) if discriminant.negative => {
+                discriminant.magnitude -= 1;
+                if discriminant.magnitude == 0 {
+                    discriminant.negative = false;
+                }
+                discriminant
+            }
+            Some(mut discriminant) => {
+                if discriminant.magnitude == u64::MAX {
+                    let msg = format!("discriminant overflow on value after {}", u64::MAX);
+                    return Err(Error::new(Span::call_site(), msg));
+                }
+                discriminant.magnitude += 1;
+                discriminant
+            }
+        };
+        insert(self, discriminant)
+    }
+
+    pub fn inferred_repr(&self) -> Result<Atom> {
+        if let Some(repr) = self.repr {
+            return Ok(repr);
+        }
+        if self.values.is_empty() {
+            return Ok(U8);
+        }
+        let min = *self.values.iter().next().unwrap();
+        let max = *self.values.iter().next_back().unwrap();
+        for bounds in &BOUNDS {
+            if bounds.min <= min && max <= bounds.max {
+                return Ok(bounds.repr);
+            }
+        }
+        let msg = "these discriminant values do not fit in any supported enum repr type";
+        Err(Error::new(Span::call_site(), msg))
+    }
+}
+
+fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> {
+    match expr {
+        Expr::Lit(expr) => {
+            if let Lit::Int(lit) = &expr.lit {
+                let discriminant = lit.base10_parse::<Discriminant>()?;
+                let repr = parse_int_suffix(lit.suffix())?;
+                return Ok((discriminant, repr));
+            }
+        }
+        Expr::Unary(unary) => {
+            if let UnOp::Neg(_) = unary.op {
+                let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?;
+                discriminant.negative ^= true;
+                return Ok((discriminant, repr));
+            }
+        }
+        _ => {}
+    }
+    Err(Error::new_spanned(
+        expr,
+        "enums with non-integer literal discriminants are not supported yet",
+    ))
+}
+
+fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discriminant> {
+    if let Some(expected_repr) = set.repr {
+        for bounds in &BOUNDS {
+            if bounds.repr != expected_repr {
+                continue;
+            }
+            if bounds.min <= discriminant && discriminant <= bounds.max {
+                break;
+            }
+            let msg = format!(
+                "discriminant value `{}` is outside the limits of {}",
+                discriminant, expected_repr,
+            );
+            return Err(Error::new(Span::call_site(), msg));
+        }
+    }
+    if set.values.insert(discriminant) {
+        set.previous = Some(discriminant);
+        Ok(discriminant)
+    } else {
+        let msg = format!("discriminant value `{}` already exists", discriminant);
+        Err(Error::new(Span::call_site(), msg))
+    }
+}
+
+impl Discriminant {
+    const fn zero() -> Self {
+        Discriminant {
+            negative: false,
+            magnitude: 0,
+        }
+    }
+
+    const fn pos(u: u64) -> Self {
+        Discriminant {
+            negative: false,
+            magnitude: u,
+        }
+    }
+
+    const fn neg(i: i64) -> Self {
+        Discriminant {
+            negative: i < 0,
+            // This is `i.abs() as u64` but without overflow on MIN. Uses the
+            // fact that MIN.wrapping_abs() wraps back to MIN whose binary
+            // representation is 1<<63, and thus the `as u64` conversion
+            // produces 1<<63 too which happens to be the correct unsigned
+            // magnitude.
+            magnitude: i.wrapping_abs() as u64,
+        }
+    }
+}
+
+impl Display for Discriminant {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if self.negative {
+            f.write_str("-")?;
+        }
+        Display::fmt(&self.magnitude, f)
+    }
+}
+
+impl ToTokens for Discriminant {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if self.negative {
+            Token![-](Span::call_site()).to_tokens(tokens);
+        }
+        Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens);
+    }
+}
+
+impl FromStr for Discriminant {
+    type Err = Error;
+
+    fn from_str(mut s: &str) -> Result<Self> {
+        let negative = s.starts_with('-');
+        if negative {
+            s = &s[1..];
+        }
+        match s.parse::<u64>() {
+            Ok(magnitude) => Ok(Discriminant {
+                negative,
+                magnitude,
+            }),
+            Err(_) => Err(Error::new(
+                Span::call_site(),
+                "discriminant value outside of supported range",
+            )),
+        }
+    }
+}
+
+impl Ord for Discriminant {
+    fn cmp(&self, other: &Self) -> Ordering {
+        match (self.negative, other.negative) {
+            (true, true) => self.magnitude.cmp(&other.magnitude).reverse(),
+            (true, false) => Ordering::Less, // negative < positive
+            (false, true) => Ordering::Greater, // positive > negative
+            (false, false) => self.magnitude.cmp(&other.magnitude),
+        }
+    }
+}
+
+impl PartialOrd for Discriminant {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
+    if suffix.is_empty() {
+        return Ok(None);
+    }
+    if let Some(atom) = Atom::from_str(suffix) {
+        match atom {
+            U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize => return Ok(Some(atom)),
+            _ => {}
+        }
+    }
+    let msg = format!("unrecognized integer suffix: `{}`", suffix);
+    Err(Error::new(Span::call_site(), msg))
+}
+
+struct Bounds {
+    repr: Atom,
+    min: Discriminant,
+    max: Discriminant,
+}
+
+const BOUNDS: [Bounds; 8] = [
+    Bounds {
+        repr: U8,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u8::MAX as u64),
+    },
+    Bounds {
+        repr: I8,
+        min: Discriminant::neg(std::i8::MIN as i64),
+        max: Discriminant::pos(std::i8::MAX as u64),
+    },
+    Bounds {
+        repr: U16,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u16::MAX as u64),
+    },
+    Bounds {
+        repr: I16,
+        min: Discriminant::neg(std::i16::MIN as i64),
+        max: Discriminant::pos(std::i16::MAX as u64),
+    },
+    Bounds {
+        repr: U32,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u32::MAX as u64),
+    },
+    Bounds {
+        repr: I32,
+        min: Discriminant::neg(std::i32::MIN as i64),
+        max: Discriminant::pos(std::i32::MAX as u64),
+    },
+    Bounds {
+        repr: U64,
+        min: Discriminant::zero(),
+        max: Discriminant::pos(std::u64::MAX),
+    },
+    Bounds {
+        repr: I64,
+        min: Discriminant::neg(std::i64::MIN),
+        max: Discriminant::pos(std::i64::MAX as u64),
+    },
+];
diff --git a/syntax/mod.rs b/syntax/mod.rs
index d21c0835a..4702a6969 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -4,6 +4,7 @@ pub mod atom;
 mod attrs;
 pub mod check;
 mod derive;
+mod discriminant;
 mod doc;
 pub mod error;
 pub mod ident;
@@ -17,11 +18,12 @@ pub mod symbol;
 mod tokens;
 pub mod types;
 
+use self::discriminant::Discriminant;
 use self::parse::kw;
 use proc_macro2::{Ident, Span};
 use syn::punctuated::Punctuated;
 use syn::token::{Brace, Bracket, Paren};
-use syn::{Lifetime, LitStr, Token, Type as RustType};
+use syn::{Expr, Lifetime, LitStr, Token, Type as RustType};
 
 pub use self::atom::Atom;
 pub use self::derive::Derive;
@@ -61,6 +63,7 @@ pub struct Enum {
     pub ident: Ident,
     pub brace_token: Brace,
     pub variants: Vec<Variant>,
+    pub repr: Atom,
 }
 
 pub struct ExternFn {
@@ -106,7 +109,8 @@ pub struct Receiver {
 
 pub struct Variant {
     pub ident: Ident,
-    pub discriminant: u32,
+    pub discriminant: Discriminant,
+    pub expr: Option<Expr>,
 }
 
 pub enum Type {
diff --git a/syntax/parse.rs b/syntax/parse.rs
index f84ff3288..a11a5c881 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -1,3 +1,4 @@
+use crate::syntax::discriminant::DiscriminantSet;
 use crate::syntax::report::Errors;
 use crate::syntax::Atom::*;
 use crate::syntax::{
@@ -5,16 +6,13 @@ use crate::syntax::{
     Struct, Ty1, Type, TypeAlias, Var, Variant,
 };
 use proc_macro2::TokenStream;
-use quote::{format_ident, quote};
-use std::collections::HashSet;
-use std::u32;
+use quote::{format_ident, quote, quote_spanned};
 use syn::parse::{ParseStream, Parser};
 use syn::punctuated::Punctuated;
 use syn::{
-    Abi, Attribute, Error, Expr, ExprLit, Fields, FnArg, ForeignItem, ForeignItemFn,
-    ForeignItemType, GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Lit, Pat,
-    PathArguments, Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath,
-    TypeReference, TypeSlice, Variant as RustVariant,
+    Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
+    GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, Pat, PathArguments, Result,
+    ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
 };
 
 pub mod kw {
@@ -62,6 +60,7 @@ fn parse_struct(cx: &mut Errors, item: ItemStruct) -> Result<Api> {
         attrs::Parser {
             doc: Some(&mut doc),
             derives: Some(&mut derives),
+            ..Default::default()
         },
     );
 
@@ -105,69 +104,71 @@ fn parse_enum(cx: &mut Errors, item: ItemEnum) -> Result<Api> {
         ));
     }
 
-    let doc = attrs::parse_doc(cx, &item.attrs);
+    let mut doc = Doc::new();
+    let mut repr = None;
+    attrs::parse(
+        cx,
+        &item.attrs,
+        attrs::Parser {
+            doc: Some(&mut doc),
+            repr: Some(&mut repr),
+            ..Default::default()
+        },
+    );
 
     let mut variants = Vec::new();
-    let mut discriminants = HashSet::new();
-    let mut prev_discriminant = None;
+    let mut discriminants = DiscriminantSet::new(repr);
     for variant in item.variants {
         match variant.fields {
             Fields::Unit => {}
             _ => {
-                return Err(Error::new_spanned(
-                    variant,
-                    "enums with data are not supported yet",
-                ));
+                cx.error(variant, "enums with data are not supported yet");
+                break;
             }
         }
-        if variant.discriminant.is_none() && prev_discriminant == Some(u32::MAX) {
-            let msg = format!("discriminant overflow on value after {}", u32::MAX);
-            return Err(Error::new_spanned(variant, msg));
-        }
-        let discriminant =
-            parse_discriminant(&variant)?.unwrap_or_else(|| prev_discriminant.map_or(0, |n| n + 1));
-        if !discriminants.insert(discriminant) {
-            let msg = format!("discriminant value `{}` already exists", discriminant);
-            return Err(Error::new_spanned(variant, msg));
-        }
+        let expr = variant.discriminant.as_ref().map(|(_, expr)| expr);
+        let try_discriminant = match &expr {
+            Some(lit) => discriminants.insert(lit),
+            None => discriminants.insert_next(),
+        };
+        let discriminant = match try_discriminant {
+            Ok(discriminant) => discriminant,
+            Err(err) => {
+                cx.error(variant, err);
+                break;
+            }
+        };
+        let expr = variant.discriminant.map(|(_, expr)| expr);
         variants.push(Variant {
             ident: variant.ident,
             discriminant,
+            expr,
         });
-        prev_discriminant = Some(discriminant);
+    }
+
+    let enum_token = item.enum_token;
+    let brace_token = item.brace_token;
+
+    let mut repr = U8;
+    match discriminants.inferred_repr() {
+        Ok(inferred) => repr = inferred,
+        Err(err) => {
+            let span = quote_spanned!(brace_token.span=> #enum_token {});
+            cx.error(span, err);
+            variants.clear();
+        }
     }
 
     Ok(Api::Enum(Enum {
         doc,
-        enum_token: item.enum_token,
+        enum_token,
         ident: item.ident,
-        brace_token: item.brace_token,
+        brace_token,
         variants,
+        repr,
     }))
 }
 
-fn parse_discriminant(variant: &RustVariant) -> Result<Option<u32>> {
-    match &variant.discriminant {
-        None => Ok(None),
-        Some((
-            _,
-            Expr::Lit(ExprLit {
-                lit: Lit::Int(n), ..
-            }),
-        )) => match n.base10_parse() {
-            Ok(val) => Ok(Some(val)),
-            Err(_) => Err(Error::new_spanned(
-                variant,
-                "cannot parse enum discriminant as an integer",
-            )),
-        },
-        _ => Err(Error::new_spanned(
-            variant,
-            "enums with non-integer literal discriminants are not supported yet",
-        )),
-    }
-}
-
 fn parse_foreign_mod(cx: &mut Errors, foreign_mod: ItemForeignMod, out: &mut Vec<Api>) {
     let lang = match parse_lang(foreign_mod.abi) {
         Ok(lang) => lang,
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index 9a1fee05a..4ed264a78 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -1,6 +1,6 @@
 use crate::syntax::atom::Atom::*;
 use crate::syntax::{
-    Derive, Enum, ExternFn, ExternType, Receiver, Ref, Signature, Slice, Struct, Ty1, Type,
+    Atom, Derive, Enum, ExternFn, ExternType, Receiver, Ref, Signature, Slice, Struct, Ty1, Type,
     TypeAlias, Var,
 };
 use proc_macro2::{Ident, Span, TokenStream};
@@ -75,6 +75,12 @@ impl ToTokens for Derive {
     }
 }
 
+impl ToTokens for Atom {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        Ident::new(self.as_ref(), Span::call_site()).to_tokens(tokens);
+    }
+}
+
 impl ToTokens for ExternType {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         // Notional token range for error reporting purposes.


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 94572299d..d4e96fc22 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -44,7 +44,7 @@ pub mod ffi {
         fn c_return_ref_rust_vec(c: &C) -> &Vec<u8>;
         fn c_return_identity(_: usize) -> usize;
         fn c_return_sum(_: usize, _: usize) -> usize;
-        fn c_return_enum(n: u32) -> Enum;
+        fn c_return_enum(n: u16) -> Enum;
 
         fn c_take_primitive(n: usize);
         fn c_take_shared(shared: Shared);
@@ -89,6 +89,7 @@ pub mod ffi {
         type COwnedEnum;
     }
 
+    #[repr(u32)]
     enum COwnedEnum {
         CVal1,
         CVal2,
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 21637d8d6..af7a4a069 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -107,10 +107,10 @@ size_t c_return_identity(size_t n) { return n; }
 
 size_t c_return_sum(size_t n1, size_t n2) { return n1 + n2; }
 
-Enum c_return_enum(uint32_t n) {
-  if (n <= static_cast<uint32_t>(Enum::AVal)) {
+Enum c_return_enum(uint16_t n) {
+  if (n <= static_cast<uint16_t>(Enum::AVal)) {
     return Enum::AVal;
-  } else if (n <= static_cast<uint32_t>(Enum::BVal)) {
+  } else if (n <= static_cast<uint16_t>(Enum::BVal)) {
     return Enum::BVal;
   } else {
     return Enum::CVal;
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 795b4a9fb..1173a0a86 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -7,7 +7,7 @@ namespace tests {
 
 struct R;
 struct Shared;
-enum class Enum : uint32_t;
+enum class Enum : uint16_t;
 
 class C {
 public:
@@ -46,7 +46,7 @@ rust::Vec<uint8_t> c_return_rust_vec();
 const rust::Vec<uint8_t> &c_return_ref_rust_vec(const C &c);
 size_t c_return_identity(size_t n);
 size_t c_return_sum(size_t n1, size_t n2);
-Enum c_return_enum(uint32_t n);
+Enum c_return_enum(uint16_t n);
 
 void c_take_primitive(size_t n);
 void c_take_shared(Shared shared);
diff --git a/tests/ui/enum_inconsistent.rs b/tests/ui/enum_inconsistent.rs
new file mode 100644
index 000000000..cd5ffa5b9
--- /dev/null
+++ b/tests/ui/enum_inconsistent.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    enum Bad {
+        A = 1u16,
+        B = 2i64,
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/enum_inconsistent.stderr b/tests/ui/enum_inconsistent.stderr
new file mode 100644
index 000000000..c5b427a8c
--- /dev/null
+++ b/tests/ui/enum_inconsistent.stderr
@@ -0,0 +1,5 @@
+error: expected u16, found i64
+ --> $DIR/enum_inconsistent.rs:5:9
+  |
+5 |         B = 2i64,
+  |         ^^^^^^^^
diff --git a/tests/ui/enum_match_without_wildcard.stderr b/tests/ui/enum_match_without_wildcard.stderr
index 93a89572f..a5a021549 100644
--- a/tests/ui/enum_match_without_wildcard.stderr
+++ b/tests/ui/enum_match_without_wildcard.stderr
@@ -1,11 +1,11 @@
-error[E0004]: non-exhaustive patterns: `A { repr: 2u32..=std::u32::MAX }` not covered
+error[E0004]: non-exhaustive patterns: `A { repr: 2u8..=std::u8::MAX }` not covered
   --> $DIR/enum_match_without_wildcard.rs:12:11
    |
 1  | #[cxx::bridge]
    | -------------- `ffi::A` defined here
 ...
 12 |     match a {
-   |           ^ pattern `A { repr: 2u32..=std::u32::MAX }` not covered
+   |           ^ pattern `A { repr: 2u8..=std::u8::MAX }` not covered
    |
    = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
    = note: the matched value is of type `ffi::A`
diff --git a/tests/ui/enum_out_of_bounds.rs b/tests/ui/enum_out_of_bounds.rs
new file mode 100644
index 000000000..1aec5f0a5
--- /dev/null
+++ b/tests/ui/enum_out_of_bounds.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    #[repr(u32)]
+    enum Bad {
+        A = 0xFFFF_FFFF_FFFF_FFFF,
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/enum_out_of_bounds.stderr b/tests/ui/enum_out_of_bounds.stderr
new file mode 100644
index 000000000..f437ea125
--- /dev/null
+++ b/tests/ui/enum_out_of_bounds.stderr
@@ -0,0 +1,5 @@
+error: discriminant value `18446744073709551615` is outside the limits of u32
+ --> $DIR/enum_out_of_bounds.rs:5:9
+  |
+5 |         A = 0xFFFF_FFFF_FFFF_FFFF,
+  |         ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/enum_overflows.rs b/tests/ui/enum_overflows.rs
index 3f351f020..29de1a008 100644
--- a/tests/ui/enum_overflows.rs
+++ b/tests/ui/enum_overflows.rs
@@ -1,14 +1,14 @@
 #[cxx::bridge]
 mod ffi {
     enum Good1 {
-        A = 0xffffffff,
+        A = 0xFFFF_FFFF_FFFF_FFFF,
     }
     enum Good2 {
-        B = 0xffffffff,
+        B = 0xFFFF_FFFF_FFFF_FFFF,
         C = 2020,
     }
     enum Bad {
-        D = 0xfffffffe,
+        D = 0xFFFF_FFFF_FFFF_FFFE,
         E,
         F,
     }
diff --git a/tests/ui/enum_overflows.stderr b/tests/ui/enum_overflows.stderr
index db92f7f97..ed58b61c5 100644
--- a/tests/ui/enum_overflows.stderr
+++ b/tests/ui/enum_overflows.stderr
@@ -1,4 +1,4 @@
-error: discriminant overflow on value after 4294967295
+error: discriminant overflow on value after 18446744073709551615
   --> $DIR/enum_overflows.rs:13:9
    |
 13 |         F,
diff --git a/tests/ui/enum_unsatisfiable.rs b/tests/ui/enum_unsatisfiable.rs
new file mode 100644
index 000000000..619128743
--- /dev/null
+++ b/tests/ui/enum_unsatisfiable.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    enum Bad {
+        A = -0xFFFF_FFFF_FFFF_FFFF,
+        B = 0xFFFF_FFFF_FFFF_FFFF,
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/enum_unsatisfiable.stderr b/tests/ui/enum_unsatisfiable.stderr
new file mode 100644
index 000000000..99852ca6c
--- /dev/null
+++ b/tests/ui/enum_unsatisfiable.stderr
@@ -0,0 +1,8 @@
+error: these discriminant values do not fit in any supported enum repr type
+ --> $DIR/enum_unsatisfiable.rs:3:5
+  |
+3 | /     enum Bad {
+4 | |         A = -0xFFFF_FFFF_FFFF_FFFF,
+5 | |         B = 0xFFFF_FFFF_FFFF_FFFF,
+6 | |     }
+  | |_____^