• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 25379163335

05 May 2026 01:25PM UTC coverage: 72.479% (+6.2%) from 66.241%
25379163335

Pull #734

github

web-flow
Merge 889e3cde4 into 0912e7c24
Pull Request #734: feat!: PHP 8 union, intersection, DNF, and class-union type hints

2871 of 3074 new or added lines in 21 files covered. (93.4%)

3 existing lines in 2 files now uncovered.

11514 of 15886 relevant lines covered (72.48%)

33.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.46
/src/builders/enum_builder.rs
1
use std::{ffi::CString, ptr};
2

3
use crate::{
4
    builders::FunctionBuilder,
5
    convert::IntoZval,
6
    describe::DocComments,
7
    enum_::{Discriminant, EnumCase},
8
    error::Result,
9
    ffi::{zend_enum_add_case, zend_register_internal_enum},
10
    flags::{DataType, MethodFlags},
11
    types::{ZendStr, Zval},
12
    zend::{ClassEntry, FunctionEntry},
13
};
14

15
/// A builder for PHP enums.
16
#[must_use]
17
pub struct EnumBuilder {
18
    pub(crate) name: String,
19
    pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
20
    pub(crate) cases: Vec<&'static EnumCase>,
21
    pub(crate) datatype: DataType,
22
    register: Option<fn(&'static mut ClassEntry)>,
23
    pub(crate) docs: DocComments,
24
}
25

26
impl EnumBuilder {
27
    /// Creates a new enum builder with the given name.
28
    pub fn new<T: Into<String>>(name: T) -> Self {
6✔
29
        Self {
6✔
30
            name: name.into(),
6✔
31
            methods: Vec::default(),
6✔
32
            cases: Vec::default(),
6✔
33
            datatype: DataType::Undef,
6✔
34
            register: None,
6✔
35
            docs: DocComments::default(),
6✔
36
        }
6✔
37
    }
6✔
38

39
    /// Adds an enum case to the enum.
40
    ///
41
    /// # Panics
42
    ///
43
    /// If the case's data type does not match the enum's data type
44
    pub fn case(mut self, case: &'static EnumCase) -> Self {
5✔
45
        let data_type = case.data_type();
5✔
46
        assert!(
5✔
47
            data_type == self.datatype || self.cases.is_empty(),
5✔
48
            "Cannot add case with data type {:?} to enum with data type {:?}",
49
            data_type,
50
            self.datatype
51
        );
52

53
        self.datatype = data_type;
4✔
54
        self.cases.push(case);
4✔
55

56
        self
4✔
57
    }
4✔
58

59
    /// Adds a method to the enum.
60
    pub fn method(mut self, method: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
×
61
        self.methods.push((method, flags));
×
62
        self
×
63
    }
×
64

65
    /// Function to register the class with PHP. This function is called after
66
    /// the class is built.
67
    ///
68
    /// # Parameters
69
    ///
70
    /// * `register` - The function to call to register the class.
71
    pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
×
72
        self.register = Some(register);
×
73
        self
×
74
    }
×
75

76
    /// Add documentation comments to the enum.
77
    pub fn docs(mut self, docs: DocComments) -> Self {
1✔
78
        self.docs = docs;
1✔
79
        self
1✔
80
    }
1✔
81

82
    /// Registers the enum with PHP.
83
    ///
84
    /// # Panics
85
    ///
86
    /// If the registration function was not set prior to calling this
87
    /// method.
88
    ///
89
    /// # Errors
90
    ///
91
    /// If the enum could not be registered, e.g. due to an invalid name or
92
    /// data type.
93
    pub fn register(self) -> Result<()> {
×
94
        let mut methods = self
×
95
            .methods
×
96
            .into_iter()
×
97
            .map(|(method, flags)| {
×
98
                method.build().map(|mut method| {
×
99
                    method.flags |= flags.bits();
×
100
                    method
×
101
                })
×
102
            })
×
103
            .collect::<Result<Vec<_>>>()?;
×
104
        methods.push(FunctionEntry::end());
×
105

106
        let class = unsafe {
×
107
            zend_register_internal_enum(
×
108
                CString::new(self.name)?.as_ptr(),
×
NEW
109
                crate::flags::data_type_as_u32(&self.datatype).try_into()?,
×
110
                methods.into_boxed_slice().as_ptr(),
×
111
            )
112
        };
113

114
        for case in self.cases {
×
115
            let name = ZendStr::new_interned(case.name, true);
×
116
            let value = match &case.discriminant {
×
117
                Some(value) => Self::create_enum_value(value)?,
×
118
                None => ptr::null_mut(),
×
119
            };
120
            unsafe {
×
121
                zend_enum_add_case(class, name.into_raw(), value);
×
122
            }
×
123
        }
124

125
        if let Some(register) = self.register {
×
126
            register(unsafe { &mut *class });
×
127
        } else {
×
128
            panic!("Enum was not registered with a registration function");
×
129
        }
130

131
        Ok(())
×
132
    }
×
133

134
    fn create_enum_value(discriminant: &Discriminant) -> Result<*mut Zval> {
×
135
        let value: Zval = match discriminant {
×
136
            Discriminant::Int(i) => i.into_zval(false)?,
×
137
            Discriminant::String(s) => s.into_zval(true)?,
×
138
        };
139

140
        let boxed_value = Box::new(value);
×
141
        Ok(Box::into_raw(boxed_value).cast())
×
142
    }
×
143
}
144

145
#[cfg(test)]
146
mod tests {
147
    use super::*;
148
    use crate::enum_::Discriminant;
149

150
    const case1: EnumCase = EnumCase {
151
        name: "Variant1",
152
        discriminant: None,
153
        docs: &[],
154
    };
155
    const case2: EnumCase = EnumCase {
156
        name: "Variant2",
157
        discriminant: Some(Discriminant::Int(42)),
158
        docs: &[],
159
    };
160
    const case3: EnumCase = EnumCase {
161
        name: "Variant3",
162
        discriminant: Some(Discriminant::String("foo")),
163
        docs: &[],
164
    };
165

166
    #[test]
167
    fn test_new_enum_builder() {
1✔
168
        let builder = EnumBuilder::new("MyEnum");
1✔
169
        assert_eq!(builder.name, "MyEnum");
1✔
170
        assert!(builder.methods.is_empty());
1✔
171
        assert!(builder.cases.is_empty());
1✔
172
        assert_eq!(builder.datatype, DataType::Undef);
1✔
173
        assert!(builder.register.is_none());
1✔
174
    }
1✔
175

176
    #[test]
177
    fn test_enum_case() {
1✔
178
        let builder = EnumBuilder::new("MyEnum").case(&case1);
1✔
179
        assert_eq!(builder.cases.len(), 1);
1✔
180
        assert_eq!(builder.cases[0].name, "Variant1");
1✔
181
        assert_eq!(builder.datatype, DataType::Undef);
1✔
182

183
        let builder = EnumBuilder::new("MyEnum").case(&case2);
1✔
184
        assert_eq!(builder.cases.len(), 1);
1✔
185
        assert_eq!(builder.cases[0].name, "Variant2");
1✔
186
        assert_eq!(builder.cases[0].discriminant, Some(Discriminant::Int(42)));
1✔
187
        assert_eq!(builder.datatype, DataType::Long);
1✔
188

189
        let builder = EnumBuilder::new("MyEnum").case(&case3);
1✔
190
        assert_eq!(builder.cases.len(), 1);
1✔
191
        assert_eq!(builder.cases[0].name, "Variant3");
1✔
192
        assert_eq!(
1✔
193
            builder.cases[0].discriminant,
1✔
194
            Some(Discriminant::String("foo"))
195
        );
196
        assert_eq!(builder.datatype, DataType::String);
1✔
197
    }
1✔
198

199
    #[test]
200
    #[should_panic(expected = "Cannot add case with data type Long to enum with data type Undef")]
201
    fn test_enum_case_mismatch() {
1✔
202
        #[allow(unused_must_use)]
203
        EnumBuilder::new("MyEnum").case(&case1).case(&case2); // This should panic because case2 has a different data type
1✔
204
    }
1✔
205

206
    const docs: DocComments = &["This is a test enum"];
207
    #[test]
208
    fn test_docs() {
1✔
209
        let builder = EnumBuilder::new("MyEnum").docs(docs);
1✔
210
        assert_eq!(builder.docs.len(), 1);
1✔
211
        assert_eq!(builder.docs[0], "This is a test enum");
1✔
212
    }
1✔
213
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc