Skip to content

Commit b66f21d

Browse files
committed
feat(enum): implement IntoZval and FromZval for enums
Refs: #178
1 parent 4d8e7aa commit b66f21d

File tree

9 files changed

+219
-33
lines changed

9 files changed

+219
-33
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ skeptic = "0.13"
2525

2626
[build-dependencies]
2727
anyhow = "1"
28-
bindgen = "0.68.1"
28+
bindgen = "0.70"
2929
cc = "1.0"
3030
skeptic = "0.13"
3131

allowed_bindings.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ bind! {
8888
zend_declare_property,
8989
zend_do_implement_interface,
9090
zend_enum_add_case,
91+
zend_enum_get_case,
9192
zend_enum_new,
9293
zend_execute_data,
9394
zend_function_entry,

crates/macros/src/enum_.rs

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ pub fn parser(mut input: ItemEnum) -> Result<TokenStream> {
101101
}
102102
}
103103

104-
let enum_props = Enum {
105-
ident: &input.ident,
106-
attrs: php_attr,
104+
let enum_props = Enum::new(
105+
&input.ident,
106+
&php_attr,
107107
docs,
108108
cases,
109-
flags: None, // TODO: Implement flags support
110-
};
109+
None, // TODO: Implement flags support
110+
);
111111

112112
Ok(quote! {
113113
#[allow(dead_code)]
@@ -120,28 +120,45 @@ pub fn parser(mut input: ItemEnum) -> Result<TokenStream> {
120120
#[derive(Debug)]
121121
pub struct Enum<'a> {
122122
ident: &'a Ident,
123-
attrs: PhpEnumAttribute,
123+
name: String,
124124
docs: Vec<String>,
125125
cases: Vec<EnumCase>,
126-
// TODO: Implement flags support
127-
#[allow(dead_code)]
128126
flags: Option<String>,
129127
}
130128

131-
impl ToTokens for Enum<'_> {
132-
fn to_tokens(&self, tokens: &mut TokenStream) {
129+
impl<'a> Enum<'a> {
130+
fn new(
131+
ident: &'a Ident,
132+
attrs: &PhpEnumAttribute,
133+
docs: Vec<String>,
134+
cases: Vec<EnumCase>,
135+
flags: Option<String>,
136+
) -> Self {
137+
let name = attrs.rename.rename(ident.to_string(), RenameRule::Pascal);
138+
139+
Self {
140+
ident,
141+
name,
142+
docs,
143+
cases,
144+
flags,
145+
}
146+
}
147+
148+
fn registered_class(&self) -> TokenStream {
133149
let ident = &self.ident;
134-
let enum_name = self
135-
.attrs
136-
.rename
137-
.rename(ident.to_string(), RenameRule::Pascal);
138-
let flags = quote! { ::ext_php_rs::flags::ClassFlags::Enum };
150+
let name = &self.name;
151+
let flags = self
152+
.flags
153+
.as_ref()
154+
.map(|f| quote! { | #f })
155+
.unwrap_or_default();
156+
let flags = quote! { ::ext_php_rs::flags::ClassFlags::Enum #flags };
139157
let docs = &self.docs;
140-
let cases = &self.cases;
141158

142-
let class = quote! {
159+
quote! {
143160
impl ::ext_php_rs::class::RegisteredClass for #ident {
144-
const CLASS_NAME: &'static str = #enum_name;
161+
const CLASS_NAME: &'static str = #name;
145162
const BUILDER_MODIFIER: ::std::option::Option<
146163
fn(::ext_php_rs::builders::ClassBuilder) -> ::ext_php_rs::builders::ClassBuilder
147164
> = None;
@@ -186,14 +203,54 @@ impl ToTokens for Enum<'_> {
186203
::ext_php_rs::internal::class::PhpClassImplCollector::<Self>::default().get_constants()
187204
}
188205
}
189-
};
190-
let enum_impl = quote! {
191-
impl ::ext_php_rs::enum_::PhpEnum for #ident {
206+
}
207+
}
208+
209+
fn registered_enum(&self) -> TokenStream {
210+
let ident = &self.ident;
211+
let cases = &self.cases;
212+
let case_from_names = self.cases.iter().map(|case| {
213+
let ident = &case.ident;
214+
let name = &case.name;
215+
quote! {
216+
#name => Ok(Self::#ident)
217+
}
218+
});
219+
let case_to_names = self.cases.iter().map(|case| {
220+
let ident = &case.ident;
221+
let name = &case.name;
222+
quote! {
223+
Self::#ident => #name
224+
}
225+
});
226+
227+
quote! {
228+
impl ::ext_php_rs::enum_::RegisteredEnum for #ident {
192229
const CASES: &'static [::ext_php_rs::enum_::EnumCase] = &[
193230
#(#cases,)*
194231
];
232+
233+
fn from_name(name: &str) -> ::ext_php_rs::error::Result<Self> {
234+
match name {
235+
#(#case_from_names,)*
236+
_ => Err(::ext_php_rs::error::Error::InvalidProperty),
237+
}
238+
}
239+
240+
fn to_name(&self) -> &'static str {
241+
match self {
242+
#(#case_to_names,)*
243+
}
244+
}
195245
}
196-
};
246+
}
247+
}
248+
}
249+
250+
impl ToTokens for Enum<'_> {
251+
fn to_tokens(&self, tokens: &mut TokenStream) {
252+
let class = self.registered_class();
253+
let enum_impl = self.registered_enum();
197254

198255
tokens.extend(quote! {
199256
#class

docsrs_bindings.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* automatically generated by rust-bindgen 0.68.1 */
1+
/* automatically generated by rust-bindgen 0.70.1 */
22

33
#[repr(C)]
44
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
@@ -2723,6 +2723,12 @@ extern "C" {
27232723
value: *mut zval,
27242724
);
27252725
}
2726+
extern "C" {
2727+
pub fn zend_enum_get_case(
2728+
ce: *mut zend_class_entry,
2729+
name: *mut zend_string,
2730+
) -> *mut zend_object;
2731+
}
27262732
extern "C" {
27272733
pub static mut zend_ce_throwable: *mut zend_class_entry;
27282734
}

src/builders/enum_builder.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
ffi::{zend_enum_add_case, zend_register_internal_enum},
88
flags::{DataType, MethodFlags},
99
types::{ZendStr, Zval},
10-
zend::FunctionEntry,
10+
zend::{ClassEntry, FunctionEntry},
1111
};
1212

1313
#[must_use]
@@ -16,6 +16,7 @@ pub struct EnumBuilder {
1616
pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
1717
pub(crate) cases: Vec<&'static EnumCase>,
1818
pub(crate) datatype: DataType,
19+
register: Option<fn(&'static mut ClassEntry)>,
1920
}
2021

2122
impl EnumBuilder {
@@ -25,6 +26,7 @@ impl EnumBuilder {
2526
methods: Vec::default(),
2627
cases: Vec::default(),
2728
datatype: DataType::Undef,
29+
register: None,
2830
}
2931
}
3032

@@ -48,6 +50,17 @@ impl EnumBuilder {
4850
self
4951
}
5052

53+
/// Function to register the class with PHP. This function is called after
54+
/// the class is built.
55+
///
56+
/// # Parameters
57+
///
58+
/// * `register` - The function to call to register the class.
59+
pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
60+
self.register = Some(register);
61+
self
62+
}
63+
5164
pub fn register(self) -> Result<()> {
5265
let mut methods = self
5366
.methods
@@ -84,6 +97,12 @@ impl EnumBuilder {
8497
}
8598
}
8699

100+
if let Some(register) = self.register {
101+
register(unsafe { &mut *class });
102+
} else {
103+
panic!("Enum was not registered with a registration function",);
104+
}
105+
87106
Ok(())
88107
}
89108
}

src/builders/module.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{convert::TryFrom, ffi::CString, mem, ptr};
22

33
use super::{ClassBuilder, FunctionBuilder};
44
#[cfg(feature = "enum")]
5-
use crate::{builders::enum_builder::EnumBuilder, enum_::PhpEnum};
5+
use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
66
use crate::{
77
class::RegisteredClass,
88
constant::IntoConst,
@@ -215,7 +215,7 @@ impl ModuleBuilder<'_> {
215215
#[cfg(feature = "enum")]
216216
pub fn r#enum<T>(mut self) -> Self
217217
where
218-
T: RegisteredClass + PhpEnum,
218+
T: RegisteredClass + RegisteredEnum,
219219
{
220220
self.enums.push(|| {
221221
let mut builder = EnumBuilder::new(T::CLASS_NAME);
@@ -226,7 +226,9 @@ impl ModuleBuilder<'_> {
226226
builder = builder.add_method(method, flags);
227227
}
228228

229-
builder
229+
builder.registration(|ce| {
230+
T::get_metadata().set_ce(ce);
231+
})
230232
});
231233

232234
self

src/enum_.rs

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,107 @@
11
//! This module defines the `PhpEnum` trait and related types for Rust enums that are exported to PHP.
2+
use std::ptr;
3+
24
use crate::{
3-
convert::IntoZval,
5+
boxed::ZBox,
6+
class::RegisteredClass,
7+
convert::{FromZendObject, FromZval, IntoZendObject, IntoZval},
48
describe::DocComments,
59
error::{Error, Result},
6-
flags::DataType,
7-
types::Zval,
10+
ffi::zend_enum_get_case,
11+
flags::{ClassFlags, DataType},
12+
types::{ZendObject, ZendStr, Zval},
813
};
914

1015
/// Implemented on Rust enums which are exported to PHP.
11-
pub trait PhpEnum {
16+
pub trait RegisteredEnum {
1217
/// The cases of the enum.
1318
const CASES: &'static [EnumCase];
19+
20+
/// # Errors
21+
///
22+
/// - [`Error::InvalidProperty`] if the enum does not have a case with the given name, an error is returned.
23+
fn from_name(name: &str) -> Result<Self>
24+
where
25+
Self: Sized;
26+
27+
/// Returns the variant name of the enum as it is registered in PHP.
28+
fn to_name(&self) -> &'static str;
29+
}
30+
31+
impl<T> FromZendObject<'_> for T
32+
where
33+
T: RegisteredEnum,
34+
{
35+
fn from_zend_object(obj: &ZendObject) -> Result<Self> {
36+
if !ClassFlags::from_bits_truncate(unsafe { (*obj.ce).ce_flags }).contains(ClassFlags::Enum)
37+
{
38+
return Err(Error::InvalidProperty);
39+
}
40+
41+
let name = obj
42+
.get_properties()?
43+
.get("name")
44+
.and_then(Zval::indirect)
45+
.and_then(Zval::str)
46+
.ok_or(Error::InvalidProperty)?;
47+
48+
T::from_name(name)
49+
}
50+
}
51+
52+
impl<T> FromZval<'_> for T
53+
where
54+
T: RegisteredEnum + RegisteredClass,
55+
{
56+
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
57+
58+
fn from_zval(zval: &Zval) -> Option<Self> {
59+
zval.object()
60+
.and_then(|obj| Self::from_zend_object(obj).ok())
61+
}
62+
}
63+
64+
impl<T> IntoZendObject for T
65+
where
66+
T: RegisteredEnum + RegisteredClass,
67+
{
68+
fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
69+
let mut name = ZendStr::new(T::to_name(&self), false);
70+
let variant = unsafe {
71+
zend_enum_get_case(
72+
ptr::from_ref(T::get_metadata().ce()).cast_mut(),
73+
&raw mut *name,
74+
)
75+
};
76+
77+
Ok(unsafe { ZBox::from_raw(variant) })
78+
}
79+
}
80+
81+
impl<T> IntoZval for T
82+
where
83+
T: RegisteredEnum + RegisteredClass,
84+
{
85+
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
86+
const NULLABLE: bool = false;
87+
88+
fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
89+
let obj = self.into_zend_object()?;
90+
zv.set_object(obj.into_raw());
91+
Ok(())
92+
}
1493
}
94+
// impl<'a, T> IntoZval for T
95+
// where
96+
// T: RegisteredEnum + RegisteredClass + IntoZendObject
97+
// {
98+
// const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
99+
// const NULLABLE: bool = false;
100+
//
101+
// fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
102+
// let obj = self.into_zend_object()?;
103+
// }
104+
// }
15105

16106
/// Represents a case in a PHP enum.
17107
pub struct EnumCase {

tests/src/integration/enum_/enum.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@
1717
assert(StringBackedEnum::from('bar') === StringBackedEnum::Variant2);
1818
assert(StringBackedEnum::tryFrom('foo') === StringBackedEnum::Variant1);
1919
assert(StringBackedEnum::tryFrom('baz') === null);
20+
21+
assert(test_enum(TestEnum::Variant2) === TestEnum::Variant1);

0 commit comments

Comments
 (0)