Merge pull request #170 from jgalenson/enums

Support C-style enums
This commit is contained in:
David Tolnay 2020-04-30 22:49:50 -07:00 committed by GitHub
commit 02d58bb593
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 395 additions and 12 deletions

View File

@ -3,7 +3,7 @@ use crate::gen::{include, Opt};
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::namespace::Namespace;
use crate::syntax::symbol::Symbol;
use crate::syntax::{mangle, Api, ExternFn, ExternType, Signature, Struct, Type, Types, Var};
use crate::syntax::{mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, Types, Var};
use proc_macro2::Ident;
use std::collections::HashMap;
@ -64,6 +64,10 @@ pub(super) fn gen(
out.next_section();
write_struct(out, strct);
}
Api::Enum(enm) => {
out.next_section();
write_enum(out, enm);
}
Api::RustType(ety) => {
if let Some(methods) = methods_for_type.get(&ety.ident) {
out.next_section();
@ -353,6 +357,22 @@ fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: &[&Ex
writeln!(out, "}};");
}
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);
for variant in &enm.variants {
write!(out, " ");
write!(out, "{}", variant.ident);
if let Some(discriminant) = &variant.discriminant {
write!(out, " = {}", discriminant);
}
writeln!(out, ",");
}
writeln!(out, "}};");
}
fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
let mut has_cxx_throws = false;
for api in apis {

View File

@ -2,7 +2,7 @@ use crate::syntax::atom::Atom::{self, *};
use crate::syntax::namespace::Namespace;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
self, check, mangle, Api, ExternFn, ExternType, Signature, Struct, Type, Types,
self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, Types,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
@ -36,6 +36,7 @@ pub fn bridge(namespace: &Namespace, ffi: ItemMod) -> Result<TokenStream> {
match api {
Api::Include(_) | Api::RustType(_) => {}
Api::Struct(strct) => expanded.extend(expand_struct(strct)),
Api::Enum(enm) => expanded.extend(expand_enum(enm)),
Api::CxxType(ety) => expanded.extend(expand_cxx_type(ety)),
Api::CxxFunction(efn) => {
expanded.extend(expand_cxx_function_shim(namespace, efn, types));
@ -123,6 +124,34 @@ fn expand_struct(strct: &Struct) -> TokenStream {
}
}
fn expand_enum(enm: &Enum) -> TokenStream {
let ident = &enm.ident;
let doc = &enm.doc;
let variants = enm.variants.iter().scan(0, |next_discriminant, variant| {
// This span on the pub makes "private type in public interface" errors
// appear in the right place.
let vis = Token![pub](variant.ident.span());
let variant_ident = &variant.ident;
let discriminant = match variant.discriminant {
None => *next_discriminant,
Some(val) => val,
};
*next_discriminant = discriminant + 1;
Some(quote!( #vis const #variant_ident: Self = #ident(#discriminant)))
});
quote! {
#doc
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct #ident(u32);
#[allow(non_upper_case_globals)]
impl #ident {
#(#variants;)*
}
}
}
fn expand_cxx_type(ety: &ExternType) -> TokenStream {
let ident = &ety.ident;
let doc = &ety.doc;

View File

@ -1,9 +1,11 @@
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{
error, ident, Api, ExternFn, ExternType, Lang, Receiver, Ref, Slice, Struct, Ty1, Type, Types,
error, ident, Api, Enum, ExternFn, ExternType, Lang, Receiver, Ref, Slice, Struct, Ty1, Type,
Types,
};
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
use quote::{quote, ToTokens};
use std::collections::HashSet;
use std::fmt::Display;
use syn::{Error, Result};
@ -41,6 +43,7 @@ fn do_typecheck(cx: &mut Check) {
for api in cx.apis {
match api {
Api::Struct(strct) => check_api_struct(cx, strct),
Api::Enum(enm) => check_api_enum(cx, enm),
Api::CxxType(ty) | Api::RustType(ty) => check_api_type(cx, ty),
Api::CxxFunction(efn) | Api::RustFunction(efn) => check_api_fn(cx, efn),
_ => {}
@ -68,6 +71,7 @@ impl Check<'_> {
fn check_type_ident(cx: &mut Check, ident: &Ident) {
if Atom::from(ident).is_none()
&& !cx.types.structs.contains_key(ident)
&& !cx.types.enums.contains_key(ident)
&& !cx.types.cxx.contains(ident)
&& !cx.types.rust.contains(ident)
{
@ -188,6 +192,28 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) {
}
}
fn check_api_enum(cx: &mut Check, enm: &Enum) {
check_reserved_name(cx, &enm.ident);
if enm.variants.is_empty() {
let span = span_for_enum_error(enm);
cx.error(span, "enums without any variants are not supported");
}
let mut discriminants = HashSet::new();
enm.variants.iter().fold(0, |next_discriminant, variant| {
let discriminant = match variant.discriminant {
None => next_discriminant,
Some(val) => val,
};
if !discriminants.insert(discriminant) {
let msg = format!("discriminant value `{}` already exists", discriminant);
cx.error(span_for_enum_error(enm), msg);
}
discriminant + 1
});
}
fn check_api_type(cx: &mut Check, ty: &ExternType) {
check_reserved_name(cx, &ty.ident);
}
@ -320,6 +346,13 @@ fn span_for_struct_error(strct: &Struct) -> TokenStream {
quote!(#struct_token #brace_token)
}
fn span_for_enum_error(enm: &Enum) -> TokenStream {
let enum_token = enm.enum_token;
let mut brace_token = Group::new(Delimiter::Brace, TokenStream::new());
brace_token.set_span(enm.brace_token.span);
quote!(#enum_token #brace_token)
}
fn span_for_receiver_error(receiver: &Receiver) -> TokenStream {
let ampersand = receiver.ampersand;
let lifetime = &receiver.lifetime;

View File

@ -23,6 +23,12 @@ pub(crate) fn check_all(apis: &[Api], errors: &mut Vec<Error>) {
errors.extend(check(&field.ident).err());
}
}
Api::Enum(enm) => {
errors.extend(check(&enm.ident).err());
for variant in &enm.variants {
errors.extend(check(&variant.ident).err());
}
}
Api::CxxType(ety) | Api::RustType(ety) => {
errors.extend(check(&ety.ident).err());
}

View File

@ -29,6 +29,7 @@ pub use self::types::Types;
pub enum Api {
Include(LitStr),
Struct(Struct),
Enum(Enum),
CxxType(ExternType),
CxxFunction(ExternFn),
RustType(ExternType),
@ -50,6 +51,14 @@ pub struct Struct {
pub fields: Vec<Var>,
}
pub struct Enum {
pub doc: Doc,
pub enum_token: Token![enum],
pub ident: Ident,
pub brace_token: Brace,
pub variants: Vec<Variant>,
}
pub struct ExternFn {
pub lang: Lang,
pub doc: Doc,
@ -83,6 +92,11 @@ pub struct Receiver {
pub shorthand: bool,
}
pub struct Variant {
pub ident: Ident,
pub discriminant: Option<u32>,
}
pub enum Type {
Ident(Ident),
RustBox(Box<Ty1>),

View File

@ -1,14 +1,15 @@
use crate::syntax::Atom::*;
use crate::syntax::{
attrs, error, Api, Doc, ExternFn, ExternType, Lang, Receiver, Ref, Signature, Slice, Struct,
Ty1, Type, Var,
attrs, error, Api, Doc, Enum, ExternFn, ExternType, Lang, Receiver, Ref, Signature, Slice,
Struct, Ty1, Type, Var, Variant,
};
use quote::{format_ident, quote};
use syn::punctuated::Punctuated;
use syn::{
Abi, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, GenericArgument, Ident,
Item, ItemForeignMod, ItemStruct, Pat, PathArguments, Result, ReturnType, Token,
Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
Abi, 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,
};
pub mod kw {
@ -23,6 +24,10 @@ pub fn parse_items(items: Vec<Item>) -> Result<Vec<Api>> {
let strct = parse_struct(item)?;
apis.push(strct);
}
Item::Enum(item) => {
let enm = parse_enum(item)?;
apis.push(enm);
}
Item::ForeignMod(foreign_mod) => {
let functions = parse_foreign_mod(foreign_mod)?;
apis.extend(functions);
@ -78,6 +83,75 @@ fn parse_struct(item: ItemStruct) -> Result<Api> {
}))
}
fn parse_enum(item: ItemEnum) -> Result<Api> {
let generics = &item.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
let enum_token = item.enum_token;
let ident = &item.ident;
let where_clause = &generics.where_clause;
let span = quote!(#enum_token #ident #generics #where_clause);
return Err(Error::new_spanned(
span,
"enums with generic parameters are not allowed",
));
}
let mut doc = Doc::new();
attrs::parse(&item.attrs, &mut doc, None)?;
for variant in &item.variants {
match &variant.fields {
Fields::Unit => {}
_ => {
return Err(Error::new_spanned(
variant,
"enums with data are not allowed",
))
}
}
}
Ok(Api::Enum(Enum {
doc,
enum_token: item.enum_token,
ident: item.ident,
brace_token: item.brace_token,
variants: item
.variants
.into_iter()
.map(parse_variant)
.collect::<Result<_>>()?,
}))
}
fn parse_variant(variant: RustVariant) -> Result<Variant> {
match &variant.discriminant {
None => Ok(Variant {
ident: variant.ident,
discriminant: None,
}),
Some((
_,
Expr::Lit(ExprLit {
lit: Lit::Int(n), ..
}),
)) => match n.base10_digits().parse() {
Ok(val) => Ok(Variant {
ident: variant.ident,
discriminant: 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",
)),
}
}
fn parse_foreign_mod(foreign_mod: ItemForeignMod) -> Result<Vec<Api>> {
let lang = parse_lang(foreign_mod.abi)?;
let api_type = match lang {

View File

@ -1,6 +1,6 @@
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::set::OrderedSet as Set;
use crate::syntax::{Api, Derive, ExternType, Struct, Type};
use crate::syntax::{Api, Derive, Enum, ExternType, Struct, Type};
use proc_macro2::Ident;
use quote::quote;
use std::collections::BTreeMap as Map;
@ -9,6 +9,7 @@ use syn::{Error, Result};
pub struct Types<'a> {
pub all: Set<'a, Type>,
pub structs: Map<Ident, &'a Struct>,
pub enums: Map<Ident, &'a Enum>,
pub cxx: Set<'a, Ident>,
pub rust: Set<'a, Ident>,
}
@ -17,6 +18,7 @@ impl<'a> Types<'a> {
pub fn collect(apis: &'a [Api]) -> Result<Self> {
let mut all = Set::new();
let mut structs = Map::new();
let mut enums = Map::new();
let mut cxx = Set::new();
let mut rust = Set::new();
@ -46,7 +48,11 @@ impl<'a> Types<'a> {
Api::Include(_) => {}
Api::Struct(strct) => {
let ident = &strct.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
if structs.contains_key(ident)
|| enums.contains_key(ident)
|| cxx.contains(ident)
|| rust.contains(ident)
{
return Err(duplicate_struct(strct));
}
structs.insert(strct.ident.clone(), strct);
@ -54,16 +60,35 @@ impl<'a> Types<'a> {
visit(&mut all, &field.ty);
}
}
Api::Enum(enm) => {
let ident = &enm.ident;
if structs.contains_key(ident)
|| enums.contains_key(ident)
|| cxx.contains(ident)
|| rust.contains(ident)
{
return Err(duplicate_enum(enm));
}
enums.insert(enm.ident.clone(), enm);
}
Api::CxxType(ety) => {
let ident = &ety.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
if structs.contains_key(ident)
|| enums.contains_key(ident)
|| cxx.contains(ident)
|| rust.contains(ident)
{
return Err(duplicate_type(ety));
}
cxx.insert(ident);
}
Api::RustType(ety) => {
let ident = &ety.ident;
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
if structs.contains_key(ident)
|| enums.contains_key(ident)
|| cxx.contains(ident)
|| rust.contains(ident)
{
return Err(duplicate_type(ety));
}
rust.insert(ident);
@ -82,6 +107,7 @@ impl<'a> Types<'a> {
Ok(Types {
all,
structs,
enums,
cxx,
rust,
})
@ -126,6 +152,13 @@ fn duplicate_struct(strct: &Struct) -> Error {
Error::new_spanned(range, "duplicate type")
}
fn duplicate_enum(enm: &Enum) -> Error {
let enum_token = enm.enum_token;
let ident = &enm.ident;
let range = quote!(#enum_token #ident);
Error::new_spanned(range, "duplicate type")
}
fn duplicate_type(ety: &ExternType) -> Error {
let type_token = ety.type_token;
let ident = &ety.ident;

View File

@ -13,6 +13,12 @@ pub mod ffi {
z: usize,
}
enum Enum {
AVal,
BVal = 2020,
CVal,
}
extern "C" {
include!("tests/ffi/tests.h");
@ -36,6 +42,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_take_primitive(n: usize);
fn c_take_shared(shared: Shared);
@ -57,6 +64,7 @@ pub mod ffi {
fn c_take_ref_rust_vec(v: &Vec<u8>);
fn c_take_ref_rust_vec_copy(v: &Vec<u8>);
fn c_take_callback(callback: fn(String) -> usize);
fn c_take_enum(e: Enum);
fn c_try_return_void() -> Result<()>;
fn c_try_return_primitive() -> Result<usize>;
@ -92,6 +100,7 @@ pub mod ffi {
fn r_return_ref_rust_vec(shared: &Shared) -> &Vec<u8>;
fn r_return_identity(_: usize) -> usize;
fn r_return_sum(_: usize, _: usize) -> usize;
fn r_return_enum(n: u32) -> Enum;
fn r_take_primitive(n: usize);
fn r_take_shared(shared: Shared);
@ -105,6 +114,7 @@ pub mod ffi {
fn r_take_unique_ptr_string(s: UniquePtr<CxxString>);
fn r_take_rust_vec(v: Vec<u8>);
fn r_take_ref_rust_vec(v: &Vec<u8>);
fn r_take_enum(e: Enum);
fn r_try_return_void() -> Result<()>;
fn r_try_return_primitive() -> Result<usize>;
@ -198,6 +208,16 @@ fn r_return_sum(n1: usize, n2: usize) -> usize {
n1 + n2
}
fn r_return_enum(n: u32) -> ffi::Enum {
if n <= 0 {
ffi::Enum::AVal
} else if n <= 2020 {
ffi::Enum::BVal
} else {
ffi::Enum::CVal
}
}
fn r_take_primitive(n: usize) {
assert_eq!(n, 2020);
}
@ -247,6 +267,10 @@ fn r_take_ref_rust_vec(v: &Vec<u8>) {
let _ = v;
}
fn r_take_enum(e: ffi::Enum) {
let _ = e;
}
fn r_try_return_void() -> Result<(), Error> {
Ok(())
}

View File

@ -106,6 +106,16 @@ 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)) {
return Enum::AVal;
} else if (n <= static_cast<uint32_t>(Enum::BVal)) {
return Enum::BVal;
} else {
return Enum::CVal;
}
}
void c_take_primitive(size_t n) {
if (n == 2020) {
cxx_test_suite_set_correct();
@ -238,6 +248,12 @@ void c_take_callback(rust::Fn<size_t(rust::String)> callback) {
callback("2020");
}
void c_take_enum(Enum e) {
if (e == Enum::AVal) {
cxx_test_suite_set_correct();
}
}
void c_try_return_void() {}
size_t c_try_return_primitive() { return 2020; }
@ -295,6 +311,9 @@ extern "C" const char *cxx_run_test() noexcept {
ASSERT(*r_return_unique_ptr_string() == "2020");
ASSERT(r_return_identity(2020) == 2020);
ASSERT(r_return_sum(2020, 1) == 2021);
ASSERT(r_return_enum(0) == Enum::AVal);
ASSERT(r_return_enum(1) == Enum::BVal);
ASSERT(r_return_enum(2021) == Enum::CVal);
r_take_primitive(2020);
r_take_shared(Shared{2020});
@ -306,6 +325,7 @@ extern "C" const char *cxx_run_test() noexcept {
r_take_rust_string(rust::String("2020"));
r_take_unique_ptr_string(
std::unique_ptr<std::string>(new std::string("2020")));
r_take_enum(Enum::AVal);
ASSERT(r_try_return_primitive() == 2020);
try {

View File

@ -7,6 +7,7 @@ namespace tests {
struct R;
struct Shared;
enum class Enum : uint32_t;
class C {
public:
@ -40,6 +41,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);
void c_take_primitive(size_t n);
void c_take_shared(Shared shared);
@ -61,6 +63,7 @@ void c_take_rust_vec_shared_forward_iterator(rust::Vec<Shared> v);
void c_take_ref_rust_vec(const rust::Vec<uint8_t> &v);
void c_take_ref_rust_vec_copy(const rust::Vec<uint8_t> &v);
void c_take_callback(rust::Fn<size_t(rust::String)> callback);
void c_take_enum(Enum e);
void c_try_return_void();
size_t c_try_return_primitive();

View File

@ -51,6 +51,18 @@ fn test_c_return() {
);
assert_eq!(2020, ffi::c_return_identity(2020));
assert_eq!(2021, ffi::c_return_sum(2020, 1));
match ffi::c_return_enum(0) {
ffi::Enum::AVal => {}
_ => assert!(false),
}
match ffi::c_return_enum(1) {
ffi::Enum::BVal => {}
_ => assert!(false),
}
match ffi::c_return_enum(2021) {
ffi::Enum::CVal => {}
_ => assert!(false),
}
}
#[test]
@ -106,6 +118,7 @@ fn test_c_take() {
]));
check!(ffi::c_take_ref_rust_vec(&test_vec));
check!(ffi::c_take_ref_rust_vec_copy(&test_vec));
check!(ffi::c_take_enum(ffi::Enum::AVal));
}
#[test]

8
tests/ui/data_enums.rs Normal file
View File

@ -0,0 +1,8 @@
#[cxx::bridge]
mod ffi {
enum A {
Field(u64),
}
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: enums with data are not allowed
--> $DIR/data_enums.rs:4:9
|
4 | Field(u64),
| ^^^^^^^^^^

View File

@ -0,0 +1,15 @@
#[cxx::bridge]
mod ffi {
enum A {
V1 = 10,
V2 = 10,
}
enum B {
V1 = 10,
V2,
V3 = 11,
}
}
fn main() {}

View File

@ -0,0 +1,18 @@
error: discriminant value `10` already exists
--> $DIR/duplicate_enum_discriminants.rs:3:5
|
3 | / enum A {
4 | | V1 = 10,
5 | | V2 = 10,
6 | | }
| |_____^
error: discriminant value `11` already exists
--> $DIR/duplicate_enum_discriminants.rs:8:5
|
8 | / enum B {
9 | | V1 = 10,
10 | | V2,
11 | | V3 = 11,
12 | | }
| |_____^

8
tests/ui/empty_enum.rs Normal file
View File

@ -0,0 +1,8 @@
#[cxx::bridge]
mod ffi {
enum A {
}
}
fn main() {}

View File

@ -0,0 +1,7 @@
error: enums without any variants are not supported
--> $DIR/empty_enum.rs:3:5
|
3 | / enum A {
4 | |
5 | | }
| |_____^

View File

@ -0,0 +1,16 @@
#[cxx::bridge]
mod ffi {
enum A {
FieldA,
FieldB,
}
}
fn main() {}
fn matcher(a: ffi::A) -> u32 {
match a {
ffi::A::FieldA => 2020,
ffi::A::FieldB => 2021,
}
}

View File

@ -0,0 +1,11 @@
error[E0004]: non-exhaustive patterns: `A(2u32..=std::u32::MAX)` not covered
--> $DIR/enum_match_without_wildcard.rs:12:11
|
1 | #[cxx::bridge]
| -------------- `ffi::A` defined here
...
12 | match a {
| ^ pattern `A(2u32..=std::u32::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`

8
tests/ui/generic_enum.rs Normal file
View File

@ -0,0 +1,8 @@
#[cxx::bridge]
mod ffi {
enum A<T> {
Field,
}
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: enums with generic parameters are not allowed
--> $DIR/generic_enum.rs:3:5
|
3 | enum A<T> {
| ^^^^^^^^^

View File

@ -0,0 +1,8 @@
#[cxx::bridge]
mod ffi {
enum A {
Field = 2020 + 1,
}
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: enums with non-integer literal discriminants are not supported
--> $DIR/non_integer_discriminant_enum.rs:4:9
|
4 | Field = 2020 + 1,
| ^^^^^^^^^^^^^^^^