return type casting for arrays

This commit is contained in:
Robin Appelman 2019-04-06 15:57:07 +02:00
commit f20c2b5fd3
6 changed files with 108 additions and 15 deletions

View file

@ -7,7 +7,9 @@ mod cache;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, AttributeArgs, FnArg, Ident, Item, ItemFn, LitStr, Pat, Type};
use syn::{
parse_macro_input, AttributeArgs, FnArg, Ident, Item, ItemFn, LitStr, Pat, ReturnType, Type,
};
/// See the [crate documentation](index.html) for details
#[proc_macro_attribute]
@ -76,6 +78,12 @@ fn export_fn(item: ItemFn) -> TokenStream {
)
});
let return_type = decl.output;
let return_type = match return_type {
ReturnType::Default => quote!(),
ReturnType::Type(_, return_type) => quote!(: #return_type),
};
quote! {
#[no_mangle]
pub unsafe extern "C" fn #name(data: *const ::ivory::zend::ExecuteData, retval: *mut ::ivory::zend::ZVal) {
@ -88,7 +96,7 @@ fn export_fn(item: ItemFn) -> TokenStream {
}
let mut args = data.args();
#(#arg_cast);*
let result = #body;
let result #return_type = #body;
let php_val = ::ivory::PhpVal::from(result);
let zval = ::ivory::zend::ZVal::from(php_val);

View file

@ -73,12 +73,12 @@ impl From<PhpVal> for Result<PhpVal, CastError> {
}
macro_rules! impl_from_phpval {
($type:ty, $variant:ident) => {
($type:ty, $variant:ident, $type2:ty) => {
// non nullable version
impl From<PhpVal> for Result<$type, CastError> {
impl From<PhpVal> for Result<$type2, CastError> {
fn from(val: PhpVal) -> Self {
match val {
PhpVal::$variant(val) => Ok(val),
PhpVal::$variant(val) => Ok(val as $type2),
_ => Err(CastError {
actual: val.get_type(),
}),
@ -87,12 +87,12 @@ macro_rules! impl_from_phpval {
}
// nullable version
impl From<PhpVal> for Result<Option<$type>, CastError> {
impl From<PhpVal> for Result<Option<$type2>, CastError> {
fn from(val: PhpVal) -> Self {
match val {
PhpVal::Null => Ok(None),
PhpVal::Undef => Ok(None),
PhpVal::$variant(val) => Ok(Some(val)),
PhpVal::$variant(val) => Ok(Some(val as $type2)),
_ => Err(CastError {
actual: val.get_type(),
}),
@ -100,18 +100,26 @@ macro_rules! impl_from_phpval {
}
}
impl From<$type> for PhpVal {
fn from(input: $type) -> Self {
PhpVal::$variant(input)
impl From<$type2> for PhpVal {
fn from(input: $type2) -> Self {
PhpVal::$variant(input as $type)
}
}
};
}
impl_from_phpval!(i64, Long);
impl_from_phpval!(f64, Double);
impl_from_phpval!(bool, Bool);
impl_from_phpval!(String, String);
impl_from_phpval!(i64, Long, i64);
impl_from_phpval!(i64, Long, i32);
impl_from_phpval!(i64, Long, i16);
impl_from_phpval!(i64, Long, i8);
impl_from_phpval!(i64, Long, u64);
impl_from_phpval!(i64, Long, u32);
impl_from_phpval!(i64, Long, u16);
impl_from_phpval!(i64, Long, u8);
impl_from_phpval!(f64, Double, f64);
impl_from_phpval!(f64, Double, f32);
impl_from_phpval!(bool, Bool, bool);
impl_from_phpval!(String, String, String);
impl From<()> for PhpVal {
fn from(_input: ()) -> Self {

View file

@ -258,6 +258,36 @@ impl From<PhpVal> for ZVal {
u1: ty.into(),
u2: _zval_struct__bindgen_ty_2 { extra: 0 },
}),
PhpVal::Array(vec) => {
// unlike reading, when creating a zval array all the extra bits actually matter
// so we just use zend's builtin methods for creating arrays
unsafe {
let map: *mut _zend_array = _zend_new_array(vec.len() as u32);
let mut arr = zval {
value: zend_value { arr: map },
u1: ty.into(),
u2: _zval_struct__bindgen_ty_2 { extra: 0 },
};
let arr_ptr: *mut zval = &mut arr;
for (key, val) in vec.into_iter() {
let val_ptr: *mut zval =
Box::into_raw(Box::new(ZVal::from(val))) as *mut zval;
match key {
ArrayKey::Int(index) => {
add_index_zval(arr_ptr, index, val_ptr);
}
ArrayKey::String(key) => {
let key_len = key.len();
let bytes =
Box::into_raw(key.into_bytes().into_boxed_slice()) as *const i8;
add_assoc_zval_ex(arr_ptr, bytes, key_len, val_ptr);
}
}
}
ZVal(arr)
}
}
_ => unimplemented!(),
}
}

View file

@ -154,6 +154,9 @@ fn main() {
.whitelist_function("php_info_print_table_row")
.whitelist_function("php_info_print_table_end")
.whitelist_function("php_printf")
.whitelist_function("_zend_new_array")
.whitelist_function("add_index_zval")
.whitelist_function("add_assoc_zval_ex")
.whitelist_type("zval")
.whitelist_type("zend_execute_data")
.whitelist_type("zend_module_entry")

View file

@ -2,8 +2,8 @@ use std::fmt::Debug;
use crate::imported::imported_fn;
use ivory::externs::printf;
use ivory::PhpVal;
use ivory::{ivory_export, ivory_module};
use ivory::{ArrayKey, PhpVal};
mod imported;
@ -66,6 +66,30 @@ fn return_string() -> String {
"some string data".to_string()
}
#[ivory_export]
fn return_array_simple() -> Vec<i32> {
vec![-10, 10, 0]
}
#[ivory_export]
fn return_array_gap() -> Vec<(u32, i32)> {
vec![(0u32, -10), (1, 10), (10, 0)]
}
#[ivory_export]
fn return_array_mixed() -> Vec<(ArrayKey, PhpVal)> {
vec![
(0u32.into(), PhpVal::from(-10)),
("foo".to_string().into(), PhpVal::from(10)),
("bar".to_string().into(), PhpVal::from(0.5)),
]
}
#[ivory_export]
fn return_array_nested() -> Vec<Vec<i32>> {
vec![vec![1, 2], vec![3, 4]]
}
ivory_module!({
name: "tests",
version: "0.0.1",

View file

@ -105,6 +105,26 @@ test_return!(
"return_string",
"Command line code:1:\nstring(16) \"some string data\"\n"
);
test_return!(
test_return_array_simple,
"return_array_simple",
"Command line code:1:\narray(3) {\n [0] =>\n int(-10)\n [1] =>\n int(10)\n [2] =>\n int(0)\n}\n"
);
test_return!(
test_return_array_gap,
"return_array_gap",
"Command line code:1:\narray(3) {\n [0] =>\n int(-10)\n [1] =>\n int(10)\n [10] =>\n int(0)\n}\n"
);
test_return!(
test_return_array_mixed,
"return_array_mixed",
"Command line code:1:\narray(3) {\n [0] =>\n int(-10)\n \'foo\' =>\n int(10)\n \'bar\' =>\n double(0.5)\n}\n"
);
test_return!(
test_return_array_nested,
"return_array_nested",
"Command line code:1:\narray(2) {\n [0] =>\n array(2) {\n [0] =>\n int(1)\n [1] =>\n int(2)\n }\n [1] =>\n array(2) {\n [0] =>\n int(3)\n [1] =>\n int(4)\n }\n}\n"
);
#[test]
fn test_imported() {