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

extphprs / ext-php-rs / 23718488839

29 Mar 2026 08:35PM UTC coverage: 65.13% (+31.0%) from 34.103%
23718488839

push

github

web-flow
ci(coverage): switch from tarpaulin to cargo-llvm-cov (#702)

7811 of 11993 relevant lines covered (65.13%)

32.6 hits per line

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

37.04
/src/types/array/iterators.rs
1
use cfg_if::cfg_if;
2
use std::{
3
    convert::TryInto,
4
    iter::{DoubleEndedIterator, ExactSizeIterator, Iterator},
5
    ptr,
6
};
7

8
use super::{ArrayKey, ZendHashTable};
9
use crate::boxed::ZBox;
10
use crate::{
11
    convert::FromZval,
12
    ffi::{
13
        HashPosition, zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
14
        zend_hash_get_current_key_zval_ex, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
15
    },
16
    types::Zval,
17
};
18

19
#[cfg(php85)]
20
use crate::ffi::zend_hash_key_type_HASH_KEY_NON_EXISTENT;
21

22
/// Immutable iterator upon a reference to a hashtable.
23
pub struct Iter<'a> {
24
    ht: &'a ZendHashTable,
25
    current_num: i64,
26
    end_num: i64,
27
    pos: HashPosition,
28
    end_pos: HashPosition,
29
}
30

31
impl<'a> Iter<'a> {
32
    /// Creates a new iterator over a hashtable.
33
    ///
34
    /// # Parameters
35
    ///
36
    /// * `ht` - The hashtable to iterate.
37
    ///
38
    /// # Panics
39
    ///
40
    /// Panics if the hashtable length exceeds `i64::MAX`.
41
    #[must_use]
42
    pub fn new(ht: &'a ZendHashTable) -> Self {
49✔
43
        let end_num: i64 = ht
49✔
44
            .len()
49✔
45
            .try_into()
49✔
46
            .expect("Integer overflow in hashtable length");
49✔
47
        let end_pos = if ht.nNumOfElements > 0 {
49✔
48
            ht.nNumOfElements - 1
49✔
49
        } else {
50
            0
×
51
        };
52

53
        Self {
49✔
54
            ht,
49✔
55
            current_num: 0,
49✔
56
            end_num,
49✔
57
            pos: 0,
49✔
58
            end_pos,
49✔
59
        }
49✔
60
    }
49✔
61

62
    /// Advances the iterator and returns the next key-value pair as raw Zvals.
63
    ///
64
    /// Returns `None` when iteration is finished.
65
    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
173✔
66
        if self.current_num >= self.end_num {
173✔
67
            return None;
45✔
68
        }
128✔
69

70
        let key_type = unsafe {
128✔
71
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
128✔
72
        };
73

74
        // Key type `1` is string (HASH_KEY_IS_STRING)
75
        // Key type `2` is long (HASH_KEY_IS_LONG)
76
        // Key type `3` is null/non-existent (HASH_KEY_NON_EXISTENT)
77
        // Pre-PHP 8.5 returns int, PHP 8.5+ returns enum u32
78
        cfg_if! {
79
            if #[cfg(php85)] {
80
                if key_type == zend_hash_key_type_HASH_KEY_NON_EXISTENT {
81
                    return None;
82
                }
83
            } else {
84
                // Pre-PHP 8.5: function returns signed int (i32)
85
                // Check for -1 (defensive) and 3 (HASH_KEY_NON_EXISTENT)
86
                if key_type == -1 || key_type == 3 {
128✔
87
                    return None;
×
88
                }
128✔
89
            }
90
        }
91

92
        let mut key = Zval::new();
128✔
93

94
        unsafe {
128✔
95
            zend_hash_get_current_key_zval_ex(
128✔
96
                ptr::from_ref(self.ht).cast_mut(),
128✔
97
                (&raw const key).cast_mut(),
128✔
98
                &raw mut self.pos,
128✔
99
            );
128✔
100
        }
128✔
101
        let value = unsafe {
128✔
102
            let val_ptr =
128✔
103
                zend_hash_get_current_data_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos);
128✔
104

105
            if val_ptr.is_null() {
128✔
106
                return None;
×
107
            }
128✔
108

109
            &*val_ptr
128✔
110
        };
111

112
        if !key.is_long() && !key.is_string() {
128✔
113
            key.set_long(self.current_num);
×
114
        }
128✔
115

116
        unsafe { zend_hash_move_forward_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos) };
128✔
117
        self.current_num += 1;
128✔
118

119
        Some((key, value))
128✔
120
    }
173✔
121
}
122

123
impl<'a> IntoIterator for &'a ZendHashTable {
124
    type Item = (ArrayKey<'a>, &'a Zval);
125
    type IntoIter = Iter<'a>;
126

127
    /// Returns an iterator over the key(s) and value contained inside the
128
    /// hashtable.
129
    ///
130
    /// # Example
131
    ///
132
    /// ```no_run
133
    /// use ext_php_rs::types::ZendHashTable;
134
    ///
135
    /// let mut ht = ZendHashTable::new();
136
    ///
137
    /// for (key, val) in ht.iter() {
138
    /// //   ^ Index if inserted at an index.
139
    /// //        ^ Optional string key, if inserted like a hashtable.
140
    /// //             ^ Inserted value.
141
    ///
142
    ///     dbg!(key, val);
143
    /// }
144
    #[inline]
145
    fn into_iter(self) -> Self::IntoIter {
49✔
146
        Iter::new(self)
49✔
147
    }
49✔
148
}
149

150
impl<'a> Iterator for Iter<'a> {
151
    type Item = (ArrayKey<'a>, &'a Zval);
152

153
    fn next(&mut self) -> Option<Self::Item> {
173✔
154
        self.next_zval()
173✔
155
            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
173✔
156
    }
173✔
157

158
    fn count(self) -> usize
×
159
    where
×
160
        Self: Sized,
×
161
    {
162
        self.ht.len()
×
163
    }
×
164
}
165

166
impl ExactSizeIterator for Iter<'_> {
167
    fn len(&self) -> usize {
×
168
        self.ht.len()
×
169
    }
×
170
}
171

172
impl DoubleEndedIterator for Iter<'_> {
173
    fn next_back(&mut self) -> Option<Self::Item> {
×
174
        if self.end_num <= self.current_num {
×
175
            return None;
×
176
        }
×
177

178
        let key_type = unsafe {
×
179
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
180
        };
181

182
        cfg_if! {
183
            if #[cfg(php85)] {
184
                if key_type == zend_hash_key_type_HASH_KEY_NON_EXISTENT {
185
                    return None;
186
                }
187
            } else {
188
                // Pre-PHP 8.5: function returns signed int (i32)
189
                // Check for -1 (defensive) and 3 (HASH_KEY_NON_EXISTENT)
190
                if key_type == -1 || key_type == 3 {
×
191
                    return None;
×
192
                }
×
193
            }
194
        }
195

196
        let key = Zval::new();
×
197

198
        unsafe {
×
199
            zend_hash_get_current_key_zval_ex(
×
200
                ptr::from_ref(self.ht).cast_mut(),
×
201
                (&raw const key).cast_mut(),
×
202
                &raw mut self.end_pos,
×
203
            );
×
204
        }
×
205
        let value = unsafe {
×
206
            &*zend_hash_get_current_data_ex(
×
207
                ptr::from_ref(self.ht).cast_mut(),
×
208
                &raw mut self.end_pos,
×
209
            )
×
210
        };
211

212
        let key = match ArrayKey::from_zval(&key) {
×
213
            Some(key) => key,
×
214
            None => ArrayKey::Long(self.end_num),
×
215
        };
216

217
        unsafe {
218
            zend_hash_move_backwards_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.end_pos)
×
219
        };
220
        self.end_num -= 1;
×
221

222
        Some((key, value))
×
223
    }
×
224
}
225

226
/// Immutable iterator which iterates over the values of the hashtable, as it
227
/// was a set or list.
228
pub struct Values<'a>(Iter<'a>);
229

230
impl<'a> Values<'a> {
231
    /// Creates a new iterator over a hashtables values.
232
    ///
233
    /// # Parameters
234
    ///
235
    /// * `ht` - The hashtable to iterate.
236
    #[must_use]
237
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
238
        Self(Iter::new(ht))
×
239
    }
×
240
}
241

242
impl<'a> Iterator for Values<'a> {
243
    type Item = &'a Zval;
244

245
    fn next(&mut self) -> Option<Self::Item> {
×
246
        self.0.next().map(|(_, zval)| zval)
×
247
    }
×
248

249
    fn count(self) -> usize
×
250
    where
×
251
        Self: Sized,
×
252
    {
253
        self.0.count()
×
254
    }
×
255
}
256

257
impl ExactSizeIterator for Values<'_> {
258
    fn len(&self) -> usize {
×
259
        self.0.len()
×
260
    }
×
261
}
262

263
impl DoubleEndedIterator for Values<'_> {
264
    fn next_back(&mut self) -> Option<Self::Item> {
×
265
        self.0.next_back().map(|(_, zval)| zval)
×
266
    }
×
267
}
268

269
impl FromIterator<Zval> for ZBox<ZendHashTable> {
270
    fn from_iter<T: IntoIterator<Item = Zval>>(iter: T) -> Self {
×
271
        let mut ht = ZendHashTable::new();
×
272
        for item in iter {
×
273
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
×
274
            // `val` to a zval fails.
×
275
            let _ = ht.push(item);
×
276
        }
×
277
        ht
×
278
    }
×
279
}
280

281
impl FromIterator<(i64, Zval)> for ZBox<ZendHashTable> {
282
    fn from_iter<T: IntoIterator<Item = (i64, Zval)>>(iter: T) -> Self {
×
283
        let mut ht = ZendHashTable::new();
×
284
        for (key, val) in iter {
×
285
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
×
286
            // `val` to a zval fails.
×
287
            let _ = ht.insert_at_index(key, val);
×
288
        }
×
289
        ht
×
290
    }
×
291
}
292

293
impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
294
    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
×
295
        let mut ht = ZendHashTable::new();
×
296
        for (key, val) in iter {
×
297
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
×
298
            // `val` to a zval fails.
×
299
            let _ = ht.insert(key, val);
×
300
        }
×
301
        ht
×
302
    }
×
303
}
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