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

extphprs / ext-php-rs / 26832618561

02 Jun 2026 04:11PM UTC coverage: 72.397% (-0.08%) from 72.479%
26832618561

push

github

ptondereau
fix(tests): wrap PHP function handlers in zend_fastcall! for windows

FunctionHandler resolves to extern "C" on unix and to
extern "vectorcall" on windows. The new compound-type integration
handlers (class union, intersection, DNF, primitive union) and the
src/builders/function.rs noop_handler test helper were declared
as plain extern "C", which only matches the unix alias and so
failed to type-check on windows with E0308.

Wrap each handler in zend_fastcall! { ... }, the same macro
closure.rs and builders/class.rs already use. The macro rewrites
the ABI to vectorcall on windows and stays C on unix.

Also gates the noop_handler helper behind cfg(php83) since the
tests that consume it are all PHP 8.3+ (class union, intersection,
DNF return types).

Drops a redundant `#![cfg_attr(windows, feature(abi_vectorcall))]`
attribute inside src/describe/mod.rs's tests module: inner
`feature` attributes only take effect at the crate root, so it was
a no-op that produced "the `#![feature]` attribute can only be
used at the crate root" on every windows compile. The crate root
already enables the feature in src/lib.rs.

0 of 1 new or added line in 1 file covered. (0.0%)

257 existing lines in 10 files now uncovered.

11564 of 15973 relevant lines covered (72.4%)

33.46 hits per line

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

76.75
/src/types/array/entry.rs
1
//! Entry API for [`ZendHashTable`], similar to Rust's `std::collections::hash_map::Entry`.
2
//!
3
//! This module provides an ergonomic API for working with entries in a PHP
4
//! hashtable, allowing conditional insertion or modification based on whether
5
//! a key already exists.
6
//!
7
//! # Examples
8
//!
9
//! ```no_run
10
//! use ext_php_rs::types::ZendHashTable;
11
//!
12
//! let mut ht = ZendHashTable::new();
13
//!
14
//! // Insert a default value if the key doesn't exist
15
//! ht.entry("counter").or_insert(0i64);
16
//!
17
//! // Modify the value if it exists
18
//! ht.entry("counter").and_modify(|v| {
19
//!     if let Some(n) = v.long() {
20
//!         v.set_long(n + 1);
21
//!     }
22
//! });
23
//!
24
//! // Or use or_insert_with for lazy initialization
25
//! ht.entry("computed").or_insert_with(|| "computed value");
26
//! ```
27

28
use super::{ArrayKey, ZendHashTable};
29
use crate::error::Error;
30
use crate::{
31
    convert::IntoZval,
32
    error::Result,
33
    ffi::{
34
        zend_hash_find, zend_hash_index_find, zend_hash_index_update, zend_hash_str_find,
35
        zend_hash_str_update, zend_hash_update, zend_ulong,
36
    },
37
    types::Zval,
38
};
39
use std::mem::ManuallyDrop;
40

41
/// A view into a single entry in a [`ZendHashTable`], which may either be vacant or
42
/// occupied.
43
///
44
/// This enum is constructed from the [`entry`] method on [`ZendHashTable`].
45
///
46
/// [`entry`]: ZendHashTable::entry
47
pub enum Entry<'a, 'k> {
48
    /// An occupied entry.
49
    Occupied(OccupiedEntry<'a, 'k>),
50
    /// A vacant entry.
51
    Vacant(VacantEntry<'a, 'k>),
52
}
53

54
/// A view into an occupied entry in a [`ZendHashTable`].
55
///
56
/// It is part of the [`Entry`] enum.
57
pub struct OccupiedEntry<'a, 'k> {
58
    ht: &'a mut ZendHashTable,
59
    key: ArrayKey<'k>,
60
}
61

62
/// A view into a vacant entry in a [`ZendHashTable`].
63
///
64
/// It is part of the [`Entry`] enum.
65
pub struct VacantEntry<'a, 'k> {
66
    ht: &'a mut ZendHashTable,
67
    key: ArrayKey<'k>,
68
}
69

70
impl<'a, 'k> Entry<'a, 'k> {
71
    /// Ensures a value is in the entry by inserting the default if empty, and
72
    /// returns a mutable reference to the value in the entry.
73
    ///
74
    /// # Parameters
75
    ///
76
    /// * `default` - The default value to insert if the entry is vacant.
77
    ///
78
    /// # Returns
79
    ///
80
    /// A result containing a mutable reference to the value, or an error if
81
    /// the insertion failed.
82
    ///
83
    /// # Errors
84
    ///
85
    /// Returns an error if the value conversion to [`Zval`] fails or if
86
    /// the key contains a null byte (for string keys).
87
    ///
88
    /// # Example
89
    ///
90
    /// ```no_run
91
    /// use ext_php_rs::types::ZendHashTable;
92
    ///
93
    /// let mut ht = ZendHashTable::new();
94
    /// ht.entry("key").or_insert("default value");
95
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("default value"));
96
    /// ```
97
    pub fn or_insert<V: IntoZval>(self, default: V) -> Result<&'a mut Zval> {
7✔
98
        match self {
7✔
99
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
2✔
100
            Entry::Vacant(entry) => entry.insert(default),
5✔
101
        }
102
    }
7✔
103

104
    /// Ensures a value is in the entry by inserting the result of the default
105
    /// function if empty, and returns a mutable reference to the value in the
106
    /// entry.
107
    ///
108
    /// # Parameters
109
    ///
110
    /// * `default` - A function that returns the default value to insert.
111
    ///
112
    /// # Returns
113
    ///
114
    /// A result containing a mutable reference to the value, or an error if
115
    /// the insertion failed.
116
    ///
117
    /// # Errors
118
    ///
119
    /// Returns an error if the value conversion to [`Zval`] fails or if
120
    /// the key contains a null byte (for string keys).
121
    ///
122
    /// # Example
123
    ///
124
    /// ```no_run
125
    /// use ext_php_rs::types::ZendHashTable;
126
    ///
127
    /// let mut ht = ZendHashTable::new();
128
    /// ht.entry("key").or_insert_with(|| "computed value");
129
    /// ```
130
    pub fn or_insert_with<V: IntoZval, F: FnOnce() -> V>(self, default: F) -> Result<&'a mut Zval> {
1✔
131
        match self {
1✔
132
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
133
            Entry::Vacant(entry) => entry.insert(default()),
1✔
134
        }
135
    }
1✔
136

137
    /// Ensures a value is in the entry by inserting the result of the default
138
    /// function if empty. The function receives a reference to the key.
139
    ///
140
    /// # Parameters
141
    ///
142
    /// * `default` - A function that takes the key and returns the default value.
143
    ///
144
    /// # Returns
145
    ///
146
    /// A result containing a mutable reference to the value, or an error if
147
    /// the insertion failed.
148
    ///
149
    /// # Errors
150
    ///
151
    /// Returns an error if the value conversion to [`Zval`] fails or if
152
    /// the key contains a null byte (for string keys).
153
    ///
154
    /// # Example
155
    ///
156
    /// ```no_run
157
    /// use ext_php_rs::types::ZendHashTable;
158
    ///
159
    /// let mut ht = ZendHashTable::new();
160
    /// ht.entry("key").or_insert_with_key(|k| format!("value for {}", k));
161
    /// ```
162
    pub fn or_insert_with_key<V: IntoZval, F: FnOnce(&ArrayKey<'k>) -> V>(
×
163
        self,
×
164
        default: F,
×
165
    ) -> Result<&'a mut Zval> {
×
166
        match self {
×
167
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
168
            Entry::Vacant(entry) => {
×
169
                let value = default(entry.key());
×
170
                entry.insert(value)
×
171
            }
172
        }
173
    }
×
174

175
    /// Returns a reference to this entry's key.
176
    ///
177
    /// # Example
178
    ///
179
    /// ```no_run
180
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
181
    ///
182
    /// let mut ht = ZendHashTable::new();
183
    /// assert_eq!(ht.entry("key").key(), &ArrayKey::Str("key"));
184
    /// ```
185
    #[must_use]
186
    pub fn key(&self) -> &ArrayKey<'k> {
×
187
        match self {
×
188
            Entry::Occupied(entry) => entry.key(),
×
189
            Entry::Vacant(entry) => entry.key(),
×
190
        }
191
    }
×
192

193
    /// Provides in-place mutable access to an occupied entry before any
194
    /// potential inserts into the map.
195
    ///
196
    /// # Parameters
197
    ///
198
    /// * `f` - A function that modifies the value in place.
199
    ///
200
    /// # Returns
201
    ///
202
    /// The entry, allowing for method chaining.
203
    ///
204
    /// # Example
205
    ///
206
    /// ```no_run
207
    /// use ext_php_rs::types::ZendHashTable;
208
    ///
209
    /// let mut ht = ZendHashTable::new();
210
    /// ht.insert("counter", 0i64);
211
    ///
212
    /// ht.entry("counter")
213
    ///     .and_modify(|v| {
214
    ///         if let Some(n) = v.long() {
215
    ///             v.set_long(n + 1);
216
    ///         }
217
    ///     })
218
    ///     .or_insert(0i64);
219
    /// ```
220
    #[must_use]
221
    pub fn and_modify<F: FnOnce(&mut Zval)>(self, f: F) -> Self {
2✔
222
        match self {
2✔
223
            Entry::Occupied(mut entry) => {
1✔
224
                if let Some(value) = entry.get_mut() {
1✔
225
                    f(value);
1✔
226
                }
1✔
227
                Entry::Occupied(entry)
1✔
228
            }
229
            Entry::Vacant(entry) => Entry::Vacant(entry),
1✔
230
        }
231
    }
2✔
232
}
233

234
impl<'a, 'k> Entry<'a, 'k>
235
where
236
    'k: 'a,
237
{
238
    /// Ensures a value is in the entry by inserting the default value if empty,
239
    /// and returns a mutable reference to the value in the entry.
240
    ///
241
    /// This is a convenience method that uses `Default::default()` as the
242
    /// default value.
243
    ///
244
    /// # Returns
245
    ///
246
    /// A result containing a mutable reference to the value, or an error if
247
    /// the insertion failed.
248
    ///
249
    /// # Errors
250
    ///
251
    /// Returns an error if the key contains a null byte (for string keys).
252
    ///
253
    /// # Example
254
    ///
255
    /// ```no_run
256
    /// use ext_php_rs::types::ZendHashTable;
257
    ///
258
    /// let mut ht = ZendHashTable::new();
259
    /// // Inserts a null Zval if the key doesn't exist
260
    /// ht.entry("key").or_default();
261
    /// ```
262
    pub fn or_default(self) -> Result<&'a mut Zval> {
×
263
        match self {
×
264
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
265
            Entry::Vacant(entry) => entry.insert(Zval::new()),
×
266
        }
267
    }
×
268
}
269

270
impl<'a, 'k> OccupiedEntry<'a, 'k> {
271
    /// Creates a new occupied entry.
272
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
4✔
273
        Self { ht, key }
4✔
274
    }
4✔
275

276
    /// Gets a reference to the key in the entry.
277
    ///
278
    /// # Example
279
    ///
280
    /// ```no_run
281
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
282
    ///
283
    /// let mut ht = ZendHashTable::new();
284
    /// ht.insert("key", "value");
285
    ///
286
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
287
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
288
    /// }
289
    /// ```
290
    #[must_use]
291
    pub fn key(&self) -> &ArrayKey<'k> {
×
292
        &self.key
×
293
    }
×
294

295
    /// Gets a reference to the value in the entry.
296
    ///
297
    /// # Returns
298
    ///
299
    /// A result containing a reference to the value, or an error if
300
    /// the key conversion failed.
301
    ///
302
    /// # Errors
303
    ///
304
    /// Returns an error if the key contains a null byte (for string keys).
305
    ///
306
    /// # Example
307
    ///
308
    /// ```no_run
309
    /// use ext_php_rs::types::{ZendHashTable, Zval};
310
    ///
311
    /// let mut ht = ZendHashTable::new();
312
    /// ht.insert("key", "value");
313
    ///
314
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
315
    ///     assert_eq!(entry.get().and_then(Zval::str), Some("value"));
316
    /// }
317
    /// ```
318
    #[must_use]
319
    pub fn get(&self) -> Option<&Zval> {
2✔
320
        match &self.key {
2✔
321
            ArrayKey::Long(index) => unsafe {
×
322
                #[allow(clippy::cast_sign_loss)]
323
                zend_hash_index_find(self.ht, *index as zend_ulong).as_ref()
×
324
            },
325
            ArrayKey::String(key) => unsafe {
×
326
                zend_hash_str_find(self.ht, key.as_ptr().cast(), key.len()).as_ref()
×
327
            },
328
            ArrayKey::Str(key) => unsafe {
2✔
329
                zend_hash_str_find(self.ht, key.as_ptr().cast(), key.len()).as_ref()
2✔
330
            },
UNCOV
331
            ArrayKey::ZendString(key) => unsafe {
×
UNCOV
332
                zend_hash_find(self.ht, key.as_ptr().cast_mut()).as_ref()
×
333
            },
334
        }
335
    }
2✔
336

337
    /// Gets a mutable reference to the value in the entry.
338
    ///
339
    /// # Returns
340
    ///
341
    /// A result containing a mutable reference to the value, or an error if
342
    /// the key conversion failed.
343
    ///
344
    /// # Errors
345
    ///
346
    /// Returns an error if the key contains a null byte (for string keys).
347
    ///
348
    /// # Example
349
    ///
350
    /// ```no_run
351
    /// use ext_php_rs::types::ZendHashTable;
352
    ///
353
    /// let mut ht = ZendHashTable::new();
354
    /// ht.insert("counter", 0i64);
355
    ///
356
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("counter") {
357
    ///     if let Some(v) = entry.get_mut() {
358
    ///         if let Some(n) = v.long() {
359
    ///             v.set_long(n + 1);
360
    ///         }
361
    ///     }
362
    /// }
363
    /// ```
364
    pub fn get_mut(&mut self) -> Option<&mut Zval> {
1✔
365
        get_value_mut(&self.key, self.ht)
1✔
366
    }
1✔
367

368
    /// Converts the entry into a mutable reference to the value.
369
    ///
370
    /// If you need multiple references to the `OccupiedEntry`, see [`get_mut`].
371
    ///
372
    /// [`get_mut`]: OccupiedEntry::get_mut
373
    ///
374
    /// # Returns
375
    ///
376
    /// A result containing a mutable reference to the value with the entry's
377
    /// lifetime, or an error if the key conversion failed.
378
    ///
379
    /// # Errors
380
    ///
381
    /// Returns an error if the key contains a null byte (for string keys).
382
    ///
383
    /// # Example
384
    ///
385
    /// ```no_run
386
    /// use ext_php_rs::types::ZendHashTable;
387
    ///
388
    /// let mut ht = ZendHashTable::new();
389
    /// ht.insert("key", "value");
390
    ///
391
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
392
    ///     let value = entry.into_mut().unwrap();
393
    ///     // value has the lifetime of the hashtable borrow
394
    /// }
395
    /// ```
396
    #[must_use]
397
    pub fn into_mut(self) -> Option<&'a mut Zval> {
2✔
398
        get_value_mut(&self.key, self.ht)
2✔
399
    }
2✔
400

401
    /// Sets the value of the entry, returning the old value.
402
    ///
403
    /// # Parameters
404
    ///
405
    /// * `value` - The new value to set.
406
    ///
407
    /// # Returns
408
    ///
409
    /// A result containing the old value (shallow cloned), or an error if the
410
    /// insertion failed.
411
    ///
412
    /// # Errors
413
    ///
414
    /// Returns an error if the value conversion to [`Zval`] fails or if
415
    /// the key contains a null byte (for string keys).
416
    ///
417
    /// # Example
418
    ///
419
    /// ```no_run
420
    /// use ext_php_rs::types::{ZendHashTable, Zval};
421
    ///
422
    /// let mut ht = ZendHashTable::new();
423
    /// ht.insert("key", "old");
424
    ///
425
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("key") {
426
    ///     let old = entry.insert("new").unwrap();
427
    ///     assert_eq!(old.as_ref().and_then(Zval::str), Some("old"));
428
    /// }
429
    /// ```
430
    pub fn insert<V: IntoZval>(&mut self, value: V) -> Result<Option<Zval>> {
1✔
431
        let old = self.get().map(Zval::shallow_clone);
1✔
432
        insert_value(&self.key, self.ht, value)?;
1✔
433
        Ok(old)
1✔
434
    }
1✔
435

436
    /// Takes ownership of the key and value from the entry, removing it from
437
    /// the hashtable.
438
    ///
439
    /// # Returns
440
    ///
441
    /// A result containing a tuple of the key and the removed value (shallow
442
    /// cloned), or an error if the removal failed.
443
    ///
444
    /// # Errors
445
    ///
446
    /// Returns an error if the key contains a null byte (for string keys).
447
    ///
448
    /// # Example
449
    ///
450
    /// ```no_run
451
    /// use ext_php_rs::types::ZendHashTable;
452
    ///
453
    /// let mut ht = ZendHashTable::new();
454
    /// ht.insert("key", "value");
455
    ///
456
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
457
    ///     let (key, value) = entry.remove_entry();
458
    ///     assert!(ht.get("key").is_none());
459
    /// }
460
    /// ```
461
    pub fn remove_entry(self) -> (ArrayKey<'k>, Option<Zval>) {
×
462
        let value = self.get().map(Zval::shallow_clone);
×
UNCOV
463
        self.ht.remove(self.key.clone());
×
UNCOV
464
        (self.key, value)
×
UNCOV
465
    }
×
466

467
    /// Removes the value from the entry, returning it.
468
    ///
469
    /// # Returns
470
    ///
471
    /// A result containing the removed value (shallow cloned), or an error if
472
    /// the removal failed.
473
    ///
474
    /// # Errors
475
    ///
476
    /// Returns an error if the key contains a null byte (for string keys).
477
    ///
478
    /// # Example
479
    ///
480
    /// ```no_run
481
    /// use ext_php_rs::types::ZendHashTable;
482
    ///
483
    /// let mut ht = ZendHashTable::new();
484
    /// ht.insert("key", "value");
485
    ///
486
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
487
    ///     let value = entry.remove().unwrap();
488
    ///     assert_eq!(value.str(), Some("value"));
489
    /// }
490
    /// ```
491
    pub fn remove(self) -> Option<Zval> {
1✔
492
        let value = self.get().map(Zval::shallow_clone);
1✔
493
        self.ht.remove(self.key);
1✔
494
        value
1✔
495
    }
1✔
496
}
497

498
impl<'a, 'k> VacantEntry<'a, 'k> {
499
    /// Creates a new vacant entry.
500
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
7✔
501
        Self { ht, key }
7✔
502
    }
7✔
503

504
    /// Gets a reference to the key that would be used when inserting a value
505
    /// through the `VacantEntry`.
506
    ///
507
    /// # Example
508
    ///
509
    /// ```no_run
510
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
511
    ///
512
    /// let mut ht = ZendHashTable::new();
513
    ///
514
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
515
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
516
    /// }
517
    /// ```
518
    #[must_use]
UNCOV
519
    pub fn key(&self) -> &ArrayKey<'k> {
×
UNCOV
520
        &self.key
×
UNCOV
521
    }
×
522

523
    /// Take ownership of the key.
524
    ///
525
    /// # Example
526
    ///
527
    /// ```no_run
528
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
529
    ///
530
    /// let mut ht = ZendHashTable::new();
531
    ///
532
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
533
    ///     let key = entry.into_key();
534
    ///     assert_eq!(key, ArrayKey::Str("key"));
535
    /// }
536
    /// ```
537
    #[must_use]
538
    pub fn into_key(self) -> ArrayKey<'k> {
1✔
539
        self.key
1✔
540
    }
1✔
541

542
    /// Sets the value of the entry with the `VacantEntry`'s key, and returns
543
    /// a mutable reference to it.
544
    ///
545
    /// # Parameters
546
    ///
547
    /// * `value` - The value to insert.
548
    ///
549
    /// # Returns
550
    ///
551
    /// A result containing a mutable reference to the inserted value, or an
552
    /// error if the insertion failed.
553
    ///
554
    /// # Errors
555
    ///
556
    /// Returns an error if the value conversion to [`Zval`] fails or if
557
    /// the key contains a null byte (for string keys).
558
    ///
559
    /// # Example
560
    ///
561
    /// ```no_run
562
    /// use ext_php_rs::types::ZendHashTable;
563
    ///
564
    /// let mut ht = ZendHashTable::new();
565
    ///
566
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
567
    ///     entry.insert("value");
568
    /// }
569
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
570
    /// ```
571
    pub fn insert<V: IntoZval>(self, value: V) -> Result<&'a mut Zval> {
6✔
572
        insert_value(&self.key, self.ht, value)
6✔
573
    }
6✔
574
}
575

576
/// Helper function to get a mutable value from the hashtable by key.
577
#[inline]
578
fn get_value_mut<'a>(key: &ArrayKey<'_>, ht: &'a mut ZendHashTable) -> Option<&'a mut Zval> {
3✔
579
    match key {
3✔
UNCOV
580
        ArrayKey::Long(index) => unsafe {
×
581
            #[allow(clippy::cast_sign_loss)]
582
            zend_hash_index_find(ht, *index as zend_ulong).as_mut()
×
583
        },
UNCOV
584
        ArrayKey::String(key) => unsafe {
×
UNCOV
585
            zend_hash_str_find(ht, key.as_ptr().cast(), key.len()).as_mut()
×
586
        },
587
        ArrayKey::Str(key) => unsafe {
3✔
588
            zend_hash_str_find(ht, key.as_ptr().cast(), key.len()).as_mut()
3✔
589
        },
UNCOV
590
        ArrayKey::ZendString(key) => unsafe {
×
UNCOV
591
            zend_hash_find(ht, key.as_ptr().cast_mut()).as_mut()
×
592
        },
593
    }
594
}
3✔
595

596
/// Helper function to insert a value into the hashtable by key.
597
fn insert_value<'a, V: IntoZval>(
7✔
598
    key: &ArrayKey<'_>,
7✔
599
    ht: &'a mut ZendHashTable,
7✔
600
    value: V,
7✔
601
) -> Result<&'a mut Zval> {
7✔
602
    // Wrap in ManuallyDrop to prevent drop from freeing the underlying data -
603
    // ownership is transferred to the hash table via shallow copy
604
    let mut val = ManuallyDrop::new(value.into_zval(false)?);
7✔
605
    match key {
7✔
606
        ArrayKey::Long(index) => unsafe {
1✔
607
            #[allow(clippy::cast_sign_loss)]
608
            zend_hash_index_update(ht, *index as zend_ulong, &raw mut *val)
1✔
609
                .as_mut()
1✔
610
                .ok_or(Error::InvalidPointer)
1✔
611
        },
UNCOV
612
        ArrayKey::String(key) => unsafe {
×
UNCOV
613
            zend_hash_str_update(ht, key.as_ptr().cast(), key.len(), &raw mut *val)
×
UNCOV
614
                .as_mut()
×
UNCOV
615
                .ok_or(Error::InvalidPointer)
×
616
        },
617
        ArrayKey::Str(key) => unsafe {
5✔
618
            zend_hash_str_update(ht, key.as_ptr().cast(), key.len(), &raw mut *val)
5✔
619
                .as_mut()
5✔
620
                .ok_or(Error::InvalidPointer)
5✔
621
        },
622
        ArrayKey::ZendString(key) => unsafe {
1✔
623
            // zend_hash_update does the addref itself for non-interned strings.
624
            zend_hash_update(ht, key.as_ptr().cast_mut(), &raw mut *val)
1✔
625
                .as_mut()
1✔
626
                .ok_or(Error::InvalidPointer)
1✔
627
        },
628
    }
629
}
7✔
630

631
#[cfg(test)]
632
#[cfg(feature = "embed")]
633
mod tests {
634
    use super::*;
635
    use crate::embed::Embed;
636
    use crate::types::ZendStr;
637

638
    #[test]
639
    fn test_entry_or_insert() {
1✔
640
        Embed::run(|| {
1✔
641
            let mut ht = ZendHashTable::new();
1✔
642

643
            // Insert into vacant entry
644
            let result = ht.entry("key").or_insert("value");
1✔
645
            assert!(result.is_ok());
1✔
646
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
1✔
647

648
            // Entry already exists, should return existing value
649
            let result = ht.entry("key").or_insert("other");
1✔
650
            assert!(result.is_ok());
1✔
651
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
1✔
652
        });
1✔
653
    }
1✔
654

655
    #[test]
656
    fn test_entry_or_insert_with() {
1✔
657
        Embed::run(|| {
1✔
658
            let mut ht = ZendHashTable::new();
1✔
659

660
            let result = ht.entry("key").or_insert_with(|| "computed");
1✔
661
            assert!(result.is_ok());
1✔
662
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("computed"));
1✔
663
        });
1✔
664
    }
1✔
665

666
    #[test]
667
    fn test_entry_and_modify() {
1✔
668
        Embed::run(|| {
1✔
669
            let mut ht = ZendHashTable::new();
1✔
670
            let _ = ht.insert("counter", 5i64);
1✔
671

672
            let result = ht
1✔
673
                .entry("counter")
1✔
674
                .and_modify(|v| {
1✔
675
                    if let Some(n) = v.long() {
1✔
676
                        v.set_long(n + 1);
1✔
677
                    }
1✔
678
                })
1✔
679
                .or_insert(0i64);
1✔
680

681
            assert!(result.is_ok());
1✔
682
            assert_eq!(ht.get("counter").and_then(Zval::long), Some(6));
1✔
683
        });
1✔
684
    }
1✔
685

686
    #[test]
687
    fn test_entry_and_modify_vacant() {
1✔
688
        Embed::run(|| {
1✔
689
            let mut ht = ZendHashTable::new();
1✔
690

691
            // and_modify on vacant entry should be a no-op
692
            let result = ht
1✔
693
                .entry("key")
1✔
694
                .and_modify(|v| {
1✔
UNCOV
695
                    v.set_long(100);
×
UNCOV
696
                })
×
697
                .or_insert(42i64);
1✔
698

699
            assert!(result.is_ok());
1✔
700
            assert_eq!(ht.get("key").and_then(Zval::long), Some(42));
1✔
701
        });
1✔
702
    }
1✔
703

704
    #[test]
705
    fn test_occupied_entry_insert() {
1✔
706
        Embed::run(|| {
1✔
707
            let mut ht = ZendHashTable::new();
1✔
708
            let _ = ht.insert("key", "old");
1✔
709

710
            if let Entry::Occupied(mut entry) = ht.entry("key") {
1✔
711
                let old = entry.insert("new").expect("insert should succeed");
1✔
712
                assert_eq!(old.as_ref().and_then(Zval::str), Some("old"));
1✔
UNCOV
713
            }
×
714
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("new"));
1✔
715
        });
1✔
716
    }
1✔
717

718
    #[test]
719
    fn test_occupied_entry_remove() {
1✔
720
        Embed::run(|| {
1✔
721
            let mut ht = ZendHashTable::new();
1✔
722
            let _ = ht.insert("key", "value");
1✔
723

724
            if let Entry::Occupied(entry) = ht.entry("key") {
1✔
725
                let value = entry.remove().expect("remove should succeed");
1✔
726
                assert_eq!(value.str(), Some("value"));
1✔
UNCOV
727
            }
×
728
            assert!(ht.get("key").is_none());
1✔
729
        });
1✔
730
    }
1✔
731

732
    #[test]
733
    fn test_entry_with_numeric_key() {
1✔
734
        Embed::run(|| {
1✔
735
            let mut ht = ZendHashTable::new();
1✔
736

737
            let result = ht.entry(42i64).or_insert("value");
1✔
738
            assert!(result.is_ok());
1✔
739
            assert_eq!(ht.get_index(42).and_then(|v| v.str()), Some("value"));
1✔
740
        });
1✔
741
    }
1✔
742

743
    #[test]
744
    fn test_vacant_entry_into_key() {
1✔
745
        Embed::run(|| {
1✔
746
            let mut ht = ZendHashTable::new();
1✔
747

748
            if let Entry::Vacant(entry) = ht.entry("my_key") {
1✔
749
                let key = entry.into_key();
1✔
750
                assert_eq!(key, ArrayKey::Str("my_key"));
1✔
UNCOV
751
            }
×
752
        });
1✔
753
    }
1✔
754

755
    #[test]
756
    fn test_entry_with_binary_key() {
1✔
757
        Embed::run(|| {
1✔
758
            let mut ht = ZendHashTable::new();
1✔
759
            let key = "\0MyClass\0myProp";
1✔
760
            let result = ht.entry(key).or_insert("value");
1✔
761
            assert!(result.is_ok());
1✔
762
        });
1✔
763
    }
1✔
764

765
    #[test]
766
    fn test_entry_with_zend_string_key() {
1✔
767
        Embed::run(|| {
1✔
768
            let mut ht = ZendHashTable::new();
1✔
769
            let key = ZendStr::new("hello", false);
1✔
770

771
            let result = ht.entry(&key).or_insert("value");
1✔
772
            assert!(result.is_ok());
1✔
773
            assert_eq!(ht.get(&key).and_then(|v| v.str()), Some("value"));
1✔
774
        });
1✔
775
    }
1✔
776
}
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