@@ -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
281+ . long ( )
282+ . map_or_else ( || "null" . to_string ( ) , |v| v. to_string ( ) ) ,
283+ DataType :: Double => zval
284+ . double ( )
285+ . map_or_else ( || "null" . to_string ( ) , |v| v. to_string ( ) ) ,
286+ DataType :: String => {
287+ if let Some ( s) = zval. str ( ) {
288+ let escaped = s
289+ . replace ( '\\' , "\\ \\ " )
290+ . replace ( '\'' , "\\ '" )
291+ . replace ( '\n' , "\\ n" )
292+ . replace ( '\r' , "\\ r" )
293+ . replace ( '\t' , "\\ t" ) ;
294+ format ! ( "'{escaped}'" )
295+ } else {
296+ "null" . to_string ( )
297+ }
298+ }
299+ DataType :: Array => {
300+ #[ allow( clippy:: explicit_iter_loop) ]
301+ if let Some ( arr) = zval. array ( ) {
302+ // Check if array has sequential numeric keys starting from 0
303+ let is_sequential = arr. iter ( ) . enumerate ( ) . all ( |( i, ( key, _) ) | {
304+ matches ! ( key, crate :: types:: ArrayKey :: Long ( idx) if i64 :: try_from( i) . is_ok_and( |ii| idx == ii) )
305+ } ) ;
306+
307+ let mut parts = Vec :: new ( ) ;
308+ for ( key, val) in arr. iter ( ) {
309+ let val_str = zval_to_stub ( val) ;
310+ if is_sequential {
311+ parts. push ( val_str) ;
312+ } else {
313+ match key {
314+ crate :: types:: ArrayKey :: Long ( idx) => {
315+ parts. push ( format ! ( "{idx} => {val_str}" ) ) ;
316+ }
317+ crate :: types:: ArrayKey :: String ( key) => {
318+ let key_escaped = key. replace ( '\\' , "\\ \\ " ) . replace ( '\'' , "\\ '" ) ;
319+ parts. push ( format ! ( "'{key_escaped}' => {val_str}" ) ) ;
320+ }
321+ crate :: types:: ArrayKey :: Str ( key) => {
322+ let key_escaped = key. replace ( '\\' , "\\ \\ " ) . 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