This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 5807f72fd feat(compiler): update toString/__repr__/std::fmt::Debug for 
generated ref code (#3264)
5807f72fd is described below

commit 5807f72fd6e0f1fe7be64902c2f27e095b8b55fa
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Feb 5 04:44:20 2026 +0800

    feat(compiler): update toString/__repr__/std::fmt::Debug for generated ref 
code (#3264)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    update `toString/__repr__/std::fmt::Debug` for generated ref code to
    avoid circular reference recursion issue
    
    ## Related issues
    
    #3099
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 compiler/fory_compiler/generators/base.py   | 24 +++++++++-
 compiler/fory_compiler/generators/java.py   | 72 +++++++++++++++++++++++++++++
 compiler/fory_compiler/generators/python.py | 61 +++++++++++++++++++++++-
 compiler/fory_compiler/generators/rust.py   | 70 +++++++++++++++++++++++++++-
 4 files changed, 224 insertions(+), 3 deletions(-)

diff --git a/compiler/fory_compiler/generators/base.py 
b/compiler/fory_compiler/generators/base.py
index 2e5b4ed4d..69cdaee48 100644
--- a/compiler/fory_compiler/generators/base.py
+++ b/compiler/fory_compiler/generators/base.py
@@ -22,7 +22,14 @@ from dataclasses import dataclass
 from pathlib import Path
 from typing import List, Optional
 
-from fory_compiler.ir.ast import Schema, FieldType
+from fory_compiler.ir.ast import (
+    Schema,
+    FieldType,
+    PrimitiveType,
+    NamedType,
+    ListType,
+    MapType,
+)
 
 
 @dataclass
@@ -207,6 +214,21 @@ class BaseGenerator(ABC):
             return True
         return bool(file_default)
 
+    def format_idl_type(self, field_type: FieldType) -> str:
+        """Return an IDL-style type name for display purposes."""
+        if isinstance(field_type, PrimitiveType):
+            return field_type.kind.value
+        if isinstance(field_type, NamedType):
+            return field_type.name
+        if isinstance(field_type, ListType):
+            element = self.format_idl_type(field_type.element_type)
+            return f"list<{element}>"
+        if isinstance(field_type, MapType):
+            key = self.format_idl_type(field_type.key_type)
+            value = self.format_idl_type(field_type.value_type)
+            return f"map<{key}, {value}>"
+        return "object"
+
     def get_license_header(self, comment_prefix: str = "//") -> str:
         """Get the Apache license header."""
         lines = [
diff --git a/compiler/fory_compiler/generators/java.py 
b/compiler/fory_compiler/generators/java.py
index 8f0e7bdc3..a4a576c62 100644
--- a/compiler/fory_compiler/generators/java.py
+++ b/compiler/fory_compiler/generators/java.py
@@ -491,6 +491,10 @@ class JavaGenerator(BaseGenerator):
         for line in self.generate_bytes_methods(message.name):
             lines.append(f"    {line}")
 
+        # toString method
+        for line in self.generate_tostring_method(message):
+            lines.append(f"    {line}")
+
         # equals method
         for line in self.generate_equals_method(message):
             lines.append(f"    {line}")
@@ -952,6 +956,10 @@ class JavaGenerator(BaseGenerator):
             "char",
         }
 
+    def java_string_literal(self, value: str) -> str:
+        escaped = value.replace("\\", "\\\\").replace('"', '\\"')
+        return f'"{escaped}"'
+
     def generate_nested_message(
         self,
         message: Message,
@@ -1017,6 +1025,10 @@ class JavaGenerator(BaseGenerator):
         for line in self.generate_bytes_methods(message.name):
             lines.append(f"    {line}")
 
+        # toString method
+        for line in self.generate_tostring_method(message):
+            lines.append(f"    {line}")
+
         # equals method
         for line in self.generate_equals_method(message):
             lines.append(f"    {line}")
@@ -1365,6 +1377,66 @@ class JavaGenerator(BaseGenerator):
                 )
         return False
 
+    def field_type_has_any(self, field_type: FieldType) -> bool:
+        """Return True if field type or its children is any."""
+        if isinstance(field_type, PrimitiveType):
+            return field_type.kind == PrimitiveKind.ANY
+        if isinstance(field_type, ListType):
+            return self.field_type_has_any(field_type.element_type)
+        if isinstance(field_type, MapType):
+            return self.field_type_has_any(
+                field_type.key_type
+            ) or self.field_type_has_any(field_type.value_type)
+        return False
+
+    def field_has_ref(self, field: Field) -> bool:
+        if field.ref:
+            return True
+        if isinstance(field.field_type, ListType):
+            return field.element_ref
+        if isinstance(field.field_type, MapType):
+            return field.field_type.value_ref
+        return False
+
+    def field_needs_safe_repr(self, field: Field) -> bool:
+        return self.field_has_ref(field) or 
self.field_type_has_any(field.field_type)
+
+    def message_needs_safe_repr(self, message: Message) -> bool:
+        return any(self.field_needs_safe_repr(field) for field in 
message.fields)
+
+    def generate_tostring_method(self, message: Message) -> List[str]:
+        """Generate toString() method for a message."""
+        lines: List[str] = []
+        lines.append("@Override")
+        lines.append("public String toString() {")
+        if not message.fields:
+            lines.append(f'    return "{message.name}()";')
+            lines.append("}")
+            lines.append("")
+            return lines
+        lines.append("    StringBuilder sb = new StringBuilder();")
+        lines.append(f'    sb.append("{message.name}(");')
+        for i, field in enumerate(message.fields):
+            field_name = self.to_camel_case(field.name)
+            if i > 0:
+                lines.append('    sb.append(", ");')
+            lines.append(f'    sb.append("{field_name}=");')
+            if self.field_needs_safe_repr(field):
+                placeholder = f"{self.format_idl_type(field.field_type)}(...)"
+                placeholder_literal = self.java_string_literal(placeholder)
+                lines.append(
+                    f'    sb.append({field_name} == null ? "null" : 
{placeholder_literal});'
+                )
+            elif self.is_primitive_array_field(field):
+                lines.append(f"    sb.append(Arrays.toString({field_name}));")
+            else:
+                lines.append(f"    sb.append({field_name});")
+        lines.append('    sb.append(")");')
+        lines.append("    return sb.toString();")
+        lines.append("}")
+        lines.append("")
+        return lines
+
     def generate_equals_method(self, message: Message) -> List[str]:
         """Generate equals() method for a message."""
         lines = []
diff --git a/compiler/fory_compiler/generators/python.py 
b/compiler/fory_compiler/generators/python.py
index 83bb1e146..b4c049ff8 100644
--- a/compiler/fory_compiler/generators/python.py
+++ b/compiler/fory_compiler/generators/python.py
@@ -370,8 +370,14 @@ class PythonGenerator(BaseGenerator):
         comment = self.format_type_id_comment(message, f"{ind}#")
         if comment:
             lines.append(comment)
+        needs_repr = self.message_needs_safe_repr(message)
+        decorator_args: List[str] = []
         if not self.get_effective_evolving(message):
-            lines.append(f"{ind}@pyfory.dataclass(evolving=False)")
+            decorator_args.append("evolving=False")
+        if needs_repr:
+            decorator_args.append("repr=False")
+        if decorator_args:
+            lines.append(f"{ind}@pyfory.dataclass({', 
'.join(decorator_args)})")
         else:
             lines.append(f"{ind}@pyfory.dataclass")
         lines.append(f"{ind}class {message.name}:")
@@ -414,6 +420,10 @@ class PythonGenerator(BaseGenerator):
         ):
             lines.append("")
 
+        if needs_repr:
+            lines.extend(self.generate_repr_method(message, indent=indent))
+            lines.append("")
+
         return_type = ".".join([msg.name for msg in lineage])
         lines.extend(self.generate_bytes_methods(return_type, indent))
 
@@ -638,6 +648,55 @@ class PythonGenerator(BaseGenerator):
             and not element_optional
         )
 
+    def field_type_has_any(self, field_type: FieldType) -> bool:
+        """Return True if field type or its children is any."""
+        if isinstance(field_type, PrimitiveType):
+            return field_type.kind == PrimitiveKind.ANY
+        if isinstance(field_type, ListType):
+            return self.field_type_has_any(field_type.element_type)
+        if isinstance(field_type, MapType):
+            return self.field_type_has_any(
+                field_type.key_type
+            ) or self.field_type_has_any(field_type.value_type)
+        return False
+
+    def field_has_ref(self, field: Field) -> bool:
+        if field.ref:
+            return True
+        if isinstance(field.field_type, ListType):
+            return field.element_ref
+        if isinstance(field.field_type, MapType):
+            return field.field_type.value_ref
+        return False
+
+    def field_needs_safe_repr(self, field: Field) -> bool:
+        return self.field_has_ref(field) or 
self.field_type_has_any(field.field_type)
+
+    def message_needs_safe_repr(self, message: Message) -> bool:
+        return any(self.field_needs_safe_repr(field) for field in 
message.fields)
+
+    def generate_repr_method(self, message: Message, indent: int = 0) -> 
List[str]:
+        """Generate a repr method that avoids recursive ref expansion."""
+        lines: List[str] = []
+        ind = "    " * indent
+        lines.append(f"{ind}    def __repr__(self) -> str:")
+        if not message.fields:
+            lines.append(f'{ind}        return "{message.name}()"')
+            return lines
+        lines.append(f"{ind}        parts = []")
+        for field in message.fields:
+            field_name = self.safe_name(self.to_snake_case(field.name))
+            if self.field_needs_safe_repr(field):
+                type_label = self.format_idl_type(field.field_type)
+                placeholder = f"{type_label}(...)"
+                placeholder_literal = repr(placeholder)
+                expr = f"({placeholder_literal} if self.{field_name} is not 
None else 'None')"
+            else:
+                expr = f"repr(self.{field_name})"
+            lines.append(f'{ind}        parts.append("{field_name}=" + 
{expr})')
+        lines.append(f'{ind}        return "{message.name}(" + ", 
".join(parts) + ")"')
+        return lines
+
     def get_default_factory(self, field: Field) -> Optional[str]:
         """Get default factory name for list/map fields."""
         if field.optional:
diff --git a/compiler/fory_compiler/generators/rust.py 
b/compiler/fory_compiler/generators/rust.py
index 67cb6df1f..48fad1f9b 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -419,7 +419,10 @@ class RustGenerator(BaseGenerator):
         comment = self.format_type_id_comment(message, "//")
         if comment:
             lines.append(comment)
-        derives = ["ForyObject", "Debug"]
+        needs_safe_debug = self.message_needs_safe_debug(message)
+        derives = ["ForyObject"]
+        if not needs_safe_debug:
+            derives.append("Debug")
         if not self.message_has_any(message):
             derives.extend(["Clone", "PartialEq", "Default"])
         lines.append(f"#[derive({', '.join(derives)})]")
@@ -437,6 +440,9 @@ class RustGenerator(BaseGenerator):
 
         lines.append("}")
         lines.append("")
+        if needs_safe_debug:
+            lines.extend(self.generate_debug_impl(message))
+            lines.append("")
         lines.extend(self.generate_bytes_impl(type_name))
 
         return lines
@@ -459,6 +465,68 @@ class RustGenerator(BaseGenerator):
             ) or self.field_type_has_any(field_type.value_type)
         return False
 
+    def field_has_ref(self, field: Field) -> bool:
+        if field.ref:
+            return True
+        if isinstance(field.field_type, ListType):
+            return field.element_ref
+        if isinstance(field.field_type, MapType):
+            return field.field_type.value_ref
+        return False
+
+    def field_needs_safe_debug(self, field: Field) -> bool:
+        return self.field_has_ref(field) or 
self.field_type_has_any(field.field_type)
+
+    def message_needs_safe_debug(self, message: Message) -> bool:
+        return any(self.field_needs_safe_debug(field) for field in 
message.fields)
+
+    def rust_string_literal(self, value: str) -> str:
+        escaped = value.replace("\\", "\\\\").replace('"', '\\"')
+        return f'"{escaped}"'
+
+    def generate_debug_impl(self, message: Message) -> List[str]:
+        """Generate a Debug impl that avoids recursive ref expansion."""
+        lines: List[str] = []
+        type_name = message.name
+        lines.append(f"impl std::fmt::Debug for {type_name} {{")
+        lines.append(
+            "    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> 
std::fmt::Result {"
+        )
+        if not message.fields:
+            lines.append(
+                f"        f.write_str({self.rust_string_literal(type_name + ' 
{}')})"
+            )
+            lines.append("    }")
+            lines.append("}")
+            return lines
+        lines.append(
+            f"        f.write_str({self.rust_string_literal(type_name + ' { 
')})?;"
+        )
+        for i, field in enumerate(message.fields):
+            field_name = self.to_snake_case(field.name)
+            if i > 0:
+                lines.append('        f.write_str(", ")?;')
+            lines.append(
+                f"        f.write_str({self.rust_string_literal(field_name + 
': ')})?;"
+            )
+            if self.field_needs_safe_debug(field):
+                placeholder = f"{self.format_idl_type(field.field_type)}(...)"
+                placeholder_literal = self.rust_string_literal(placeholder)
+                if field.optional:
+                    lines.append(f"        if self.{field_name}.is_some() {{")
+                    lines.append(f"            
f.write_str({placeholder_literal})?;")
+                    lines.append("        } else {")
+                    lines.append('            f.write_str("None")?;')
+                    lines.append("        }")
+                else:
+                    lines.append(f"        
f.write_str({placeholder_literal})?;")
+            else:
+                lines.append(f'        write!(f, "{{:?}}", 
self.{field_name})?;')
+        lines.append('        f.write_str(" }")')
+        lines.append("    }")
+        lines.append("}")
+        return lines
+
     def generate_nested_module(
         self,
         message: Message,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to