Skip to content

Commit a1fe00d

Browse files
committed
feat(enum): implement TryFrom and Into for backed enum
Refs: #178
1 parent d91db42 commit a1fe00d

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

crates/macros/src/enum_.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub fn parser(mut input: ItemEnum) -> Result<TokenStream> {
107107
docs,
108108
cases,
109109
None, // TODO: Implement flags support
110+
discriminant_type,
110111
);
111112

112113
Ok(quote! {
@@ -121,6 +122,7 @@ pub fn parser(mut input: ItemEnum) -> Result<TokenStream> {
121122
pub struct Enum<'a> {
122123
ident: &'a Ident,
123124
name: String,
125+
discriminant_type: DiscriminantType,
124126
docs: Vec<String>,
125127
cases: Vec<EnumCase>,
126128
flags: Option<String>,
@@ -133,12 +135,14 @@ impl<'a> Enum<'a> {
133135
docs: Vec<String>,
134136
cases: Vec<EnumCase>,
135137
flags: Option<String>,
138+
discriminant_type: DiscriminantType,
136139
) -> Self {
137140
let name = attrs.rename.rename(ident.to_string(), RenameRule::Pascal);
138141

139142
Self {
140143
ident,
141144
name,
145+
discriminant_type,
142146
docs,
143147
cases,
144148
flags,
@@ -245,16 +249,93 @@ impl<'a> Enum<'a> {
245249
}
246250
}
247251
}
252+
253+
pub fn impl_try_from(&self) -> TokenStream {
254+
if self.discriminant_type == DiscriminantType::None {
255+
return quote! {};
256+
}
257+
let discriminant_type = match self.discriminant_type {
258+
DiscriminantType::Integer => quote! { i64 },
259+
DiscriminantType::String => quote! { &str },
260+
DiscriminantType::None => unreachable!("Discriminant type should not be None here"),
261+
};
262+
let ident = &self.ident;
263+
let cases = self.cases.iter().map(|case| {
264+
let ident = &case.ident;
265+
match case
266+
.discriminant
267+
.as_ref()
268+
.expect("Discriminant should be set")
269+
{
270+
Discriminant::String(s) => quote! { #s => Ok(Self::#ident) },
271+
Discriminant::Integer(i) => quote! { #i => Ok(Self::#ident) },
272+
}
273+
});
274+
275+
quote! {
276+
impl TryFrom<#discriminant_type> for #ident {
277+
type Error = ::ext_php_rs::error::Error;
278+
279+
fn try_from(value: #discriminant_type) -> ::ext_php_rs::error::Result<Self> {
280+
match value {
281+
#(
282+
#cases,
283+
)*
284+
_ => Err(::ext_php_rs::error::Error::InvalidProperty),
285+
}
286+
}
287+
}
288+
}
289+
}
290+
291+
pub fn impl_into(&self) -> TokenStream {
292+
if self.discriminant_type == DiscriminantType::None {
293+
return quote! {};
294+
}
295+
let discriminant_type = match self.discriminant_type {
296+
DiscriminantType::Integer => quote! { i64 },
297+
DiscriminantType::String => quote! { &'static str },
298+
DiscriminantType::None => unreachable!("Discriminant type should not be None here"),
299+
};
300+
let ident = &self.ident;
301+
let cases = self.cases.iter().map(|case| {
302+
let ident = &case.ident;
303+
match case
304+
.discriminant
305+
.as_ref()
306+
.expect("Discriminant should be set")
307+
{
308+
Discriminant::String(s) => quote! { Self::#ident => #s },
309+
Discriminant::Integer(i) => quote! { Self::#ident => #i },
310+
}
311+
});
312+
313+
quote! {
314+
impl Into<#discriminant_type> for #ident {
315+
fn into(self) -> #discriminant_type {
316+
match self {
317+
#(
318+
#cases,
319+
)*
320+
}
321+
}
322+
}
323+
}
324+
}
248325
}
249326

250327
impl ToTokens for Enum<'_> {
251328
fn to_tokens(&self, tokens: &mut TokenStream) {
252329
let class = self.registered_class();
253330
let enum_impl = self.registered_enum();
331+
let impl_try_from = self.impl_try_from();
332+
let impl_into = self.impl_into();
254333

255334
tokens.extend(quote! {
256335
#class
257336
#enum_impl
337+
#impl_try_from
338+
#impl_into
258339
});
259340
}
260341
}

tests/src/integration/enum_/enum.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
declare(strict_types=1);
44

55
$enum_variant = TestEnum::Variant1;
6-
var_dump($enum_variant);
76
assert($enum_variant === TestEnum::Variant1);
87
assert($enum_variant !== TestEnum::Variant2);
98
assert(TestEnum::cases() === [TestEnum::Variant1, TestEnum::Variant2]);
@@ -18,4 +17,4 @@
1817
assert(StringBackedEnum::tryFrom('foo') === StringBackedEnum::Variant1);
1918
assert(StringBackedEnum::tryFrom('baz') === null);
2019

21-
assert(test_enum(TestEnum::Variant2) === TestEnum::Variant1);
20+
assert(test_enum(TestEnum::Variant1) === StringBackedEnum::Variant2);

tests/src/integration/enum_/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ext_php_rs::{php_enum, php_function, prelude::ModuleBuilder, wrap_function};
1+
use ext_php_rs::{error::Result, php_enum, php_function, prelude::ModuleBuilder, wrap_function};
22

33
#[php_enum]
44
#[php(allow_native_discriminants)]
@@ -26,10 +26,11 @@ pub enum StringBackedEnum {
2626
}
2727

2828
#[php_function]
29-
pub fn test_enum(a: TestEnum) -> TestEnum {
29+
pub fn test_enum(a: TestEnum) -> Result<StringBackedEnum> {
30+
let str: &str = StringBackedEnum::Variant2.into();
3031
match a {
31-
TestEnum::Variant1 => TestEnum::Variant2,
32-
TestEnum::Variant2 => TestEnum::Variant1,
32+
TestEnum::Variant1 => str.try_into(),
33+
TestEnum::Variant2 => Ok(StringBackedEnum::Variant1),
3334
}
3435
}
3536

0 commit comments

Comments
 (0)