@@ -253,6 +253,85 @@ pub trait IntoZvalDyn {
253253
254254 /// Returns the PHP type of the type.
255255 fn get_type ( & self ) -> DataType ;
256+
257+ /// Returns the PHP stub representation of this value.
258+ ///
259+ /// This is used when generating PHP stub files for IDE autocompletion.
260+ /// The returned string should be a valid PHP literal.
261+ fn stub_value ( & self ) -> String {
262+ // Default implementation - convert to zval and format
263+ match self . as_zval ( false ) {
264+ Ok ( zval) => zval_to_stub ( & zval) ,
265+ Err ( _) => "null" . to_string ( ) ,
266+ }
267+ }
268+ }
269+
270+ /// Converts a Zval to its PHP stub representation.
271+ #[ must_use]
272+ #[ allow( clippy:: match_same_arms) ]
273+ pub fn zval_to_stub ( zval : & Zval ) -> String {
274+ use crate :: flags:: DataType ;
275+
276+ match zval. get_type ( ) {
277+ DataType :: Null | DataType :: Undef => "null" . to_string ( ) ,
278+ DataType :: True => "true" . to_string ( ) ,
279+ DataType :: False => "false" . to_string ( ) ,
280+ DataType :: Long => zval. long ( ) . map_or_else ( || "null" . to_string ( ) , |v| v. to_string ( ) ) ,
281+ DataType :: Double => zval. double ( ) . map_or_else ( || "null" . to_string ( ) , |v| v. to_string ( ) ) ,
282+ DataType :: String => {
283+ if let Some ( s) = zval. str ( ) {
284+ let escaped = s
285+ . replace ( '\\' , "\\ \\ " )
286+ . replace ( '\'' , "\\ '" )
287+ . replace ( '\n' , "\\ n" )
288+ . replace ( '\r' , "\\ r" )
289+ . replace ( '\t' , "\\ t" ) ;
290+ format ! ( "'{escaped}'" )
291+ } else {
292+ "null" . to_string ( )
293+ }
294+ }
295+ DataType :: Array => {
296+ #[ allow( clippy:: explicit_iter_loop) ]
297+ if let Some ( arr) = zval. array ( ) {
298+ // Check if array has sequential numeric keys starting from 0
299+ let is_sequential = arr. iter ( ) . enumerate ( ) . all ( |( i, ( key, _) ) | {
300+ matches ! ( key, crate :: types:: ArrayKey :: Long ( idx) if idx == i as i64 )
301+ } ) ;
302+
303+ let mut parts = Vec :: new ( ) ;
304+ for ( key, val) in arr. iter ( ) {
305+ let val_str = zval_to_stub ( val) ;
306+ if is_sequential {
307+ parts. push ( val_str) ;
308+ } else {
309+ match key {
310+ crate :: types:: ArrayKey :: Long ( idx) => {
311+ parts. push ( format ! ( "{idx} => {val_str}" ) ) ;
312+ }
313+ crate :: types:: ArrayKey :: String ( key) => {
314+ let key_escaped = key
315+ . replace ( '\\' , "\\ \\ " )
316+ . replace ( '\'' , "\\ '" ) ;
317+ parts. push ( format ! ( "'{key_escaped}' => {val_str}" ) ) ;
318+ }
319+ crate :: types:: ArrayKey :: Str ( key) => {
320+ let key_escaped = key
321+ . replace ( '\\' , "\\ \\ " )
322+ . replace ( '\'' , "\\ '" ) ;
323+ parts. push ( format ! ( "'{key_escaped}' => {val_str}" ) ) ;
324+ }
325+ }
326+ }
327+ }
328+ format ! ( "[{}]" , parts. join( ", " ) )
329+ } else {
330+ "[]" . to_string ( )
331+ }
332+ }
333+ _ => "null" . to_string ( ) ,
334+ }
256335}
257336
258337impl < T : IntoZval + Clone > IntoZvalDyn for T {
0 commit comments