From 4d970c870b585c7cda4c69afb3ba2766ed75e2e0 Mon Sep 17 00:00:00 2001 From: norbytus Date: Sun, 6 Apr 2025 18:29:36 +0300 Subject: [PATCH 1/6] refactor(macro): Make rename as Trait && separate to common rename, method_rename --- crates/macros/src/impl_.rs | 46 +--------- crates/macros/src/parsing.rs | 170 ++++++++++++++++++++--------------- 2 files changed, 103 insertions(+), 113 deletions(-) diff --git a/crates/macros/src/impl_.rs b/crates/macros/src/impl_.rs index 9fa5f16bdc..83303de29f 100644 --- a/crates/macros/src/impl_.rs +++ b/crates/macros/src/impl_.rs @@ -8,7 +8,7 @@ use syn::{Ident, ItemImpl, Lit}; use crate::constant::PhpConstAttribute; use crate::function::{Args, CallType, Function, MethodReceiver}; use crate::helpers::get_docs; -use crate::parsing::{PhpRename, RenameRule, Visibility}; +use crate::parsing::{MethodRename, PhpRename, Rename, RenameRule, Visibility}; use crate::prelude::*; /// Method types. @@ -184,7 +184,7 @@ impl<'a> ParsedImpl<'a> { match items { syn::ImplItem::Const(c) => { let attr = PhpConstAttribute::from_attributes(&c.attrs)?; - let name = self.rename_constants.rename(c.ident.to_string()); + let name = c.ident.rename(&self.rename_constants); let name = attr.rename.rename(name); let docs = get_docs(&attr.attrs)?; c.attrs.retain(|attr| !attr.path().is_ident("php")); @@ -197,7 +197,7 @@ impl<'a> ParsedImpl<'a> { } syn::ImplItem::Fn(method) => { let attr = PhpFunctionImplAttribute::from_attributes(&method.attrs)?; - let name = self.rename_methods.rename(method.sig.ident.to_string()); + let name = method.sig.ident.rename_method(&self.rename_methods); let name = attr.rename.rename(name); let docs = get_docs(&attr.attrs)?; method.attrs.retain(|attr| !attr.path().is_ident("php")); @@ -318,43 +318,3 @@ impl quote::ToTokens for FnBuilder { .to_tokens(tokens); } } - -#[cfg(test)] -mod tests { - use super::RenameRule; - - #[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)); - } - } - - #[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)); - } -} diff --git a/crates/macros/src/parsing.rs b/crates/macros/src/parsing.rs index d72cdbdd9e..25a51fb1e4 100644 --- a/crates/macros/src/parsing.rs +++ b/crates/macros/src/parsing.rs @@ -1,5 +1,25 @@ use darling::FromMeta; +const MAGIC_METHOD: [&str; 17] = [ + "__construct", + "__destruct", + "__call", + "__call_static", + "__get", + "__set", + "__isset", + "__unset", + "__sleep", + "__wakeup", + "__serialize", + "__unserialize", + "__to_string", + "__invoke", + "__set_state", + "__clone", + "__debug_info", +]; + #[derive(Debug, FromMeta)] pub enum Visibility { #[darling(rename = "public")] @@ -10,6 +30,14 @@ pub enum Visibility { Protected, } +pub trait Rename { + fn rename(&self, rule: &RenameRule) -> String; +} + +pub trait MethodRename: Rename { + fn rename_method(&self, rule: &RenameRule) -> String; +} + #[derive(FromMeta, Debug, Default)] #[darling(default)] pub struct PhpRename { @@ -24,7 +52,7 @@ impl PhpRename { let name = name.as_ref(); self.rename .as_ref() - .map_or_else(|| name.to_string(), |r| r.rename(name)) + .map_or_else(|| name.to_string(), |r| name.rename(r)) }, ToString::to_string, ) @@ -52,52 +80,58 @@ pub enum RenameRule { } 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::Pascal => ident_case::RenameRule::PascalCase.apply_to_field(field), - Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(field), - Self::ScreamingSnakeCase => { - ident_case::RenameRule::ScreamingSnakeCase.apply_to_field(field) - } - Self::None => unreachable!(), - }, - }, + fn rename(&self, value: impl AsRef) -> String { + match *self { + Self::None => value.as_ref().to_string(), + Self::Camel => ident_case::RenameRule::CamelCase.apply_to_field(value.as_ref()), + Self::Pascal => ident_case::RenameRule::PascalCase.apply_to_field(value.as_ref()), + Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(value.as_ref()), + Self::ScreamingSnakeCase => { + ident_case::RenameRule::ScreamingSnakeCase.apply_to_field(value.as_ref()) + } + } + } +} + +impl Rename for &str { + fn rename(&self, rule: &RenameRule) -> String { + rule.rename(self) + } +} + +impl Rename for syn::Ident { + fn rename(&self, rule: &RenameRule) -> String { + let s = self.to_string(); + rule.rename(s) + } +} + +impl MethodRename for syn::Ident { + fn rename_method(&self, rule: &RenameRule) -> String { + self.to_string().as_str().rename_method(rule) + } +} + +impl MethodRename for &str { + fn rename_method(&self, rule: &RenameRule) -> String { + let s = self.to_string(); + + if MAGIC_METHOD.contains(&s.as_str()) { + s + } else { + self.rename(rule) } } } #[cfg(test)] mod tests { - use super::{PhpRename, RenameRule}; + use crate::parsing::{MethodRename, Rename}; + + use super::{PhpRename, RenameRule, MAGIC_METHOD}; #[test] - fn test_php_rename() { + fn php_rename() { let rename = PhpRename { name: Some("test".to_string()), rename: None, @@ -132,45 +166,41 @@ mod tests { } #[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::Pascal.rename(magic)); - assert_eq!(expected, RenameRule::Snake.rename(magic)); - assert_eq!(expected, RenameRule::ScreamingSnakeCase.rename(magic)); + fn rename_magic_method() { + 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::Pascal)); + assert_eq!(magic, magic.rename_method(&RenameRule::Snake)); + assert_eq!(magic, magic.rename_method(&RenameRule::ScreamingSnakeCase)); } } #[test] - fn test_rename_php_methods() { + fn rename_method() { + let &(original, camel, snake, pascal, screaming_snake) = + &("get_name", "getName", "get_name", "GetName", "GET_NAME"); + assert_eq!(original, original.rename_method(&RenameRule::None)); + assert_eq!(camel, original.rename_method(&RenameRule::Camel)); + assert_eq!(pascal, original.rename_method(&RenameRule::Pascal)); + assert_eq!(snake, original.rename_method(&RenameRule::Snake)); + assert_eq!( + screaming_snake, + original.rename_method(&RenameRule::ScreamingSnakeCase) + ); + } + + #[test] + fn rename() { let &(original, camel, snake, pascal, screaming_snake) = &("get_name", "getName", "get_name", "GetName", "GET_NAME"); - assert_eq!(original, RenameRule::None.rename(original)); - assert_eq!(camel, RenameRule::Camel.rename(original)); - assert_eq!(pascal, RenameRule::Pascal.rename(original)); - assert_eq!(snake, RenameRule::Snake.rename(original)); + assert_eq!(original, original.rename(&RenameRule::None)); + assert_eq!(camel, original.rename(&RenameRule::Camel)); + assert_eq!(pascal, original.rename(&RenameRule::Pascal)); + assert_eq!(snake, original.rename(&RenameRule::Snake)); assert_eq!( screaming_snake, - RenameRule::ScreamingSnakeCase.rename(original) + original.rename(&RenameRule::ScreamingSnakeCase) ); } } From 65feb24a7644e9d084576e95e85e93f5746de80a Mon Sep 17 00:00:00 2001 From: Xenira <1288524+Xenira@users.noreply.github.com> Date: Tue, 15 Apr 2025 20:14:48 +0200 Subject: [PATCH 2/6] test(rename): add explicit tests for rename Refs: #420 --- crates/macros/src/parsing.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/crates/macros/src/parsing.rs b/crates/macros/src/parsing.rs index 25a51fb1e4..c5a590feae 100644 --- a/crates/macros/src/parsing.rs +++ b/crates/macros/src/parsing.rs @@ -167,12 +167,33 @@ mod tests { #[test] fn rename_magic_method() { - for magic in MAGIC_METHOD { + 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, magic.rename_method(&RenameRule::None)); - assert_eq!(magic, magic.rename_method(&RenameRule::Camel)); - assert_eq!(magic, magic.rename_method(&RenameRule::Pascal)); - assert_eq!(magic, magic.rename_method(&RenameRule::Snake)); - assert_eq!(magic, magic.rename_method(&RenameRule::ScreamingSnakeCase)); + assert_eq!(expected, magic.rename_method(&RenameRule::Camel)); + assert_eq!(expected, magic.rename_method(&RenameRule::Pascal)); + assert_eq!(expected, magic.rename_method(&RenameRule::Snake)); + assert_eq!( + expected, + magic.rename_method(&RenameRule::ScreamingSnakeCase) + ); } } From 1f73d47ecb53ec261828efbc3f28689d6cf2cd30 Mon Sep 17 00:00:00 2001 From: norbytus Date: Wed, 16 Apr 2025 22:18:58 +0300 Subject: [PATCH 3/6] fix(macro): Fix rename some magic methods --- crates/macros/src/parsing.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/macros/src/parsing.rs b/crates/macros/src/parsing.rs index c5a590feae..bb84e74e43 100644 --- a/crates/macros/src/parsing.rs +++ b/crates/macros/src/parsing.rs @@ -114,12 +114,23 @@ impl MethodRename for syn::Ident { impl MethodRename for &str { fn rename_method(&self, rule: &RenameRule) -> String { - let s = self.to_string(); - - if MAGIC_METHOD.contains(&s.as_str()) { - s - } else { - self.rename(rule) + match rule { + RenameRule::None => self.to_string(), + _ => { + if MAGIC_METHOD.contains(self) { + if self == &MAGIC_METHOD[12] { + "__toString".to_string() + } else if self == &MAGIC_METHOD[16] { + "__debugInfo".to_string() + } else if self == &MAGIC_METHOD[3] { + "__callStatic".to_string() + } else { + self.to_string() + } + } else { + self.rename(rule) + } + } } } } From 53531536d256aeeee1ed79f8a507555038a57b8d Mon Sep 17 00:00:00 2001 From: norbytus Date: Wed, 16 Apr 2025 22:20:02 +0300 Subject: [PATCH 4/6] test(macro): Add test for magic method call --- tests/src/integration/magic_method.php | 37 +++++++++++ tests/src/integration/magic_method.rs | 4 ++ tests/src/lib.rs | 91 ++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 tests/src/integration/magic_method.php create mode 100644 tests/src/integration/magic_method.rs diff --git a/tests/src/integration/magic_method.php b/tests/src/integration/magic_method.php new file mode 100644 index 0000000000..c0b31e393e --- /dev/null +++ b/tests/src/integration/magic_method.php @@ -0,0 +1,37 @@ +count = 10; +// __get +assert(10 === $magicMethod->count); +assert(null === $magicMethod->test); + +//__isset +assert(true === isset($magicMethod->count)); +assert(false === isset($magicMethod->noCount)); + +// __unset +unset($magicMethod->count); +assert(0 === $magicMethod->count); + +// __toString +assert("0" === $magicMethod->__toString()); +assert("0" === (string) $magicMethod); + +// __invoke +assert(34 === $magicMethod(34)); + +// __debugInfo +$debug = print_r($magicMethod, true); +$expectedDebug = "MagicMethod Object\n(\n [count] => 0\n)\n"; +assert($expectedDebug === $debug); + +// __call +assert("Hello" === $magicMethod->callMagicMethod(1, 2, 3)); +assert(null === $magicMethod->callUndefinedMagicMethod()); + +// __call_static +assert("Hello from static call 1 2 3" === MagicMethod::callStaticSomeMagic(1, 2, 3)); +assert(null === MagicMethod::callUndefinedStaticSomeMagic()); diff --git a/tests/src/integration/magic_method.rs b/tests/src/integration/magic_method.rs new file mode 100644 index 0000000000..145a8cf5e8 --- /dev/null +++ b/tests/src/integration/magic_method.rs @@ -0,0 +1,4 @@ +#[test] +fn magic_method() { + assert!(crate::integration::run_php("magic_method.php")); +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 78ce9ae6b7..5bda19bb54 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -225,10 +225,100 @@ pub fn test_class(string: String, number: i32) -> TestClass { } } +#[php_class] +pub struct MagicMethod(i64); + +#[php_impl] +impl MagicMethod { + pub fn __construct() -> Self { + Self(0) + } + + pub fn __destruct(&self) {} + + pub fn __call(&self, name: String, _arguments: HashMap) -> Zval { + let mut z = Zval::new(); + if name == "callMagicMethod" { + let s = "Hello".to_string(); + + let _ = z.set_string(s.as_str(), false); + z + } else { + z.set_null(); + z + } + } + + pub fn __call_static(name: String, arguments: HashMap) -> Zval { + let mut zval = Zval::new(); + if name == "callStaticSomeMagic" { + let concat_args = format!( + "Hello from static call {}", + arguments + .iter() + .filter(|(_, v)| v.is_long()) + .map(|(_, s)| s.long().unwrap().to_string()) + .collect::>() + .join(" ") + ); + + let _ = zval.set_string(&concat_args, false); + zval + } else { + zval.set_null(); + zval + } + } + + pub fn __get(&self, name: String) -> Zval { + let mut v = Zval::new(); + v.set_null(); + if name == "count" { + v.set_long(self.0); + } + + v + } + + pub fn __set(&mut self, prop_name: String, val: &Zval) { + if val.is_long() && prop_name == "count" { + self.0 = val.long().unwrap() + } + } + + pub fn __isset(&self, prop_name: String) -> bool { + "count" == prop_name + } + + pub fn __unset(&mut self, prop_name: String) { + if prop_name == "count" { + self.0 = 0; + } + } + + pub fn __to_string(&self) -> String { + self.0.to_string() + } + + pub fn __invoke(&self, n: i64) -> i64 { + self.0 + n + } + + pub fn __debug_info(&self) -> HashMap { + let mut h: HashMap = HashMap::new(); + let mut z = Zval::new(); + z.set_long(self.0); + h.insert("count".to_string(), z); + + h + } +} + #[php_module] pub fn build_module(module: ModuleBuilder) -> ModuleBuilder { module .class::() + .class::() .function(wrap_function!(test_str)) .function(wrap_function!(test_string)) .function(wrap_function!(test_bool)) @@ -321,6 +411,7 @@ mod integration { mod closure; mod globals; mod iterator; + mod magic_method; mod nullable; mod number; mod object; From 18225dcc6f51ce1dad8f3f3e8151bfd8185e18f8 Mon Sep 17 00:00:00 2001 From: norbytus Date: Wed, 16 Apr 2025 22:59:35 +0300 Subject: [PATCH 5/6] refactor(macro): Change if condition --- crates/macros/src/parsing.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/macros/src/parsing.rs b/crates/macros/src/parsing.rs index bb84e74e43..6b8bdfd881 100644 --- a/crates/macros/src/parsing.rs +++ b/crates/macros/src/parsing.rs @@ -118,14 +118,11 @@ impl MethodRename for &str { RenameRule::None => self.to_string(), _ => { if MAGIC_METHOD.contains(self) { - if self == &MAGIC_METHOD[12] { - "__toString".to_string() - } else if self == &MAGIC_METHOD[16] { - "__debugInfo".to_string() - } else if self == &MAGIC_METHOD[3] { - "__callStatic".to_string() - } else { - self.to_string() + match *self { + "__to_string" => "__toString".to_string(), + "__debug_info" => "__debugInfo".to_string(), + "__call_static" => "__callStatic".to_string(), + _ => self.to_string(), } } else { self.rename(rule) From a9dbedf1007cf0a0657c4a3b77e00b3ba42d6e01 Mon Sep 17 00:00:00 2001 From: norbytus Date: Wed, 16 Apr 2025 22:59:55 +0300 Subject: [PATCH 6/6] test(macro): Fix tests --- tests/src/integration/magic_method.php | 2 +- tests/src/lib.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/src/integration/magic_method.php b/tests/src/integration/magic_method.php index c0b31e393e..069022249a 100644 --- a/tests/src/integration/magic_method.php +++ b/tests/src/integration/magic_method.php @@ -33,5 +33,5 @@ assert(null === $magicMethod->callUndefinedMagicMethod()); // __call_static -assert("Hello from static call 1 2 3" === MagicMethod::callStaticSomeMagic(1, 2, 3)); +assert("Hello from static call 6" === MagicMethod::callStaticSomeMagic(1, 2, 3)); assert(null === MagicMethod::callUndefinedStaticSomeMagic()); diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 5bda19bb54..ea783476f1 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -257,9 +257,10 @@ impl MagicMethod { arguments .iter() .filter(|(_, v)| v.is_long()) - .map(|(_, s)| s.long().unwrap().to_string()) + .map(|(_, s)| s.long().unwrap()) .collect::>() - .join(" ") + .iter() + .sum::() ); let _ = zval.set_string(&concat_args, false);