Repository: dtolnay/cxx (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`

Pull request description:
Choose enum discriminant type based on repr attributes, suffixes, values
Fixes #175.

In order, the choice of repr is based on:

- **#\[repr(...)\] attribute**

    ```rust
    #[repr(u16)]
    enum E {  //:u16
        A,
        B,
    }
    ```

- otherwise, **integer suffix**

    ```rust
    enum E {  //:isize
        A = 0isize,
        B,
    }
    ```

- otherwise, **range of discriminant values**

    The first of `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64` that can fit all of the values.

    ```rust
    enum E {  //:i16
        A = -1,
        B = 2020,
    }
    ```

FYI @jgalenson 

Patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 8f54bd7c..0849d908 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 fc129c6d..f28d955f 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 a2ff7b7f..6e5fa880 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 f48a210e..16541a3f 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 00000000..67fa14d8
--- /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 d21c0835..4702a696 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 f84ff328..a11a5c88 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 9a1fee05..4ed264a7 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 94572299..d4e96fc2 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 21637d8d..af7a4a06 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 795b4a9f..1173a0a8 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 00000000..cd5ffa5b
--- /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 00000000..c5b427a8
--- /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 93a89572..a5a02154 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 00000000..1aec5f0a
--- /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 00000000..f437ea12
--- /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 3f351f02..29de1a00 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 db92f7f9..ed58b61c 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 00000000..61912874
--- /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 00000000..99852ca6
--- /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 | |     }
+  | |_____^