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 2e3bef8a0 feat(scala): update generated annotation (#3682)
2e3bef8a0 is described below

commit 2e3bef8a065d2eb3131732cdc6b8364b2492b129
Author: Shawn Yang <[email protected]>
AuthorDate: Fri May 15 19:56:18 2026 +0800

    feat(scala): update generated annotation (#3682)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    #3681
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## 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/scala.py         | 11 ++++++--
 .../fory_compiler/tests/test_scala_generator.py    | 33 +++++++++++++++++-----
 docs/compiler/generated-code.md                    |  6 ++--
 docs/compiler/schema-idl.md                        |  2 +-
 docs/guide/scala/schema-idl.md                     |  5 +++-
 docs/guide/xlang/field-reference-tracking.md       |  6 +++-
 .../scala/ForySerializerDerivationTest.scala       |  4 +--
 .../fory/serializer/scala/ScalaXlangPeer.scala     | 11 ++++----
 8 files changed, 57 insertions(+), 21 deletions(-)

diff --git a/compiler/fory_compiler/generators/scala.py 
b/compiler/fory_compiler/generators/scala.py
index 59a859be9..4337a61f4 100644
--- a/compiler/fory_compiler/generators/scala.py
+++ b/compiler/fory_compiler/generators/scala.py
@@ -402,7 +402,11 @@ class ScalaGenerator(BaseGenerator):
                 nullable=field.optional,
                 element_optional=field.element_optional,
                 element_ref=field.element_ref,
-                top_level_ref=field.ref,
+                # Message fields own top-level ref metadata through the field
+                # annotation below. Type-use @Ref is reserved for nested
+                # element/value/payload refs so optional top-level refs do not
+                # carry duplicate metadata like Option[Foo @Ref] plus @Ref.
+                top_level_ref=False,
                 parent_stack=current_stack,
             )
             if field.ref and self.is_ref_target_type(field.field_type, 
current_stack):
@@ -443,7 +447,10 @@ class ScalaGenerator(BaseGenerator):
             nullable=field.optional,
             element_optional=field.element_optional,
             element_ref=field.element_ref,
-            top_level_ref=field.ref,
+            # Message constructor parameters use @Ref on the parameter for
+            # top-level ref metadata. Nested refs still flow through
+            # element_ref/value_ref type-use annotations.
+            top_level_ref=False,
             parent_stack=parent_stack,
         )
         ref_annotation = (
diff --git a/compiler/fory_compiler/tests/test_scala_generator.py 
b/compiler/fory_compiler/tests/test_scala_generator.py
index 283182412..7eeb83163 100644
--- a/compiler/fory_compiler/tests/test_scala_generator.py
+++ b/compiler/fory_compiler/tests/test_scala_generator.py
@@ -97,7 +97,8 @@ def 
test_scala_generator_uses_mutable_normal_class_for_construction_cycles():
     node = files["graph/Node.scala"]
     assert "final class Node() derives ForySerializer" in node
     assert 'var id: String = ""' in node
-    assert "var parent: Option[Node @Ref] = None" in node
+    assert "@Ref\n    @ForyField(id = 2)\n    var parent: Option[Node] = None" 
in node
+    assert "Option[Node @Ref]" not in node
 
 
 def 
test_scala_generator_uses_mutable_normal_class_for_nested_construction_cycles():
@@ -121,7 +122,11 @@ def 
test_scala_generator_uses_mutable_normal_class_for_nested_construction_cycle
     assert "object Envelope {" in envelope
     assert "final class Node() derives ForySerializer" in envelope
     assert 'var id: String = ""' in envelope
-    assert "var parent: Option[Envelope.Node @Ref] = None" in envelope
+    assert (
+        "@Ref\n        @ForyField(id = 2)\n        var parent: 
Option[Envelope.Node] = None"
+        in envelope
+    )
+    assert "Option[Envelope.Node @Ref]" not in envelope
 
 
 def test_scala_generator_keeps_container_recursive_messages_as_case_classes():
@@ -166,7 +171,8 @@ def 
test_scala_generator_marks_container_cycle_with_constructor_edge_mutable():
     assert "final class Node() derives ForySerializer" in node
     assert "var edges: List[Edge @Ref] = List.empty" in node
     assert "final class Edge() derives ForySerializer" in edge
-    assert "var owner: Option[Node @Ref] = None" in edge
+    assert "@Ref\n    @ForyField(id = 2)\n    var owner: Option[Node] = None" 
in edge
+    assert "Option[Node @Ref]" not in edge
 
 
 def test_scala_generator_marks_nested_owner_child_cycles_mutable():
@@ -189,7 +195,11 @@ def 
test_scala_generator_marks_nested_owner_child_cycles_mutable():
     assert "final class Envelope() derives ForySerializer" in envelope
     assert "var root: Option[Envelope.Node] = None" in envelope
     assert "final class Node() derives ForySerializer" in envelope
-    assert "var owner: Option[Envelope @Ref] = None" in envelope
+    assert (
+        "@Ref\n        @ForyField(id = 2)\n        var owner: Option[Envelope] 
= None"
+        in envelope
+    )
+    assert "Option[Envelope @Ref]" not in envelope
 
 
 def test_scala_generator_marks_union_mediated_cycles_mutable():
@@ -211,7 +221,8 @@ def 
test_scala_generator_marks_union_mediated_cycles_mutable():
     node = files["graph/Node.scala"]
     assert "final class Node() derives ForySerializer" in node
     assert 'var id: String = ""' in node
-    assert "var choice: Choice @Ref = null" in node
+    assert "@Ref\n    @ForyField(id = 2)\n    var choice: Choice = null" in 
node
+    assert "Choice @Ref" not in node
 
 
 def test_scala_generator_collects_nested_union_payload_imports():
@@ -272,7 +283,11 @@ def 
test_scala_generator_marks_nested_union_mediated_cycles_mutable():
     assert "case NodeCase(value: Envelope.Node)" in envelope
     assert "final class Node() derives ForySerializer" in envelope
     assert 'var id: String = ""' in envelope
-    assert "var choice: Envelope.Choice @Ref = null" in envelope
+    assert (
+        "@Ref\n        @ForyField(id = 2)\n        var choice: Envelope.Choice 
= null"
+        in envelope
+    )
+    assert "Envelope.Choice @Ref" not in envelope
 
 
 def 
test_scala_generator_resolves_shadowed_nested_types_before_top_level_types():
@@ -297,7 +312,11 @@ def 
test_scala_generator_resolves_shadowed_nested_types_before_top_level_types()
 
     envelope = files["graph/Envelope.scala"]
     assert "final class Node() derives ForySerializer" in envelope
-    assert "var parent: Option[Envelope.Node @Ref] = None" in envelope
+    assert (
+        "@Ref\n        @ForyField(id = 2)\n        var parent: 
Option[Envelope.Node] = None"
+        in envelope
+    )
+    assert "Option[Envelope.Node @Ref]" not in envelope
     assert "@ForyField(id = 1) root: Option[Envelope.Node]" in envelope
 
 
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 723a4eb4c..0d8183b6d 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -1087,7 +1087,7 @@ final class Node() derives ForySerializer {
 
   @Ref
   @ForyField(id = 2)
-  var parent: Option[Node @Ref] = None
+  var parent: Option[Node] = None
 }
 ```
 
@@ -1128,7 +1128,9 @@ enum Animal derives ForySerializer {
 }
 ```
 
-`optional T` fields generate `Option[T]`. Reference tracking uses `@Ref`.
+`optional T` fields generate `Option[T]`. Top-level message references use
+`@Ref` on the field or constructor parameter. Nested element/value references
+use type-use annotations such as `List[Node @Ref]`.
 
 ### Registration
 
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index ab98a0e07..b30ce9083 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -925,7 +925,7 @@ message Node {
 | C++        | `Node parent`  | `std::shared_ptr<Node> parent`             |
 | JavaScript | `parent: Node` | `parent: Node` (no ref distinction)        |
 | Dart       | `Node parent`  | `Node parent` with `@ForyField(ref: true)` |
-| Scala      | `parent: Node` | `parent: Node @Ref`                        |
+| Scala      | `parent: Node` | `@Ref parent: Node`                        |
 
 Rust uses `Arc` by default; use `ref(thread_safe=false)` or `ref(weak=true)`
 to customize pointer types. For protobuf option syntax, see
diff --git a/docs/guide/scala/schema-idl.md b/docs/guide/scala/schema-idl.md
index 7f7dd6cf3..f89e81e76 100644
--- a/docs/guide/scala/schema-idl.md
+++ b/docs/guide/scala/schema-idl.md
@@ -91,11 +91,14 @@ final class Node() derives ForySerializer {
 
   @Ref
   @ForyField(id = 2)
-  var parent: Option[Node @Ref] = None
+  var parent: Option[Node] = None
 }
 ```
 
 `@Ref` is the JVM reference-tracking annotation for Scala macro and IDL APIs.
+Use field or constructor-parameter `@Ref` for a top-level `ref T` field. Use
+type-use `T @Ref` only for nested element/value/payload refs, such as
+`list<ref T>`.
 
 Generated xlang collection fields use immutable Scala collection types:
 `List[T]`, `Set[T]`, and `Map[K, V]`. The runtime xlang serializers can also
diff --git a/docs/guide/xlang/field-reference-tracking.md 
b/docs/guide/xlang/field-reference-tracking.md
index a580cd207..7bce18592 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -210,10 +210,14 @@ final class Node() derives ForySerializer {
 
   @Ref
   @ForyField(id = 2)
-  var parent: Option[Node @Ref] = None
+  var parent: Option[Node] = None
 }
 ```
 
+For Scala, top-level field reference tracking is owned by `@Ref` on the field 
or
+constructor parameter. Type-use `T @Ref` is for nested element/value/payload
+references, such as `List[Node @Ref]`.
+
 #### Go: Struct Tags
 
 ```go
diff --git 
a/scala/src/test/scala-3/org/apache/fory/serializer/scala/ForySerializerDerivationTest.scala
 
b/scala/src/test/scala-3/org/apache/fory/serializer/scala/ForySerializerDerivationTest.scala
index ad7ef8b82..aaf5893ce 100644
--- 
a/scala/src/test/scala-3/org/apache/fory/serializer/scala/ForySerializerDerivationTest.scala
+++ 
b/scala/src/test/scala-3/org/apache/fory/serializer/scala/ForySerializerDerivationTest.scala
@@ -98,7 +98,7 @@ object ForySerializerDerivationTest {
 
     @Ref
     @ForyField(id = 2)
-    var parent: Option[RefNode @Ref] = None
+    var parent: Option[RefNode] = None
   }
 
   @ForyStruct
@@ -108,7 +108,7 @@ object ForySerializerDerivationTest {
 
     @Ref
     @ForyField(id = 2)
-    var choice: Option[UnionCycle @Ref] = None
+    var choice: Option[UnionCycle] = None
   }
 
   @ForyStruct
diff --git 
a/scala/src/test/scala-3/org/apache/fory/serializer/scala/ScalaXlangPeer.scala 
b/scala/src/test/scala-3/org/apache/fory/serializer/scala/ScalaXlangPeer.scala
index 4e22e1195..c03622fe0 100644
--- 
a/scala/src/test/scala-3/org/apache/fory/serializer/scala/ScalaXlangPeer.scala
+++ 
b/scala/src/test/scala-3/org/apache/fory/serializer/scala/ScalaXlangPeer.scala
@@ -240,8 +240,8 @@ final case class RefInnerSchemaConsistent(id: Int, name: 
String) derives ForySer
 
 @ForyStruct
 final case class RefOuterSchemaConsistent(
-    inner1: Option[RefInnerSchemaConsistent @Ref],
-    inner2: Option[RefInnerSchemaConsistent @Ref])
+    @Ref inner1: Option[RefInnerSchemaConsistent],
+    @Ref inner2: Option[RefInnerSchemaConsistent])
     derives ForySerializer
 
 @ForyStruct
@@ -249,8 +249,8 @@ final case class RefInnerCompatible(id: Int, name: String) 
derives ForySerialize
 
 @ForyStruct
 final case class RefOuterCompatible(
-    inner1: Option[RefInnerCompatible @Ref],
-    inner2: Option[RefInnerCompatible @Ref])
+    @Ref inner1: Option[RefInnerCompatible],
+    @Ref inner2: Option[RefInnerCompatible])
     derives ForySerializer
 
 @ForyStruct
@@ -267,7 +267,8 @@ final case class RefOverrideContainer(
 final class CircularRefStruct derives ForySerializer {
   var name: String = ""
 
-  var selfRef: Option[CircularRefStruct @Ref] = None
+  @Ref
+  var selfRef: Option[CircularRefStruct] = None
 }
 
 @ForyStruct


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

Reply via email to