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-site.git
commit 907c5fea7f368527c791dae36dc5084f8aa8c1af Author: chaokunyang <[email protected]> AuthorDate: Fri May 15 11:18:15 2026 +0000 π synced local 'docs/guide/' with remote 'docs/guide/' --- docs/guide/java/field-configuration.md | 40 +++-- docs/guide/kotlin/static-generated-serializers.md | 4 +- docs/guide/scala/fory-creation.md | 20 +++ docs/guide/scala/index.md | 4 +- docs/guide/scala/schema-idl.md | 179 ++++++++++++++++++++++ docs/guide/xlang/field-nullability.md | 1 + docs/guide/xlang/field-reference-tracking.md | 39 ++++- 7 files changed, 263 insertions(+), 24 deletions(-) diff --git a/docs/guide/java/field-configuration.md b/docs/guide/java/field-configuration.md index 1d119c1fc0..d625eeccad 100644 --- a/docs/guide/java/field-configuration.md +++ b/docs/guide/java/field-configuration.md @@ -25,8 +25,9 @@ This page explains how to configure field-level metadata for serialization in Ja Apache Foryβ’ provides field-level configuration through annotations: -- **`@ForyField`**: Configure field metadata (id, ref, dynamic) +- **`@ForyField`**: Configure field metadata (id, dynamic) - **`@Nullable`**: Mark a field type or nested type position as nullable +- **`@Ref`**: Enable field or nested-element reference tracking - **`@Ignore`**: Exclude fields from serialization - **Integer type annotations**: Control integer encoding (varint, fixed, tagged, unsigned) @@ -76,8 +77,8 @@ public class User { @ForyField(id = 2) private String email; - @ForyField(id = 3, ref = true) - private List<User> friends; + @ForyField(id = 3) + private List<@Ref User> friends; @ForyField(id = 4, dynamic = ForyField.Dynamic.TRUE) private Object data; @@ -89,11 +90,11 @@ public class User { | Parameter | Type | Default | Description | | --------- | --------- | ------- | -------------------------------------- | | `id` | `int` | `-1` | Non-negative field tag ID, or no ID | -| `ref` | `boolean` | `false` | Enable reference tracking | | `dynamic` | `Dynamic` | `AUTO` | Control polymorphism for struct fields | Use `@Nullable` on the field type or nested type position for nullable schema -metadata. `@ForyField` does not carry nullability. +metadata and `@Ref` for reference tracking. `@ForyField` does not carry either +setting. ## Field ID (`id`) @@ -164,7 +165,7 @@ public class Record { - When a field is non-nullable, Fory skips writing the null flag. - Boxed types (`Integer`, `Long`, etc.) that can be null should use `@Nullable`. -## Reference Tracking (`ref`) +## Reference Tracking (`@Ref`) Enable reference tracking for fields that may be shared or circular: @@ -172,11 +173,13 @@ Enable reference tracking for fields that may be shared or circular: public class RefOuter { // Both fields may point to the same inner object @Nullable - @ForyField(id = 0, ref = true) + @ForyField(id = 0) + @Ref private RefInner inner1; @Nullable - @ForyField(id = 1, ref = true) + @ForyField(id = 1) + @Ref private RefInner inner2; } @@ -186,7 +189,8 @@ public class CircularRef { // Self-referencing field for circular references @Nullable - @ForyField(id = 1, ref = true) + @ForyField(id = 1) + @Ref private CircularRef selfRef; } ``` @@ -198,8 +202,8 @@ public class CircularRef { **Notes**: -- Default is `ref = false` (no reference tracking) -- When `ref = false`, avoids IdentityMap overhead and skips ref tracking flag +- Fields without `@Ref` do not use field-wrapper reference tracking +- Avoid `@Ref` when values are not shared or circular, so Fory can skip the reference flag - Reference tracking only takes effect when global ref tracking is enabled ## Dynamic (Polymorphism Control) @@ -451,7 +455,8 @@ public class Document { // Reference-tracked field for shared/circular references @Nullable - @ForyField(id = 9, ref = true) + @ForyField(id = 9) + @Ref private Document parent; // Ignored field (not serialized) @@ -578,13 +583,13 @@ public class User { Xlang mode has **stricter default values** due to type system differences between languages: - **Nullable**: Fields are non-nullable by default -- **Ref tracking**: Disabled by default (`ref = false`) +- **Ref tracking**: Disabled by default unless the field type uses `@Ref` - **Polymorphism**: Concrete types are non-polymorphic by default In xlang mode, you **need to configure fields** when: - A field can be null (use `@Nullable`) -- A field needs reference tracking for shared/circular objects (use `ref = true`) +- A field needs reference tracking for shared/circular objects (use `@Ref`) - Integer types need specific encoding for cross-language compatibility - You want to reduce metadata size (use field IDs) @@ -602,7 +607,8 @@ public class User { private String email; @Nullable - @ForyField(id = 3, ref = true) // Must declare ref for shared objects + @ForyField(id = 3) + @Ref // Must declare @Ref for shared objects private User friend; } ``` @@ -619,7 +625,7 @@ public class User { 1. **Configure field IDs**: Recommended for compatible mode to reduce serialization cost 2. **Use `@Nullable` for nullable fields**: Required for fields that can be null -3. **Enable ref tracking for shared objects**: Use `ref = true` when objects are shared or circular +3. **Enable ref tracking for shared objects**: Use `@Ref` when objects are shared or circular 4. **Use `@Ignore` or `transient` for sensitive data**: Passwords, tokens, internal state 5. **Choose appropriate encoding**: `varint` for small values, `fixed` for full-range values 6. **Keep IDs stable**: Once assigned, don't change field IDs @@ -631,7 +637,7 @@ public class User { | ----------------------------- | -------------------------------------- | | `@ForyField(id = N)` | Field tag ID to reduce metadata size | | `@Nullable` | Mark field or nested type as nullable | -| `@ForyField(ref = true)` | Enable reference tracking | +| `@Ref` | Enable reference tracking | | `@ForyField(dynamic = ...)` | Control polymorphism for struct fields | | `@Ignore` | Exclude field from serialization | | `@Int32Type(encoding = ...)` | 32-bit signed integer encoding | diff --git a/docs/guide/kotlin/static-generated-serializers.md b/docs/guide/kotlin/static-generated-serializers.md index 8836ac44e1..99eb4edb38 100644 --- a/docs/guide/kotlin/static-generated-serializers.md +++ b/docs/guide/kotlin/static-generated-serializers.md @@ -131,8 +131,8 @@ the schema is always read from Kotlin source nullability. ## References -`@ForyField(ref = true)` is not supported by Kotlin xlang generated -serializers. Generated reads construct Kotlin values through primary +Kotlin xlang generated serializers reject every `@Ref` annotation, including +`@Ref(enable = false)`. Generated reads construct Kotlin values through primary constructors, so they cannot publish partially constructed objects for cyclic back-references. Use non-cyclic schemas for Kotlin xlang structs. diff --git a/docs/guide/scala/fory-creation.md b/docs/guide/scala/fory-creation.md index b79e618563..a3f0a2ba6e 100644 --- a/docs/guide/scala/fory-creation.md +++ b/docs/guide/scala/fory-creation.md @@ -133,3 +133,23 @@ val fory = Fory.builder() ScalaSerializers.registerSerializers(fory) ``` + +## Cross-Language Mode + +For Scala xlang or schema IDL generated code, enable xlang and register the +Scala serializers before registering generated model types: + +```scala +val fory = Fory.builder() + .withXlang(true) + .withCompatible(true) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) +ExampleForyRegistration.register(fory) +``` + +In xlang mode, Scala collections use canonical `list`, `set`, and `map` +payloads instead of Scala factory payloads. Generated optional fields use +`Option[T]`. diff --git a/docs/guide/scala/index.md b/docs/guide/scala/index.md index 60d5da14c5..382b43e1c1 100644 --- a/docs/guide/scala/index.md +++ b/docs/guide/scala/index.md @@ -29,7 +29,8 @@ Apache Foryβ’ Scala provides optimized serializers for Scala types, built on to - `Option` types - Scala 2 and 3 enumerations -Both Scala 2 and Scala 3 are supported. +The runtime artifact supports Scala 2.13 and Scala 3. Schema IDL generated +Scala source and macro-derived xlang serializers require Scala 3. ## Features @@ -98,3 +99,4 @@ Fory Scala is built on top of Fory Java. Most configuration options, features, a - [Fory Creation](fory-creation.md) - Scala-specific Fory setup requirements - [Type Serialization](type-serialization.md) - Serializing Scala types - [Default Values](default-values.md) - Scala class default values support +- [Schema IDL And Xlang](schema-idl.md) - Scala 3 generated models and macro-derived xlang serializers diff --git a/docs/guide/scala/schema-idl.md b/docs/guide/scala/schema-idl.md new file mode 100644 index 0000000000..7f7dd6cf33 --- /dev/null +++ b/docs/guide/scala/schema-idl.md @@ -0,0 +1,179 @@ +--- +title: Schema IDL And Xlang +sidebar_position: 4 +id: schema_idl +license: | + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +The Fory schema IDL Scala target generates Scala 3 source for xlang payloads. +The runtime artifact remains cross-built for Scala 2.13 and Scala 3; only the +schema IDL output and quoted macro derivation require Scala 3. + +## Setup + +Generated Scala code uses the public macro API in `org.apache.fory.scala` and +the shared JVM annotations in `org.apache.fory.annotation`. Macro internals live +under `org.apache.fory.scala.internal`. + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForySerializer +import org.apache.fory.serializer.scala.ScalaSerializers + +val fory = Fory.builder() + .withXlang(true) + .withCompatible(true) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) +ExampleForyRegistration.register(fory) +``` + +For `ThreadSafeFory`, generated registration helpers install a callback so each +runtime instance gets the same serializers. + +Generated helpers register message type identities before installing message +serializers. This two-phase order lets mutually recursive message graphs build +descriptor metadata through the normal `TypeResolver` path without placeholder +serializers or Scala-specific registration state in Java core. Enums and unions +are registered with their serializers directly because their derived serializers +own case dispatch. + +## Generated Messages + +Acyclic messages generate case classes: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 2) email: Option[String] +) derives ForySerializer +``` + +Schema `optional T` fields are stored as `Option[T]`. + +Messages in compiler-detected construction cycles generate normal classes with +mutable serialized fields so the deserializer can allocate and register the +object before reading fields that can point back to it. A top-level `ref Foo`, +nested `list<ref Foo>`, or `any` field does not by itself force this shape. +The compiler analyzes message and union dependencies together, so +message-to-union-to-message cycles also make the participating messages normal +classes. Acyclic owner messages that only contain a cyclic nested type remain +case classes. + +Reference tracking is expressed with the shared `@Ref` annotation, including +type-use positions: + +```scala +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node @Ref] = None +} +``` + +`@Ref` is the JVM reference-tracking annotation for Scala macro and IDL APIs. + +Generated xlang collection fields use immutable Scala collection types: +`List[T]`, `Set[T]`, and `Map[K, V]`. The runtime xlang serializers can also +rebuild supported mutable collection interfaces such as `scala.collection.Seq` +and `scala.collection.Map`, but concrete mutable collection classes are outside +the schema IDL surface unless explicitly generated. + +## Generated Enums + +IDL enums generate Scala 3 enums only. No Java enum sidecar is emitted. + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum Status { + @ForyEnumId(0) + case Unknown + + @ForyEnumId(1) + case Ok +} +``` + +Generated registration uses `ScalaSerializers.registerEnum(...)` so the stable +Fory enum IDs from case-level `@ForyEnumId` metadata are used in xlang mode. + +## Generated Unions + +IDL unions generate Scala 3 ADT enums with macro-derived serializers: + +```scala +import org.apache.fory.annotation.{ForyCase, ForyUnion, UInt32Type} +import org.apache.fory.config.Int32Encoding +import org.apache.fory.scala.ForySerializer + +@ForyUnion +enum SearchTarget derives ForySerializer { + @ForyCase(id = 0) + case UnknownCase(caseId: Int, value: Any) + + @ForyCase(id = 1) + case UserCase(value: User) + + @ForyCase(id = 2) + case FixedIdCase(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) +} +``` + +Schema-defined union cases must use positive IDs. Case ID `0` is reserved for +the Scala unknown-case carrier, whose payload stores the original positive case +ID and the deserialized value. When a reader sees a newer positive case ID, it +returns `UnknownCase(originalId, value)` instead of failing solely because the +case ID is not known locally. + +The macro writes the existing xlang union envelope directly. It does not +allocate temporary Java `Union` carriers. + +## Manual Scala 3 Derivation + +Manual Scala 3 models can derive the same serializer typeclass: + +```scala +@ForyStruct +final class Record(@ForyField(id = 1) val id: Int) derives ForySerializer { + @ForyField(id = 2) + var name: String = "" +} +``` + +The macro generates direct constructor calls for constructor-owned fields and +direct assignments for mutable post-construction fields. It builds descriptor +metadata from Scala compile-time types, including nested generics, `Option`, +arrays, scalar encoding annotations, nullability, and `@Ref` metadata. Java +reflection is not the source of truth for generated Scala metadata. + +During copy, cyclic graphs are supported when the copied root can be allocated +and registered before cyclic fields are copied, which is the normal-class shape +used by schema IDL for construction cycles. If a copy starts at an immutable +constructor-owned value that participates in the cycle, such as a Scala enum +case or case class, the serializer fails with a clear error because no copied +identity can be published until construction has completed. diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md index bea7f3f092..5d891fc7d4 100644 --- a/docs/guide/xlang/field-nullability.md +++ b/docs/guide/xlang/field-nullability.md @@ -36,6 +36,7 @@ The following types are nullable by default: - Go pointer types (`*int32`, `*string`, etc.) - Rust `Option<T>` - Python `Optional[T]` +- Scala `Option[T]` | Field Type | Default Nullable | Null Flag Written | | ------------------------------------------ | ---------------- | ----------------- | diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md index 9d32c0e88b..a580cd2070 100644 --- a/docs/guide/xlang/field-reference-tracking.md +++ b/docs/guide/xlang/field-reference-tracking.md @@ -69,6 +69,17 @@ let fory = Fory::builder() .track_ref(true).build(); ``` +### Scala + +```scala +val fory = Fory.builder() + .withXlang(true) + .withCompatible(true) + .withRefTracking(true) + .withScalaOptimizationEnabled(true) + .build() +``` + ## Wire Format When reference tracking is enabled, nullable fields write a **ref flag byte** before the value: @@ -121,10 +132,11 @@ By default, **most fields do not track references** even when global `refTrackin | Go | No | None (use `fory:"ref"` to enable) | | C++ | Yes | `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>` | | Rust | No | `Rc<T>`, `Arc<T>`, `Weak<T>` | +| Scala | No | None (use `@Ref` to enable) | ### Customizing Per-Field Ref Tracking -#### Java: @ForyField Annotation +#### Java: @Ref Annotation ```java public class Document { @@ -132,12 +144,11 @@ public class Document { String title; // Enable ref tracking for this field - @ForyField(ref = true) + @Ref Author author; // Shared across documents, track refs to avoid duplicates - @ForyField(ref = true) - List<Tag> tags; + List<@Ref Tag> tags; } ``` @@ -183,6 +194,26 @@ struct Document { } ``` +#### Scala: @Ref Annotation + +Scala schema IDL and Scala 3 macro derivation use the same shared JVM `@Ref` +annotation: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node @Ref] = None +} +``` + #### Go: Struct Tags ```go --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
