Skip to content

Commit c150a54

Browse files
authored
fix(array): fix double ended iterator implementation (#351)
Refs: #316
1 parent b86ce64 commit c150a54

File tree

4 files changed

+97
-6
lines changed

4 files changed

+97
-6
lines changed

src/types/array.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,9 @@ impl ToOwned for ZendHashTable {
573573
pub struct Iter<'a> {
574574
ht: &'a ZendHashTable,
575575
current_num: i64,
576+
end_num: i64,
576577
pos: HashPosition,
578+
end_pos: HashPosition,
577579
}
578580

579581
#[derive(Debug, PartialEq)]
@@ -627,10 +629,22 @@ impl<'a> Iter<'a> {
627629
///
628630
/// * `ht` - The hashtable to iterate.
629631
pub fn new(ht: &'a ZendHashTable) -> Self {
632+
let end_num: i64 = ht
633+
.len()
634+
.try_into()
635+
.expect("Integer overflow in hashtable length");
636+
let end_pos = if ht.nNumOfElements > 0 {
637+
ht.nNumOfElements - 1
638+
} else {
639+
0
640+
};
641+
630642
Self {
631643
ht,
632644
current_num: 0,
645+
end_num,
633646
pos: 0,
647+
end_pos,
634648
}
635649
}
636650
}
@@ -686,6 +700,10 @@ impl ExactSizeIterator for Iter<'_> {
686700

687701
impl DoubleEndedIterator for Iter<'_> {
688702
fn next_back(&mut self) -> Option<Self::Item> {
703+
if self.end_num <= self.current_num {
704+
return None;
705+
}
706+
689707
let key_type = unsafe {
690708
zend_hash_get_current_key_type_ex(
691709
self.ht as *const ZendHashTable as *mut ZendHashTable,
@@ -703,35 +721,39 @@ impl DoubleEndedIterator for Iter<'_> {
703721
zend_hash_get_current_key_zval_ex(
704722
self.ht as *const ZendHashTable as *mut ZendHashTable,
705723
&key as *const Zval as *mut Zval,
706-
&mut self.pos as *mut HashPosition,
724+
&mut self.end_pos as *mut HashPosition,
707725
);
708726
}
709727
let value = unsafe {
710728
&*zend_hash_get_current_data_ex(
711729
self.ht as *const ZendHashTable as *mut ZendHashTable,
712-
&mut self.pos as *mut HashPosition,
730+
&mut self.end_pos as *mut HashPosition,
713731
)
714732
};
715733

716734
let key = match ArrayKey::from_zval(&key) {
717735
Some(key) => key,
718-
None => ArrayKey::Long(self.current_num),
736+
None => ArrayKey::Long(self.end_num),
719737
};
720738

721739
unsafe {
722740
zend_hash_move_backwards_ex(
723741
self.ht as *const ZendHashTable as *mut ZendHashTable,
724-
&mut self.pos as *mut HashPosition,
742+
&mut self.end_pos as *mut HashPosition,
725743
)
726744
};
727-
self.current_num -= 1;
745+
self.end_num -= 1;
728746

729747
Some((key, value))
730748
}
731749
}
732750

733751
impl<'a> Iter<'a> {
734752
pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
753+
if self.current_num >= self.end_num {
754+
return None;
755+
}
756+
735757
let key_type = unsafe {
736758
zend_hash_get_current_key_type_ex(
737759
self.ht as *const ZendHashTable as *mut ZendHashTable,

tests/src/integration/iterator.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
assert(iter_next([]) === []);
4+
assert(iter_next([1, 2, 3]) === [0, 1, 1, 2, 2, 3]);
5+
assert(iter_back([]) === []);
6+
assert(iter_back([1, 2, 3]) === [2, 3, 1, 2, 0, 1]);
7+
8+
assert(iter_next_back([], 2) === [null, null]);
9+
assert(iter_next_back([1, 2 ,3], 2) === [2, 3, 0, 1, 1, 2, null, null]);
10+
var_dump(iter_next_back([1, 2, 3, 4, 5], 3));
11+
assert(iter_next_back([1, 2, 3, 4, 5], 3) === [4, 5, 0, 1, 1, 2, 3, 4, 2, 3, null, null, null]);

tests/src/integration/iterator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[test]
2+
fn iterator_works() {
3+
assert!(crate::integration::run_php("iterator.php"));
4+
}

tests/src/lib.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use ext_php_rs::{
33
binary::Binary,
44
boxed::ZBox,
55
prelude::*,
6-
types::{ZendHashTable, ZendObject, Zval},
6+
types::{ArrayKey, ZendHashTable, ZendObject, Zval},
77
zend::ProcessGlobals,
88
};
99
use std::collections::HashMap;
@@ -112,6 +112,59 @@ pub fn test_callable(call: ZendCallable, a: String) -> Zval {
112112
call.try_call(vec![&a]).expect("Failed to call function")
113113
}
114114

115+
#[php_function]
116+
pub fn iter_next(ht: &ZendHashTable) -> Vec<Zval> {
117+
ht.iter()
118+
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
119+
.collect()
120+
}
121+
122+
#[php_function]
123+
pub fn iter_back(ht: &ZendHashTable) -> Vec<Zval> {
124+
ht.iter()
125+
.rev()
126+
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
127+
.collect()
128+
}
129+
130+
#[php_function]
131+
pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec<Option<Zval>> {
132+
let mut result = Vec::with_capacity(ht.len());
133+
let mut iter = ht.iter();
134+
135+
for i in 0..ht.len() + modulus {
136+
let entry = if i % modulus == 0 {
137+
iter.next_back()
138+
} else {
139+
iter.next()
140+
};
141+
142+
if let Some((k, v)) = entry {
143+
result.push(Some(key_to_zval(k)));
144+
result.push(Some(v.shallow_clone()));
145+
} else {
146+
result.push(None);
147+
}
148+
}
149+
150+
result
151+
}
152+
153+
fn key_to_zval(key: ArrayKey) -> Zval {
154+
match key {
155+
ArrayKey::String(s) => {
156+
let mut zval = Zval::new();
157+
let _ = zval.set_string(s.as_str(), false);
158+
zval
159+
}
160+
ArrayKey::Long(l) => {
161+
let mut zval = Zval::new();
162+
zval.set_long(l);
163+
zval
164+
}
165+
}
166+
}
167+
115168
#[php_class]
116169
pub struct TestClass {
117170
string: String,
@@ -220,6 +273,7 @@ mod integration {
220273
mod class;
221274
mod closure;
222275
mod globals;
276+
mod iterator;
223277
mod nullable;
224278
mod number;
225279
mod object;

0 commit comments

Comments
 (0)