Skip to content

Commit caae058

Browse files
committed
feat(enum): add support for string backed enums
Refs: #178
1 parent 1aba325 commit caae058

File tree

5 files changed

+52
-45
lines changed

5 files changed

+52
-45
lines changed

crates/macros/src/enum_.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::convert::TryFrom;
22

3-
use darling::FromAttributes;
3+
use darling::{util::Flag, FromAttributes};
44
use itertools::Itertools;
55
use proc_macro2::TokenStream;
66
use quote::{quote, ToTokens};
7-
use syn::{Expr, Fields, Ident, ItemEnum, Lit};
7+
use syn::{Fields, Ident, ItemEnum, Lit};
88

99
use crate::{
1010
helpers::get_docs,
@@ -18,7 +18,7 @@ struct PhpEnumAttribute {
1818
#[darling(flatten)]
1919
rename: PhpRename,
2020
#[darling(default)]
21-
allow_discriminants: bool,
21+
allow_native_discriminants: Flag,
2222
rename_cases: Option<RenameRule>,
2323
vis: Option<Visibility>,
2424
attrs: Vec<syn::Attribute>,
@@ -29,7 +29,7 @@ struct PhpEnumAttribute {
2929
struct PhpEnumVariantAttribute {
3030
#[darling(flatten)]
3131
rename: PhpRename,
32-
discriminant: Option<Expr>,
32+
discriminant: Option<Lit>,
3333
// TODO: Implement doc support for enum variants
3434
#[allow(dead_code)]
3535
attrs: Vec<syn::Attribute>,
@@ -47,8 +47,8 @@ pub fn parser(mut input: ItemEnum) -> Result<TokenStream> {
4747
if variant.fields != Fields::Unit {
4848
bail!("Enum cases must be unit variants, found: {:?}", variant);
4949
}
50-
if !php_attr.allow_discriminants && variant.discriminant.is_some() {
51-
bail!(variant => "Native discriminants are currently not exported to PHP. To set a discriminant, use the `#[php(allow_discriminants)]` attribute on the enum. To export discriminants, set the #[php(discriminant = ...)] attribute on the enum case.");
50+
if !php_attr.allow_native_discriminants.is_present() && variant.discriminant.is_some() {
51+
bail!(variant => "Native discriminants are currently not exported to PHP. To set a discriminant, use the `#[php(allow_native_discriminants)]` attribute on the enum. To export discriminants, set the #[php(discriminant = ...)] attribute on the enum case.");
5252
}
5353

5454
let variant_attr = PhpEnumVariantAttribute::from_attributes(&variant.attrs)?;
@@ -219,21 +219,17 @@ enum Discriminant {
219219
Integer(i64),
220220
}
221221

222-
impl TryFrom<&Expr> for Discriminant {
222+
impl TryFrom<&Lit> for Discriminant {
223223
type Error = syn::Error;
224224

225-
fn try_from(expr: &Expr) -> Result<Self> {
226-
match expr {
227-
Expr::Lit(expr) => match &expr.lit {
228-
Lit::Str(s) => Ok(Discriminant::String(s.value())),
229-
Lit::Int(i) => i.base10_parse::<i64>().map(Discriminant::Integer).map_err(
230-
|_| err!(expr => "Invalid integer literal for enum case: {:?}", expr.lit),
231-
),
232-
_ => bail!(expr => "Unsupported discriminant type: {:?}", expr.lit),
233-
},
234-
_ => {
235-
bail!(expr => "Unsupported discriminant type, expected a literal of type string or i64, found: {:?}", expr);
236-
}
225+
fn try_from(lit: &Lit) -> Result<Self> {
226+
match lit {
227+
Lit::Str(s) => Ok(Discriminant::String(s.value())),
228+
Lit::Int(i) => i
229+
.base10_parse::<i64>()
230+
.map(Discriminant::Integer)
231+
.map_err(|_| err!(lit => "Invalid integer literal for enum case: {:?}", lit)),
232+
_ => bail!(lit => "Unsupported discriminant type: {:?}", lit),
237233
}
238234
}
239235
}
@@ -242,7 +238,7 @@ impl ToTokens for Discriminant {
242238
fn to_tokens(&self, tokens: &mut TokenStream) {
243239
tokens.extend(match self {
244240
Discriminant::String(s) => {
245-
quote! { ::ext_php_rs::enum_::Discriminant::String(#s.to_string()) }
241+
quote! { ::ext_php_rs::enum_::Discriminant::String(#s) }
246242
}
247243
Discriminant::Integer(i) => {
248244
quote! { ::ext_php_rs::enum_::Discriminant::Int(#i) }

src/builders/enum_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
error::Result,
77
ffi::{zend_enum_add_case, zend_register_internal_enum},
88
flags::{DataType, MethodFlags},
9-
types::ZendStr,
9+
types::{ZendStr, Zval},
1010
zend::FunctionEntry,
1111
};
1212

@@ -73,7 +73,7 @@ impl EnumBuilder {
7373
let name = ZendStr::new(case.name, true);
7474
let value = match &case.discriminant {
7575
Some(value) => {
76-
let value = value.into_zval(false)?;
76+
let value: Zval = value.try_into()?;
7777
let mut zv = core::mem::ManuallyDrop::new(value);
7878
(&raw mut zv).cast()
7979
}

src/enum_.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
//! This module defines the `PhpEnum` trait and related types for Rust enums that are exported to PHP.
22
use crate::{
3-
convert::IntoZval, describe::DocComments, error::Result, flags::DataType, types::Zval,
3+
convert::IntoZval,
4+
describe::DocComments,
5+
error::{Error, Result},
6+
flags::DataType,
7+
types::Zval,
48
};
59

610
/// Implemented on Rust enums which are exported to PHP.
@@ -39,17 +43,13 @@ pub enum Discriminant {
3943
String(&'static str),
4044
}
4145

42-
impl Discriminant {
43-
/// Converts the discriminant to a PHP value.
44-
///
45-
/// # Errors
46-
///
47-
/// Returns an error if the conversion fails. See [`String`] and [`i64`] [`IntoZval`] implementations
48-
/// for more details on potential errors.
49-
pub fn into_zval(&self, persistent: bool) -> Result<Zval> {
50-
match self {
51-
Discriminant::Int(i) => i.into_zval(persistent),
52-
Discriminant::String(s) => s.into_zval(persistent),
46+
impl TryFrom<&Discriminant> for Zval {
47+
type Error = Error;
48+
49+
fn try_from(value: &Discriminant) -> Result<Self> {
50+
match value {
51+
Discriminant::Int(i) => i.into_zval(false),
52+
Discriminant::String(s) => s.into_zval(true),
5353
}
5454
}
5555
}

tests/src/integration/enum_/enum.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
var_dump($enum_variant);
77
assert($enum_variant === TestEnum::Variant1);
88
assert($enum_variant !== TestEnum::Variant2);
9+
assert(TestEnum::cases() === [TestEnum::Variant1, TestEnum::Variant2]);
910

10-
$backed = IntBackedEnum::Variant1;
11-
assert($backed->value === 1);
11+
assert(IntBackedEnum::Variant1->value === 1);
12+
assert(IntBackedEnum::from(2) === IntBackedEnum::Variant2);
13+
assert(IntBackedEnum::tryFrom(1) === IntBackedEnum::Variant1);
14+
assert(IntBackedEnum::tryFrom(3) === null);
15+
16+
assert(StringBackedEnum::Variant1->value === 'foo');
17+
assert(StringBackedEnum::from('bar') === StringBackedEnum::Variant2);
18+
assert(StringBackedEnum::tryFrom('foo') === StringBackedEnum::Variant1);
19+
assert(StringBackedEnum::tryFrom('baz') === null);

tests/src/integration/enum_/mod.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use ext_php_rs::{php_enum, prelude::ModuleBuilder};
22

33
#[php_enum]
4-
#[php(allow_discriminants)]
4+
#[php(allow_native_discriminants)]
55
pub enum TestEnum {
66
// #[php(discriminant = 2)]
77
Variant1,
@@ -17,16 +17,19 @@ pub enum IntBackedEnum {
1717
Variant2,
1818
}
1919

20-
// #[php_enum]
21-
// pub enum StringBackedEnum {
22-
// #[php(discriminant = "foo")]
23-
// Variant1,
24-
// #[php(discriminant = "bar")]
25-
// Variant2,
26-
// }
20+
#[php_enum]
21+
pub enum StringBackedEnum {
22+
#[php(discriminant = "foo")]
23+
Variant1,
24+
#[php(discriminant = "bar")]
25+
Variant2,
26+
}
2727

2828
pub fn build_module(builder: ModuleBuilder) -> ModuleBuilder {
29-
builder.r#enum::<TestEnum>().r#enum::<IntBackedEnum>()
29+
builder
30+
.r#enum::<TestEnum>()
31+
.r#enum::<IntBackedEnum>()
32+
.r#enum::<StringBackedEnum>()
3033
}
3134

3235
#[cfg(test)]

0 commit comments

Comments
 (0)