@@ -2,9 +2,63 @@ use darling::util::Flag;
22use darling:: { FromAttributes , FromMeta , ToTokens } ;
33use proc_macro2:: TokenStream ;
44use quote:: { TokenStreamExt , quote} ;
5- use syn:: { Attribute , Expr , Fields , ItemStruct } ;
5+ use syn:: { Attribute , Expr , Fields , GenericArgument , ItemStruct , PathArguments , Type } ;
66
77use crate :: helpers:: get_docs;
8+
9+ /// Check if a type is `Option<T>` and return the inner type if so.
10+ fn is_option_type ( ty : & Type ) -> Option < & Type > {
11+ let Type :: Path ( type_path) = ty else {
12+ return None ;
13+ } ;
14+ if type_path. qself . is_some ( ) {
15+ return None ;
16+ }
17+ let segments = & type_path. path . segments ;
18+ if segments. len ( ) != 1 {
19+ return None ;
20+ }
21+ let segment = & segments[ 0 ] ;
22+ if segment. ident != "Option" {
23+ return None ;
24+ }
25+ let PathArguments :: AngleBracketed ( args) = & segment. arguments else {
26+ return None ;
27+ } ;
28+ if args. args . len ( ) != 1 {
29+ return None ;
30+ }
31+ if let GenericArgument :: Type ( inner) = & args. args [ 0 ] {
32+ return Some ( inner) ;
33+ }
34+ None
35+ }
36+
37+ /// Convert an expression to a PHP-compatible default string for stub generation.
38+ fn expr_to_php_default_string ( expr : & Expr ) -> String {
39+ // For simple literals, we can convert them directly
40+ // For complex expressions, we use a string representation
41+ match expr {
42+ Expr :: Lit ( lit) => match & lit. lit {
43+ syn:: Lit :: Str ( s) => format ! ( "'{}'" , s. value( ) . replace( '\'' , "\\ '" ) ) ,
44+ syn:: Lit :: Int ( i) => i. to_string ( ) ,
45+ syn:: Lit :: Float ( f) => f. to_string ( ) ,
46+ syn:: Lit :: Bool ( b) => if b. value { "true" } else { "false" } . to_string ( ) ,
47+ _ => expr. to_token_stream ( ) . to_string ( ) ,
48+ } ,
49+ Expr :: Array ( _) => "[]" . to_string ( ) ,
50+ Expr :: Path ( path) => {
51+ // Handle constants like `None`, `true`, `false`
52+ let path_str = path. to_token_stream ( ) . to_string ( ) ;
53+ if path_str == "None" {
54+ "null" . to_string ( )
55+ } else {
56+ path_str
57+ }
58+ }
59+ _ => expr. to_token_stream ( ) . to_string ( ) ,
60+ }
61+ }
862use crate :: parsing:: { PhpNameContext , PhpRename , RenameRule , ident_to_php_name, validate_php_name} ;
963use crate :: prelude:: * ;
1064
@@ -192,6 +246,7 @@ fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<
192246
193247 result. push ( Property {
194248 ident,
249+ ty : & field. ty ,
195250 name,
196251 attr,
197252 docs,
@@ -205,6 +260,7 @@ fn parse_fields<'a>(fields: impl Iterator<Item = &'a mut syn::Field>) -> Result<
205260#[ derive( Debug ) ]
206261struct Property < ' a > {
207262 pub ident : & ' a syn:: Ident ,
263+ pub ty : & ' a syn:: Type ,
208264 pub name : String ,
209265 pub attr : PropAttributes ,
210266 pub docs : Vec < String > ,
@@ -240,6 +296,7 @@ fn generate_registered_class_impl(
240296 let instance_fields = instance_props. iter ( ) . map ( |prop| {
241297 let name = & prop. name ;
242298 let field_ident = prop. ident ;
299+ let field_ty = prop. ty ;
243300 let flags = prop
244301 . attr
245302 . flags
@@ -248,11 +305,25 @@ fn generate_registered_class_impl(
248305 . unwrap_or ( quote ! { :: ext_php_rs:: flags:: PropertyFlags :: Public } ) ;
249306 let docs = & prop. docs ;
250307
308+ // Determine if the property is nullable (type is Option<T>)
309+ let nullable = is_option_type ( field_ty) . is_some ( ) ;
310+
311+ // Get the default value as a PHP-compatible string for stub generation
312+ let default_str = if let Some ( default_expr) = & prop. attr . default {
313+ let s = expr_to_php_default_string ( default_expr) ;
314+ quote ! { :: std:: option:: Option :: Some ( #s) }
315+ } else {
316+ quote ! { :: std:: option:: Option :: None }
317+ } ;
318+
251319 quote ! {
252320 ( #name, :: ext_php_rs:: internal:: property:: PropertyInfo {
253321 prop: :: ext_php_rs:: props:: Property :: field( |this: & mut Self | & mut this. #field_ident) ,
254322 flags: #flags,
255- docs: & [ #( #docs, ) * ]
323+ docs: & [ #( #docs, ) * ] ,
324+ ty: :: std:: option:: Option :: Some ( <#field_ty as :: ext_php_rs:: convert:: IntoZval >:: TYPE ) ,
325+ nullable: #nullable,
326+ default : #default_str,
256327 } )
257328 }
258329 } ) ;
@@ -262,6 +333,7 @@ fn generate_registered_class_impl(
262333 // const
263334 let static_fields = static_props. iter ( ) . map ( |prop| {
264335 let name = & prop. name ;
336+ let field_ty = prop. ty ;
265337 let base_flags = prop
266338 . attr
267339 . flags
@@ -277,11 +349,23 @@ fn generate_registered_class_impl(
277349 quote ! { :: std:: option:: Option :: None }
278350 } ;
279351
352+ // Determine if the property is nullable (type is Option<T>)
353+ let nullable = is_option_type ( field_ty) . is_some ( ) ;
354+
355+ // Get the default value as a PHP-compatible string for stub generation
356+ let default_str = if let Some ( default_expr) = & prop. attr . default {
357+ let s = expr_to_php_default_string ( default_expr) ;
358+ quote ! { :: std:: option:: Option :: Some ( #s) }
359+ } else {
360+ quote ! { :: std:: option:: Option :: None }
361+ } ;
362+
280363 // Use from_bits_retain to combine flags in a const context
364+ // Tuple: (name, flags, default_value, docs, type, nullable, default_str)
281365 quote ! {
282366 ( #name, :: ext_php_rs:: flags:: PropertyFlags :: from_bits_retain(
283367 ( #base_flags) . bits( ) | :: ext_php_rs:: flags:: PropertyFlags :: Static . bits( )
284- ) , #default_value, & [ #( #docs, ) * ] as & [ & str ] )
368+ ) , #default_value, & [ #( #docs, ) * ] as & [ & str ] , :: std :: option :: Option :: Some ( <#field_ty as :: ext_php_rs :: convert :: IntoZval > :: TYPE ) , #nullable , #default_str )
285369 }
286370 } ) ;
287371
@@ -391,8 +475,9 @@ fn generate_registered_class_impl(
391475 }
392476
393477 #[ must_use]
394- fn static_properties( ) -> & ' static [ ( & ' static str , :: ext_php_rs:: flags:: PropertyFlags , :: std:: option:: Option <& ' static ( dyn :: ext_php_rs:: convert:: IntoZvalDyn + Sync ) >, & ' static [ & ' static str ] ) ] {
395- static STATIC_PROPS : & [ ( & str , :: ext_php_rs:: flags:: PropertyFlags , :: std:: option:: Option <& ' static ( dyn :: ext_php_rs:: convert:: IntoZvalDyn + Sync ) >, & [ & str ] ) ] = & [ #( #static_fields, ) * ] ;
478+ #[ allow( clippy:: type_complexity) ]
479+ fn static_properties( ) -> & ' static [ ( & ' static str , :: ext_php_rs:: flags:: PropertyFlags , :: std:: option:: Option <& ' static ( dyn :: ext_php_rs:: convert:: IntoZvalDyn + Sync ) >, & ' static [ & ' static str ] , :: std:: option:: Option <:: ext_php_rs:: flags:: DataType >, bool , :: std:: option:: Option <& ' static str >) ] {
480+ static STATIC_PROPS : & [ ( & str , :: ext_php_rs:: flags:: PropertyFlags , :: std:: option:: Option <& ' static ( dyn :: ext_php_rs:: convert:: IntoZvalDyn + Sync ) >, & [ & str ] , :: std:: option:: Option <:: ext_php_rs:: flags:: DataType >, bool , :: std:: option:: Option <& ' static str >) ] = & [ #( #static_fields, ) * ] ;
396481 STATIC_PROPS
397482 }
398483
0 commit comments