From 7c484a0e182061be10f0cbdf122f2d2422fbe111 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 21 Sep 2021 21:22:05 +1200 Subject: [PATCH] Refactor `ZendHashTable` and `Zval` (#76) * Call zval destructor when changing zval type and dropping * Remove zval return from array insert functions #73 The functions actually returned references to the new zval value, so there's not much point in returning. * Remove `ZendHashTable` wrapper Replaced by returning references to the actual hashtable, and having a new type `OwnedHashTable` when creating a new hashtable. * Remove pointless `drop` field from `OwnedHashTable` * Added `Values` iterator for Zend hashtable * Change iterators to double ended and exact size --- build.rs | 2 + example/skel/src/lib.rs | 114 +------- example/skel/test.php | 17 +- src/php/enums.rs | 12 +- src/php/globals.rs | 10 +- src/php/types/array.rs | 580 ++++++++++++++++++++++++---------------- src/php/types/binary.rs | 2 +- src/php/types/mod.rs | 1 + src/php/types/object.rs | 29 +- src/php/types/rc.rs | 50 ++++ src/php/types/zval.rs | 273 ++++++++----------- 11 files changed, 562 insertions(+), 528 deletions(-) create mode 100644 src/php/types/rc.rs diff --git a/build.rs b/build.rs index a04515a4dc..7931874172 100644 --- a/build.rs +++ b/build.rs @@ -313,4 +313,6 @@ const ALLOWED_BINDINGS: &[&str] = &[ "_ZEND_TYPE_NULLABLE_BIT", "ts_rsrc_id", "_ZEND_TYPE_NAME_BIT", + "zval_ptr_dtor", + "zend_refcounted_h", ]; diff --git a/example/skel/src/lib.rs b/example/skel/src/lib.rs index 34d1470a08..6a2855f5bb 100644 --- a/example/skel/src/lib.rs +++ b/example/skel/src/lib.rs @@ -1,125 +1,25 @@ mod allocator; -use allocator::PhpAllocator; use ext_php_rs::{ + parse_args, php::{ + args::Arg, + enums::DataType, exceptions::PhpException, + execution_data::ExecutionData, + function::FunctionBuilder, types::{ + array::OwnedHashTable, callable::Callable, closure::Closure, object::{ClassObject, ClassRef}, + zval::{FromZval, IntoZval, Zval}, }, }, php_class, prelude::*, }; -// #[php_function] -// pub fn hello_world() -> String { -// let call = Callable::try_from_name("strpos").unwrap(); - -// eprintln!("im callin"); -// let val = call.try_call(vec![&"hello world", &"w"]); -// dbg!(val); -// "Ok".into() -// } - -// #[php_const] -// const SKEL_TEST_CONST: &str = "Test constant"; -// #[php_const] -// const SKEL_TEST_LONG_CONST: i32 = 1234; - -// #[php_function(optional = "z")] -// pub fn skeleton_version(x: ZendHashTable, y: f64, z: Option) -> String { -// dbg!(x, y, z); -// "Hello".into() -// } - -// #[php_function(optional = "z")] -// pub fn skeleton_array( -// arr: ZendHashTable, -// x: i32, -// y: f64, -// z: Option, -// ) -> Result { -// for (k, x, y) in arr.iter() { -// println!("{:?} {:?} {:?}", k, x, y.string()); -// } - -// dbg!(x, y, z); - -// let mut new = ZendHashTable::new(); -// new.insert("Hello", &"World") -// .map_err(|_| "Couldn't insert into hashtable")?; -// Ok(new) -// } - -// #[php_function(optional = "i", defaults(i = 5))] -// pub fn test_array(i: i32, b: Option) -> Vec { -// dbg!(i, b); -// vec![1, 2, 3, 4] -// } - -// #[php_function(optional = "offset", defaults(offset = 0))] -// pub fn rust_strpos(haystack: &str, needle: &str, offset: i64) -> Option { -// let haystack = haystack.chars().skip(offset as usize).collect::(); -// haystack.find(needle) -// } - -// #[php_function] -// pub fn example_exception() -> Result { -// Err("Bad here") -// } - -// #[php_function] -// pub fn skel_unpack<'a>( -// mut arr: HashMap, -// ) -> Result, PhpException<'a>> { -// arr.insert("hello".into(), "not world".into()); -// Ok(arr) -// } - -// #[php_function] -// pub fn test_extern() -> i32 { -// // let y = unsafe { strpos("hello", "e", None) }; -// // dbg!(y); -// // let x = unsafe { test_func() }; -// // dbg!(x.try_call(vec![])); -// 0 -// } - -// #[php_function] -// pub fn test_lifetimes<'a>() -> ZendHashTable<'a> { -// ZendHashTable::try_from(&HashMap::::new()).unwrap() -// } - -#[php_function] -pub fn test_str(input: &str) -> &str { - input -} - -// #[no_mangle] -// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) { -// info_table_start!(); -// info_table_row!("skeleton extension", "enabled"); -// info_table_end!(); -// } - -// #[php_class(name = "Redis\\Exception\\RedisClientException")] -// #[extends(ClassEntry::exception())] -// #[derive(Default)] -// struct RedisException; - -// #[php_function] -// pub fn test_exception() -> Result> { -// Err(PhpException::from_class::( -// "Hello world".into(), -// )) -// } - -// #[global_allocator] -// static GLOBAL: PhpAllocator = PhpAllocator::new(); - #[php_class] #[property(test = 0)] #[property(another = "Hello world")] diff --git a/example/skel/test.php b/example/skel/test.php index 430ef471ec..cf76234230 100644 --- a/example/skel/test.php +++ b/example/skel/test.php @@ -1,15 +1,16 @@ 'world']); +//include 'vendor/autoload.php'; -$ext = new ReflectionExtension('skel'); +//$ext = new ReflectionExtension('skel'); -dd($ext); +//dd($ext); -$x = fn_once(); -$x(); -$x(); +//$x = fn_once(); +//$x(); +//$x(); -// $x = get_closure(); +//// $x = get_closure(); -// var_dump($x(5)); +//// var_dump($x(5)); diff --git a/src/php/enums.rs b/src/php/enums.rs index 9b4ad1c6a8..a279f97056 100644 --- a/src/php/enums.rs +++ b/src/php/enums.rs @@ -107,15 +107,13 @@ impl TryFrom for DataType { } } -impl TryFrom for DataType { - type Error = Error; - +impl From for DataType { #[allow(clippy::bad_bit_mask)] - fn try_from(value: u32) -> Result { + fn from(value: u32) -> Self { macro_rules! contains { ($c: ident, $t: ident) => { if (value & $c) == $c { - return Ok(DataType::$t); + return DataType::$t; } }; } @@ -134,12 +132,12 @@ impl TryFrom for DataType { contains!(IS_NULL, Null); if (value & IS_OBJECT) == IS_OBJECT { - return Ok(DataType::Object(None)); + return DataType::Object(None); } contains!(IS_UNDEF, Undef); - Err(Error::UnknownDatatype(value)) + DataType::Mixed } } diff --git a/src/php/globals.rs b/src/php/globals.rs index 2c00fd8e05..cc1b596a9f 100644 --- a/src/php/globals.rs +++ b/src/php/globals.rs @@ -2,7 +2,7 @@ use crate::bindings::{_zend_executor_globals, ext_php_rs_executor_globals}; -use super::types::array::ZendHashTable; +use super::types::array::HashTable; /// Stores global variables used in the PHP executor. pub type ExecutorGlobals = _zend_executor_globals; @@ -17,11 +17,7 @@ impl ExecutorGlobals { } /// Attempts to retrieve the global class hash table. - pub fn class_table(&self) -> Option { - if self.class_table.is_null() { - return None; - } - - unsafe { ZendHashTable::from_ptr(self.class_table, false) }.ok() + pub fn class_table(&self) -> Option<&HashTable> { + unsafe { self.class_table.as_ref() } } } diff --git a/src/php/types/array.rs b/src/php/types/array.rs index 72b151c5ee..baa034b8bb 100644 --- a/src/php/types/array.rs +++ b/src/php/types/array.rs @@ -6,18 +6,22 @@ use std::{ convert::{TryFrom, TryInto}, ffi::CString, fmt::Debug, - marker::PhantomData, + iter::FromIterator, + mem::ManuallyDrop, + ops::{Deref, DerefMut}, + ptr::NonNull, u64, }; use crate::{ bindings::{ - HashTable, _Bucket, _zend_new_array, zend_array_destroy, zend_array_dup, zend_hash_clean, + _Bucket, _zend_new_array, zend_array_destroy, zend_array_dup, zend_hash_clean, zend_hash_index_del, zend_hash_index_find, zend_hash_index_update, zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update, HT_MIN_SIZE, }, errors::{Error, Result}, + php::enums::DataType, }; use super::{ @@ -25,72 +29,13 @@ use super::{ zval::{FromZval, IntoZval, Zval}, }; -/// Result type returned after attempting to insert an element into a hash table. -#[derive(Debug)] -pub enum HashTableInsertResult<'a> { - /// The element was inserted into the hash table successfully. - Ok, - /// The element was inserted into the hash table successfully, over-writing an existing element. - OkWithOverwrite(&'a Zval), -} - -/// A PHP array, which internally is a hash table. -pub struct ZendHashTable<'a> { - ptr: *mut HashTable, - free: bool, - phantom: PhantomData<&'a HashTable>, -} - -impl<'a> ZendHashTable<'a> { - /// Creates a new, empty, PHP associative array. - pub fn new() -> Self { - Self::with_capacity(HT_MIN_SIZE) - } - - /// Creates a new, empty, PHP associative array with an initial size. - /// - /// # Parameters - /// - /// * `size` - The size to initialize the array with. - pub fn with_capacity(size: u32) -> Self { - // SAFETY: PHP allocater handles the creation of the - // array. - let ptr = unsafe { _zend_new_array(size) }; - Self { - ptr, - free: true, - phantom: PhantomData, - } - } - - /// Creates a new hash table wrapper. - /// This _will not_ be freed when it goes out of scope in Rust. - /// - /// # Parameters - /// - /// * `ptr` - The pointer of the actual hash table. - /// * `free` - Whether the pointer should be freed when the resulting [`ZendHashTable`] - /// goes out of scope. - /// - /// # Safety - /// - /// As a raw pointer is given this function is unsafe, you must ensure that the pointer is valid when calling - /// the function. A simple null check is done but this is not sufficient in most cases. - pub unsafe fn from_ptr(ptr: *mut HashTable, free: bool) -> Result { - if ptr.is_null() { - return Err(Error::InvalidPointer); - } - - Ok(Self { - ptr, - free, - phantom: PhantomData, - }) - } +/// PHP array, which is represented in memory as a hashtable. +pub use crate::bindings::HashTable; +impl HashTable { /// Returns the current number of elements in the array. pub fn len(&self) -> usize { - unsafe { *self.ptr }.nNumOfElements as usize + self.nNumOfElements as usize } /// Returns whether the hash table is empty. @@ -100,7 +45,7 @@ impl<'a> ZendHashTable<'a> { /// Clears the hash table, removing all values. pub fn clear(&mut self) { - unsafe { zend_hash_clean(self.ptr) } + unsafe { zend_hash_clean(self) } } /// Attempts to retrieve a value from the hash table with a string key. @@ -113,9 +58,9 @@ impl<'a> ZendHashTable<'a> { /// /// * `Some(&Zval)` - A reference to the zval at the position in the hash table. /// * `None` - No value at the given position was found. - pub fn get(&self, key: &str) -> Option<&Zval> { + pub fn get(&self, key: &'_ str) -> Option<&Zval> { let str = CString::new(key).ok()?; - unsafe { zend_hash_str_find(self.ptr, str.as_ptr(), key.len() as _).as_ref() } + unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() } } /// Attempts to retrieve a value from the hash table with an index. @@ -129,7 +74,7 @@ impl<'a> ZendHashTable<'a> { /// * `Some(&Zval)` - A reference to the zval at the position in the hash table. /// * `None` - No value at the given position was found. pub fn get_index(&self, key: u64) -> Option<&Zval> { - unsafe { zend_hash_index_find(self.ptr, key).as_ref() } + unsafe { zend_hash_index_find(self, key).as_ref() } } /// Attempts to remove a value from the hash table with a string key. @@ -142,10 +87,9 @@ impl<'a> ZendHashTable<'a> { /// /// * `Some(())` - Key was successfully removed. /// * `None` - No key was removed, did not exist. - pub fn remove(&self, key: &str) -> Option<()> { - let result = unsafe { - zend_hash_str_del(self.ptr, CString::new(key).ok()?.as_ptr(), key.len() as _) - }; + pub fn remove(&mut self, key: &str) -> Option<()> { + let result = + unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) }; if result < 0 { None @@ -164,8 +108,8 @@ impl<'a> ZendHashTable<'a> { /// /// * `Ok(())` - Key was successfully removed. /// * `None` - No key was removed, did not exist. - pub fn remove_index(&self, key: u64) -> Option<()> { - let result = unsafe { zend_hash_index_del(self.ptr, key) }; + pub fn remove_index(&mut self, key: u64) -> Option<()> { + let result = unsafe { zend_hash_index_del(self, key) }; if result < 0 { None @@ -175,65 +119,44 @@ impl<'a> ZendHashTable<'a> { } /// Attempts to insert an item into the hash table, or update if the key already exists. - /// Returns a result containing a [`HashTableInsertResult`], which will indicate a successful - /// insert, with the insert result variants either containing the overwritten value or nothing. + /// Returns nothing in a result if successful. /// /// # Parameters /// /// * `key` - The key to insert the value at in the hash table. /// * `value` - The value to insert into the hash table. - pub fn insert(&mut self, key: &str, val: V) -> Result + pub fn insert(&mut self, key: &str, val: V) -> Result<()> where V: IntoZval, { let mut val = val.into_zval(false)?; - let existing_ptr = unsafe { + unsafe { zend_hash_str_update( - self.ptr, + self, CString::new(key)?.as_ptr(), key.len() as u64, &mut val, ) }; val.release(); - - // Should we be claiming this Zval into rust? - // I'm not sure if the PHP GC will collect this. - - // SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer. - // In the latter case, `as_ref()` will return `None`. - Ok(match unsafe { existing_ptr.as_ref() } { - Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr), - None => HashTableInsertResult::Ok, - }) + Ok(()) } - /// Inserts an item into the hash table at a specified index, - /// or updates if the key already exists. + /// Inserts an item into the hash table at a specified index, or updates if the key already exists. + /// Returns nothing in a result if successful. /// /// # Parameters /// /// * `key` - The index at which the value should be inserted. /// * `val` - The value to insert into the hash table. - /// - /// # Returns - /// - /// * `Some(&Zval)` - The existing value in the hash table that was overriden. - /// * `None` - The element was inserted. - pub fn insert_at_index(&mut self, key: u64, val: V) -> Result + pub fn insert_at_index(&mut self, key: u64, val: V) -> Result<()> where V: IntoZval, { let mut val = val.into_zval(false)?; - let existing_ptr = unsafe { zend_hash_index_update(self.ptr, key, &mut val) }; + unsafe { zend_hash_index_update(self, key, &mut val) }; val.release(); - - // SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer. - // In the latter case, `as_ref()` will return `None`. - Ok(match unsafe { existing_ptr.as_ref() } { - Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr), - None => HashTableInsertResult::Ok, - }) + Ok(()) } /// Pushes an item onto the end of the hash table. Returns a result containing nothing if the @@ -247,26 +170,32 @@ impl<'a> ZendHashTable<'a> { V: IntoZval, { let mut val = val.into_zval(false)?; - unsafe { zend_hash_next_index_insert(self.ptr, &mut val) }; + unsafe { zend_hash_next_index_insert(self, &mut val) }; val.release(); Ok(()) } - /// Returns an iterator over the hash table. + /// Returns an iterator over the key(s) and value contained inside the hashtable. #[inline] - pub fn iter(&self) -> Iter<'_> { + pub fn iter(&self) -> Iter { Iter::new(self) } - /// Converts the hash table into a raw pointer to be passed to Zend. - pub(crate) fn into_ptr(mut self) -> *mut HashTable { - self.free = false; - self.ptr + /// Returns an iterator over the values contained inside the hashtable, as if it was a set or list. + #[inline] + pub fn values(&self) -> Values { + Values::new(self) + } + + /// Clones the hash table, returning an [`OwnedHashTable`]. + pub fn to_owned(&self) -> OwnedHashTable { + let ptr = unsafe { zend_array_dup(self as *const HashTable as *mut HashTable) }; + unsafe { OwnedHashTable::from_ptr(ptr) } } } -impl<'a> Debug for ZendHashTable<'a> { +impl Debug for HashTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_map() .entries( @@ -277,108 +206,251 @@ impl<'a> Debug for ZendHashTable<'a> { } } -impl<'a> Default for ZendHashTable<'a> { - fn default() -> Self { - Self::new() +/// Immutable iterator upon a reference to a hashtable. +pub struct Iter<'a> { + ht: &'a HashTable, + pos: Option>, + end: Option>, +} + +impl<'a> Iter<'a> { + /// Creates a new iterator over a hashtable. + /// + /// # Parameters + /// + /// * `ht` - The hashtable to iterate. + pub fn new(ht: &'a HashTable) -> Self { + Self { + ht, + pos: NonNull::new(ht.arData), + end: NonNull::new(unsafe { ht.arData.offset(ht.nNumUsed as isize) }), + } } } -impl<'a> Drop for ZendHashTable<'a> { - fn drop(&mut self) { - if self.free { - unsafe { zend_array_destroy(self.ptr) }; +impl<'a> Iterator for Iter<'a> { + type Item = (u64, Option, &'a Zval); + + fn next(&mut self) -> Option { + let pos = self.pos?; + + if pos == self.end? { + return None; } + + let bucket = unsafe { pos.as_ref() }; + let key = unsafe { ZendString::from_ptr(bucket.key, false) } + .and_then(|s| s.try_into()) + .ok(); + + self.pos = NonNull::new(unsafe { pos.as_ptr().offset(1) }); + + Some((bucket.h, key, &bucket.val)) + } + + fn count(self) -> usize + where + Self: Sized, + { + self.ht.len() } } -impl<'a> Clone for ZendHashTable<'a> { - fn clone(&self) -> Self { - // SAFETY: If this fails then `emalloc` failed - we are doomed anyway? - // `from_ptr()` checks if the ptr is null. - unsafe { - let ptr = zend_array_dup(self.ptr); - Self::from_ptr(ptr, true).expect("ZendHashTable cloning failed when duplicating array.") +impl<'a> ExactSizeIterator for Iter<'a> { + fn len(&self) -> usize { + self.ht.len() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option { + let end = self.end?; + + if end == self.pos? { + return None; } + + let new_end = NonNull::new(unsafe { end.as_ptr().offset(-1) })?; + let bucket = unsafe { new_end.as_ref() }; + let key = unsafe { ZendString::from_ptr(bucket.key, false) } + .and_then(|s| s.try_into()) + .ok(); + self.end = Some(new_end); + + Some((bucket.h, key, &bucket.val)) } } -impl<'a> IntoIterator for ZendHashTable<'a> { - type Item = (u64, Option, &'a Zval); - type IntoIter = IntoIter<'a>; +/// Immutable iterator which iterates over the values of the hashtable, as it was a set or list. +pub struct Values<'a>(Iter<'a>); - fn into_iter(self) -> Self::IntoIter { - Self::IntoIter::new(self) +impl<'a> Values<'a> { + /// Creates a new iterator over a hashtables values. + /// + /// # Parameters + /// + /// * `ht` - The hashtable to iterate. + pub fn new(ht: &'a HashTable) -> Self { + Self(Iter::new(ht)) } } -macro_rules! build_iter { - ($name: ident, $ht: ty) => { - pub struct $name<'a> { - ht: $ht, - pos: *mut _Bucket, - end: *mut _Bucket, - } +impl<'a> Iterator for Values<'a> { + type Item = &'a Zval; + + fn next(&mut self) -> Option { + self.0.next().map(|(_, _, zval)| zval) + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } +} + +impl<'a> ExactSizeIterator for Values<'a> { + fn len(&self) -> usize { + self.0.len() + } +} - impl<'a> $name<'a> { - pub fn new(ht: $ht) -> Self { - let ptr = unsafe { *ht.ptr }; - let pos = ptr.arData; - let end = unsafe { ptr.arData.offset(ptr.nNumUsed as isize) }; - Self { ht, pos, end } - } +impl<'a> DoubleEndedIterator for Values<'a> { + fn next_back(&mut self) -> Option { + self.0.next_back().map(|(_, _, zval)| zval) + } +} + +/// A container used to 'own' a Zend hashtable. Dereferences to a reference to [`HashTable`]. +/// +/// When this struct is dropped, it will also destroy the internal hashtable, unless the `into_raw` +/// function is used. +pub struct OwnedHashTable { + ptr: NonNull, +} + +impl OwnedHashTable { + /// Creates a new, empty, PHP associative array. + pub fn new() -> Self { + Self::with_capacity(HT_MIN_SIZE) + } + + /// Creates a new, empty, PHP associative array with an initial size. + /// + /// # Parameters + /// + /// * `size` - The size to initialize the array with. + pub fn with_capacity(size: u32) -> Self { + // SAFETY: PHP allocater handles the creation of the array. + unsafe { + let ptr = _zend_new_array(size); + Self::from_ptr(ptr) } + } - impl<'a> Iterator for $name<'a> { - type Item = (u64, Option, &'a Zval); - - fn next(&mut self) -> Option { - // iterator complete - if self.pos == self.end { - return None; - } - - let result = if let Some(val) = unsafe { self.pos.as_ref() } { - // SAFETY: We can ensure safety further by checking if it is null before - // converting it to a reference (val.key.as_ref() returns None if ptr == null) - let str_key = unsafe { ZendString::from_ptr(val.key, false) } - .and_then(|s| s.try_into()) - .ok(); - - Some((val.h, str_key, &val.val)) - } else { - None - }; - - self.pos = unsafe { self.pos.offset(1) }; - result - } - - fn count(self) -> usize - where - Self: Sized, - { - unsafe { *self.ht.ptr }.nNumOfElements as usize - } + /// Creates an owned hashtable from a hashtable pointer, which will be freed when the + /// resulting Rust object is dropped. + /// + /// # Parameters + /// + /// * `ptr` - Hashtable pointer. + /// + /// # Panics + /// + /// Panics if the given pointer is null. + /// + /// # Safety + /// + /// Caller must ensure that the given pointer is a valid hashtable pointer, including + /// non-null and properly aligned. + pub unsafe fn from_ptr(ptr: *mut HashTable) -> Self { + Self { + ptr: NonNull::new(ptr).expect("Invalid hashtable pointer given"), } - }; + } + + /// Returns the inner pointer to the hashtable, without destroying the + pub fn into_inner(self) -> *mut HashTable { + let this = ManuallyDrop::new(self); + this.ptr.as_ptr() + } +} + +impl Deref for OwnedHashTable { + type Target = HashTable; + + fn deref(&self) -> &Self::Target { + // SAFETY: all constructors ensure a valid ptr is present + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for OwnedHashTable { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: all constructors ensure a valid, owned ptr is present + unsafe { self.ptr.as_mut() } + } +} + +impl Debug for OwnedHashTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deref().fmt(f) + } } -build_iter!(Iter, &'a ZendHashTable<'a>); -build_iter!(IntoIter, ZendHashTable<'a>); +impl Default for OwnedHashTable { + fn default() -> Self { + Self::new() + } +} -impl<'a, V> TryFrom> for HashMap +impl Clone for OwnedHashTable { + fn clone(&self) -> Self { + self.deref().to_owned() + } +} + +impl Drop for OwnedHashTable { + fn drop(&mut self) { + unsafe { zend_array_destroy(self.ptr.as_mut()) }; + } +} + +impl IntoZval for OwnedHashTable { + const TYPE: DataType = DataType::Array; + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + zv.set_hashtable(self); + Ok(()) + } +} + +impl<'a> FromZval<'a> for &'a HashTable { + const TYPE: DataType = DataType::Array; + + fn from_zval(zval: &'a Zval) -> Option { + zval.array() + } +} + +/////////////////////////////////////////// +//// HashMap +/////////////////////////////////////////// + +impl TryFrom<&HashTable> for HashMap where - V: FromZval<'a>, + for<'a> V: FromZval<'a>, { type Error = Error; - fn try_from(zht: ZendHashTable<'a>) -> Result { - let mut hm = HashMap::with_capacity(zht.len()); + fn try_from(value: &HashTable) -> Result { + let mut hm = HashMap::with_capacity(value.len()); - for (idx, key, val) in zht.into_iter() { + for (idx, key, val) in value.iter() { hm.insert( key.unwrap_or_else(|| idx.to_string()), - V::from_zval(val).ok_or(Error::ZvalConversion(val.get_type()?))?, + V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?, ); } @@ -386,18 +458,19 @@ where } } -impl<'a, K, V> TryFrom> for ZendHashTable<'a> +impl TryFrom> for OwnedHashTable where K: AsRef, V: IntoZval, { type Error = Error; - fn try_from(hm: HashMap) -> Result { - let mut ht = - ZendHashTable::with_capacity(hm.len().try_into().map_err(|_| Error::IntegerOverflow)?); + fn try_from(value: HashMap) -> Result { + let mut ht = OwnedHashTable::with_capacity( + value.len().try_into().map_err(|_| Error::IntegerOverflow)?, + ); - for (k, v) in hm.into_iter() { + for (k, v) in value.into_iter() { ht.insert(k.as_ref(), v)?; } @@ -405,53 +478,64 @@ where } } -/// Implementation converting a Rust HashTable into a ZendHashTable. -impl<'a, 'b, K, V> TryFrom<&'a HashMap> for ZendHashTable<'b> +impl IntoZval for HashMap where K: AsRef, - V: IntoZval + Clone, + V: IntoZval, { - type Error = Error; + const TYPE: DataType = DataType::Array; - fn try_from(hm: &'a HashMap) -> Result { - let mut ht = - ZendHashTable::with_capacity(hm.len().try_into().map_err(|_| Error::IntegerOverflow)?); + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + let arr = self.try_into()?; + zv.set_hashtable(arr); + Ok(()) + } +} - for (k, v) in hm.iter() { - ht.insert(k.as_ref(), v.clone())?; - } +impl FromZval<'_> for HashMap +where + for<'a> T: FromZval<'a>, +{ + const TYPE: DataType = DataType::Array; - Ok(ht) + fn from_zval(zval: &Zval) -> Option { + zval.array().and_then(|arr| arr.try_into().ok()) } } -/// Implementation for converting a reference to `ZendHashTable` into a `Vec` of given type. -/// Will return an error type if one of the values inside the array cannot be converted into -/// a type `T`. -impl<'a, V> TryFrom> for Vec +/////////////////////////////////////////// +//// Vec +/////////////////////////////////////////// + +impl TryFrom<&HashTable> for Vec where - V: FromZval<'a>, + for<'a> T: FromZval<'a>, { type Error = Error; - fn try_from(ht: ZendHashTable<'a>) -> Result { - ht.into_iter() - .map(|(_, _, v)| V::from_zval(v).ok_or(Error::ZvalConversion(v.get_type()?))) - .collect::>>() + fn try_from(value: &HashTable) -> Result { + let mut vec = Vec::with_capacity(value.len()); + + for (_, _, val) in value.iter() { + vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?); + } + + Ok(vec) } } -impl<'a, V> TryFrom> for ZendHashTable<'a> +impl TryFrom> for OwnedHashTable where - V: IntoZval, + T: IntoZval, { type Error = Error; - fn try_from(vec: Vec) -> Result { - let mut ht = - ZendHashTable::with_capacity(vec.len().try_into().map_err(|_| Error::IntegerOverflow)?); + fn try_from(value: Vec) -> Result { + let mut ht = OwnedHashTable::with_capacity( + value.len().try_into().map_err(|_| Error::IntegerOverflow)?, + ); - for val in vec.into_iter() { + for val in value.into_iter() { ht.push(val)?; } @@ -459,20 +543,62 @@ where } } -impl<'a, V> TryFrom<&Vec> for ZendHashTable<'a> +impl IntoZval for Vec where - V: IntoZval + Clone, + T: IntoZval, { - type Error = Error; + const TYPE: DataType = DataType::Array; + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + let arr = self.try_into()?; + zv.set_hashtable(arr); + Ok(()) + } +} + +impl FromZval<'_> for Vec +where + for<'a> T: FromZval<'a>, +{ + const TYPE: DataType = DataType::Array; - fn try_from(vec: &Vec) -> Result { - let mut ht = - ZendHashTable::with_capacity(vec.len().try_into().map_err(|_| Error::IntegerOverflow)?); + fn from_zval(zval: &Zval) -> Option { + zval.array().and_then(|arr| arr.try_into().ok()) + } +} - for val in vec.iter() { - ht.push(val.clone())?; +impl FromIterator for OwnedHashTable { + fn from_iter>(iter: T) -> Self { + let mut ht = OwnedHashTable::new(); + for item in iter.into_iter() { + // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval + // fails. + let _ = ht.push(item); } + ht + } +} - Ok(ht) +impl FromIterator<(u64, Zval)> for OwnedHashTable { + fn from_iter>(iter: T) -> Self { + let mut ht = OwnedHashTable::new(); + for (key, val) in iter.into_iter() { + // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval + // fails. + let _ = ht.insert_at_index(key, val); + } + ht + } +} + +impl<'a> FromIterator<(&'a str, Zval)> for OwnedHashTable { + fn from_iter>(iter: T) -> Self { + let mut ht = OwnedHashTable::new(); + for (key, val) in iter.into_iter() { + // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval + // fails. + let _ = ht.insert(key, val); + } + ht } } diff --git a/src/php/types/binary.rs b/src/php/types/binary.rs index 568c929196..9bdfe1d4ca 100644 --- a/src/php/types/binary.rs +++ b/src/php/types/binary.rs @@ -56,7 +56,7 @@ impl TryFrom for Binary { type Error = Error; fn try_from(value: Zval) -> Result { - Self::from_zval(&value).ok_or(Error::ZvalConversion(value.get_type()?)) + Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type())) } } diff --git a/src/php/types/mod.rs b/src/php/types/mod.rs index e6ac9b5d1c..7bb0e5e9a4 100644 --- a/src/php/types/mod.rs +++ b/src/php/types/mod.rs @@ -9,6 +9,7 @@ pub mod callable; pub mod closure; pub mod long; pub mod object; +pub mod rc; pub mod string; pub mod zval; diff --git a/src/php/types/object.rs b/src/php/types/object.rs index f0139b2d2c..dc709f3f47 100644 --- a/src/php/types/object.rs +++ b/src/php/types/object.rs @@ -18,13 +18,14 @@ use crate::{ zend_objects_clone_members, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, }, errors::{Error, Result}, - php::{class::ClassEntry, enums::DataType, types::string::ZendString}, + php::{ + class::ClassEntry, + enums::DataType, + types::{array::HashTable, string::ZendString}, + }, }; -use super::{ - array::ZendHashTable, - zval::{FromZval, IntoZval, Zval}, -}; +use super::zval::{FromZval, IntoZval, Zval}; pub type ZendObject = zend_object; pub type ZendObjectHandlers = zend_object_handlers; @@ -134,12 +135,12 @@ impl ZendObject { } /// Attempts to retrieve the properties of the object. Returned inside a Zend Hashtable. - pub fn get_properties(&self) -> Result { + pub fn get_properties(&self) -> Result<&HashTable> { unsafe { - ZendHashTable::from_ptr( - self.handlers()?.get_properties.ok_or(Error::InvalidScope)?(self.mut_ptr()), - false, - ) + self.handlers()? + .get_properties + .and_then(|props| props(self.mut_ptr()).as_ref()) + .ok_or(Error::InvalidScope) } } @@ -156,11 +157,6 @@ impl ZendObject { fn mut_ptr(&self) -> *mut Self { (self as *const Self) as *mut Self } - - /// Increments the objects reference counter by 1. - pub(crate) fn refcount_inc(&mut self) { - self.gc.refcount += 1; - } } impl Debug for ZendObject { @@ -172,7 +168,7 @@ impl Debug for ZendObject { ); if let Ok(props) = self.get_properties() { - for (id, key, val) in props.into_iter() { + for (id, key, val) in props.iter() { dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val); } } @@ -181,6 +177,7 @@ impl Debug for ZendObject { } } +/// Wrapper struct used to return a reference to a PHP object. pub struct ClassRef<'a, T: RegisteredClass> { ptr: &'a mut ZendClassObject, } diff --git a/src/php/types/rc.rs b/src/php/types/rc.rs new file mode 100644 index 0000000000..167ae34fed --- /dev/null +++ b/src/php/types/rc.rs @@ -0,0 +1,50 @@ +//! Utilities for interacting with refcounted PHP types. + +use crate::bindings::{zend_refcounted_h, zend_string}; + +use super::object::ZendObject; + +/// Object used to store Zend reference counter. +pub type ZendRefcount = zend_refcounted_h; + +/// Implemented on refcounted types. +pub trait PhpRc { + /// Returns an immutable reference to the corresponding refcount object. + fn get_rc(&self) -> &ZendRefcount; + + /// Returns a mutable reference to the corresponding refcount object. + fn get_rc_mut(&mut self) -> &mut ZendRefcount; + + /// Returns the number of references to the object. + fn get_count(&self) -> u32 { + self.get_rc().refcount + } + + /// Increments the reference counter by 1. + fn inc_count(&mut self) { + self.get_rc_mut().refcount += 1 + } + + /// Decrements the reference counter by 1. + fn dec_count(&mut self) { + self.get_rc_mut().refcount -= 1; + } +} + +macro_rules! rc { + ($($t: ty),*) => { + $( + impl PhpRc for $t { + fn get_rc(&self) -> &ZendRefcount { + &self.gc + } + + fn get_rc_mut(&mut self) -> &mut ZendRefcount { + &mut self.gc + } + } + )* + }; +} + +rc!(ZendObject, zend_string); diff --git a/src/php/types/zval.rs b/src/php/types/zval.rs index ec84a97eaf..8ba09b37a6 100644 --- a/src/php/types/zval.rs +++ b/src/php/types/zval.rs @@ -3,7 +3,6 @@ use core::slice; use std::{ - collections::HashMap, convert::{TryFrom, TryInto}, fmt::Debug, ptr, @@ -11,8 +10,8 @@ use std::{ use crate::{ bindings::{ - _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, ext_php_rs_zend_string_release, - zend_is_callable, zend_resource, zend_value, zval, + _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, zend_resource, + zend_value, zval, zval_ptr_dtor, }, errors::{Error, Result}, php::pack::Pack, @@ -24,7 +23,12 @@ use crate::php::{ types::{long::ZendLong, string::ZendString}, }; -use super::{array::ZendHashTable, callable::Callable, object::ZendObject}; +use super::{ + array::{HashTable, OwnedHashTable}, + callable::Callable, + object::ZendObject, + rc::PhpRc, +}; /// Zend value. Represents most data types that are in the Zend engine. pub type Zval = zval; @@ -34,9 +38,7 @@ unsafe impl Sync for Zval {} impl Zval { /// Creates a new, empty zval. - pub(crate) const fn new() -> Self { - // NOTE: Once the `Drop` implementation has been improved this can be public. - // At the moment, if we were to create a new zval with an array it wouldn't drop the array. + pub const fn new() -> Self { Self { value: zend_value { ptr: ptr::null_mut(), @@ -145,10 +147,19 @@ impl Zval { } } - /// Returns the value of the zval if it is an array. - pub fn array(&self) -> Option { + /// Returns an immutable reference to the underlying zval hashtable if the zval contains an array. + pub fn array(&self) -> Option<&HashTable> { + if self.is_array() { + unsafe { self.value.arr.as_ref() } + } else { + None + } + } + + /// Returns a mutable reference to the underlying zval hashtable if the zval contains an array. + pub fn array_mut(&mut self) -> Option<&mut HashTable> { if self.is_array() { - unsafe { ZendHashTable::from_ptr(self.value.arr, false) }.ok() + unsafe { self.value.arr.as_mut() } } else { None } @@ -164,7 +175,16 @@ impl Zval { } /// Returns the value of the zval if it is a reference. - pub fn reference(&self) -> Option<&mut Zval> { + pub fn reference(&self) -> Option<&Zval> { + if self.is_reference() { + Some(&unsafe { self.value.ref_.as_ref() }?.val) + } else { + None + } + } + + /// Returns a mutable reference to the underlying zval if it is a reference. + pub fn reference_mut(&mut self) -> Option<&mut Zval> { if self.is_reference() { Some(&mut unsafe { self.value.ref_.as_mut() }?.val) } else { @@ -193,8 +213,8 @@ impl Zval { } /// Returns the type of the Zval. - pub fn get_type(&self) -> Result { - DataType::try_from(unsafe { self.u1.v.type_ } as u32) + pub fn get_type(&self) -> DataType { + DataType::from(unsafe { self.u1.v.type_ } as u32) } /// Returns true if the zval is a long, false otherwise. @@ -265,9 +285,9 @@ impl Zval { /// * `val` - The value to set the zval as. /// * `persistent` - Whether the string should persist between requests. pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> { + self.change_type(ZvalTypeFlags::StringEx); let zend_str = ZendString::new(val, persistent)?; self.value.str_ = zend_str.release(); - self.u1.type_info = ZvalTypeFlags::StringEx.bits(); Ok(()) } @@ -277,9 +297,9 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_binary(&mut self, val: Vec) { + self.change_type(ZvalTypeFlags::StringEx); let ptr = T::pack_into(val); self.value.str_ = ptr; - self.u1.type_info = ZvalTypeFlags::StringEx.bits(); } /// Sets the value of the zval as a interned string. Returns nothing in a result when successful. @@ -288,9 +308,9 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_interned_string(&mut self, val: &str) -> Result<()> { + self.change_type(ZvalTypeFlags::InternedStringEx); let zend_str = ZendString::new_interned(val)?; self.value.str_ = zend_str.release(); - self.u1.type_info = ZvalTypeFlags::InternedStringEx.bits(); Ok(()) } @@ -300,8 +320,12 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_long>(&mut self, val: T) { - self.value.lval = val.into(); - self.u1.type_info = ZvalTypeFlags::Long.bits(); + self._set_long(val.into()) + } + + fn _set_long(&mut self, val: ZendLong) { + self.change_type(ZvalTypeFlags::Long); + self.value.lval = val; } /// Sets the value of the zval as a double. @@ -310,8 +334,12 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_double>(&mut self, val: T) { - self.value.dval = val.into(); - self.u1.type_info = ZvalTypeFlags::Double.bits(); + self._set_double(val.into()) + } + + fn _set_double(&mut self, val: f64) { + self.change_type(ZvalTypeFlags::Double); + self.value.dval = val; } /// Sets the value of the zval as a boolean. @@ -320,18 +348,22 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_bool>(&mut self, val: T) { - self.u1.type_info = if val.into() { - DataType::True.as_u32() + self._set_bool(val.into()) + } + + fn _set_bool(&mut self, val: bool) { + self.change_type(if val { + ZvalTypeFlags::True } else { - DataType::False.as_u32() - }; + ZvalTypeFlags::False + }); } /// Sets the value of the zval as null. /// /// This is the default of a zval. pub fn set_null(&mut self) { - self.u1.type_info = ZvalTypeFlags::Null.bits(); + self.change_type(ZvalTypeFlags::Null); } /// Sets the value of the zval as a resource. @@ -340,7 +372,7 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_resource(&mut self, val: *mut zend_resource) { - self.u1.type_info = ZvalTypeFlags::ResourceEx.bits(); + self.change_type(ZvalTypeFlags::ResourceEx); self.value.res = val; } @@ -350,19 +382,29 @@ impl Zval { /// /// * `val` - The value to set the zval as. pub fn set_object(&mut self, val: &mut ZendObject) { - val.refcount_inc(); - self.u1.type_info = ZvalTypeFlags::ObjectEx.bits(); + self.change_type(ZvalTypeFlags::ObjectEx); + val.inc_count(); // TODO(david): not sure if this is needed :/ self.value.obj = (val as *const ZendObject) as *mut ZendObject; } - /// Sets the value of the zval as an array. Returns nothng in a result on success. + /// Sets the value of the zval as an array. Returns nothing in a result on success. /// /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_array(&mut self, val: ZendHashTable) { - self.u1.type_info = ZvalTypeFlags::ArrayEx.bits(); - self.value.arr = val.into_ptr(); + pub fn set_array>(&mut self, val: T) -> Result<()> { + self.set_hashtable(val.try_into()?); + Ok(()) + } + + /// Sets the value of the zval as an array. Returns nothing in a result on success. + /// + /// # Parameters + /// + /// * `val` - The value to set the zval as. + pub fn set_hashtable(&mut self, val: OwnedHashTable) { + self.change_type(ZvalTypeFlags::ArrayEx); + self.value.arr = val.into_inner(); } /// Used to drop the Zval but keep the value of the zval intact. @@ -371,8 +413,20 @@ impl Zval { /// will not be copied, but the pointer to the value (string for example) will be /// copied. pub(crate) fn release(mut self) { + // NOTE(david): don't use `change_type` here as we are wanting to keep the contents intact. self.u1.type_info = ZvalTypeFlags::Null.bits(); } + + /// Changes the type of the zval, freeing the current contents when applicable. + /// + /// # Parameters + /// + /// * `ty` - The new type of the zval. + fn change_type(&mut self, ty: ZvalTypeFlags) { + // SAFETY: we have exclusive mutable access to this zval so can free the contents. + unsafe { zval_ptr_dtor(self) }; + self.u1.type_info = ty.bits(); + } } impl Debug for Zval { @@ -381,41 +435,37 @@ impl Debug for Zval { let ty = self.get_type(); dbg.field("type", &ty); - if let Ok(ty) = ty { - macro_rules! field { - ($value: expr) => { - dbg.field("val", &$value) - }; - } - - match ty { - DataType::Undef => field!(Option::<()>::None), - DataType::Null => field!(Option::<()>::None), - DataType::False => field!(false), - DataType::True => field!(true), - DataType::Long => field!(self.long()), - DataType::Double => field!(self.double()), - DataType::String | DataType::Mixed => field!(self.string()), - DataType::Array => field!(self.array()), - DataType::Object(_) => field!(self.object()), - DataType::Resource => field!(self.resource()), - DataType::Reference => field!(self.reference()), - DataType::Callable => field!(self.string()), - DataType::ConstantExpression => field!(Option::<()>::None), - DataType::Void => field!(Option::<()>::None), - DataType::Bool => field!(self.bool()), + macro_rules! field { + ($value: expr) => { + dbg.field("val", &$value) }; } + match ty { + DataType::Undef => field!(Option::<()>::None), + DataType::Null => field!(Option::<()>::None), + DataType::False => field!(false), + DataType::True => field!(true), + DataType::Long => field!(self.long()), + DataType::Double => field!(self.double()), + DataType::String | DataType::Mixed => field!(self.string()), + DataType::Array => field!(self.array()), + DataType::Object(_) => field!(self.object()), + DataType::Resource => field!(self.resource()), + DataType::Reference => field!(self.reference()), + DataType::Callable => field!(self.string()), + DataType::ConstantExpression => field!(Option::<()>::None), + DataType::Void => field!(Option::<()>::None), + DataType::Bool => field!(self.bool()), + }; + dbg.finish() } } impl Drop for Zval { fn drop(&mut self) { - if self.is_string() { - unsafe { ext_php_rs_zend_string_release(self.value.str_) }; - } + self.change_type(ZvalTypeFlags::Null); } } @@ -448,6 +498,15 @@ pub trait IntoZval: Sized { fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>; } +impl IntoZval for Zval { + const TYPE: DataType = DataType::Mixed; + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + *zv = self; + Ok(()) + } +} + /// An object-safe version of the [`IntoZval`] trait. /// /// This trait is automatically implemented on any type that implements both [`IntoZval`] and [`Clone`]. @@ -591,44 +650,6 @@ where } } -impl<'a> IntoZval for ZendHashTable<'a> { - const TYPE: DataType = DataType::Array; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - zv.set_array(self); - Ok(()) - } -} - -impl IntoZval for Vec -where - T: IntoZval, -{ - const TYPE: DataType = DataType::Array; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - let hm = self - .try_into() - .map_err(|_| Error::ZvalConversion(DataType::Array))?; - zv.set_array(hm); - Ok(()) - } -} - -impl IntoZval for HashMap -where - K: AsRef, - V: IntoZval, -{ - const TYPE: DataType = DataType::Array; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - let hm = self.try_into()?; - zv.set_array(hm); - Ok(()) - } -} - /// Allows zvals to be converted into Rust types in a fallible way. Reciprocal of the [`IntoZval`] /// trait. pub trait FromZval<'a>: Sized { @@ -668,7 +689,7 @@ macro_rules! try_from_zval { type Error = Error; fn try_from(value: Zval) -> Result { - Self::from_zval(&value).ok_or(Error::ZvalConversion(value.get_type()?)) + Self::from_zval(&value).ok_or(Error::ZvalConversion(value.get_type())) } } }; @@ -707,64 +728,6 @@ impl<'a> FromZval<'a> for &'a str { } } -impl<'a> FromZval<'a> for ZendHashTable<'a> { - const TYPE: DataType = DataType::Array; - - fn from_zval(zval: &'a Zval) -> Option { - zval.array() - } -} - -impl<'a, T> FromZval<'a> for Vec -where - T: FromZval<'a>, -{ - const TYPE: DataType = DataType::Array; - - fn from_zval(zval: &'a Zval) -> Option { - zval.array().and_then(|arr| arr.try_into().ok()) - } -} - -impl TryFrom for Vec -where - for<'a> T: FromZval<'a>, -{ - type Error = Error; - - fn try_from(value: Zval) -> Result { - value - .array() - .ok_or(Error::ZvalConversion(value.get_type()?))? - .try_into() - } -} - -impl<'a, T> FromZval<'a> for HashMap -where - T: FromZval<'a>, -{ - const TYPE: DataType = DataType::Array; - - fn from_zval(zval: &'a Zval) -> Option { - zval.array().and_then(|arr| arr.try_into().ok()) - } -} - -impl TryFrom for HashMap -where - for<'a> T: FromZval<'a>, -{ - type Error = Error; - - fn try_from(value: Zval) -> Result { - value - .array() - .ok_or(Error::ZvalConversion(value.get_type()?))? - .try_into() - } -} - impl<'a> FromZval<'a> for Callable<'a> { const TYPE: DataType = DataType::Callable;