Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _Enum! macros to replace _ arms when matching shared enums #1376

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions book/src/shared.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -95,7 +100,7 @@ fn main() {
Suit::Diamonds => ...,
Suit::Hearts => ...,
Suit::Spades => ...,
_ => ..., // fallback arm
_Suit!(unlisted) => ..., // fallback arm
}
}
```
Expand Down
52 changes: 50 additions & 2 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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)]
Expand Down
26 changes: 21 additions & 5 deletions syntax/discriminant.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Self> {
match self.sign {
Sign::Negative => {
Expand Down Expand Up @@ -274,10 +277,10 @@ fn parse_int_suffix(suffix: &str) -> Result<Option<Atom>> {
}

#[derive(Copy, Clone)]
struct Limits {
pub(crate) struct Limits {
repr: Atom,
min: Discriminant,
max: Discriminant,
pub min: Discriminant,
pub max: Discriminant,
}

impl Limits {
Expand Down Expand Up @@ -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!(),
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB: this TODO should obviously be fixed before this can be merged.

}
}
20 changes: 15 additions & 5 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down
Loading