Skip to content

Commit 822f37b

Browse files
committed
fix(array): fix double ended iterator implementation
Refs: #316
1 parent 64fbb41 commit 822f37b

File tree

4 files changed

+98
-6
lines changed

4 files changed

+98
-6
lines changed

src/types/array.rs

Lines changed: 24 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,19 @@ 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.len().try_into().unwrap();
633+
let end_pos = if ht.nNumOfElements > 0 {
634+
ht.nNumOfElements - 1
635+
} else {
636+
0
637+
};
638+
630639
Self {
631640
ht,
632641
current_num: 0,
642+
end_num,
633643
pos: 0,
644+
end_pos,
634645
}
635646
}
636647
}
@@ -686,6 +697,10 @@ impl ExactSizeIterator for Iter<'_> {
686697

687698
impl DoubleEndedIterator for Iter<'_> {
688699
fn next_back(&mut self) -> Option<Self::Item> {
700+
if self.end_num <= self.current_num {
701+
return None;
702+
}
703+
689704
let key_type = unsafe {
690705
zend_hash_get_current_key_type_ex(
691706
self.ht as *const ZendHashTable as *mut ZendHashTable,
@@ -703,35 +718,39 @@ impl DoubleEndedIterator for Iter<'_> {
703718
zend_hash_get_current_key_zval_ex(
704719
self.ht as *const ZendHashTable as *mut ZendHashTable,
705720
&key as *const Zval as *mut Zval,
706-
&mut self.pos as *mut HashPosition,
721+
&mut self.end_pos as *mut HashPosition,
707722
);
708723
}
709724
let value = unsafe {
710725
&*zend_hash_get_current_data_ex(
711726
self.ht as *const ZendHashTable as *mut ZendHashTable,
712-
&mut self.pos as *mut HashPosition,
727+
&mut self.end_pos as *mut HashPosition,
713728
)
714729
};
715730

716731
let key = match ArrayKey::from_zval(&key) {
717732
Some(key) => key,
718-
None => ArrayKey::Long(self.current_num),
733+
None => ArrayKey::Long(self.end_num),
719734
};
720735

721736
unsafe {
722737
zend_hash_move_backwards_ex(
723738
self.ht as *const ZendHashTable as *mut ZendHashTable,
724-
&mut self.pos as *mut HashPosition,
739+
&mut self.end_pos as *mut HashPosition,
725740
)
726741
};
727-
self.current_num -= 1;
742+
self.end_num -= 1;
728743

729744
Some((key, value))
730745
}
731746
}
732747

733748
impl<'a> Iter<'a> {
734749
pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
750+
if self.current_num >= self.end_num {
751+
return None;
752+
}
753+
735754
let key_type = unsafe {
736755
zend_hash_get_current_key_type_ex(
737756
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: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#![cfg_attr(windows, feature(abi_vectorcall))]
2-
use ext_php_rs::{binary::Binary, prelude::*, types::ZendObject, types::Zval};
2+
use ext_php_rs::{
3+
binary::Binary,
4+
prelude::*,
5+
types::{ArrayKey, ZendHashTable, ZendObject, Zval},
6+
};
37
use std::collections::HashMap;
48

59
#[php_function]
@@ -72,6 +76,59 @@ pub fn test_callable(call: ZendCallable, a: String) -> Zval {
7276
call.try_call(vec![&a]).expect("Failed to call function")
7377
}
7478

79+
#[php_function]
80+
pub fn iter_next(ht: &ZendHashTable) -> Vec<Zval> {
81+
ht.iter()
82+
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
83+
.collect()
84+
}
85+
86+
#[php_function]
87+
pub fn iter_back(ht: &ZendHashTable) -> Vec<Zval> {
88+
ht.iter()
89+
.rev()
90+
.flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()])
91+
.collect()
92+
}
93+
94+
#[php_function]
95+
pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec<Option<Zval>> {
96+
let mut result = Vec::with_capacity(ht.len());
97+
let mut iter = ht.iter();
98+
99+
for i in 0..ht.len() + modulus {
100+
let entry = if i % modulus == 0 {
101+
iter.next_back()
102+
} else {
103+
iter.next()
104+
};
105+
106+
if let Some((k, v)) = entry {
107+
result.push(Some(key_to_zval(k)));
108+
result.push(Some(v.shallow_clone()));
109+
} else {
110+
result.push(None);
111+
}
112+
}
113+
114+
result
115+
}
116+
117+
fn key_to_zval(key: ArrayKey) -> Zval {
118+
match key {
119+
ArrayKey::String(s) => {
120+
let mut zval = Zval::new();
121+
let _ = zval.set_string(s.as_str(), false);
122+
zval
123+
}
124+
ArrayKey::Long(l) => {
125+
let mut zval = Zval::new();
126+
zval.set_long(l);
127+
zval
128+
}
129+
}
130+
}
131+
75132
#[php_class]
76133
pub struct TestClass {
77134
string: String,
@@ -179,6 +236,7 @@ mod integration {
179236
mod callable;
180237
mod class;
181238
mod closure;
239+
mod iterator;
182240
mod nullable;
183241
mod number;
184242
mod object;

0 commit comments

Comments
 (0)