Skip to content

Feat: create interface #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
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