From ac6f89e63b34b34706e2df488f3363f03ecdaa66 Mon Sep 17 00:00:00 2001 From: chaokunyang Date: Fri, 15 May 2026 19:22:49 +0800 Subject: [PATCH] update generated annotation --- compiler/fory_compiler/generators/scala.py | 11 +++++-- .../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 +-- .../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 59a859be98..4337a61f48 100644 --- a/compiler/fory_compiler/generators/scala.py +++ b/compiler/fory_compiler/generators/scala.py @@ -402,7 +402,11 @@ def generate_normal_class( 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 @@ def generate_parameter(self, field: Field, parent_stack: List[Message]) -> str: 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 2831824121..7eeb83163d 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 723a4eb4cf..0d8183b6d9 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 ab98a0e07f..b30ce90831 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 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 7f7dd6cf33..f89e81e76a 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`. 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 a580cd2070..7bce185926 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 ad7ef8b821..aaf5893ceb 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 4e22e11957..c03622fe04 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