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 7dbaabcea fix(rust): apply consistent pascalcase naming for nested 
types (#3548)
7dbaabcea is described below

commit 7dbaabcea0c0aa89ec54274b3f36b4205b9b03c6
Author: Uğur Tafralı <[email protected]>
AuthorDate: Sun Apr 12 14:02:47 2026 +0300

    fix(rust): apply consistent pascalcase naming for nested types (#3548)
    
    ## Why?
    Rust generator was creating naming conflicts for nested types where
    module paths and struct names could collide and cause compilation
    errors.
    
    ## What does this PR do?
    Applies consistent PascalCase naming to type names when resolving nested
    type references and building struct definitions. Added tests for nested
    type scenarios.
    
    ## Related issues
    Fixes #3547
    
    ## AI Contribution Checklist (if applicable)
    N/A
    
    ## Does this PR introduce any user-facing change?
    Yes, code that previously failed to compile due to naming conflicts will
    now generate valid Rust code.
    
    ## Benchmark (if perf-related)
    N/A
---
 compiler/fory_compiler/generators/rust.py         | 18 +++---
 compiler/fory_compiler/tests/test_nested_types.py | 76 +++++++++++++++++++++++
 2 files changed, 85 insertions(+), 9 deletions(-)

diff --git a/compiler/fory_compiler/generators/rust.py 
b/compiler/fory_compiler/generators/rust.py
index 48fad1f9b..75752face 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -172,9 +172,9 @@ class RustGenerator(BaseGenerator):
         if "." in type_name:
             parts = type_name.split(".")
             parents = [self.to_snake_case(name) for name in parts[:-1]]
-            path = "::".join(parents + [parts[-1]])
+            path = "::".join(parents + [self.to_pascal_case(parts[-1])])
             return f"crate::{module}::{path}"
-        return f"crate::{module}::{type_name}"
+        return f"crate::{module}::{self.to_pascal_case(type_name)}"
 
     def generate_bytes_impl(self, type_name: str) -> List[str]:
         lines = []
@@ -413,7 +413,7 @@ class RustGenerator(BaseGenerator):
         """Generate a Rust struct."""
         lines = []
 
-        type_name = message.name
+        type_name = self.to_pascal_case(message.name)
 
         # Derive macros
         comment = self.format_type_id_comment(message, "//")
@@ -487,7 +487,7 @@ class RustGenerator(BaseGenerator):
     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
+        type_name = self.to_pascal_case(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 {"
@@ -766,20 +766,20 @@ class RustGenerator(BaseGenerator):
             target_parents = parts[:-1]
             base_name = parts[-1]
             return self.build_relative_type_name(
-                current_parents, target_parents, base_name
+                current_parents, target_parents, self.to_pascal_case(base_name)
             )
         if not parent_stack:
-            return type_name
+            return self.to_pascal_case(type_name)
 
         for i in range(len(parent_stack) - 1, -1, -1):
             message = parent_stack[i]
             if message.get_nested_type(type_name) is not None:
                 target_parents = [msg.name for msg in parent_stack[: i + 1]]
                 return self.build_relative_type_name(
-                    current_parents, target_parents, type_name
+                    current_parents, target_parents, 
self.to_pascal_case(type_name)
                 )
 
-        return type_name
+        return self.to_pascal_case(type_name)
 
     def collect_uses(self, field_type: FieldType, uses: Set[str]):
         """Collect required use statements for a field type."""
@@ -921,7 +921,7 @@ class RustGenerator(BaseGenerator):
         parent_stack: Optional[List[Message]],
     ):
         """Generate registration code for a message and its nested types."""
-        type_name = self.get_type_path(message.name, parent_stack)
+        type_name = self.get_type_path(self.to_pascal_case(message.name), 
parent_stack)
         reg_name = self.get_registration_type_name(message.name, parent_stack)
 
         # Register nested enums first
diff --git a/compiler/fory_compiler/tests/test_nested_types.py 
b/compiler/fory_compiler/tests/test_nested_types.py
index 8515dcdc7..4dafcb5b7 100644
--- a/compiler/fory_compiler/tests/test_nested_types.py
+++ b/compiler/fory_compiler/tests/test_nested_types.py
@@ -17,10 +17,15 @@
 
 """Tests for FDL nested type support."""
 
+from pathlib import Path
+from textwrap import dedent
+
 import pytest
 
 from fory_compiler.frontend.fdl.lexer import Lexer
 from fory_compiler.frontend.fdl.parser import Parser
+from fory_compiler.generators.base import GeneratorOptions
+from fory_compiler.generators.rust import RustGenerator
 from fory_compiler.ir.ast import NamedType, ListType
 from fory_compiler.ir.validator import SchemaValidator
 
@@ -369,5 +374,76 @@ class TestSchemaTypeLookup:
         assert "Deep" in type_names
 
 
+class TestRustNestedTypeGeneration:
+    """Rust-specific tests for nested type code generation."""
+
+    def _generate_rust(self, source: str) -> str:
+        lexer = Lexer(source)
+        parser = Parser(lexer.tokenize())
+        schema = parser.parse()
+        options = GeneratorOptions(output_dir=Path("/tmp"))
+        generator = RustGenerator(schema, options)
+        files = generator.generate()
+        return "\n".join(f.content for f in files)
+
+    def test_nested_message_no_name_collision(self):
+        """Regression for #3547: struct and module must not share the same 
identifier."""
+        source = dedent("""
+            message foo {
+                message Bar {
+                    string baz = 1;
+                }
+                Bar bar = 1;
+            }
+        """)
+        content = self._generate_rust(source)
+        assert "pub mod foo {" in content
+        assert "pub struct Foo {" in content
+        assert "pub struct foo" not in content
+
+    def test_nested_message_field_type_qualified(self):
+        """Field referencing a nested type resolves to module-qualified 
path."""
+        source = dedent("""
+            message foo {
+                message Bar {
+                    string baz = 1;
+                }
+                Bar bar = 1;
+            }
+        """)
+        content = self._generate_rust(source)
+        assert "pub bar: foo::Bar," in content
+
+    def test_nested_message_registration_uses_pascal_struct(self):
+        """register_by_namespace must reference the PascalCase struct name."""
+        source = dedent("""
+            message foo {
+                message Bar {
+                    string baz = 1;
+                }
+                Bar bar = 1;
+            }
+        """)
+        content = self._generate_rust(source)
+        assert "register_by_namespace::<Foo>" in content
+        assert 'register_by_namespace::<Foo>("default", "foo")' in content
+        assert "register_by_namespace::<foo::Bar>" in content
+
+    def test_pascal_case_names_unchanged(self):
+        """Messages already in PascalCase must not be renamed."""
+        source = dedent("""
+            message Outer {
+                message Inner {
+                    string value = 1;
+                }
+                Inner inner = 1;
+            }
+        """)
+        content = self._generate_rust(source)
+        assert "pub struct Outer {" in content
+        assert "pub struct Inner {" in content
+        assert "pub outer: " not in content
+
+
 if __name__ == "__main__":
     pytest.main([__file__, "-v"])


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

Reply via email to