diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 78dc8e76e0..a93033494d 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -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, diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 4bf9f3adbd..8d288da18b 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -27,11 +27,11 @@ pub fn wrap(input: syn::Path) -> Result { #[darling(default)] pub struct FnArgs { /// The name of the function. - name: Option, + pub name: Option, /// The first optional argument of the function signature. - optional: Option, + pub optional: Option, /// Default values for optional arguments. - defaults: HashMap, + pub defaults: HashMap, } pub fn parser(opts: TokenStream, input: ItemFn) -> Result { @@ -80,6 +80,7 @@ pub enum CallType<'a> { class: &'a syn::Path, receiver: MethodReceiver, }, + MethodInterface, } /// Type of receiver on the method. @@ -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))* @@ -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! { @@ -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. diff --git a/crates/macros/src/helpers.rs b/crates/macros/src/helpers.rs index dfc4b8affb..bfdb232064 100644 --- a/crates/macros/src/helpers.rs +++ b/crates/macros/src/helpers.rs @@ -1,6 +1,9 @@ use crate::class::{parse_attribute, ParsedAttribute}; +use darling::FromMeta; use syn::Attribute; +pub type Docs = Vec; + /// Takes a list of attributes and returns a list of doc comments retrieved from /// the attributes. pub fn get_docs(attrs: &[Attribute]) -> Vec { @@ -14,3 +17,41 @@ pub fn get_docs(attrs: &[Attribute]) -> Vec { 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), + } + } +} diff --git a/crates/macros/src/impl_.rs b/crates/macros/src/impl_.rs index c79152cb76..f74eeb87da 100644 --- a/crates/macros/src/impl_.rs +++ b/crates/macros/src/impl_.rs @@ -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) -> 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> 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) } } } @@ -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)?; @@ -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)); } } diff --git a/crates/macros/src/interface.rs b/crates/macros/src/interface.rs new file mode 100644 index 0000000000..5ddd11144d --- /dev/null +++ b/crates/macros/src/interface.rs @@ -0,0 +1,184 @@ +use std::collections::HashMap; + +use darling::ast::NestedMeta; +use darling::{FromMeta, ToTokens}; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::parse::ParseStream; +use syn::{Attribute, Expr, Fields, ItemStruct, ItemTrait, Lit, LitStr, Meta, Token}; + +use crate::function::{Args, CallType, Function, MethodReceiver}; +use crate::helpers::get_docs; +use crate::prelude::*; + +#[derive(Debug)] +enum MethodVis { + Public, + Private, + Protected, +} + +#[derive(Debug)] +struct MethodArgs { + name: String, + optional: Option, + defaults: HashMap, + vis: MethodVis, +} + +#[derive(Debug, Default, FromMeta)] +#[darling(default)] +struct InterfaceArgs { + name: Option, +} + +#[derive(Debug, Default)] +struct InterfaceAttrs { + implements: Vec, + docs: Vec, +} + +impl InterfaceAttrs { + fn parse(&mut self, attrs: &mut Vec) -> Result<()> { + let mut unparsed = vec![]; + unparsed.append(attrs); + for attr in unparsed { + let path = attr.path(); + + if path.is_ident("implements") { + let implements: syn::Expr = match attr.parse_args() { + Ok(extends) => extends, + Err(_) => bail!(attr => "Invalid arguments passed to implements attribute."), + }; + self.implements.push(implements); + } + } + self.docs = get_docs(attrs); + Ok(()) + } +} + +impl MethodArgs { + fn new(name: String) -> Self { + Self { + name, + optional: Default::default(), + defaults: Default::default(), + vis: MethodVis::Public, + } + } + + fn parse(&mut self, attrs: &mut Vec) -> Result<()> { + let mut unparsed = vec![]; + unparsed.append(attrs); + for attr in unparsed { + let path = &attr.path(); + + if path.is_ident("optional") { + if self.optional.is_some() { + bail!(attr => "Only one `#[optional]` attribute is valid per method."); + } + let optional = attr.parse_args().map_err( + |e| err!(e.span() => "Invalid arguments passed to `#[optional]` attribute. {}", e), + )?; + self.optional = Some(optional); + } else if path.is_ident("defaults") { + let defaults = HashMap::from_meta(&attr.meta).map_err( + |e| err!(e.span() => "Invalid arguments passed to `#[defaults]` attribute. {}", e), + )?; + self.defaults = defaults; + } else if path.is_ident("public") { + self.vis = MethodVis::Public; + } else if path.is_ident("protected") { + self.vis = MethodVis::Protected; + } else if path.is_ident("private") { + self.vis = MethodVis::Private; + } else { + attrs.push(attr); + } + } + Ok(()) + } +} + +pub fn parser(args: TokenStream, mut input: ItemTrait) -> Result { + let meta = NestedMeta::parse_meta_list(args)?; + let args = match InterfaceArgs::from_list(&meta) { + Ok(args) => args, + Err(e) => bail!(input => "Failed to parse impl attribute arguments: {:?}", e), + }; + + let mut parsed = ParsedTrait { functions: vec![] }; + parsed.parse(input.items.iter_mut())?; + let interface_struct_name = format_ident!("PhpInterface{}", input.ident); + let functions = &parsed.functions; + + Ok(quote::quote! { + #input + + pub(crate) struct #interface_struct_name; + + fn get_methods() -> ::std::vec::Vec< + (::ext_php_rs::builders::FunctionBuilder<'static>, ::ext_php_rs::flags::MethodFlags) + > { + vec![#(#functions),*] + } + }) +} + +#[derive(Debug)] +struct ParsedTrait { + functions: Vec, +} + +impl ParsedTrait { + fn parse<'a>(&mut self, items: impl Iterator) -> Result<()> { + for item in items { + match item { + syn::TraitItem::Fn(method) => { + let name = method.sig.ident.to_string(); + let docs = get_docs(&method.attrs); + let mut opts = MethodArgs::new(name); + opts.parse(&mut method.attrs)?; + + let args = Args::parse_from_fnargs(method.sig.inputs.iter(), opts.defaults)?; + let func = + Function::new(&method.sig, Some(opts.name), args, opts.optional, docs)?; + + let builder = func.function_builder(CallType::MethodInterface)?; + self.functions.push(FnBuilder { + builder, + vis: opts.vis, + }); + } + _ => todo!(), + } + } + Ok(()) + } +} + +#[derive(Debug)] +struct FnBuilder { + pub builder: TokenStream, + pub vis: MethodVis, +} + +impl quote::ToTokens for FnBuilder { + fn to_tokens(&self, tokens: &mut TokenStream) { + let builder = &self.builder; + // TODO(cole_d): allow more flags via attributes + let mut flags = vec![]; + flags.push(match self.vis { + MethodVis::Public => quote! { ::ext_php_rs::flags::MethodFlags::Public }, + MethodVis::Protected => quote! { ::ext_php_rs::flags::MethodFlags::Protected }, + MethodVis::Private => quote! { ::ext_php_rs::flags::MethodFlags::Private }, + }); + flags.push(quote! { ::ext_php_rs::flags::MethodFlags::Abstract }); + + quote! { + (#builder, #(#flags)|*) + } + .to_tokens(tokens); + } +} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 588a487dca..789960aa7c 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -6,6 +6,8 @@ mod fastcall; mod function; mod helpers; mod impl_; +mod interface; +mod method; mod module; mod syn_ext; mod zval; @@ -13,6 +15,7 @@ mod zval; use proc_macro::TokenStream; use syn::{ parse_macro_input, DeriveInput, ItemConst, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, + ItemTrait, TraitItem, }; extern crate proc_macro; @@ -664,6 +667,15 @@ pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream { .into() } +#[proc_macro_attribute] +pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemTrait); + + interface::parser(args.into(), input) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + /// # `#[php_extern]` Attribute /// /// Attribute used to annotate `extern` blocks which are deemed as PHP @@ -993,6 +1005,7 @@ pub(crate) mod prelude { } } } + use crate::helpers::GetDocs; pub(crate) use crate::{bail, err}; pub(crate) type Result = std::result::Result; diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs new file mode 100644 index 0000000000..b313971db3 --- /dev/null +++ b/crates/macros/src/method.rs @@ -0,0 +1,147 @@ +use crate::function::{Args, FnArgs, MethodReceiver}; +use crate::prelude::*; +use crate::{function::Function, helpers::GetDocs}; +use darling::ast::NestedMeta; +use darling::FromMeta; +use proc_macro2::TokenStream; +use syn::{FnArg, Ident, ImplItemFn, ItemFn, TraitItemFn}; + +pub trait FunctionLike: GetDocs { + fn name(&self) -> String; + + fn args(&self) -> impl Iterator; + + fn signature(&self) -> &syn::Signature; +} + +pub trait MethodLike: FunctionLike {} + +// TraitMethod to Interface >>> +impl GetDocs for TraitItemFn { + fn get_docs(&self) -> Vec { + self.attrs.as_slice().get_docs() + } +} + +impl FunctionLike for TraitItemFn { + fn name(&self) -> String { + self.sig.ident.to_string() + } + + fn args(&self) -> impl Iterator { + self.sig.inputs.iter() + } + + fn signature(&self) -> &syn::Signature { + &self.sig + } +} + +impl MethodLike for TraitItemFn {} +// <<< TraitMethod to Interface + +// ImplMethod to class method >>> +impl GetDocs for ImplItemFn { + fn get_docs(&self) -> Vec { + self.attrs.as_slice().get_docs() + } +} + +impl FunctionLike for ImplItemFn { + fn name(&self) -> String { + self.sig.ident.to_string() + } + + fn args(&self) -> impl Iterator { + self.sig.inputs.iter() + } + + fn signature(&self) -> &syn::Signature { + &self.sig + } +} + +impl MethodLike for ImplItemFn {} +// <<< ImplMethod to class method + +// Function to function >>> +impl GetDocs for ItemFn { + fn get_docs(&self) -> Vec { + self.attrs.as_slice().get_docs() + } +} + +impl FunctionLike for ItemFn { + fn name(&self) -> String { + self.sig.ident.to_string() + } + + fn args(&self) -> impl Iterator { + self.sig.inputs.iter() + } + + fn signature(&self) -> &syn::Signature { + &self.sig + } +} +// Function to function >>> + +pub trait ToFunction<'a> { + fn to_function(&'a self, opts: TokenStream) -> Result>; +} + +impl<'a, T: FunctionLike> ToFunction<'a> for T { + fn to_function(&'a self, opts: TokenStream) -> Result> { + let meta = NestedMeta::parse_meta_list(opts)?; + let opts = match FnArgs::from_list(&meta) { + Ok(opts) => opts, + Err(e) => bail!("Failed to parse attribute options: {:?}", e), + }; + + let args = Args::parse_from_fnargs(self.args(), opts.defaults)?; + + let docs = self.get_docs(); + + Function::new(self.signature(), opts.name, args, opts.optional, docs) + } +} + +//------------------- + +#[derive(Debug)] +enum MethodVis { + Public, + Private, + Protected, +} + +#[derive(Debug)] +enum MethodTy { + Normal, + Constructor, + Getter, + Setter, + Abstract, +} + +#[derive(Debug)] +pub struct MethodArgs { + fn_args: FnArgs, + vis: MethodVis, + ty: MethodTy, +} + +pub trait ToMethod<'a> { + fn to_method(&'a self, opts: TokenStream) -> Result>; +} + +impl<'a, T: MethodLike> ToMethod<'a> for T { + fn to_method(&'a self, opts: TokenStream) -> Result> { + let meta = NestedMeta::parse_meta_list(opts)?; + let opts = match FnArgs::from_list(&meta) { + Ok(opts) => opts, + Err(e) => bail!("Failed to parse attribute options: {:?}", e), + }; + todo!() + } +} diff --git a/src/builders/class.rs b/src/builders/class.rs index c717b426d9..d35e37c9e3 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -9,7 +9,7 @@ use crate::{ exception::PhpException, ffi::{ zend_declare_class_constant, zend_declare_property, zend_do_implement_interface, - zend_register_internal_class_ex, + zend_register_internal_class_ex, zend_register_internal_interface, }, flags::{ClassFlags, MethodFlags, PropertyFlags}, types::{ZendClassObject, ZendObject, ZendStr, Zval}, @@ -293,16 +293,24 @@ impl ClassBuilder { let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry; self.ce.info.internal.builtin_functions = func; - let class = unsafe { - zend_register_internal_class_ex( - &mut self.ce, - match self.extends { - Some(ptr) => (ptr as *const _) as *mut _, - None => std::ptr::null_mut(), - }, - ) - .as_mut() - .ok_or(Error::InvalidPointer)? + let class = if self.ce.flags().contains(ClassFlags::Interface) { + unsafe { + zend_register_internal_interface(&mut self.ce) + .as_mut() + .ok_or(Error::InvalidPointer)? + } + } else { + unsafe { + zend_register_internal_class_ex( + &mut self.ce, + match self.extends { + Some(ptr) => (ptr as *const _) as *mut _, + None => std::ptr::null_mut(), + }, + ) + .as_mut() + .ok_or(Error::InvalidPointer)? + } }; // disable serialization if the class has an associated object @@ -364,3 +372,76 @@ impl ClassBuilder { Ok(()) } } + +pub struct InterfaceBuilder { + class_builder: ClassBuilder, +} + +impl InterfaceBuilder { + pub fn new>(name: T) -> Self { + Self { + class_builder: ClassBuilder::new(name), + } + } + + pub fn implements(mut self, interface: &'static ClassEntry) -> Self { + self.class_builder = self.class_builder.implements(interface); + + self + } + + pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self { + self.class_builder = self.class_builder.method(func, flags); + + self + } + + pub fn constant>( + mut self, + name: T, + value: impl IntoZval + 'static, + docs: DocComments, + ) -> Result { + self.class_builder = self.class_builder.constant(name, value, docs)?; + + Ok(self) + } + + pub fn dyn_constant>( + mut self, + name: T, + value: &'static dyn IntoZvalDyn, + docs: DocComments, + ) -> Result { + self.class_builder = self.class_builder.dyn_constant(name, value, docs)?; + + Ok(self) + } + + pub fn flags(mut self, flags: ClassFlags) -> Self { + self.class_builder = self.class_builder.flags(flags); + self + } + + pub fn object_override(mut self) -> Self { + self.class_builder = self.class_builder.object_override::(); + + self + } + + pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self { + self.class_builder = self.class_builder.registration(register); + + self + } + + pub fn docs(mut self, docs: DocComments) -> Self { + self.class_builder = self.class_builder.docs(docs); + self + } + + pub fn builder(mut self) -> ClassBuilder { + self.class_builder = self.class_builder.flags(ClassFlags::Interface); + self.class_builder + } +} diff --git a/src/builders/module.rs b/src/builders/module.rs index f78630be09..496d22f7cd 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -1,6 +1,6 @@ use std::{convert::TryFrom, ffi::CString, mem, ptr}; -use super::{ClassBuilder, FunctionBuilder}; +use super::{class::InterfaceBuilder, ClassBuilder, FunctionBuilder}; use crate::{ class::RegisteredClass, constant::IntoConst, @@ -167,6 +167,37 @@ impl ModuleBuilder<'_> { self } + pub fn interface(mut self) -> Self { + self.classes.push(|| { + let mut builder = InterfaceBuilder::new(T::CLASS_NAME); + for (method, flags) in T::method_builders() { + builder = builder.method(method, flags); + } + for iface in T::IMPLEMENTS { + builder = builder.implements(iface()); + } + for (name, value, docs) in T::constants() { + builder = builder + .dyn_constant(*name, *value, docs) + .expect("Failed to register constant"); + } + + let mut class_builder = builder.builder(); + + if let Some(modifier) = T::BUILDER_MODIFIER { + class_builder = modifier(class_builder); + } + + class_builder + .object_override::() + .registration(|ce| { + T::get_metadata().set_ce(ce); + }) + .docs(T::DOC_COMMENTS) + }); + self + } + /// Adds a class to the extension. pub fn class(mut self) -> Self { self.classes.push(|| { diff --git a/src/lib.rs b/src/lib.rs index 8af7fdd333..3543455f3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,8 +47,8 @@ pub mod prelude { pub use crate::php_println; pub use crate::types::ZendCallable; pub use crate::{ - php_class, php_const, php_extern, php_function, php_impl, php_module, wrap_constant, - wrap_function, zend_fastcall, ZvalConvert, + php_class, php_const, php_extern, php_function, php_impl, php_interface, php_module, + wrap_constant, wrap_function, zend_fastcall, ZvalConvert, }; } @@ -62,6 +62,6 @@ pub const PHP_DEBUG: bool = cfg!(php_debug); pub const PHP_ZTS: bool = cfg!(php_zts); pub use ext_php_rs_derive::{ - php_class, php_const, php_extern, php_function, php_impl, php_module, wrap_constant, - wrap_function, zend_fastcall, ZvalConvert, + php_class, php_const, php_extern, php_function, php_impl, php_interface, php_module, + wrap_constant, wrap_function, zend_fastcall, ZvalConvert, };