1use std::{ffi::CString, mem::ManuallyDrop, ptr};
2
3use crate::{
4 args::ArgInfoTables,
5 builders::{
6 FunctionBuilder,
7 class::{ensure_class_name_free, first_duplicate},
8 function::free_registered_entries,
9 },
10 convert::IntoZval,
11 describe::DocComments,
12 enum_::{Discriminant, EnumCase},
13 error::{Error, Result},
14 ffi::{zend_enum_add_case, zend_register_internal_enum},
15 flags::{DataType, DataTypeExt, MethodFlags},
16 types::{ZendStr, Zval},
17 zend::{ClassEntry, ExecutorGlobals, FunctionEntry},
18};
19
20#[must_use]
22pub struct EnumBuilder {
23 pub(crate) name: String,
24 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
25 pub(crate) cases: Vec<&'static EnumCase>,
26 pub(crate) datatype: DataType,
27 register: Option<fn(&'static mut ClassEntry, ArgInfoTables)>,
28 pub(crate) docs: DocComments,
29}
30
31impl EnumBuilder {
32 pub fn new<T: Into<String>>(name: T) -> Self {
34 Self {
35 name: name.into(),
36 methods: Vec::default(),
37 cases: Vec::default(),
38 datatype: DataType::Undef,
39 register: None,
40 docs: DocComments::default(),
41 }
42 }
43
44 pub fn case(mut self, case: &'static EnumCase) -> Self {
50 let data_type = case.data_type();
51 assert!(
52 data_type == self.datatype || self.cases.is_empty(),
53 "Cannot add case with data type {:?} to enum with data type {:?}",
54 data_type,
55 self.datatype
56 );
57
58 self.datatype = data_type;
59 self.cases.push(case);
60
61 self
62 }
63
64 pub fn method(mut self, method: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
66 self.methods.push((method, flags));
67 self
68 }
69
70 pub fn registration(mut self, register: fn(&'static mut ClassEntry, ArgInfoTables)) -> Self {
81 self.register = Some(register);
82 self
83 }
84
85 pub fn docs(mut self, docs: DocComments) -> Self {
87 self.docs = docs;
88 self
89 }
90
91 fn validate(&self) -> Result<()> {
94 if let Some(name) = first_duplicate(self.methods.iter().map(|(m, _)| m.name.as_str()), true)
95 {
96 return Err(Error::DuplicateMethod {
97 method: format!("{}::{name}", self.name),
98 });
99 }
100 if let Some(name) = first_duplicate(self.cases.iter().map(|c| c.name), false) {
101 return Err(Error::DuplicateConstant {
102 constant: format!("{}::{name}", self.name),
103 });
104 }
105 Ok(())
106 }
107
108 pub fn register(self) -> Result<()> {
125 assert!(
126 !ExecutorGlobals::get().current_module.is_null(),
127 "Enums can only be registered from a module startup (MINIT) function: \
128 `do_register_internal_class` dereferences `EG(current_module)`."
129 );
130 self.validate()?;
131 ensure_class_name_free(&self.name)?;
132
133 let mut arg_info = Vec::with_capacity(self.methods.len());
134 let mut methods = Vec::with_capacity(self.methods.len() + 1);
135 for (method, flags) in self.methods {
136 let (mut entry, args) = method.build()?;
137 entry.add_flags(flags);
138 methods.push(entry);
139 arg_info.push(args);
140 }
141
142 methods.push(FunctionEntry::end());
143
144 let name = CString::new(self.name)?;
145 let backing_type = self.datatype.as_u32().try_into()?;
146 let entries = Box::into_raw(methods.into_boxed_slice());
147
148 let arg_info = ManuallyDrop::new(arg_info.into_boxed_slice());
152
153 let class = unsafe {
154 zend_register_internal_enum(
155 name.as_ptr(),
156 backing_type,
157 entries.cast::<FunctionEntry>(),
158 )
159 };
160
161 unsafe { free_registered_entries(entries) };
165 unsafe { (*class).info.internal.builtin_functions = ptr::null() };
166
167 for case in self.cases {
168 let name = ZendStr::new_interned(case.name, true);
169 let mut value = match &case.discriminant {
173 Some(value) => Some(ManuallyDrop::new(Self::create_enum_value(value)?)),
174 None => None,
175 };
176 let value = value
177 .as_mut()
178 .map_or(ptr::null_mut(), |value| &raw mut **value);
179 unsafe {
180 zend_enum_add_case(class, name.into_raw(), value);
181 }
182 }
183
184 if let Some(register) = self.register {
185 register(unsafe { &mut *class }, ManuallyDrop::into_inner(arg_info));
186 } else {
187 panic!("Enum was not registered with a registration function");
188 }
189
190 Ok(())
191 }
192
193 fn create_enum_value(discriminant: &Discriminant) -> Result<Zval> {
194 Ok(match discriminant {
195 Discriminant::Int(i) => i.into_zval(false)?,
196 Discriminant::String(s) => s.into_zval(true)?,
197 })
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::enum_::Discriminant;
205
206 #[test]
207 fn validate_rejects_a_repeated_case() {
208 const same: EnumCase = EnumCase {
209 name: "Variant1",
210 discriminant: None,
211 docs: &[],
212 };
213 let builder = EnumBuilder::new("Foo").case(&case1).case(&same);
214 assert!(matches!(
215 builder.validate(),
216 Err(Error::DuplicateConstant { constant }) if constant == "Foo::Variant1"
217 ));
218 }
219
220 #[test]
221 fn validate_rejects_methods_that_differ_only_by_case() {
222 let builder = EnumBuilder::new("Foo")
223 .method(
224 FunctionBuilder::new("label", crate::test::test_function),
225 MethodFlags::Public,
226 )
227 .method(
228 FunctionBuilder::new("Label", crate::test::test_function),
229 MethodFlags::Public,
230 );
231 assert!(matches!(
232 builder.validate(),
233 Err(Error::DuplicateMethod { method }) if method == "Foo::Label"
234 ));
235 }
236
237 const case1: EnumCase = EnumCase {
238 name: "Variant1",
239 discriminant: None,
240 docs: &[],
241 };
242 const case2: EnumCase = EnumCase {
243 name: "Variant2",
244 discriminant: Some(Discriminant::Int(42)),
245 docs: &[],
246 };
247 const case3: EnumCase = EnumCase {
248 name: "Variant3",
249 discriminant: Some(Discriminant::String("foo")),
250 docs: &[],
251 };
252
253 #[test]
254 fn test_new_enum_builder() {
255 let builder = EnumBuilder::new("MyEnum");
256 assert_eq!(builder.name, "MyEnum");
257 assert!(builder.methods.is_empty());
258 assert!(builder.cases.is_empty());
259 assert_eq!(builder.datatype, DataType::Undef);
260 assert!(builder.register.is_none());
261 }
262
263 #[test]
264 fn test_enum_case() {
265 let builder = EnumBuilder::new("MyEnum").case(&case1);
266 assert_eq!(builder.cases.len(), 1);
267 assert_eq!(builder.cases[0].name, "Variant1");
268 assert_eq!(builder.datatype, DataType::Undef);
269
270 let builder = EnumBuilder::new("MyEnum").case(&case2);
271 assert_eq!(builder.cases.len(), 1);
272 assert_eq!(builder.cases[0].name, "Variant2");
273 assert_eq!(builder.cases[0].discriminant, Some(Discriminant::Int(42)));
274 assert_eq!(builder.datatype, DataType::Long);
275
276 let builder = EnumBuilder::new("MyEnum").case(&case3);
277 assert_eq!(builder.cases.len(), 1);
278 assert_eq!(builder.cases[0].name, "Variant3");
279 assert_eq!(
280 builder.cases[0].discriminant,
281 Some(Discriminant::String("foo"))
282 );
283 assert_eq!(builder.datatype, DataType::String);
284 }
285
286 #[test]
287 #[should_panic(expected = "Cannot add case with data type Long to enum with data type Undef")]
288 fn test_enum_case_mismatch() {
289 #[allow(unused_must_use)]
290 EnumBuilder::new("MyEnum").case(&case1).case(&case2); }
292
293 const docs: DocComments = &["This is a test enum"];
294 #[test]
295 fn test_docs() {
296 let builder = EnumBuilder::new("MyEnum").docs(docs);
297 assert_eq!(builder.docs.len(), 1);
298 assert_eq!(builder.docs[0], "This is a test enum");
299 }
300}