diff --git a/book/src/shared.md b/book/src/shared.md index 4043db124..08f3d81e0 100644 --- a/book/src/shared.md +++ b/book/src/shared.md @@ -78,14 +78,19 @@ impl Suit { pub const Hearts: Self = Suit { repr: 2 }; pub const Spades: Self = Suit { repr: 3 }; } +macro_rules! _Suit { + () => { Suit{ repr: 4.. } }; + ($i:ident) => { Suit { repr: $i @ 4.. } }; +} ``` Notice you're free to treat the enum as an integer in Rust code via the public `repr` field. -Pattern matching with `match` still works but will require you to write wildcard -arms to handle the situation of an enum value that is not one of the listed -variants. +Pattern matching with `match` still works but will require you to write a +fallback arm to handle the situation of an enum value that is not one of the +listed variants. A convenience `_Enum!` macro is generated to statically ensure +that all listed variants are covered: ```rust,noplayground fn main() { @@ -95,7 +100,7 @@ fn main() { Suit::Diamonds => ..., Suit::Hearts => ..., Suit::Spades => ..., - _ => ..., // fallback arm + _Suit!(unlisted) => ..., // fallback arm } } ``` diff --git a/macro/src/expand.rs b/macro/src/expand.rs index c98b2a55e..a481d92f2 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -68,7 +68,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types) hidden.extend(expand_struct_operators(strct)); forbid.extend(expand_struct_forbid_drop(strct)); } - Api::Enum(enm) => expanded.extend(expand_enum(enm)), + Api::Enum(enm) => expanded.extend(expand_enum(enm, &ffi.ident)), Api::CxxType(ety) => { let ident = &ety.name.rust; if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) { @@ -317,7 +317,7 @@ fn expand_struct_forbid_drop(strct: &Struct) -> TokenStream { } } -fn expand_enum(enm: &Enum) -> TokenStream { +fn expand_enum(enm: &Enum, ffi: &Ident) -> TokenStream { let ident = &enm.name.rust; let doc = &enm.doc; let attrs = &enm.attrs; @@ -352,6 +352,49 @@ fn expand_enum(enm: &Enum) -> TokenStream { } }; + let gaps = { + // discriminants must be sorted to build the range patterns of the gaps between them + let discriminants = { + let mut discriminants: Vec<_> = enm + .variants + .iter() + .map(|variant| variant.discriminant) + .collect(); + discriminants.sort(); + discriminants + }; + + // When map_windows gets stabilized: + // let gaps = std::iter::once(None) + // .chain(discriminants.into_iter().map(Some)) + // .chain(std::iter::once(None)) + // .map_windows::<_, _, 2>(|&[start, end]| match (start, end) ...); + let gaps = { + let starts = + std::iter::once(None).chain(discriminants.iter().map(|d| d.checked_succ())); + let ends = discriminants + .iter() + .copied() + .map(Some) + .chain(std::iter::once(None)); + + starts.zip(ends) + }; + + let gaps = gaps.flat_map(|(start, end)| match (start, end) { + (None, None) => None, + // ..#end is not a valid range pattern when end is the first admissible value for the discriminant (typically 0 for unsigned discriminants) + (None, Some(end)) if end == enm.repr.limits().min => None, + (None, Some(end)) => Some(quote! { ..#end }), + (Some(start), None) => Some(quote! { #start.. }), + (Some(start), Some(end)) if start == end => None, + (Some(start), Some(end)) => Some(quote! { #start..#end }), + }); + + quote! { #(#gaps)|* } + }; + let gaps_macro = format_ident!("_{}", ident); + quote! { #doc #derives @@ -364,6 +407,11 @@ fn expand_enum(enm: &Enum) -> TokenStream { #(#variants)* } + #[macro_export] macro_rules! #gaps_macro { + () => { $crate::#ffi::#ident{ repr: #gaps } }; + ($i:ident) => { $crate::#ffi::#ident{ repr: $i @ #gaps } }; + } + unsafe impl ::cxx::ExternType for #ident { #[allow(unused_attributes)] // incorrect lint #[doc(hidden)] diff --git a/syntax/discriminant.rs b/syntax/discriminant.rs index a8400aa9a..e658faec7 100644 --- a/syntax/discriminant.rs +++ b/syntax/discriminant.rs @@ -1,4 +1,7 @@ -use crate::syntax::Atom::{self, *}; +use crate::syntax::{ + Atom::{self, *}, + EnumRepr, +}; use proc_macro2::{Literal, Span, TokenStream}; use quote::ToTokens; use std::cmp::Ordering; @@ -179,7 +182,7 @@ impl Discriminant { } } - #[cfg(feature = "experimental-enum-variants-from-header")] + #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build pub(crate) const fn checked_succ(self) -> Option { match self.sign { Sign::Negative => { @@ -274,10 +277,10 @@ fn parse_int_suffix(suffix: &str) -> Result> { } #[derive(Copy, Clone)] -struct Limits { +pub(crate) struct Limits { repr: Atom, - min: Discriminant, - max: Discriminant, + pub min: Discriminant, + pub max: Discriminant, } impl Limits { @@ -333,3 +336,16 @@ const LIMITS: [Limits; 8] = [ max: Discriminant::pos(i64::MAX as u64), }, ]; + +impl EnumRepr { + #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build + pub fn limits(&self) -> Limits { + match self { + &EnumRepr::Native { atom, repr_type: _ } => { + Limits::of(atom).expect("Unexpected EnumRepr") + } + #[cfg(feature = "experimental-enum-variants-from-header")] + &EnumRepr::Foreign { rust_type: _ } => todo!(), + } + } +} diff --git a/tests/test.rs b/tests/test.rs index ac396589c..6da60a31d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -80,23 +80,33 @@ fn test_c_return() { assert_eq!(2021, ffi::c_return_sum(2020, 1)); match ffi::c_return_enum(0) { enm @ ffi::Enum::AVal => assert_eq!(0, enm.repr), - _ => assert!(false), + ffi::Enum::BVal => assert!(false), + ffi::Enum::LastVal => assert!(false), + cxx_test_suite::_Enum!() => assert!(false), } match ffi::c_return_enum(1) { + ffi::Enum::AVal => assert!(false), enm @ ffi::Enum::BVal => assert_eq!(2020, enm.repr), - _ => assert!(false), + ffi::Enum::LastVal => assert!(false), + cxx_test_suite::_Enum!() => assert!(false), } match ffi::c_return_enum(2021) { + ffi::Enum::AVal => assert!(false), + ffi::Enum::BVal => assert!(false), enm @ ffi::Enum::LastVal => assert_eq!(2021, enm.repr), - _ => assert!(false), + cxx_test_suite::_Enum!() => assert!(false), } match ffi::c_return_ns_enum(0) { enm @ ffi::AEnum::AAVal => assert_eq!(0, enm.repr), - _ => assert!(false), + ffi::AEnum::ABVal => assert!(false), + ffi::AEnum::ACVal => assert!(false), + cxx_test_suite::_AEnum!() => assert!(false), } match ffi::c_return_nested_ns_enum(0) { enm @ ffi::ABEnum::ABAVal => assert_eq!(0, enm.repr), - _ => assert!(false), + ffi::ABEnum::ABBVal => assert!(false), + ffi::ABEnum::ABCVal => assert!(false), + cxx_test_suite::_ABEnum!() => assert!(false), } }