Skip to content

Commit 7bf3255

Browse files
committed
feat(array): allow negative indices (issues #453 and #454)
1 parent 688201a commit 7bf3255

File tree

3 files changed

+187
-38
lines changed

3 files changed

+187
-38
lines changed

src/types/array.rs

Lines changed: 181 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
//! Represents an array in PHP. As all arrays in PHP are associative arrays,
22
//! they are represented by hash tables.
33
4-
use std::{
5-
collections::HashMap,
6-
convert::{TryFrom, TryInto},
7-
ffi::CString,
8-
fmt::{Debug, Display},
9-
iter::FromIterator,
10-
ptr,
11-
};
12-
4+
use crate::ffi::zend_ulong;
135
use crate::{
146
boxed::{ZBox, ZBoxable},
157
convert::{FromZval, IntoZval},
@@ -25,6 +17,15 @@ use crate::{
2517
flags::DataType,
2618
types::Zval,
2719
};
20+
use std::str::FromStr;
21+
use std::{
22+
collections::HashMap,
23+
convert::{TryFrom, TryInto},
24+
ffi::CString,
25+
fmt::{Debug, Display},
26+
iter::FromIterator,
27+
ptr,
28+
};
2829

2930
/// A PHP hashtable.
3031
///
@@ -188,9 +189,39 @@ impl ZendHashTable {
188189
/// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
189190
/// ```
190191
#[must_use]
191-
pub fn get(&self, key: &'_ str) -> Option<&Zval> {
192-
let str = CString::new(key).ok()?;
193-
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() }
192+
pub fn get<'a, K>(&self, key: K) -> Option<&Zval>
193+
where
194+
K: Into<ArrayKey<'a>>,
195+
{
196+
match key.into() {
197+
ArrayKey::Long(index) => unsafe {
198+
zend_hash_index_find(self, index as zend_ulong).as_ref()
199+
},
200+
ArrayKey::String(key) => {
201+
if let Ok(index) = i64::from_str(key.as_str()) {
202+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_ref() }
203+
} else {
204+
unsafe {
205+
zend_hash_str_find(
206+
self,
207+
CString::new(key.as_str()).ok()?.as_ptr(),
208+
key.len() as _,
209+
)
210+
.as_ref()
211+
}
212+
}
213+
}
214+
ArrayKey::Str(key) => {
215+
if let Ok(index) = i64::from_str(key) {
216+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_ref() }
217+
} else {
218+
unsafe {
219+
zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
220+
.as_ref()
221+
}
222+
}
223+
}
224+
}
194225
}
195226

196227
/// Attempts to retrieve a value from the hash table with a string key.
@@ -216,9 +247,39 @@ impl ZendHashTable {
216247
/// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
217248
/// ```
218249
#[must_use]
219-
pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
220-
let str = CString::new(key).ok()?;
221-
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
250+
pub fn get_mut<'a, K>(&self, key: K) -> Option<&mut Zval>
251+
where
252+
K: Into<ArrayKey<'a>>,
253+
{
254+
match key.into() {
255+
ArrayKey::Long(index) => unsafe {
256+
zend_hash_index_find(self, index as zend_ulong).as_mut()
257+
},
258+
ArrayKey::String(key) => {
259+
if let Ok(index) = i64::from_str(key.as_str()) {
260+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_mut() }
261+
} else {
262+
unsafe {
263+
zend_hash_str_find(
264+
self,
265+
CString::new(key.as_str()).ok()?.as_ptr(),
266+
key.len() as _,
267+
)
268+
.as_mut()
269+
}
270+
}
271+
}
272+
ArrayKey::Str(key) => {
273+
if let Ok(index) = i64::from_str(key) {
274+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_mut() }
275+
} else {
276+
unsafe {
277+
zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
278+
.as_mut()
279+
}
280+
}
281+
}
282+
}
222283
}
223284

224285
/// Attempts to retrieve a value from the hash table with an index.
@@ -244,8 +305,8 @@ impl ZendHashTable {
244305
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
245306
/// ```
246307
#[must_use]
247-
pub fn get_index(&self, key: u64) -> Option<&Zval> {
248-
unsafe { zend_hash_index_find(self, key).as_ref() }
308+
pub fn get_index(&self, key: i64) -> Option<&Zval> {
309+
unsafe { zend_hash_index_find(self, key as zend_ulong).as_ref() }
249310
}
250311

251312
/// Attempts to retrieve a value from the hash table with an index.
@@ -271,8 +332,8 @@ impl ZendHashTable {
271332
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
272333
/// ```
273334
#[must_use]
274-
pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
275-
unsafe { zend_hash_index_find(self, key).as_mut() }
335+
pub fn get_index_mut(&self, key: i64) -> Option<&mut Zval> {
336+
unsafe { zend_hash_index_find(self, key as zend_ulong).as_mut() }
276337
}
277338

278339
/// Attempts to remove a value from the hash table with a string key.
@@ -299,9 +360,35 @@ impl ZendHashTable {
299360
/// ht.remove("test");
300361
/// assert_eq!(ht.len(), 0);
301362
/// ```
302-
pub fn remove(&mut self, key: &str) -> Option<()> {
303-
let result =
304-
unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) };
363+
pub fn remove<'a, K>(&mut self, key: K) -> Option<()>
364+
where
365+
K: Into<ArrayKey<'a>>,
366+
{
367+
let result = match key.into() {
368+
ArrayKey::Long(index) => unsafe { zend_hash_index_del(self, index as zend_ulong) },
369+
ArrayKey::String(key) => {
370+
if let Ok(index) = i64::from_str(key.as_str()) {
371+
unsafe { zend_hash_index_del(self, index as zend_ulong) }
372+
} else {
373+
unsafe {
374+
zend_hash_str_del(
375+
self,
376+
CString::new(key.as_str()).ok()?.as_ptr(),
377+
key.len() as _,
378+
)
379+
}
380+
}
381+
}
382+
ArrayKey::Str(key) => {
383+
if let Ok(index) = i64::from_str(key) {
384+
unsafe { zend_hash_index_del(self, index as zend_ulong) }
385+
} else {
386+
unsafe {
387+
zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
388+
}
389+
}
390+
}
391+
};
305392

306393
if result < 0 {
307394
None
@@ -334,8 +421,8 @@ impl ZendHashTable {
334421
/// ht.remove_index(0);
335422
/// assert_eq!(ht.len(), 0);
336423
/// ```
337-
pub fn remove_index(&mut self, key: u64) -> Option<()> {
338-
let result = unsafe { zend_hash_index_del(self, key) };
424+
pub fn remove_index(&mut self, key: i64) -> Option<()> {
425+
let result = unsafe { zend_hash_index_del(self, key as zend_ulong) };
339426

340427
if result < 0 {
341428
None
@@ -373,12 +460,45 @@ impl ZendHashTable {
373460
/// ht.insert("c", "C");
374461
/// assert_eq!(ht.len(), 3);
375462
/// ```
376-
pub fn insert<V>(&mut self, key: &str, val: V) -> Result<()>
463+
pub fn insert<'a, K, V>(&mut self, key: K, val: V) -> Result<()>
377464
where
465+
K: Into<ArrayKey<'a>>,
378466
V: IntoZval,
379467
{
380468
let mut val = val.into_zval(false)?;
381-
unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &raw mut val) };
469+
match key.into() {
470+
ArrayKey::Long(index) => {
471+
unsafe { zend_hash_index_update(self, index as zend_ulong, &raw mut val) };
472+
}
473+
ArrayKey::String(key) => {
474+
if let Ok(idx) = i64::from_str(&key) {
475+
unsafe { zend_hash_index_update(self, idx as zend_ulong, &mut val) };
476+
} else {
477+
unsafe {
478+
zend_hash_str_update(
479+
self,
480+
CString::new(key.as_str())?.as_ptr(),
481+
key.len(),
482+
&raw mut val,
483+
)
484+
};
485+
}
486+
}
487+
ArrayKey::Str(key) => {
488+
if let Ok(idx) = i64::from_str(key) {
489+
unsafe { zend_hash_index_update(self, idx as zend_ulong, &mut val) };
490+
} else {
491+
unsafe {
492+
zend_hash_str_update(
493+
self,
494+
CString::new(key)?.as_ptr(),
495+
key.len(),
496+
&raw mut val,
497+
)
498+
};
499+
}
500+
}
501+
}
382502
val.release();
383503
Ok(())
384504
}
@@ -411,12 +531,12 @@ impl ZendHashTable {
411531
/// ht.insert_at_index(0, "C"); // notice overriding index 0
412532
/// assert_eq!(ht.len(), 2);
413533
/// ```
414-
pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Result<()>
534+
pub fn insert_at_index<V>(&mut self, key: i64, val: V) -> Result<()>
415535
where
416536
V: IntoZval,
417537
{
418538
let mut val = val.into_zval(false)?;
419-
unsafe { zend_hash_index_update(self, key, &raw mut val) };
539+
unsafe { zend_hash_index_update(self, key as zend_ulong, &raw mut val) };
420540
val.release();
421541
Ok(())
422542
}
@@ -554,6 +674,8 @@ impl ZendHashTable {
554674
/// }
555675
/// ArrayKey::String(key) => {
556676
/// }
677+
/// ArrayKey::Str(key) => {
678+
/// }
557679
/// }
558680
/// dbg!(key, val);
559681
/// }
@@ -606,15 +728,23 @@ pub struct Iter<'a> {
606728
}
607729

608730
/// Represents the key of a PHP array, which can be either a long or a string.
609-
#[derive(Debug, PartialEq)]
610-
pub enum ArrayKey {
731+
#[derive(Debug, Clone, PartialEq)]
732+
pub enum ArrayKey<'a> {
611733
/// A numerical key.
612734
Long(i64),
613735
/// A string key.
614736
String(String),
737+
/// A string key by reference.
738+
Str(&'a str),
739+
}
740+
741+
impl From<String> for ArrayKey<'_> {
742+
fn from(value: String) -> Self {
743+
Self::String(value)
744+
}
615745
}
616746

617-
impl ArrayKey {
747+
impl ArrayKey<'_> {
618748
/// Check if the key is an integer.
619749
///
620750
/// # Returns
@@ -625,20 +755,34 @@ impl ArrayKey {
625755
match self {
626756
ArrayKey::Long(_) => true,
627757
ArrayKey::String(_) => false,
758+
ArrayKey::Str(_) => false,
628759
}
629760
}
630761
}
631762

632-
impl Display for ArrayKey {
763+
impl Display for ArrayKey<'_> {
633764
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
634765
match self {
635766
ArrayKey::Long(key) => write!(f, "{key}"),
636767
ArrayKey::String(key) => write!(f, "{key}"),
768+
ArrayKey::Str(key) => write!(f, "{key}"),
637769
}
638770
}
639771
}
640772

641-
impl<'a> FromZval<'a> for ArrayKey {
773+
impl<'a> From<&'a str> for ArrayKey<'a> {
774+
fn from(key: &'a str) -> ArrayKey<'a> {
775+
ArrayKey::Str(key)
776+
}
777+
}
778+
779+
impl<'a> From<i64> for ArrayKey<'a> {
780+
fn from(index: i64) -> ArrayKey<'a> {
781+
ArrayKey::Long(index)
782+
}
783+
}
784+
785+
impl<'a> FromZval<'a> for ArrayKey<'_> {
642786
const TYPE: DataType = DataType::String;
643787

644788
fn from_zval(zval: &'a Zval) -> Option<Self> {
@@ -680,7 +824,7 @@ impl<'a> Iter<'a> {
680824
}
681825

682826
impl<'a> IntoIterator for &'a ZendHashTable {
683-
type Item = (ArrayKey, &'a Zval);
827+
type Item = (ArrayKey<'a>, &'a Zval);
684828
type IntoIter = Iter<'a>;
685829

686830
/// Returns an iterator over the key(s) and value contained inside the
@@ -707,7 +851,7 @@ impl<'a> IntoIterator for &'a ZendHashTable {
707851
}
708852

709853
impl<'a> Iterator for Iter<'a> {
710-
type Item = (ArrayKey, &'a Zval);
854+
type Item = (ArrayKey<'a>, &'a Zval);
711855

712856
fn next(&mut self) -> Option<Self::Item> {
713857
self.next_zval()
@@ -1046,8 +1190,8 @@ impl FromIterator<Zval> for ZBox<ZendHashTable> {
10461190
}
10471191
}
10481192

1049-
impl FromIterator<(u64, Zval)> for ZBox<ZendHashTable> {
1050-
fn from_iter<T: IntoIterator<Item = (u64, Zval)>>(iter: T) -> Self {
1193+
impl FromIterator<(i64, Zval)> for ZBox<ZendHashTable> {
1194+
fn from_iter<T: IntoIterator<Item = (i64, Zval)>>(iter: T) -> Self {
10511195
let mut ht = ZendHashTable::new();
10521196
for (key, val) in iter {
10531197
// Inserting a zval cannot fail, as `push` only returns `Err` if converting

src/zend/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl ZendObjectHandlers {
181181
let self_ = &mut *obj;
182182
let struct_props = T::get_metadata().get_properties();
183183

184-
for (name, val) in struct_props {
184+
for (&name, val) in struct_props {
185185
let mut zv = Zval::new();
186186
if val.prop.get(self_, &mut zv).is_err() {
187187
continue;

tests/src/integration/iterator/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ fn key_to_zval(key: ArrayKey) -> Zval {
4848
let _ = zval.set_string(s.as_str(), false);
4949
zval
5050
}
51+
ArrayKey::Str(s) => {
52+
let mut zval = Zval::new();
53+
let _ = zval.set_string(s, false);
54+
zval
55+
}
5156
ArrayKey::Long(l) => {
5257
let mut zval = Zval::new();
5358
zval.set_long(l);

0 commit comments

Comments
 (0)