Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions compiler/fory_compiler/generators/scala.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 = (
Expand Down
33 changes: 26 additions & 7 deletions compiler/fory_compiler/tests/test_scala_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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():
Expand Down Expand Up @@ -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():
Expand All @@ -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():
Expand All @@ -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():
Expand Down Expand Up @@ -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():
Expand All @@ -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


Expand Down
6 changes: 4 additions & 2 deletions docs/compiler/generated-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ final class Node() derives ForySerializer {

@Ref
@ForyField(id = 2)
var parent: Option[Node @Ref] = None
var parent: Option[Node] = None
}
```

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/compiler/schema-idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion docs/guide/scala/schema-idl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion docs/guide/xlang/field-reference-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ object ForySerializerDerivationTest {

@Ref
@ForyField(id = 2)
var parent: Option[RefNode @Ref] = None
var parent: Option[RefNode] = None
}

@ForyStruct
Expand All @@ -108,7 +108,7 @@ object ForySerializerDerivationTest {

@Ref
@ForyField(id = 2)
var choice: Option[UnionCycle @Ref] = None
var choice: Option[UnionCycle] = None
}

@ForyStruct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,17 +240,17 @@ 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
final case class RefInnerCompatible(id: Int, name: String) derives ForySerializer

@ForyStruct
final case class RefOuterCompatible(
inner1: Option[RefInnerCompatible @Ref],
inner2: Option[RefInnerCompatible @Ref])
@Ref inner1: Option[RefInnerCompatible],
@Ref inner2: Option[RefInnerCompatible])
derives ForySerializer

@ForyStruct
Expand All @@ -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
Expand Down
Loading