Skip to content

Commit 938c002

Browse files
committed
feat(enum): add stub generation
Refs: #178
1 parent 1018ae1 commit 938c002

File tree

6 files changed

+189
-11
lines changed

6 files changed

+189
-11
lines changed

src/builders/enum_builder.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{ffi::CString, ptr};
22

33
use crate::{
44
builders::FunctionBuilder,
5+
describe::DocComments,
56
enum_::EnumCase,
67
error::Result,
78
ffi::{zend_enum_add_case, zend_register_internal_enum},
@@ -10,26 +11,35 @@ use crate::{
1011
zend::{ClassEntry, FunctionEntry},
1112
};
1213

14+
/// A builder for PHP enums.
1315
#[must_use]
1416
pub struct EnumBuilder {
1517
pub(crate) name: String,
1618
pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
1719
pub(crate) cases: Vec<&'static EnumCase>,
1820
pub(crate) datatype: DataType,
1921
register: Option<fn(&'static mut ClassEntry)>,
22+
pub(crate) docs: DocComments,
2023
}
2124

2225
impl EnumBuilder {
26+
/// Creates a new enum builder with the given name.
2327
pub fn new<T: Into<String>>(name: T) -> Self {
2428
Self {
2529
name: name.into(),
2630
methods: Vec::default(),
2731
cases: Vec::default(),
2832
datatype: DataType::Undef,
2933
register: None,
34+
docs: DocComments::default(),
3035
}
3136
}
3237

38+
/// Adds an enum case to the enum.
39+
///
40+
/// # Panics
41+
///
42+
/// If the case's data type does not match the enum's data type
3343
pub fn case(mut self, case: &'static EnumCase) -> Self {
3444
let data_type = case.data_type();
3545
assert!(
@@ -45,7 +55,8 @@ impl EnumBuilder {
4555
self
4656
}
4757

48-
pub fn add_method(mut self, method: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
58+
/// Adds a method to the enum.
59+
pub fn method(mut self, method: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
4960
self.methods.push((method, flags));
5061
self
5162
}
@@ -61,6 +72,23 @@ impl EnumBuilder {
6172
self
6273
}
6374

75+
/// Add documentation comments to the enum.
76+
pub fn docs(mut self, docs: DocComments) -> Self {
77+
self.docs = docs;
78+
self
79+
}
80+
81+
/// Registers the enum with PHP.
82+
///
83+
/// # Panics
84+
///
85+
/// If the registration function was not set prior to calling this
86+
/// method.
87+
///
88+
/// # Errors
89+
///
90+
/// If the enum could not be registered, e.g. due to an invalid name or
91+
/// data type.
6492
pub fn register(self) -> Result<()> {
6593
let mut methods = self
6694
.methods

src/builders/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ mod module;
1212
mod sapi;
1313

1414
pub use class::ClassBuilder;
15+
#[cfg(feature = "enum")]
16+
pub use enum_builder::EnumBuilder;
1517
pub use function::FunctionBuilder;
1618
#[cfg(all(php82, feature = "embed"))]
1719
pub use ini::IniBuilder;

src/builders/module.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,14 @@ impl ModuleBuilder<'_> {
223223
builder = builder.case(case);
224224
}
225225
for (method, flags) in T::method_builders() {
226-
builder = builder.add_method(method, flags);
226+
builder = builder.method(method, flags);
227227
}
228228

229-
builder.registration(|ce| {
230-
T::get_metadata().set_ce(ce);
231-
})
229+
builder
230+
.registration(|ce| {
231+
T::get_metadata().set_ce(ce);
232+
})
233+
.docs(T::DOC_COMMENTS)
232234
});
233235

234236
self

src/describe/mod.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//! CLI application to generate PHP stub files used by IDEs.
33
use std::vec::Vec as StdVec;
44

5+
#[cfg(feature = "enum")]
6+
use crate::builders::EnumBuilder;
57
use crate::{
68
builders::{ClassBuilder, FunctionBuilder},
79
constant::IntoConst,
@@ -67,6 +69,9 @@ pub struct Module {
6769
pub functions: Vec<Function>,
6870
/// Classes exported by the extension.
6971
pub classes: Vec<Class>,
72+
#[cfg(feature = "enum")]
73+
/// Enums exported by the extension.
74+
pub enums: Vec<Enum>,
7075
/// Constants exported by the extension.
7176
pub constants: Vec<Constant>,
7277
}
@@ -95,6 +100,13 @@ impl From<ModuleBuilder<'_>> for Module {
95100
.map(Constant::from)
96101
.collect::<StdVec<_>>()
97102
.into(),
103+
#[cfg(feature = "enum")]
104+
enums: builder
105+
.enums
106+
.into_iter()
107+
.map(|e| e().into())
108+
.collect::<StdVec<_>>()
109+
.into(),
98110
}
99111
}
100112
}
@@ -216,6 +228,86 @@ impl From<ClassBuilder> for Class {
216228
}
217229
}
218230

231+
#[cfg(feature = "enum")]
232+
/// Represents an exported enum.
233+
#[repr(C)]
234+
#[derive(Debug, PartialEq)]
235+
pub struct Enum {
236+
/// Name of the enum.
237+
pub name: RString,
238+
/// Documentation comments for the enum.
239+
pub docs: DocBlock,
240+
/// Cases of the enum.
241+
pub cases: Vec<EnumCase>,
242+
/// Backing type of the enum.
243+
pub backing_type: Option<RString>,
244+
}
245+
246+
#[cfg(feature = "enum")]
247+
impl From<EnumBuilder> for Enum {
248+
fn from(val: EnumBuilder) -> Self {
249+
Self {
250+
name: val.name.into(),
251+
docs: DocBlock(
252+
val.docs
253+
.iter()
254+
.map(|d| (*d).into())
255+
.collect::<StdVec<_>>()
256+
.into(),
257+
),
258+
cases: val
259+
.cases
260+
.into_iter()
261+
.map(EnumCase::from)
262+
.collect::<StdVec<_>>()
263+
.into(),
264+
backing_type: match val.datatype {
265+
DataType::Long => Some("int".into()),
266+
DataType::String => Some("string".into()),
267+
_ => None,
268+
}
269+
.into(),
270+
}
271+
}
272+
}
273+
274+
#[cfg(feature = "enum")]
275+
/// Represents a case in an exported enum.
276+
#[repr(C)]
277+
#[derive(Debug, PartialEq)]
278+
pub struct EnumCase {
279+
/// Name of the enum case.
280+
pub name: RString,
281+
/// Documentation comments for the enum case.
282+
pub docs: DocBlock,
283+
/// Value of the enum case.
284+
pub value: Option<RString>,
285+
}
286+
287+
#[cfg(feature = "enum")]
288+
impl From<&'static crate::enum_::EnumCase> for EnumCase {
289+
fn from(val: &'static crate::enum_::EnumCase) -> Self {
290+
Self {
291+
name: val.name.into(),
292+
docs: DocBlock(
293+
val.docs
294+
.iter()
295+
.map(|d| (*d).into())
296+
.collect::<StdVec<_>>()
297+
.into(),
298+
),
299+
value: val
300+
.discriminant
301+
.as_ref()
302+
.map(|v| match v {
303+
crate::enum_::Discriminant::Int(i) => i.to_string().into(),
304+
crate::enum_::Discriminant::String(s) => format!("'{s}'").into(),
305+
})
306+
.into(),
307+
}
308+
}
309+
}
310+
219311
/// Represents a property attached to an exported class.
220312
#[repr(C)]
221313
#[derive(Debug, PartialEq)]
@@ -437,6 +529,8 @@ mod tests {
437529
functions: vec![].into(),
438530
classes: vec![].into(),
439531
constants: vec![].into(),
532+
#[cfg(feature = "enum")]
533+
enums: vec![].into(),
440534
};
441535

442536
let description = Description::new(module);

src/describe/stub.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
//! Traits and implementations to convert describe units into PHP stub code.
22
3-
use crate::flags::DataType;
4-
use std::{cmp::Ordering, collections::HashMap};
3+
use std::{
4+
cmp::Ordering,
5+
collections::HashMap,
6+
fmt::{Error as FmtError, Result as FmtResult, Write},
7+
option::Option as StdOption,
8+
vec::Vec as StdVec,
9+
};
510

611
use super::{
712
abi::{Option, RString},
813
Class, Constant, DocBlock, Function, Method, MethodType, Module, Parameter, Property,
914
Visibility,
1015
};
11-
use std::fmt::{Error as FmtError, Result as FmtResult, Write};
12-
use std::{option::Option as StdOption, vec::Vec as StdVec};
16+
17+
#[cfg(feature = "enum")]
18+
use crate::describe::{Enum, EnumCase};
19+
use crate::flags::DataType;
1320

1421
/// Implemented on types which can be converted into PHP stubs.
1522
pub trait ToStub {
@@ -78,6 +85,12 @@ impl ToStub for Module {
7885
insert(ns, class.to_stub()?);
7986
}
8087

88+
#[cfg(feature = "enum")]
89+
for r#enum in &*self.enums {
90+
let (ns, _) = split_namespace(r#enum.name.as_ref());
91+
insert(ns, r#enum.to_stub()?);
92+
}
93+
8194
let mut entries: StdVec<_> = entries.iter().collect();
8295
entries.sort_by(|(l, _), (r, _)| match (l, r) {
8396
(None, _) => Ordering::Greater,
@@ -241,6 +254,41 @@ impl ToStub for Class {
241254
}
242255
}
243256

257+
#[cfg(feature = "enum")]
258+
impl ToStub for Enum {
259+
fn fmt_stub(&self, buf: &mut String) -> FmtResult {
260+
self.docs.fmt_stub(buf)?;
261+
262+
let (_, name) = split_namespace(self.name.as_ref());
263+
write!(buf, "enum {name}")?;
264+
265+
if let Option::Some(backing_type) = &self.backing_type {
266+
write!(buf, ": {backing_type}")?;
267+
}
268+
269+
writeln!(buf, " {{")?;
270+
271+
for case in self.cases.iter() {
272+
case.fmt_stub(buf)?;
273+
}
274+
275+
writeln!(buf, "}}")
276+
}
277+
}
278+
279+
#[cfg(feature = "enum")]
280+
impl ToStub for EnumCase {
281+
fn fmt_stub(&self, buf: &mut String) -> FmtResult {
282+
self.docs.fmt_stub(buf)?;
283+
284+
write!(buf, " case {}", self.name)?;
285+
if let Option::Some(value) = &self.value {
286+
write!(buf, " = {value}")?;
287+
}
288+
writeln!(buf, ";")
289+
}
290+
}
291+
244292
impl ToStub for Property {
245293
fn fmt_stub(&self, buf: &mut String) -> FmtResult {
246294
self.docs.fmt_stub(buf)?;

tests/src/integration/enum_/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ use ext_php_rs::{error::Result, php_enum, php_function, prelude::ModuleBuilder,
22

33
#[php_enum]
44
#[php(allow_native_discriminants)]
5+
/// An example enum that demonstrates how to use PHP enums with Rust.
6+
/// This enum has two variants, `Variant1` and `Variant2`.
57
pub enum TestEnum {
6-
// #[php(discriminant = 2)]
8+
/// Represents the first variant of the enum.
9+
/// This variant has a discriminant of 0.
10+
/// But PHP does not know about it.
711
Variant1,
8-
// #[php(discriminant = 1)]
12+
/// Represents the second variant of the enum.
913
Variant2 = 1,
1014
}
1115

0 commit comments

Comments
 (0)