Skip to content
1 change: 1 addition & 0 deletions allowed_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ bind! {
zend_register_internal_class_ex,
zend_register_long_constant,
zend_register_string_constant,
zend_register_internal_interface,
zend_resource,
zend_string,
zend_string_init_interned,
Expand Down
81 changes: 51 additions & 30 deletions crates/macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ pub fn wrap(input: syn::Path) -> Result<TokenStream> {
#[darling(default)]
pub struct FnArgs {
/// The name of the function.
name: Option<String>,
pub name: Option<String>,
/// The first optional argument of the function signature.
optional: Option<Ident>,
pub optional: Option<Ident>,
/// Default values for optional arguments.
defaults: HashMap<Ident, Lit>,
pub defaults: HashMap<Ident, Lit>,
}

pub fn parser(opts: TokenStream, input: ItemFn) -> Result<TokenStream> {
Expand Down Expand Up @@ -80,6 +80,7 @@ pub enum CallType<'a> {
class: &'a syn::Path,
receiver: MethodReceiver,
},
MethodInterface,
}

/// Type of receiver on the method.
Expand Down Expand Up @@ -170,7 +171,7 @@ impl<'a> Function<'a> {
}
});

let result = match call_type {
let result = match &call_type {
CallType::Function => quote! {
let parse = ex.parser()
#(.arg(&mut #required_arg_names))*
Expand All @@ -183,6 +184,16 @@ impl<'a> Function<'a> {

#ident(#({#arg_accessors}),*)
},
CallType::MethodInterface => quote! {
let parse = ex.parser()
#(.arg(&mut #required_arg_names))*
.not_required()
#(.arg(&mut #not_required_arg_names))*
.parse();
if parse.is_err() {
return;
}
},
CallType::Method { class, receiver } => {
let this = match receiver {
MethodReceiver::Static => quote! {
Expand Down Expand Up @@ -235,34 +246,44 @@ impl<'a> Function<'a> {
quote! {}
};

Ok(quote! {
::ext_php_rs::builders::FunctionBuilder::new(#name, {
::ext_php_rs::zend_fastcall! {
extern fn handler(
ex: &mut ::ext_php_rs::zend::ExecuteData,
retval: &mut ::ext_php_rs::types::Zval,
) {
use ::ext_php_rs::convert::IntoZval;

#(#arg_declarations)*
let result = {
#result
};

if let Err(e) = result.set_zval(retval, false) {
let e: ::ext_php_rs::exception::PhpException = e.into();
e.throw().expect("Failed to throw PHP exception.");
match call_type {
CallType::MethodInterface => Ok(quote! {
::ext_php_rs::builders::FunctionBuilder::new_abstract(#name)
#(.arg(#required_args))*
.not_required()
#(.arg(#not_required_args))*
#returns
#docs
}),
_ => Ok(quote! {
::ext_php_rs::builders::FunctionBuilder::new(#name, {
::ext_php_rs::zend_fastcall! {
extern fn handler(
ex: &mut ::ext_php_rs::zend::ExecuteData,
retval: &mut ::ext_php_rs::types::Zval,
) {
use ::ext_php_rs::convert::IntoZval;

#(#arg_declarations)*
let result = {
#result
};

if let Err(e) = result.set_zval(retval, false) {
let e: ::ext_php_rs::exception::PhpException = e.into();
e.throw().expect("Failed to throw PHP exception.");
}
}
}
}
handler
})
#(.arg(#required_args))*
.not_required()
#(.arg(#not_required_args))*
#returns
#docs
})
handler
})
#(.arg(#required_args))*
.not_required()
#(.arg(#not_required_args))*
#returns
#docs
}),
}
}

/// Generates a struct and impl for the `PhpFunction` trait.
Expand Down
41 changes: 41 additions & 0 deletions crates/macros/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::class::{parse_attribute, ParsedAttribute};
use darling::FromMeta;
use syn::Attribute;

pub type Docs = Vec<String>;

/// Takes a list of attributes and returns a list of doc comments retrieved from
/// the attributes.
pub fn get_docs(attrs: &[Attribute]) -> Vec<String> {
Expand All @@ -14,3 +17,41 @@ pub fn get_docs(attrs: &[Attribute]) -> Vec<String> {

docs
}

pub trait GetDocs {
fn get_docs(&self) -> Docs;
}

impl GetDocs for &[Attribute] {
fn get_docs(&self) -> Docs {
get_docs(self)
}
}

#[derive(Debug, Copy, Clone, FromMeta, Default)]
pub enum RenameRule {
/// Methods won't be renamed.
#[darling(rename = "none")]
None,
/// Methods will be converted to camelCase.
#[darling(rename = "camelCase")]
#[default]
Camel,
/// Methods will be converted to snake_case.
#[darling(rename = "snake_case")]
Snake,
}

pub trait Rename {
fn renmae(self, rule: &RenameRule) -> Self;
}

impl Rename for String {
fn renmae(self, rule: &RenameRule) -> Self {
match *rule {
RenameRule::None => self,
RenameRule::Camel => ident_case::RenameRule::CamelCase.apply_to_field(self),
RenameRule::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(self),
}
}
}
Comment on lines +30 to +57
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to let you know that this was just changed in #413. You might wanna rebase onto master

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I think do separate PR

116 changes: 42 additions & 74 deletions crates/macros/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,40 @@ use quote::quote;
use std::collections::HashMap;
use syn::{Ident, ItemImpl, Lit};

use crate::function::{Args, CallType, Function, MethodReceiver};
use crate::helpers::get_docs;
use crate::function::{Args, CallType, FnArgs, Function, MethodReceiver};
use crate::helpers::{get_docs, GetDocs, Rename, RenameRule};
use crate::prelude::*;

#[derive(Debug, Copy, Clone, FromMeta, Default)]
pub enum RenameRule {
/// Methods won't be renamed.
#[darling(rename = "none")]
None,
/// Methods will be converted to camelCase.
#[darling(rename = "camelCase")]
#[default]
Camel,
/// Methods will be converted to snake_case.
#[darling(rename = "snake_case")]
Snake,
const MAGIC_METHOD: [&'static str; 17] = [
"__construct",
"__destruct",
"__call",
"__call_static",
"__get",
"__set",
"__isset",
"__unset",
"__sleep",
"__wakeup",
"__serialize",
"__unserialize",
"__to_string",
"__invoke",
"__set_state",
"__clone",
"__debug_info",
];

trait RenameMethod {
fn rename_method(self, rule: &RenameRule) -> String;
}

impl RenameRule {
/// Change case of an identifier.
///
/// Magic methods are handled specially to make sure they're always cased
/// correctly.
pub fn rename(&self, name: impl AsRef<str>) -> String {
let name = name.as_ref();
match self {
RenameRule::None => name.to_string(),
rule => match name {
"__construct" => "__construct".to_string(),
"__destruct" => "__destruct".to_string(),
"__call" => "__call".to_string(),
"__call_static" => "__callStatic".to_string(),
"__get" => "__get".to_string(),
"__set" => "__set".to_string(),
"__isset" => "__isset".to_string(),
"__unset" => "__unset".to_string(),
"__sleep" => "__sleep".to_string(),
"__wakeup" => "__wakeup".to_string(),
"__serialize" => "__serialize".to_string(),
"__unserialize" => "__unserialize".to_string(),
"__to_string" => "__toString".to_string(),
"__invoke" => "__invoke".to_string(),
"__set_state" => "__set_state".to_string(),
"__clone" => "__clone".to_string(),
"__debug_info" => "__debugInfo".to_string(),
field => match rule {
Self::Camel => ident_case::RenameRule::CamelCase.apply_to_field(field),
Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(field),
Self::None => unreachable!(),
},
},
impl<T: AsRef<str>> RenameMethod for T {
fn rename_method(self, rule: &RenameRule) -> String {
if MAGIC_METHOD.contains(&self.as_ref()) {
self.as_ref().to_string()
} else {
self.as_ref().to_string().renmae(rule)
}
}
}
Expand Down Expand Up @@ -280,8 +263,9 @@ impl<'a> ParsedImpl<'a> {
});
}
syn::ImplItem::Fn(method) => {
let name = self.rename.rename(method.sig.ident.to_string());
let docs = get_docs(&method.attrs);
let name = method.sig.ident.to_string().renmae(&self.rename);
let docs = method.attrs.as_slice().get_docs();

let mut opts = MethodArgs::new(name);
opts.parse(&mut method.attrs)?;

Expand Down Expand Up @@ -396,40 +380,24 @@ impl quote::ToTokens for FnBuilder {

#[cfg(test)]
mod tests {
use super::RenameMethod;
use super::RenameRule;
use super::MAGIC_METHOD;

#[test]
fn test_rename_magic() {
for &(magic, expected) in &[
("__construct", "__construct"),
("__destruct", "__destruct"),
("__call", "__call"),
("__call_static", "__callStatic"),
("__get", "__get"),
("__set", "__set"),
("__isset", "__isset"),
("__unset", "__unset"),
("__sleep", "__sleep"),
("__wakeup", "__wakeup"),
("__serialize", "__serialize"),
("__unserialize", "__unserialize"),
("__to_string", "__toString"),
("__invoke", "__invoke"),
("__set_state", "__set_state"),
("__clone", "__clone"),
("__debug_info", "__debugInfo"),
] {
assert_eq!(magic, RenameRule::None.rename(magic));
assert_eq!(expected, RenameRule::Camel.rename(magic));
assert_eq!(expected, RenameRule::Snake.rename(magic));
for magic in MAGIC_METHOD {
assert_eq!(magic, magic.rename_method(&RenameRule::None));
assert_eq!(magic, magic.rename_method(&RenameRule::Camel));
assert_eq!(magic, magic.rename_method(&RenameRule::Snake));
}
}

#[test]
fn test_rename_php_methods() {
let &(original, camel, snake) = &("get_name", "getName", "get_name");
assert_eq!(original, RenameRule::None.rename(original));
assert_eq!(camel, RenameRule::Camel.rename(original));
assert_eq!(snake, RenameRule::Snake.rename(original));
assert_eq!(original, original.rename_method(&RenameRule::None));
assert_eq!(camel, original.rename_method(&RenameRule::Camel));
assert_eq!(snake, original.rename_method(&RenameRule::Snake));
}
}
Loading