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

davidcole1340 / ext-php-rs / 16274060216

14 Jul 2025 05:51PM UTC coverage: 22.552%. Remained the same
16274060216

Pull #514

github

web-flow
Merge 7ee17f41f into 31c9d9968
Pull Request #514: feat(stubs)!: add stubs for `RustClosure`

13 of 15 new or added lines in 3 files covered. (86.67%)

58 existing lines in 3 files now uncovered.

882 of 3911 relevant lines covered (22.55%)

3.69 hits per line

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

81.82
/src/args.rs
1
//! Builder and objects relating to function and method arguments.
2

3
use std::{ffi::CString, ptr};
4

5
use crate::{
6
    convert::{FromZvalMut, IntoZvalDyn},
7
    describe::{abi, Parameter},
8
    error::{Error, Result},
9
    ffi::{
10
        _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY,
11
        _zend_expected_type_Z_EXPECTED_BOOL, _zend_expected_type_Z_EXPECTED_DOUBLE,
12
        _zend_expected_type_Z_EXPECTED_LONG, _zend_expected_type_Z_EXPECTED_OBJECT,
13
        _zend_expected_type_Z_EXPECTED_RESOURCE, _zend_expected_type_Z_EXPECTED_STRING,
14
        zend_internal_arg_info, zend_wrong_parameters_count_error,
15
    },
16
    flags::DataType,
17
    types::Zval,
18
    zend::ZendType,
19
};
20

21
/// Represents an argument to a function.
22
#[must_use]
23
#[derive(Debug)]
24
pub struct Arg<'a> {
25
    name: String,
26
    r#type: DataType,
27
    as_ref: bool,
28
    allow_null: bool,
29
    pub(crate) variadic: bool,
30
    default_value: Option<String>,
31
    zval: Option<&'a mut Zval>,
32
    variadic_zvals: Vec<Option<&'a mut Zval>>,
33
}
34

35
impl<'a> Arg<'a> {
36
    /// Creates a new argument.
37
    ///
38
    /// # Parameters
39
    ///
40
    /// * `name` - The name of the parameter.
41
    /// * `_type` - The type of the parameter.
42
    pub fn new<T: Into<String>>(name: T, r#type: DataType) -> Self {
41✔
43
        Arg {
44
            name: name.into(),
123✔
45
            r#type,
46
            as_ref: false,
47
            allow_null: false,
48
            variadic: false,
49
            default_value: None,
50
            zval: None,
51
            variadic_zvals: vec![],
41✔
52
        }
53
    }
54

55
    /// Sets the argument as a reference.
56
    #[allow(clippy::wrong_self_convention)]
57
    pub fn as_ref(mut self) -> Self {
1✔
58
        self.as_ref = true;
1✔
59
        self
1✔
60
    }
61

62
    /// Sets the argument as variadic.
63
    pub fn is_variadic(mut self) -> Self {
3✔
64
        self.variadic = true;
3✔
65
        self
3✔
66
    }
67

68
    /// Sets the argument as nullable.
69
    pub fn allow_null(mut self) -> Self {
10✔
70
        self.allow_null = true;
10✔
71
        self
10✔
72
    }
73

74
    /// Sets the default value for the argument.
75
    pub fn default<T: Into<String>>(mut self, default: T) -> Self {
3✔
76
        self.default_value = Some(default.into());
6✔
77
        self
3✔
78
    }
79

80
    /// Attempts to consume the argument, converting the inner type into `T`.
81
    /// Upon success, the result is returned in a [`Result`].
82
    ///
83
    /// As this function consumes, it cannot return a reference to the
84
    /// underlying zval.
85
    ///
86
    /// # Errors
87
    ///
88
    /// If the conversion fails (or the argument contains no value), the
89
    /// argument is returned in an [`Err`] variant.
90
    pub fn consume<T>(mut self) -> Result<T, Self>
2✔
91
    where
92
        for<'b> T: FromZvalMut<'b>,
93
    {
94
        self.zval
2✔
95
            .as_mut()
96
            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
4✔
97
            .ok_or(self)
4✔
98
    }
99

100
    /// Attempts to retrieve the value of the argument.
101
    /// This will be None until the [`ArgParser`] is used to parse
102
    /// the arguments.
103
    pub fn val<T>(&'a mut self) -> Option<T>
4✔
104
    where
105
        T: FromZvalMut<'a>,
106
    {
107
        self.zval
4✔
108
            .as_mut()
109
            .and_then(|zv| T::from_zval_mut(zv.dereference_mut()))
10✔
110
    }
111

112
    /// Retrice all the variadic values for this Rust argument.
113
    pub fn variadic_vals<T>(&'a mut self) -> Vec<T>
1✔
114
    where
115
        T: FromZvalMut<'a>,
116
    {
117
        self.variadic_zvals
1✔
118
            .iter_mut()
119
            .filter_map(|zv| zv.as_mut())
5✔
120
            .filter_map(|zv| T::from_zval_mut(zv.dereference_mut()))
5✔
121
            .collect()
122
    }
123

124
    /// Attempts to return a reference to the arguments internal Zval.
125
    ///
126
    /// # Returns
127
    ///
128
    /// * `Some(&Zval)` - The internal zval.
129
    /// * `None` - The argument was empty.
130
    // TODO: Figure out if we can change this
131
    #[allow(clippy::mut_mut)]
132
    pub fn zval(&mut self) -> Option<&mut &'a mut Zval> {
2✔
133
        self.zval.as_mut()
4✔
134
    }
135

136
    /// Attempts to call the argument as a callable with a list of arguments to
137
    /// pass to the function. Note that a thrown exception inside the
138
    /// callable is not detectable, therefore you should check if the return
139
    /// value is valid rather than unwrapping. Returns a result containing the
140
    /// return value of the function, or an error.
141
    ///
142
    /// You should not call this function directly, rather through the
143
    /// [`call_user_func`](crate::call_user_func) macro.
144
    ///
145
    /// # Parameters
146
    ///
147
    /// * `params` - A list of parameters to call the function with.
148
    ///
149
    /// # Errors
150
    ///
151
    /// * `Error::Callable` - The argument is not callable.
152
    // TODO: Measure this
153
    #[allow(clippy::inline_always)]
154
    #[inline(always)]
155
    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
2✔
156
        self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
10✔
157
    }
158

159
    /// Returns the internal PHP argument info.
160
    pub(crate) fn as_arg_info(&self) -> Result<ArgInfo> {
5✔
161
        Ok(ArgInfo {
×
162
            name: CString::new(self.name.as_str())?.into_raw(),
20✔
163
            type_: ZendType::empty_from_type(
×
164
                self.r#type,
×
165
                self.as_ref,
×
166
                self.variadic,
×
167
                self.allow_null,
×
168
            )
169
            .ok_or(Error::InvalidCString)?,
×
170
            default_value: match &self.default_value {
1✔
171
                Some(val) if val.as_str() == "None" => CString::new("null")?.into_raw(),
2✔
172
                Some(val) => CString::new(val.as_str())?.into_raw(),
2✔
173
                None => ptr::null(),
4✔
174
            },
175
        })
176
    }
177
}
178

179
impl From<Arg<'_>> for _zend_expected_type {
180
    fn from(arg: Arg) -> Self {
16✔
181
        let type_id = match arg.r#type {
32✔
182
            DataType::False | DataType::True => _zend_expected_type_Z_EXPECTED_BOOL,
4✔
183
            DataType::Long => _zend_expected_type_Z_EXPECTED_LONG,
2✔
184
            DataType::Double => _zend_expected_type_Z_EXPECTED_DOUBLE,
2✔
185
            DataType::String => _zend_expected_type_Z_EXPECTED_STRING,
2✔
186
            DataType::Array => _zend_expected_type_Z_EXPECTED_ARRAY,
2✔
187
            DataType::Object(_) => _zend_expected_type_Z_EXPECTED_OBJECT,
2✔
188
            DataType::Resource => _zend_expected_type_Z_EXPECTED_RESOURCE,
2✔
189
            _ => unreachable!(),
190
        };
191

192
        if arg.allow_null {
193
            type_id + 1
8✔
194
        } else {
195
            type_id
8✔
196
        }
197
    }
198
}
199

200
impl From<Arg<'_>> for Parameter {
201
    fn from(val: Arg<'_>) -> Self {
3✔
202
        Parameter {
203
            name: val.name.into(),
9✔
204
            ty: Some(val.r#type).into(),
9✔
205
            nullable: val.allow_null,
6✔
206
            variadic: val.variadic,
6✔
207
            default: val.default_value.map(abi::RString::from).into(),
6✔
208
        }
209
    }
210
}
211

212
/// Internal argument information used by Zend.
213
pub type ArgInfo = zend_internal_arg_info;
214

215
/// Parses the arguments of a function.
216
#[must_use]
217
pub struct ArgParser<'a, 'b> {
218
    args: Vec<&'b mut Arg<'a>>,
219
    min_num_args: Option<usize>,
220
    arg_zvals: Vec<Option<&'a mut Zval>>,
221
}
222

223
impl<'a, 'b> ArgParser<'a, 'b> {
224
    /// Builds a new function argument parser.
225
    pub fn new(arg_zvals: Vec<Option<&'a mut Zval>>) -> Self {
4✔
226
        ArgParser {
227
            args: vec![],
8✔
228
            min_num_args: None,
229
            arg_zvals,
230
        }
231
    }
232

233
    /// Adds a new argument to the parser.
234
    ///
235
    /// # Parameters
236
    ///
237
    /// * `arg` - The argument to add to the parser.
238
    pub fn arg(mut self, arg: &'b mut Arg<'a>) -> Self {
3✔
239
        self.args.push(arg);
9✔
240
        self
3✔
241
    }
242

243
    /// Sets the next arguments to be added as not required.
244
    pub fn not_required(mut self) -> Self {
2✔
245
        self.min_num_args = Some(self.args.len());
2✔
246
        self
2✔
247
    }
248

249
    /// Uses the argument parser to parse the arguments contained in the given
250
    /// `ExecuteData` object. Returns successfully if the arguments were
251
    /// parsed.
252
    ///
253
    /// This function can only be safely called from within an exported PHP
254
    /// function.
255
    ///
256
    /// # Parameters
257
    ///
258
    /// * `execute_data` - The execution data from the function.
259
    ///
260
    /// # Errors
261
    ///
262
    /// Returns an [`Error`] type if there were too many or too little arguments
263
    /// passed to the function. The user has already been notified so you
264
    /// should break execution after seeing an error type.
265
    ///
266
    /// Also returns an error if the number of min/max arguments exceeds
267
    /// `u32::MAX`
268
    pub fn parse(mut self) -> Result<()> {
2✔
269
        let max_num_args = self.args.len();
6✔
270
        let mut min_num_args = self.min_num_args.unwrap_or(max_num_args);
8✔
271
        let num_args = self.arg_zvals.len();
6✔
272
        let has_variadic = self.args.last().is_some_and(|arg| arg.variadic);
6✔
273
        if has_variadic {
2✔
UNCOV
274
            min_num_args = min_num_args.saturating_sub(1);
×
275
        }
276

277
        if num_args < min_num_args || (!has_variadic && num_args > max_num_args) {
6✔
278
            // SAFETY: Exported C function is safe, return value is unused and parameters
279
            // are copied.
280
            unsafe {
281
                zend_wrong_parameters_count_error(
UNCOV
282
                    min_num_args.try_into()?,
×
283
                    max_num_args.try_into()?,
×
284
                );
285
            };
UNCOV
286
            return Err(Error::IncorrectArguments(num_args, min_num_args));
×
287
        }
288

289
        for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() {
2✔
UNCOV
290
            let arg = match self.args.get_mut(i) {
×
291
                Some(arg) => Some(arg),
4✔
292
                // Only select the last item if it's variadic
UNCOV
293
                None => self.args.last_mut().filter(|arg| arg.variadic),
×
294
            };
295
            if let Some(arg) = arg {
2✔
UNCOV
296
                if arg.variadic {
×
297
                    arg.variadic_zvals.push(arg_zval);
×
298
                } else {
299
                    arg.zval = arg_zval;
2✔
300
                }
301
            }
302
        }
303

UNCOV
304
        Ok(())
×
305
    }
306
}
307

308
#[cfg(test)]
309
mod tests {
310
    #![allow(clippy::unwrap_used)]
311
    #[cfg(feature = "embed")]
312
    use crate::embed::Embed;
313

314
    use super::*;
315

316
    #[test]
317
    fn test_new() {
318
        let arg = Arg::new("test", DataType::Long);
319
        assert_eq!(arg.name, "test");
320
        assert_eq!(arg.r#type, DataType::Long);
321
        assert!(!arg.as_ref);
322
        assert!(!arg.allow_null);
323
        assert!(!arg.variadic);
324
        assert!(arg.default_value.is_none());
325
        assert!(arg.zval.is_none());
326
        assert!(arg.variadic_zvals.is_empty());
327
    }
328

329
    #[test]
330
    fn test_as_ref() {
331
        let arg = Arg::new("test", DataType::Long).as_ref();
332
        assert!(arg.as_ref);
333
    }
334

335
    #[test]
336
    fn test_is_variadic() {
337
        let arg = Arg::new("test", DataType::Long).is_variadic();
338
        assert!(arg.variadic);
339
    }
340

341
    #[test]
342
    fn test_allow_null() {
343
        let arg = Arg::new("test", DataType::Long).allow_null();
344
        assert!(arg.allow_null);
345
    }
346

347
    #[test]
348
    fn test_default() {
349
        let arg = Arg::new("test", DataType::Long).default("default");
350
        assert_eq!(arg.default_value, Some("default".to_string()));
351

352
        // TODO: Validate type
353
    }
354

355
    #[test]
356
    fn test_consume_no_value() {
357
        let arg = Arg::new("test", DataType::Long);
358
        let result: Result<i32, _> = arg.consume();
359
        assert!(result.is_err());
360
        assert_eq!(result.unwrap_err().name, "test");
361
    }
362

363
    #[test]
364
    #[cfg(feature = "embed")]
365
    fn test_consume() {
366
        let mut arg = Arg::new("test", DataType::Long);
367
        let mut zval = Zval::from(42);
368
        arg.zval = Some(&mut zval);
369

370
        let result: Result<i32, _> = arg.consume();
371
        assert_eq!(result.unwrap(), 42);
372
    }
373

374
    #[test]
375
    fn test_val_no_value() {
376
        let mut arg = Arg::new("test", DataType::Long);
377
        let result: Option<i32> = arg.val();
378
        assert!(result.is_none());
379
    }
380

381
    #[test]
382
    #[cfg(feature = "embed")]
383
    fn test_val() {
384
        let mut arg = Arg::new("test", DataType::Long);
385
        let mut zval = Zval::from(42);
386
        arg.zval = Some(&mut zval);
387

388
        let result: Option<i32> = arg.val();
389
        assert_eq!(result.unwrap(), 42);
390
    }
391

392
    #[test]
393
    #[cfg(feature = "embed")]
394
    fn test_variadic_vals() {
395
        let mut arg = Arg::new("test", DataType::Long).is_variadic();
396
        let mut zval1 = Zval::from(42);
397
        let mut zval2 = Zval::from(43);
398
        arg.variadic_zvals.push(Some(&mut zval1));
399
        arg.variadic_zvals.push(Some(&mut zval2));
400

401
        let result: Vec<i32> = arg.variadic_vals();
402
        assert_eq!(result, vec![42, 43]);
403
    }
404

405
    #[test]
406
    fn test_zval_no_value() {
407
        let mut arg = Arg::new("test", DataType::Long);
408
        let result = arg.zval();
409
        assert!(result.is_none());
410
    }
411

412
    #[test]
413
    #[cfg(feature = "embed")]
414
    fn test_zval() {
415
        let mut arg = Arg::new("test", DataType::Long);
416
        let mut zval = Zval::from(42);
417
        arg.zval = Some(&mut zval);
418

419
        let result = arg.zval();
420
        assert!(result.is_some());
421
        assert_eq!(result.unwrap().dereference_mut().long(), Some(42));
422
    }
423

424
    #[cfg(feature = "embed")]
425
    #[test]
426
    fn test_try_call_no_value() {
427
        let arg = Arg::new("test", DataType::Long);
428
        let result = arg.try_call(vec![]);
429
        assert!(result.is_err());
430
    }
431

432
    #[test]
433
    #[cfg(feature = "embed")]
434
    fn test_try_call_not_callable() {
435
        Embed::run(|| {
436
            let mut arg = Arg::new("test", DataType::Long);
437
            let mut zval = Zval::from(42);
438
            arg.zval = Some(&mut zval);
439

440
            let result = arg.try_call(vec![]);
441
            assert!(result.is_err());
442
        });
443
    }
444

445
    // TODO: Test the callable case
446

447
    #[test]
448
    #[cfg(feature = "embed")]
449
    fn test_as_arg_info() {
450
        let arg = Arg::new("test", DataType::Long);
451
        let arg_info = arg.as_arg_info();
452
        assert!(arg_info.is_ok());
453

454
        let arg_info = arg_info.unwrap();
455
        assert!(arg_info.default_value.is_null());
456

457
        let r#type = arg_info.type_;
458
        assert_eq!(r#type.type_mask, 16);
459
    }
460

461
    #[test]
462
    #[cfg(feature = "embed")]
463
    fn test_as_arg_info_with_default() {
464
        let arg = Arg::new("test", DataType::Long).default("default");
465
        let arg_info = arg.as_arg_info();
466
        assert!(arg_info.is_ok());
467

468
        let arg_info = arg_info.unwrap();
469
        assert!(!arg_info.default_value.is_null());
470

471
        let r#type = arg_info.type_;
472
        assert_eq!(r#type.type_mask, 16);
473
    }
474

475
    #[test]
476
    fn test_type_from_arg() {
477
        let arg = Arg::new("test", DataType::Long);
478
        let actual: _zend_expected_type = arg.into();
479
        assert_eq!(actual, 0);
480

481
        let arg = Arg::new("test", DataType::Long).allow_null();
482
        let actual: _zend_expected_type = arg.into();
483
        assert_eq!(actual, 1);
484

485
        let arg = Arg::new("test", DataType::False);
486
        let actual: _zend_expected_type = arg.into();
487
        assert_eq!(actual, 2);
488

489
        let arg = Arg::new("test", DataType::False).allow_null();
490
        let actual: _zend_expected_type = arg.into();
491
        assert_eq!(actual, 3);
492

493
        let arg = Arg::new("test", DataType::True);
494
        let actual: _zend_expected_type = arg.into();
495
        assert_eq!(actual, 2);
496

497
        let arg = Arg::new("test", DataType::True).allow_null();
498
        let actual: _zend_expected_type = arg.into();
499
        assert_eq!(actual, 3);
500

501
        let arg = Arg::new("test", DataType::String);
502
        let actual: _zend_expected_type = arg.into();
503
        assert_eq!(actual, 4);
504

505
        let arg = Arg::new("test", DataType::String).allow_null();
506
        let actual: _zend_expected_type = arg.into();
507
        assert_eq!(actual, 5);
508

509
        let arg = Arg::new("test", DataType::Array);
510
        let actual: _zend_expected_type = arg.into();
511
        assert_eq!(actual, 6);
512

513
        let arg = Arg::new("test", DataType::Array).allow_null();
514
        let actual: _zend_expected_type = arg.into();
515
        assert_eq!(actual, 7);
516

517
        let arg = Arg::new("test", DataType::Resource);
518
        let actual: _zend_expected_type = arg.into();
519
        assert_eq!(actual, 14);
520

521
        let arg = Arg::new("test", DataType::Resource).allow_null();
522
        let actual: _zend_expected_type = arg.into();
523
        assert_eq!(actual, 15);
524

525
        let arg = Arg::new("test", DataType::Object(None));
526
        let actual: _zend_expected_type = arg.into();
527
        assert_eq!(actual, 18);
528

529
        let arg = Arg::new("test", DataType::Object(None)).allow_null();
530
        let actual: _zend_expected_type = arg.into();
531
        assert_eq!(actual, 19);
532

533
        let arg = Arg::new("test", DataType::Double);
534
        let actual: _zend_expected_type = arg.into();
535
        assert_eq!(actual, 20);
536

537
        let arg = Arg::new("test", DataType::Double).allow_null();
538
        let actual: _zend_expected_type = arg.into();
539
        assert_eq!(actual, 21);
540
    }
541

542
    #[test]
543
    fn test_param_from_arg() {
544
        let arg = Arg::new("test", DataType::Long)
545
            .default("default")
546
            .allow_null();
547
        let param: Parameter = arg.into();
548
        assert_eq!(param.name, "test".into());
549
        assert_eq!(param.ty, abi::Option::Some(DataType::Long));
550
        assert!(param.nullable);
551
        assert_eq!(param.default, abi::Option::Some("default".into()));
552
    }
553

554
    #[test]
555
    fn test_arg_parser_new() {
556
        let arg_zvals = vec![None, None];
557
        let parser = ArgParser::new(arg_zvals);
558
        assert_eq!(parser.arg_zvals.len(), 2);
559
        assert!(parser.args.is_empty());
560
        assert!(parser.min_num_args.is_none());
561
    }
562

563
    #[test]
564
    fn test_arg_parser_arg() {
565
        let arg_zvals = vec![None, None];
566
        let mut parser = ArgParser::new(arg_zvals);
567
        let mut arg = Arg::new("test", DataType::Long);
568
        parser = parser.arg(&mut arg);
569
        assert_eq!(parser.args.len(), 1);
570
        assert_eq!(parser.args[0].name, "test");
571
        assert_eq!(parser.args[0].r#type, DataType::Long);
572
    }
573

574
    // TODO: test parse
575
}
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

© 2025 Coveralls, Inc