You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/04/28 17:05:01 UTC
(fory) branch main updated: feat: add nested container codec for rust (#3630)
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 79aa38a28 feat: add nested container codec for rust (#3630)
79aa38a28 is described below
commit 79aa38a285606b32911aca48d628226925e24092
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Wed Apr 29 01:04:56 2026 +0800
feat: add nested container codec for rust (#3630)
## Why?
Rust generated serializers need field-local codec selection so nested
container element/key/value metadata, nullability, reference tracking,
and integer encodings can be represented consistently in compatible and
xlang serialization. The Rust derive API also now distinguishes structs,
pure enums, and union-style enums instead of routing every data shape
through one `ForyObject` derive.
## What does this PR do?
- Adds Rust field codecs for serializer-backed fields, options,
lists/sets, maps, nested container entries, integer encodings, `Any`,
and trait object fields.
- Reworks `fory-derive` field binding, read, write, and metadata
generation to dispatch through codecs when field annotations or nested
metadata require it, while preserving declared generic metadata for
unannotated container fields.
- Splits the Rust derive macros into `ForyStruct`, `ForyEnum`, and
`ForyUnion`, with validation for applying each macro to the matching
Rust data shape.
- Updates Rust IDL/codegen output to emit nested `list(...)` and
`map(...)` field attributes and the new derive macros.
- Updates Rust docs, README examples, benchmark fixtures, and tests from
`ForyObject` to the new derive names.
- Adds Rust coverage for nested codec annotations, compatible-mode
integer encoding mismatches, collection generic metadata,
trait-object/`Any` containers, and serialized benchmark payload sizes.
- Adds a dedicated C++ IDL CI job setup and keeps the C++ xlang job
naming consistent.
## Related issues
#1017
#3625
## 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?
- [x] Does this PR introduce any public API change?
- [x] Does this PR introduce any binary protocol compatibility change?
Rust users should derive `ForyStruct`, `ForyEnum`, or `ForyUnion`
instead of the previous `ForyObject` macro. Rust generated serializers
can now emit nested list/map field metadata and field-level codec
behavior for annotated containers.
## Benchmark
No benchmark results are included in the PR diff. The Rust benchmark
test fixture now asserts serialized size baselines for the covered
payloads.
---
.github/workflows/ci.yml | 29 +-
AGENTS.md | 3 +-
README.md | 8 +-
benchmarks/rust/benches/threaded_bench.rs | 4 +-
benchmarks/rust/src/data.rs | 22 +-
benchmarks/rust/src/lib.rs | 23 +
benchmarks/rust/src/serializers/fory.rs | 2 +-
compiler/README.md | 4 +-
compiler/fory_compiler/generators/rust.py | 70 +-
docs/compiler/generated-code.md | 12 +-
docs/guide/rust/basic-serialization.md | 10 +-
docs/guide/rust/cross-language.md | 4 +-
docs/guide/rust/custom-serializers.md | 6 +-
docs/guide/rust/field-configuration.md | 97 +-
docs/guide/rust/index.md | 10 +-
docs/guide/rust/polymorphism.md | 10 +-
docs/guide/rust/references.md | 8 +-
docs/guide/rust/row-format.md | 4 +-
docs/guide/rust/schema-evolution.md | 20 +-
docs/guide/rust/troubleshooting.md | 4 +-
docs/guide/rust/type-registration.md | 4 +-
docs/guide/xlang/field-nullability.md | 4 +-
docs/guide/xlang/field-reference-tracking.md | 4 +-
docs/guide/xlang/getting-started.md | 4 +-
docs/guide/xlang/serialization.md | 6 +-
rust/README.md | 36 +-
.../src/object/mod.rs => fory-core/build.rs} | 17 +-
rust/fory-core/src/context.rs | 42 +
rust/fory-core/src/fory.rs | 40 +-
rust/fory-core/src/lib.rs | 12 +-
rust/fory-core/src/meta/type_meta.rs | 155 +-
rust/fory-core/src/resolver/meta_resolver.rs | 38 +-
rust/fory-core/src/serializer/arc.rs | 5 +-
rust/fory-core/src/serializer/box_.rs | 5 +-
rust/fory-core/src/serializer/codec.rs | 2547 ++++++++++++++++++++
rust/fory-core/src/serializer/core.rs | 22 +-
rust/fory-core/src/serializer/heap.rs | 8 +
rust/fory-core/src/serializer/mod.rs | 2 +
rust/fory-core/src/serializer/mutex.rs | 5 +-
rust/fory-core/src/serializer/rc.rs | 5 +-
rust/fory-core/src/serializer/refcell.rs | 5 +-
rust/fory-core/src/serializer/skip.rs | 152 +-
rust/fory-core/src/serializer/struct_.rs | 5 +
rust/fory-core/src/serializer/trait_object.rs | 366 ++-
rust/fory-core/src/serializer/weak.rs | 10 +-
rust/fory-core/src/util/mod.rs | 6 +-
rust/fory-derive/src/lib.rs | 125 +-
rust/fory-derive/src/object/derive_enum.rs | 328 ++-
rust/fory-derive/src/object/field_codec.rs | 1175 +++++++++
rust/fory-derive/src/object/field_meta.rs | 597 +++--
rust/fory-derive/src/object/misc.rs | 198 +-
rust/fory-derive/src/object/mod.rs | 1 +
rust/fory-derive/src/object/read.rs | 921 +------
rust/fory-derive/src/object/serializer.rs | 50 +-
rust/fory-derive/src/object/util.rs | 899 +------
rust/fory-derive/src/object/write.rs | 386 +--
rust/fory-derive/src/util.rs | 69 -
rust/fory/src/lib.rs | 86 +-
rust/tests/tests/compatible/test_container.rs | 4 +-
rust/tests/tests/compatible/test_struct.rs | 64 +-
rust/tests/tests/compatible/test_struct_enum.rs | 58 +-
rust/tests/tests/mod.rs | 1 +
rust/tests/tests/test_any.rs | 57 +-
rust/tests/tests/test_array.rs | 14 +-
rust/tests/tests/test_associated_types.rs | 4 +-
rust/tests/tests/test_box.rs | 6 +-
rust/tests/tests/test_collection.rs | 6 +-
rust/tests/tests/test_complex_struct.rs | 16 +-
rust/tests/tests/test_cross_language.rs | 98 +-
rust/tests/tests/test_debug.rs | 2 +-
rust/tests/tests/test_enum.rs | 49 +-
rust/tests/tests/test_enum_compatible.rs | 42 +-
rust/tests/tests/test_ext.rs | 4 +-
rust/tests/tests/test_field_meta.rs | 389 ++-
rust/tests/tests/test_fory.rs | 18 +-
rust/tests/tests/test_generate_default.rs | 22 +-
rust/tests/tests/test_lifecycle_guard.rs | 6 +-
rust/tests/tests/test_list.rs | 4 +-
rust/tests/tests/test_map.rs | 4 +-
rust/tests/tests/test_marker.rs | 10 +-
rust/tests/tests/test_max_dyn_depth.rs | 4 +-
rust/tests/tests/test_meta.rs | 8 +-
rust/tests/tests/test_multi_thread.rs | 6 +-
rust/tests/tests/test_one_struct.rs | 6 +-
rust/tests/tests/test_rc_arc.rs | 4 +-
rust/tests/tests/test_rc_arc_trait_object.rs | 60 +-
rust/tests/tests/test_refcell.rs | 6 +-
rust/tests/tests/test_simple_struct.rs | 34 +-
rust/tests/tests/test_skip_fields.rs | 32 +-
rust/tests/tests/test_trait_object.rs | 14 +-
rust/tests/tests/test_tuple.rs | 8 +-
rust/tests/tests/test_tuple_compatible.rs | 34 +-
rust/tests/tests/test_tuple_struct.rs | 58 +-
rust/tests/tests/test_unsigned.rs | 24 +-
rust/tests/tests/test_weak.rs | 10 +-
95 files changed, 6390 insertions(+), 3486 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 243cffa28..5018d44ce 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -702,7 +702,7 @@ jobs:
if-no-files-found: ignore
cpp_xlang:
- name: C++ Xlang Test
+ name: CPP Xlang Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -737,6 +737,33 @@ jobs:
mvn -T16 --no-transfer-progress clean install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
cd fory-core
mvn -T16 --no-transfer-progress test -Dtest=org.apache.fory.xlang.CPPXlangTest
+
+ cpp_idl:
+ name: C++ IDL Tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.11
+ cache: 'pip'
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ java-version: 21
+ distribution: "temurin"
+ - name: Cache Maven local repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Install Java artifacts for IDL tests
+ run: |
+ cd java
+ mvn -T16 --no-transfer-progress clean install -DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true
- name: Run C++ IDL Tests
run: ./integration_tests/idl_tests/run_cpp_tests.sh
diff --git a/AGENTS.md b/AGENTS.md
index ca1b7a721..bb92c1387 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,6 +36,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
- Verification is required. Match validation to the real ownership path: compile, tests, xlang, native-image, non-VM compile, benchmarks, and remote CI as applicable; reasoning alone is never enough.
- Finish the whole surface. A feature or behavior change is incomplete until code, tests, docs, exports, examples, and build wiring agree, unless the user explicitly defers part of that surface.
- Keep task boundaries strict. Review tasks do not edit code, analysis-only tasks do not silently turn into implementation, and active-branch fixes must land in the active branch/workspace.
+- For non-trivial multi-step tasks, write the plan and progress into the canonical durable task file (use a matched skill/workflow file if it provides one, otherwise use a file under `tasks/`) and read that file after compaction before continuing.
## Repo-Wide Hard Rules
@@ -137,7 +138,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
## Commit And PR Expectations
-- After each finished task, create a git commit automatically for the task's tracked code and documentation changes, excluding `tasks/task-*.md`, `tasks/lessons.md`, and unrelated user changes.
+- After each finished task, create a git commit automatically for the task's tracked code and documentation changes, excluding `tasks/task-*.md`, `tasks/*-plan.md`, `tasks/*-state.md`, `tasks/lessons.md`, and unrelated user changes.
- PR titles must follow Conventional Commits; `.github/workflows/pr-lint.yml` enforces this.
- Performance changes should use the `perf` type and include benchmark data.
- See `.agents/ci-and-pr.md` for GitHub CLI triage commands and commit message examples.
diff --git a/README.md b/README.md
index 2d2bd8fc5..449694cd3 100644
--- a/README.md
+++ b/README.md
@@ -317,9 +317,9 @@ For detailed Python usage including type hints, compatibility modes, and advance
Rust native mode provides compile-time code generation via derive macros for high-performance serialization without runtime overhead.
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
@@ -564,9 +564,9 @@ public class XlangExample {
**Rust**
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct Person {
name: String,
age: i32,
diff --git a/benchmarks/rust/benches/threaded_bench.rs b/benchmarks/rust/benches/threaded_bench.rs
index 7895ef7e0..82469d614 100644
--- a/benchmarks/rust/benches/threaded_bench.rs
+++ b/benchmarks/rust/benches/threaded_bench.rs
@@ -17,12 +17,12 @@
use criterion::{criterion_group, criterion_main, Criterion};
use fory::Fory;
-use fory_derive::ForyObject;
+use fory_derive::ForyStruct;
use std::hint::black_box;
use std::sync::Arc;
use std::thread;
-#[derive(Debug, ForyObject)]
+#[derive(Debug, ForyStruct)]
pub struct UserSessionMetrics {
#[fory(id = 0)]
pub request_count: u64,
diff --git a/benchmarks/rust/src/data.rs b/benchmarks/rust/src/data.rs
index 95178ce19..5ab46c181 100644
--- a/benchmarks/rust/src/data.rs
+++ b/benchmarks/rust/src/data.rs
@@ -16,7 +16,7 @@
// under the License.
use fory::{Error, Fory};
-use fory_derive::ForyObject;
+use fory_derive::{ForyEnum, ForyStruct};
const LIST_SIZE: usize = 5;
@@ -69,7 +69,7 @@ pub trait BenchmarkCase: Clone + PartialEq + Sized + 'static {
fn create() -> Self;
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct NumericStruct {
#[fory(id = 1)]
pub f1: i32,
@@ -89,7 +89,7 @@ pub struct NumericStruct {
pub f8: i32,
}
-#[derive(Debug, Clone, PartialEq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, ForyStruct)]
pub struct Sample {
#[fory(id = 1)]
pub int_value: i32,
@@ -137,7 +137,7 @@ pub struct Sample {
pub string: String,
}
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyEnum)]
#[repr(i32)]
pub enum Player {
#[default]
@@ -145,7 +145,7 @@ pub enum Player {
Flash = 1,
}
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ForyEnum)]
#[repr(i32)]
pub enum Size {
#[default]
@@ -153,7 +153,7 @@ pub enum Size {
Large = 1,
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct Media {
#[fory(id = 1)]
pub uri: String,
@@ -181,7 +181,7 @@ pub struct Media {
pub copyright: String,
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct Image {
#[fory(id = 1)]
pub uri: String,
@@ -195,7 +195,7 @@ pub struct Image {
pub size: Size,
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct MediaContent {
#[fory(id = 1)]
pub media: Media,
@@ -203,19 +203,19 @@ pub struct MediaContent {
pub images: Vec<Image>,
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct StructList {
#[fory(id = 1)]
pub struct_list: Vec<NumericStruct>,
}
-#[derive(Debug, Clone, PartialEq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, ForyStruct)]
pub struct SampleList {
#[fory(id = 1)]
pub sample_list: Vec<Sample>,
}
-#[derive(Debug, Clone, PartialEq, Eq, ForyObject)]
+#[derive(Debug, Clone, PartialEq, Eq, ForyStruct)]
pub struct MediaContentList {
#[fory(id = 1)]
pub media_content_list: Vec<MediaContent>,
diff --git a/benchmarks/rust/src/lib.rs b/benchmarks/rust/src/lib.rs
index 65575f63a..d4fa25e9e 100644
--- a/benchmarks/rust/src/lib.rs
+++ b/benchmarks/rust/src/lib.rs
@@ -110,6 +110,19 @@ mod tests {
assert_eq!(value, decoded);
}
+ fn assert_serialized_size<T>(expected_fory: usize, expected_protobuf: usize)
+ where
+ T: BenchmarkCase,
+ ForySerializer: BenchmarkSerializer<T>,
+ ProtobufSerializer: BenchmarkSerializer<T>,
+ {
+ let value = T::create();
+ let fory_bytes = ForySerializer::new().serialize(&value).unwrap();
+ let protobuf_bytes = ProtobufSerializer::new().serialize(&value).unwrap();
+ assert_eq!(fory_bytes.len(), expected_fory);
+ assert_eq!(protobuf_bytes.len(), expected_protobuf);
+ }
+
#[test]
fn benchmark_cases_round_trip() {
assert_round_trip::<NumericStruct>();
@@ -119,4 +132,14 @@ mod tests {
assert_round_trip::<SampleList>();
assert_round_trip::<MediaContentList>();
}
+
+ #[test]
+ fn benchmark_serialized_sizes_match_baseline() {
+ assert_serialized_size::<NumericStruct>(58, 61);
+ assert_serialized_size::<Sample>(446, 375);
+ assert_serialized_size::<MediaContent>(365, 301);
+ assert_serialized_size::<StructList>(184, 315);
+ assert_serialized_size::<SampleList>(1980, 1890);
+ assert_serialized_size::<MediaContentList>(1535, 1520);
+ }
}
diff --git a/benchmarks/rust/src/serializers/fory.rs b/benchmarks/rust/src/serializers/fory.rs
index fb4f6e6b6..0a85a5e17 100644
--- a/benchmarks/rust/src/serializers/fory.rs
+++ b/benchmarks/rust/src/serializers/fory.rs
@@ -26,7 +26,7 @@ pub struct ForySerializer {
impl ForySerializer {
pub fn new() -> Self {
- let mut fory = Fory::default().xlang(true).compatible(true);
+ let mut fory = Fory::builder().xlang(true).compatible(true).build();
register_fory_types(&mut fory).expect("register benchmark types");
Self { fory }
diff --git a/compiler/README.md b/compiler/README.md
index bdf015fec..aae1e7975 100644
--- a/compiler/README.md
+++ b/compiler/README.md
@@ -364,12 +364,12 @@ type Cat struct {
Generates structs with:
-- `#[derive(ForyObject)]` macro
+- `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]` macros
- `#[fory(...)]` field attributes
- a registration helper for namespace-based registration
```rust
-#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+#[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
pub struct Cat {
pub friend: Arc<Dog>,
#[fory(nullable = true)]
diff --git a/compiler/fory_compiler/generators/rust.py b/compiler/fory_compiler/generators/rust.py
index 47bb087f4..7395b3a07 100644
--- a/compiler/fory_compiler/generators/rust.py
+++ b/compiler/fory_compiler/generators/rust.py
@@ -38,7 +38,7 @@ from fory_compiler.ir.types import PrimitiveKind
class RustGenerator(BaseGenerator):
- """Generates Rust structs with ForyObject derive macro."""
+ """Generates Rust types with Fory derive macros."""
language_name = "rust"
file_extension = ".rs"
@@ -200,7 +200,7 @@ class RustGenerator(BaseGenerator):
uses: Set[str] = set()
# Collect uses (including from nested types)
- uses.add("use fory::{Fory, ForyObject}")
+ uses.add("use fory::{Fory, ForyEnum, ForyStruct, ForyUnion}")
uses.add("use std::sync::OnceLock")
for message in self.schema.messages:
@@ -337,7 +337,7 @@ class RustGenerator(BaseGenerator):
type_name = enum.name
# Derive macros
- lines.append("#[derive(ForyObject, Debug, Clone, PartialEq, Default)]")
+ lines.append("#[derive(ForyEnum, Debug, Clone, PartialEq, Default)]")
lines.append("#[repr(i32)]")
lines.append(f"pub enum {type_name} {{")
@@ -369,7 +369,7 @@ class RustGenerator(BaseGenerator):
comment = self.format_type_id_comment(union, "//")
if comment:
lines.append(comment)
- derives = ["ForyObject", "Debug"]
+ derives = ["ForyUnion", "Debug"]
if not has_any:
derives.extend(["Clone", "PartialEq"])
lines.append(f"#[derive({', '.join(derives)})]")
@@ -421,7 +421,7 @@ class RustGenerator(BaseGenerator):
if comment:
lines.append(comment)
needs_safe_debug = self.message_needs_safe_debug(message)
- derives = ["ForyObject"]
+ derives = ["ForyStruct"]
if not needs_safe_debug:
derives.append("Debug")
if not self.message_has_any(message):
@@ -600,14 +600,13 @@ class RustGenerator(BaseGenerator):
attrs.append("nullable = true")
if field.ref:
attrs.append("ref = true")
- encoding = self.get_encoding_attr(field.field_type)
- if encoding:
- attrs.append(encoding)
- array_attr = self.get_array_attr(field)
- if array_attr:
- attrs.append(array_attr)
- if self.is_union_type(field.field_type, parent_stack):
- attrs.append('type_id = "union"')
+ nested_attr = self.get_nested_field_attr(field.field_type)
+ if nested_attr:
+ attrs.append(nested_attr)
+ else:
+ encoding = self.get_encoding_attr(field.field_type)
+ if encoding:
+ attrs.append(encoding)
if attrs:
lines.append(f"#[fory({', '.join(attrs)})]")
@@ -647,26 +646,43 @@ class RustGenerator(BaseGenerator):
PrimitiveKind.UINT32,
PrimitiveKind.UINT64,
):
- return 'encoding = "fixed"'
+ return "encoding = fixed"
if kind in (PrimitiveKind.TAGGED_INT64, PrimitiveKind.TAGGED_UINT64):
- return 'encoding = "tagged"'
+ return "encoding = tagged"
return None
- def get_array_attr(self, field: Field) -> Optional[str]:
- """Return type_id attribute for int8/uint8 arrays."""
- if not isinstance(field.field_type, ListType):
- return None
- if field.element_optional or field.element_ref:
- return None
- element_type = field.field_type.element_type
- if not isinstance(element_type, PrimitiveType):
+ def get_nested_field_attr(self, field_type: FieldType) -> Optional[str]:
+ """Return nested list/map field configuration."""
+ if isinstance(field_type, ListType):
+ element_attrs = self.get_nested_value_attrs(field_type.element_type)
+ if element_attrs:
+ return f"list(element({', '.join(element_attrs)}))"
return None
- if element_type.kind == PrimitiveKind.INT8:
- return 'type_id = "int8_array"'
- if element_type.kind == PrimitiveKind.UINT8:
- return 'type_id = "uint8_array"'
+ if isinstance(field_type, MapType):
+ key_attrs = self.get_nested_value_attrs(field_type.key_type)
+ value_attrs = self.get_nested_value_attrs(field_type.value_type)
+ parts = []
+ if key_attrs:
+ parts.append(f"key({', '.join(key_attrs)})")
+ if value_attrs or field_type.value_optional:
+ if field_type.value_optional:
+ value_attrs = ["nullable = true", *value_attrs]
+ parts.append(f"value({', '.join(value_attrs)})")
+ if parts:
+ return f"map({', '.join(parts)})"
return None
+ def get_nested_value_attrs(self, field_type: FieldType) -> List[str]:
+ attrs: List[str] = []
+ if isinstance(field_type, PrimitiveType):
+ encoding = self.get_encoding_attr(field_type)
+ if encoding:
+ attrs.append(encoding)
+ nested = self.get_nested_field_attr(field_type)
+ if nested:
+ attrs.append(nested)
+ return attrs
+
def is_union_type(
self, field_type: FieldType, parent_stack: Optional[List[Message]]
) -> bool:
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 232dfb89b..583cd79b6 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -336,7 +336,7 @@ Rust output is one module file per schema, for example:
Unions map to Rust enums with `#[fory(id = ...)]` case attributes:
```rust
-#[derive(ForyObject, Debug, Clone, PartialEq)]
+#[derive(ForyUnion, Debug, Clone, PartialEq)]
pub enum Animal {
#[fory(id = 1)]
Dog(Dog),
@@ -349,7 +349,7 @@ Nested types generate nested modules:
```rust
pub mod person {
- #[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+ #[derive(ForyEnum, Debug, Clone, PartialEq, Default)]
#[repr(i32)]
pub enum PhoneType {
#[default]
@@ -358,7 +358,7 @@ pub mod person {
Work = 2,
}
- #[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+ #[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
pub struct PhoneNumber {
#[fory(id = 1)]
pub number: String,
@@ -368,16 +368,16 @@ pub mod person {
}
```
-Messages derive `ForyObject` and include `to_bytes`/`from_bytes` helpers:
+Messages derive `ForyStruct` and include `to_bytes`/`from_bytes` helpers:
```rust
-#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
+#[derive(ForyStruct, Debug, Clone, PartialEq, Default)]
pub struct Person {
#[fory(id = 1)]
pub name: String,
#[fory(id = 7)]
pub phones: Vec<person::PhoneNumber>,
- #[fory(id = 8, type_id = "union")]
+ #[fory(id = 8)]
pub pet: Animal,
}
```
diff --git a/docs/guide/rust/basic-serialization.md b/docs/guide/rust/basic-serialization.md
index 9ac5e5d5a..6298ed426 100644
--- a/docs/guide/rust/basic-serialization.md
+++ b/docs/guide/rust/basic-serialization.md
@@ -23,7 +23,7 @@ This page covers basic object graph serialization and supported types.
## Object Graph Serialization
-Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyObject)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
+Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyStruct)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
**Key capabilities:**
@@ -35,10 +35,10 @@ Apache Fory™ provides automatic serialization of complex object graphs, preser
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
use std::collections::HashMap;
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
@@ -47,7 +47,7 @@ struct Person {
metadata: HashMap<String, String>,
}
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct Address {
street: String,
city: String,
@@ -128,7 +128,7 @@ assert_eq!(person, decoded);
| Macro | Description |
| ----------------------- | -------------------------- |
-| `#[derive(ForyObject)]` | Object graph serialization |
+| `#[derive(ForyStruct)]` | Object graph serialization |
| `#[derive(ForyRow)]` | Row-based serialization |
## Serialization APIs
diff --git a/docs/guide/rust/cross-language.md b/docs/guide/rust/cross-language.md
index 245f17542..3c80e2c9e 100644
--- a/docs/guide/rust/cross-language.md
+++ b/docs/guide/rust/cross-language.md
@@ -66,9 +66,9 @@ fory.register_by_namespace::<User>("com.example", "User");
```rust
use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Person {
name: String,
age: i32,
diff --git a/docs/guide/rust/custom-serializers.md b/docs/guide/rust/custom-serializers.md
index 87c552367..508e37e57 100644
--- a/docs/guide/rust/custom-serializers.md
+++ b/docs/guide/rust/custom-serializers.md
@@ -19,7 +19,7 @@ license: |
limitations under the License.
---
-For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` trait manually.
+For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` trait manually.
## When to Use Custom Serializers
@@ -74,7 +74,7 @@ impl ForyDefault for CustomType {
> **Note**: When implementing `ForyDefault` manually, ensure your type also implements `Default` if you use `Self::default()`.
> Alternatively, you can construct a default instance directly in `fory_default()`.
>
-> **Tip**: If your type supports `#[derive(ForyObject)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations.
+> **Tip**: If your type supports `#[derive(ForyStruct)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations.
## Registering Custom Serializers
@@ -149,5 +149,5 @@ let string = context.reader.read_utf8_string(len);
## Related Topics
- [Type Registration](type-registration.md) - Registering serializers
-- [Basic Serialization](basic-serialization.md) - Using ForyObject derive
+- [Basic Serialization](basic-serialization.md) - Using ForyStruct derive
- [Schema Evolution](schema-evolution.md) - Compatible mode
diff --git a/docs/guide/rust/field-configuration.md b/docs/guide/rust/field-configuration.md
index 0f9e1be2e..ac2a29762 100644
--- a/docs/guide/rust/field-configuration.md
+++ b/docs/guide/rust/field-configuration.md
@@ -36,9 +36,9 @@ Apache Fory™ provides the `#[fory(...)]` attribute macro to specify optional f
The `#[fory(...)]` attribute is placed on individual struct fields:
```rust
-use fory::ForyObject;
+use fory::ForyStruct;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Person {
#[fory(id = 0)]
name: String,
@@ -60,7 +60,7 @@ Multiple options are separated by commas.
Assigns a numeric ID to a field to minimize struct field meta size overhead:
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct User {
#[fory(id = 0)]
id: i64,
@@ -91,7 +91,7 @@ struct User {
Excludes a field from serialization:
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct User {
#[fory(id = 0)]
id: i64,
@@ -113,7 +113,7 @@ Controls whether null flags are written for fields:
```rust
use fory::{Fory, RcWeak};
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Record {
// RcWeak is nullable by default, override to non-nullable
#[fory(id = 0, nullable = false)]
@@ -143,7 +143,7 @@ Controls per-field reference tracking for shared ownership types:
use std::rc::Rc;
use std::sync::Arc;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Container {
// Enable reference tracking (default for Rc/Arc)
#[fory(id = 0, ref = true)]
@@ -174,18 +174,18 @@ struct Container {
Controls how integer fields are encoded:
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Metrics {
// Variable-length encoding (smaller for small values)
- #[fory(id = 0, encoding = "varint")]
+ #[fory(id = 0, encoding = varint)]
count: i64,
// Fixed-length encoding (consistent size)
- #[fory(id = 1, encoding = "fixed")]
+ #[fory(id = 1, encoding = fixed)]
timestamp: i64,
// Tagged encoding (includes type tag, u64 only)
- #[fory(id = 2, encoding = "tagged")]
+ #[fory(id = 2, encoding = tagged)]
value: u64,
}
```
@@ -203,28 +203,26 @@ struct Metrics {
- `fixed`: Best for values that use full range (e.g., timestamps, hashes)
- `tagged`: When type information needs to be preserved (u64 only)
-### Compress (`compress`)
+### Nested Collection Configuration
-A convenience shorthand for controlling integer encoding:
+Use `list(element(...))` and `map(key(...), value(...))` when an override belongs
+to a nested element instead of the outer field:
```rust
-#[derive(ForyObject)]
+use std::collections::HashMap;
+
+#[derive(ForyStruct)]
struct Data {
- // compress = true -> varint encoding (default)
- #[fory(id = 0, compress)]
- small_value: i32,
+ #[fory(list(element(encoding = fixed)))]
+ fixed_values: Vec<i32>,
- // compress = false -> fixed encoding
- #[fory(id = 1, compress = false)]
- fixed_value: u32,
+ #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))]
+ values_by_id: HashMap<Option<i32>, Option<u64>>,
}
```
-**Notes**:
-
-- `compress` or `compress = true` is equivalent to `encoding = "varint"`
-- `compress = false` is equivalent to `encoding = "fixed"`
-- If both `compress` and `encoding` are specified, they must not conflict
+`compress` has been removed. Use `encoding = varint` or `encoding = fixed`
+directly.
## Type Classification
@@ -245,10 +243,10 @@ Fory classifies field types to determine default behavior:
## Complete Example
```rust
-use fory::ForyObject;
+use fory::ForyStruct;
use std::rc::Rc;
-#[derive(ForyObject, Default)]
+#[derive(ForyStruct, Default)]
struct Document {
// Required fields with tag IDs
#[fory(id = 0)]
@@ -270,11 +268,11 @@ struct Document {
related: Option<Rc<Document>>,
// Counter with varint encoding (small values)
- #[fory(id = 5, encoding = "varint")]
+ #[fory(id = 5, encoding = varint)]
view_count: u64,
// Timestamp with fixed encoding (full range values)
- #[fory(id = 6, encoding = "fixed")]
+ #[fory(id = 6, encoding = fixed)]
created_at: i64,
// Skip sensitive field
@@ -307,7 +305,7 @@ Invalid configurations are caught at compile time:
```rust
// Error: duplicate field IDs
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Bad {
#[fory(id = 0)]
field1: String,
@@ -317,16 +315,16 @@ struct Bad {
}
// Error: invalid id value
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Bad2 {
#[fory(id = -2)] // Compile error: id must be >= -1
field: String,
}
-// Error: conflicting encoding attributes
-#[derive(ForyObject)]
+// Error: removed shorthand
+#[derive(ForyStruct)]
struct Bad3 {
- #[fory(compress = true, encoding = "fixed")] // Compile error: conflict
+ #[fory(compress = false)] // Compile error: use encoding = fixed
field: i32,
}
```
@@ -336,18 +334,18 @@ struct Bad3 {
When serializing data to be read by other languages (Java, C++, Go, Python), use field configuration to match encoding expectations:
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct CrossLangData {
// Matches Java Integer with varint
- #[fory(id = 0, encoding = "varint")]
+ #[fory(id = 0, encoding = varint)]
int_var: i32,
// Matches Java Integer with fixed
- #[fory(id = 1, encoding = "fixed")]
+ #[fory(id = 1, encoding = fixed)]
int_fixed: i32,
// Matches Java Long with tagged encoding
- #[fory(id = 2, encoding = "tagged")]
+ #[fory(id = 2, encoding = tagged)]
long_tagged: u64,
// Nullable pointer matches Java nullable reference
@@ -362,7 +360,7 @@ Compatible mode supports schema evolution. It is recommended to configure field
```rust
// Version 1
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct DataV1 {
#[fory(id = 0)]
id: i64,
@@ -372,7 +370,7 @@ struct DataV1 {
}
// Version 2: Added new field
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct DataV2 {
#[fory(id = 0)]
id: i64,
@@ -390,7 +388,7 @@ Data serialized with V1 can be deserialized with V2 (new field will be `None`).
Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead):
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Data {
id: i64,
name: String,
@@ -411,7 +409,7 @@ You **need to configure fields** when:
```rust
// Xlang mode: explicit configuration required
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct User {
#[fory(id = 0)]
name: String, // Non-nullable by default
@@ -444,14 +442,15 @@ struct User {
## Options Reference
-| Option | Syntax | Description | Valid For |
-| ---------- | ---------------------------------- | ------------------------------------ | -------------------------- |
-| `id` | `id = N` | Field tag ID to reduce metadata size | All fields |
-| `skip` | `skip` | Exclude field from serialization | All fields |
-| `nullable` | `nullable` or `nullable = bool` | Control null flag writing | All fields |
-| `ref` | `ref` or `ref = bool` | Control reference tracking | `Rc`, `Arc`, weak types |
-| `encoding` | `encoding = "varint/fixed/tagged"` | Integer encoding method | `i32`, `u32`, `i64`, `u64` |
-| `compress` | `compress` or `compress = bool` | Shorthand for varint/fixed | `i32`, `u32` |
+| Option | Syntax | Description | Valid For |
+| ---------- | -------------------------------- | ------------------------------------ | -------------------------- |
+| `id` | `id = N` | Field tag ID to reduce metadata size | All fields |
+| `skip` | `skip` | Exclude field from serialization | All fields |
+| `nullable` | `nullable` or `nullable = bool` | Control null flag writing | All fields |
+| `ref` | `ref` or `ref = bool` | Control reference tracking | `Rc`, `Arc`, weak types |
+| `encoding` | `encoding = varint/fixed/tagged` | Integer encoding method | `i32`, `u32`, `i64`, `u64` |
+| `list` | `list(element(...))` | Element field configuration | `Vec<T>` |
+| `map` | `map(key(...), value(...))` | Key/value field configuration | `HashMap<K, V>` |
## Related Topics
diff --git a/docs/guide/rust/index.md b/docs/guide/rust/index.md
index d52a45d52..3b32eac6d 100644
--- a/docs/guide/rust/index.md
+++ b/docs/guide/rust/index.md
@@ -54,9 +54,9 @@ fory = "0.13"
```rust
use fory::{Fory, Error, Reader};
-use fory::ForyObject;
+use fory::ForyStruct;
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct User {
name: String,
age: i32,
@@ -96,11 +96,11 @@ Apache Fory™ Rust is fully thread-safe: `Fory` implements both `Send` and `Syn
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
use std::sync::Arc;
use std::thread;
-#[derive(ForyObject, Clone, Copy, Debug, PartialEq)]
+#[derive(ForyStruct, Clone, Copy, Debug, PartialEq)]
struct Item {
value: i32,
}
@@ -152,7 +152,7 @@ fory-core/ # Core serialization engine
fory-derive/ # Procedural macros
├── src/
-│ ├── object/ # ForyObject macro
+│ ├── object/ # ForyStruct macro
│ └── fory_row.rs # ForyRow macro
```
diff --git a/docs/guide/rust/polymorphism.md b/docs/guide/rust/polymorphism.md
index 2afff965d..f3b7bbbb1 100644
--- a/docs/guide/rust/polymorphism.md
+++ b/docs/guide/rust/polymorphism.md
@@ -33,14 +33,14 @@ Apache Fory™ supports polymorphic serialization through trait objects, enablin
```rust
use fory::{Fory, register_trait_type};
use fory::Serializer;
-use fory::ForyObject;
+use fory::ForyStruct;
trait Animal: Serializer {
fn speak(&self) -> String;
fn name(&self) -> &str;
}
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Dog { name: String, breed: String }
impl Animal for Dog {
@@ -48,7 +48,7 @@ impl Animal for Dog {
fn name(&self) -> &str { &self.name }
}
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Cat { name: String, color: String }
impl Animal for Cat {
@@ -59,7 +59,7 @@ impl Animal for Cat {
// Register trait implementations
register_trait_type!(Animal, Dog, Cat);
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Zoo {
star_animal: Box<dyn Animal>,
}
@@ -146,7 +146,7 @@ use std::sync::Arc;
use std::rc::Rc;
use std::collections::HashMap;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct AnimalShelter {
animals_rc: Vec<Rc<dyn Animal>>,
animals_arc: Vec<Arc<dyn Animal>>,
diff --git a/docs/guide/rust/references.md b/docs/guide/rust/references.md
index e2dcb49da..f18d11cd1 100644
--- a/docs/guide/rust/references.md
+++ b/docs/guide/rust/references.md
@@ -90,12 +90,12 @@ To serialize circular references like parent-child relationships or doubly-linke
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
use fory::RcWeak;
use std::rc::Rc;
use std::cell::RefCell;
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct Node {
value: i32,
parent: RcWeak<RefCell<Node>>,
@@ -143,11 +143,11 @@ for child in &decoded.borrow().children {
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::ForyStruct;
use fory::ArcWeak;
use std::sync::{Arc, Mutex};
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Node {
val: i32,
parent: ArcWeak<Mutex<Node>>,
diff --git a/docs/guide/rust/row-format.md b/docs/guide/rust/row-format.md
index 1728d4551..c0728f746 100644
--- a/docs/guide/rust/row-format.md
+++ b/docs/guide/rust/row-format.md
@@ -110,9 +110,9 @@ assert_eq!(prefs.values().get(0), "en");
| Memory usage | Full object graph in memory | Only accessed fields in memory |
| Suitable for | Small objects, full access | Large objects, selective access |
-## ForyRow vs ForyObject
+## ForyRow vs ForyStruct
-| Feature | `#[derive(ForyRow)]` | `#[derive(ForyObject)]` |
+| Feature | `#[derive(ForyRow)]` | `#[derive(ForyStruct)]` |
| --------------- | --------------------- | -------------------------- |
| Deserialization | Zero-copy, lazy | Full object reconstruction |
| Field access | Direct from binary | Normal struct access |
diff --git a/docs/guide/rust/schema-evolution.md b/docs/guide/rust/schema-evolution.md
index 6b51ba1ac..e6dbfced1 100644
--- a/docs/guide/rust/schema-evolution.md
+++ b/docs/guide/rust/schema-evolution.md
@@ -27,17 +27,17 @@ Enable schema evolution with `compatible(true)`:
```rust
use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
use std::collections::HashMap;
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct PersonV1 {
name: String,
age: i32,
address: String,
}
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct PersonV2 {
name: String,
age: i32,
@@ -74,9 +74,9 @@ assert_eq!(person_v2.phone, None);
If a struct schema is stable and will not change, you can disable evolution for that struct to avoid compatible metadata overhead. Use `#[fory(evolving = false)]`:
```rust
-use fory::ForyObject;
+use fory::ForyStruct;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
#[fory(evolving = false)]
struct StableMessage {
id: i32,
@@ -108,9 +108,9 @@ Apache Fory™ supports three types of enum variants with full schema evolution
- **Named**: Struct-like variants (`Event::Click { x: i32, y: i32 }`)
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
-#[derive(Default, ForyObject, Debug, PartialEq)]
+#[derive(Default, ForyStruct, Debug, PartialEq)]
enum Value {
#[default]
Null,
@@ -136,17 +136,17 @@ Compatible mode enables robust schema evolution with variant type encoding (2 bi
- `0b0` = Unit, `0b1` = Unnamed, `0b10` = Named
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
// Old version
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
enum OldEvent {
Click { x: i32, y: i32 },
Scroll { delta: f64 },
}
// New version - added field and variant
-#[derive(Default, ForyObject)]
+#[derive(Default, ForyStruct)]
enum NewEvent {
#[default]
Unknown,
diff --git a/docs/guide/rust/troubleshooting.md b/docs/guide/rust/troubleshooting.md
index 6d99c551b..90afa1369 100644
--- a/docs/guide/rust/troubleshooting.md
+++ b/docs/guide/rust/troubleshooting.md
@@ -68,10 +68,10 @@ Reset the variable afterwards to avoid aborting user-facing code paths.
### Struct Field Tracing
-Add the `#[fory(debug)]` attribute alongside `#[derive(ForyObject)]` to emit hook invocations:
+Add the `#[fory(debug)]` attribute alongside `#[derive(ForyStruct)]` to emit hook invocations:
```rust
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
#[fory(debug)]
struct MyStruct {
field1: i32,
diff --git a/docs/guide/rust/type-registration.md b/docs/guide/rust/type-registration.md
index d44d0094a..d2849ac88 100644
--- a/docs/guide/rust/type-registration.md
+++ b/docs/guide/rust/type-registration.md
@@ -27,9 +27,9 @@ Register types with a numeric ID for fast, compact serialization:
```rust
use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct User {
name: String,
age: i32,
diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md
index 5bd3494e0..516039f3f 100644
--- a/docs/guide/xlang/field-nullability.md
+++ b/docs/guide/xlang/field-nullability.md
@@ -137,9 +137,9 @@ fory.register_type(Person, typename="example.Person")
### Rust
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Person {
// Non-nullable by default
name: String,
diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md
index d7729c240..6b5b28698 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -163,10 +163,10 @@ To disable reference tracking for C++ entirely, set
#### Rust: Field Attributes
```rust
-use fory::ForyObject;
+use fory::ForyStruct;
use std::rc::Rc;
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Document {
title: String,
diff --git a/docs/guide/xlang/getting-started.md b/docs/guide/xlang/getting-started.md
index 9cc41d5fe..9ebb01f6c 100644
--- a/docs/guide/xlang/getting-started.md
+++ b/docs/guide/xlang/getting-started.md
@@ -165,9 +165,9 @@ fory.RegisterNamedStruct(Person{}, "example.Person")
**Rust:**
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Person {
name: String,
age: i32,
diff --git a/docs/guide/xlang/serialization.md b/docs/guide/xlang/serialization.md
index 17fbd5c5c..1257307db 100644
--- a/docs/guide/xlang/serialization.md
+++ b/docs/guide/xlang/serialization.md
@@ -359,17 +359,17 @@ console.log(result);
```rust
use chrono::{NaiveDate, NaiveDateTime};
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
use std::collections::HashMap;
#[test]
fn complex_struct() {
- #[derive(ForyObject, Debug, PartialEq)]
+ #[derive(ForyStruct, Debug, PartialEq)]
struct Animal {
category: String,
}
- #[derive(ForyObject, Debug, PartialEq)]
+ #[derive(ForyStruct, Debug, PartialEq)]
struct Person {
c1: Vec<u8>, // binary
c2: Vec<i16>, // primitive array
diff --git a/rust/README.md b/rust/README.md
index 897e24408..6fd35dc00 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -40,9 +40,9 @@ fory = "0.16"
```rust
use fory::{Fory, Error, Reader};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct User {
name: String,
age: i32,
@@ -80,7 +80,7 @@ fn main() -> Result<(), Error> {
### 1. Object Graph Serialization
-Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyObject)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
+Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyStruct)]` macro generates efficient serialization code at compile time, eliminating runtime overhead.
**Key capabilities:**
@@ -92,10 +92,10 @@ Apache Fory™ provides automatic serialization of complex object graphs, preser
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
use std::collections::HashMap;
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
@@ -104,7 +104,7 @@ struct Person {
metadata: HashMap<String, String>,
}
-#[derive(ForyObject, Debug, PartialEq)]
+#[derive(ForyStruct, Debug, PartialEq)]
struct Address {
street: String,
city: String,
@@ -184,12 +184,12 @@ To serialize circular references like parent-child relationships or doubly-linke
```rust
use fory::{Fory, Error};
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
use fory::RcWeak;
use std::rc::Rc;
use std::cell::RefCell;
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct Node {
value: i32,
parent: RcWeak<RefCell<Node>>,
@@ -250,14 +250,14 @@ Apache Fory™ supports polymorphic serialization through trait objects, enablin
```rust
use fory::{Fory, register_trait_type};
use fory::Serializer;
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
trait Animal: Serializer {
fn speak(&self) -> String;
fn name(&self) -> &str;
}
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Dog { name: String, breed: String }
impl Animal for Dog {
@@ -265,7 +265,7 @@ impl Animal for Dog {
fn name(&self) -> &str { &self.name }
}
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Cat { name: String, color: String }
impl Animal for Cat {
@@ -276,7 +276,7 @@ impl Animal for Cat {
// Register trait implementations
register_trait_type!(Animal, Dog, Cat);
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
struct Zoo {
star_animal: Box<dyn Animal>,
}
@@ -320,17 +320,17 @@ Apache Fory™ supports schema evolution in **Compatible mode**, allowing serial
```rust
use fory::Fory;
-use fory::ForyObject;
+use fory::{ForyEnum, ForyStruct, ForyUnion};
use std::collections::HashMap;
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct PersonV1 {
name: String,
age: i32,
address: String,
}
-#[derive(ForyObject, Debug)]
+#[derive(ForyStruct, Debug)]
struct PersonV2 {
name: String,
age: i32,
@@ -380,9 +380,9 @@ Apache Fory™ supports three types of enum variants with full schema evolution
- Automatic type mismatch handling
```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyEnum, ForyStruct, ForyUnion};
-#[derive(Default, ForyObject, Debug, PartialEq)]
+#[derive(Default, ForyStruct, Debug, PartialEq)]
enum Value {
#[default]
Null,
@@ -449,7 +449,7 @@ assert_eq!(data, decoded);
### 7. Custom Serializers
-For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` trait manually. This is useful for:
+For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` trait manually. This is useful for:
- External types from other crates
- Types with special serialization requirements
diff --git a/rust/fory-derive/src/object/mod.rs b/rust/fory-core/build.rs
similarity index 73%
copy from rust/fory-derive/src/object/mod.rs
copy to rust/fory-core/build.rs
index d546adb45..fbd23e892 100644
--- a/rust/fory-derive/src/object/mod.rs
+++ b/rust/fory-core/build.rs
@@ -15,12 +15,13 @@
// specific language governing permissions and limitations
// under the License.
-mod derive_enum;
-pub(crate) mod field_meta;
-mod misc;
-pub(crate) mod read;
-mod serializer;
-pub(crate) mod util;
-mod write;
+fn main() {
+ println!("cargo:rerun-if-env-changed=ENABLE_FORY_DEBUG_OUTPUT");
-pub use serializer::derive_serializer;
+ if matches!(
+ std::env::var("ENABLE_FORY_DEBUG_OUTPUT").as_deref(),
+ Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("on")
+ ) {
+ println!("cargo:rustc-cfg=fory_debug_output");
+ }
+}
diff --git a/rust/fory-core/src/context.rs b/rust/fory-core/src/context.rs
index 059f8935b..acd567029 100644
--- a/rust/fory-core/src/context.rs
+++ b/rust/fory-core/src/context.rs
@@ -26,6 +26,7 @@ use crate::resolver::meta_resolver::{MetaReaderResolver, MetaWriterResolver};
use crate::resolver::meta_string_resolver::{MetaStringReaderResolver, MetaStringWriterResolver};
use crate::resolver::{RefReader, RefWriter};
use crate::resolver::{TypeInfo, TypeResolver};
+use crate::serializer::StructSerializer;
use crate::type_id as types;
use crate::TypeId;
use std::rc::Rc;
@@ -222,6 +223,47 @@ impl<'a> WriteContext<'a> {
.write_type_meta(&mut self.writer, type_id, &self.type_resolver)
}
+ /// Write generated struct type info without Rust TypeId hash lookups.
+ #[inline(always)]
+ pub fn write_struct_type_info<T: StructSerializer>(&mut self) -> Result<(), Error> {
+ let rust_type_id = std::any::TypeId::of::<T>();
+ let type_index = T::fory_type_index();
+ let type_id = self.type_resolver.get_type_id_by_index(type_index)?;
+ match type_id {
+ TypeId::STRUCT | TypeId::ENUM | TypeId::EXT | TypeId::TYPED_UNION => {
+ self.writer.write_u8(type_id as u8);
+ let user_type_id = self
+ .type_resolver
+ .get_user_type_id_by_index(&rust_type_id, type_index)?;
+ self.writer.write_var_u32(user_type_id);
+ }
+ TypeId::COMPATIBLE_STRUCT | TypeId::NAMED_COMPATIBLE_STRUCT => {
+ self.writer.write_u8(type_id as u8);
+ self.meta_resolver.write_type_meta_fast(
+ &mut self.writer,
+ rust_type_id,
+ type_index,
+ &self.type_resolver,
+ )?;
+ }
+ TypeId::NAMED_ENUM | TypeId::NAMED_EXT | TypeId::NAMED_STRUCT | TypeId::NAMED_UNION
+ if self.is_share_meta() =>
+ {
+ self.writer.write_u8(type_id as u8);
+ self.meta_resolver.write_type_meta_fast(
+ &mut self.writer,
+ rust_type_id,
+ type_index,
+ &self.type_resolver,
+ )?;
+ }
+ _ => {
+ self.write_any_type_info(type_id as u32, rust_type_id)?;
+ }
+ }
+ Ok(())
+ }
+
pub fn write_any_type_info(
&mut self,
fory_type_id: u32,
diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs
index bc7953441..f82d2251c 100644
--- a/rust/fory-core/src/fory.rs
+++ b/rust/fory-core/src/fory.rs
@@ -360,9 +360,9 @@ impl ForyBuilder {
///
/// ```rust, ignore
/// use fory::Fory;
-/// use fory::ForyObject;
+/// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
/// struct User {
/// name: String,
/// age: u32,
@@ -519,9 +519,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory::Fory;
- /// use fory::ForyObject;
+ /// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct Point { x: i32, y: i32 }
///
/// let fory = Fory::default();
@@ -573,9 +573,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory_core::Fory;
- /// use fory_derive::ForyObject;
+ /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct Point {
/// x: i32,
/// y: i32,
@@ -593,9 +593,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory_core::Fory;
- /// use fory_derive::ForyObject;
+ /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject, PartialEq, Debug)]
+ /// #[derive(ForyStruct, PartialEq, Debug)]
/// struct Point {
/// x: i32,
/// y: i32,
@@ -634,9 +634,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory_core::Fory;
- /// use fory_derive::ForyObject;
+ /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct Point {
/// x: i32,
/// y: i32,
@@ -774,9 +774,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory::Fory;
- /// use fory::ForyObject;
+ /// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct User { name: String, age: u32 }
///
/// let mut fory = Fory::default();
@@ -823,9 +823,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory::Fory;
- /// use fory::ForyObject;
+ /// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct User { name: String, age: u32 }
///
/// let mut fory = Fory::default();
@@ -872,9 +872,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory::Fory;
- /// use fory::ForyObject;
+ /// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct User { name: String, age: u32 }
///
/// let mut fory = Fory::default();
@@ -1028,9 +1028,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory::Fory;
- /// use fory::ForyObject;
+ /// use fory::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct Point { x: i32, y: i32 }
///
/// let fory = Fory::default();
@@ -1081,9 +1081,9 @@ impl Fory {
///
/// ```rust, ignore
/// use fory_core::{Fory, Reader};
- /// use fory_derive::ForyObject;
+ /// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
///
- /// #[derive(ForyObject)]
+ /// #[derive(ForyStruct)]
/// struct Point { x: i32, y: i32 }
///
/// let fory = Fory::default();
diff --git a/rust/fory-core/src/lib.rs b/rust/fory-core/src/lib.rs
index 272cbd097..4d1ac219b 100644
--- a/rust/fory-core/src/lib.rs
+++ b/rust/fory-core/src/lib.rs
@@ -73,16 +73,16 @@
//!
//! ```rust,ignore
//! use fory_core::{Fory, register_trait_type, Serializer};
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
//!
//! trait Animal: Serializer {
//! fn speak(&self) -> String;
//! }
//!
-//! #[derive(ForyObject, Debug)]
+//! #[derive(ForyStruct, Debug)]
//! struct Dog { name: String }
//!
-//! #[derive(ForyObject, Debug)]
+//! #[derive(ForyStruct, Debug)]
//! struct Cat { name: String }
//!
//! impl Animal for Dog {
@@ -95,7 +95,7 @@
//!
//! register_trait_type!(Animal, Dog, Cat);
//!
-//! #[derive(ForyObject)]
+//! #[derive(ForyStruct)]
//! struct Zoo {
//! star_animal: Box<dyn Animal>,
//! }
@@ -122,11 +122,11 @@
//!
//! ```rust,ignore
//! # use fory_core::Serializer;
-//! # use fory_derive::ForyObject;
+//! # use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
//! # use std::rc::Rc;
//! # use std::sync::Arc;
//! # trait Animal: Serializer { fn speak(&self) -> String; }
-//! #[derive(ForyObject)]
+//! #[derive(ForyStruct)]
//! struct Shelter {
//! animals_rc: Vec<Rc<dyn Animal>>,
//! animals_arc: Vec<Arc<dyn Animal>>,
diff --git a/rust/fory-core/src/meta/type_meta.rs b/rust/fory-core/src/meta/type_meta.rs
index 035e7cb32..fd14d4603 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -96,41 +96,53 @@ static FIELD_NAME_ENCODINGS: &[Encoding] = &[
Encoding::LowerUpperDigitSpecial,
];
-#[derive(Debug, Eq, Clone)]
+#[derive(Eq, Clone)]
pub struct FieldType {
pub type_id: u32,
pub user_type_id: u32,
pub nullable: bool,
pub track_ref: bool,
pub generics: Vec<FieldType>,
+ compatible_fingerprint: u64,
}
impl FieldType {
pub fn new(type_id: u32, nullable: bool, generics: Vec<FieldType>) -> Self {
- FieldType {
- type_id,
- user_type_id: NO_USER_TYPE_ID,
- nullable,
- track_ref: false,
- generics,
- }
+ Self::new_with_user_type_id(type_id, NO_USER_TYPE_ID, nullable, false, generics)
}
- pub fn new_with_ref(
+ pub(crate) fn new_with_user_type_id(
type_id: u32,
+ user_type_id: u32,
nullable: bool,
track_ref: bool,
generics: Vec<FieldType>,
) -> Self {
- FieldType {
+ let compatible_fingerprint = compute_field_type_fingerprint(type_id, &generics);
+ Self {
type_id,
- user_type_id: NO_USER_TYPE_ID,
+ user_type_id,
nullable,
track_ref,
generics,
+ compatible_fingerprint,
}
}
+ pub fn new_with_ref(
+ type_id: u32,
+ nullable: bool,
+ track_ref: bool,
+ generics: Vec<FieldType>,
+ ) -> Self {
+ Self::new_with_user_type_id(type_id, NO_USER_TYPE_ID, nullable, track_ref, generics)
+ }
+
+ #[inline(always)]
+ pub(crate) fn compatible_fingerprint(&self) -> u64 {
+ self.compatible_fingerprint
+ }
+
fn to_bytes(&self, writer: &mut Writer, write_flag: bool, nullable: bool) -> Result<(), Error> {
let mut header = self.type_id;
if header == NAMED_ENUM {
@@ -203,32 +215,28 @@ impl FieldType {
Ok(match type_id {
x if x == TypeId::LIST as u32 || x == TypeId::SET as u32 => {
let generic = Self::from_bytes(reader, true, None)?;
- Self {
+ Self::new_with_user_type_id(
type_id,
user_type_id,
- nullable: _nullable,
- track_ref: _ref_tracking,
- generics: vec![generic],
- }
+ _nullable,
+ _ref_tracking,
+ vec![generic],
+ )
}
x if x == TypeId::MAP as u32 => {
let key_generic = Self::from_bytes(reader, true, None)?;
let val_generic = Self::from_bytes(reader, true, None)?;
- Self {
+ Self::new_with_user_type_id(
type_id,
user_type_id,
- nullable: _nullable,
- track_ref: _ref_tracking,
- generics: vec![key_generic, val_generic],
- }
+ _nullable,
+ _ref_tracking,
+ vec![key_generic, val_generic],
+ )
+ }
+ _ => {
+ Self::new_with_user_type_id(type_id, user_type_id, _nullable, _ref_tracking, vec![])
}
- _ => Self {
- type_id,
- user_type_id,
- nullable: _nullable,
- track_ref: _ref_tracking,
- generics: vec![],
- },
})
}
}
@@ -397,6 +405,69 @@ fn fnv1a_hash_u32(hash: u64, value: u32) -> u64 {
fnv1a_hash_bytes(hash, &value.to_le_bytes())
}
+#[inline(always)]
+fn fnv1a_hash_u64(hash: u64, value: u64) -> u64 {
+ fnv1a_hash_bytes(hash, &value.to_le_bytes())
+}
+
+#[inline(always)]
+fn compatible_fingerprint_type_id(type_id: u32) -> u32 {
+ match type_id {
+ _ if type_id == STRUCT
+ || type_id == COMPATIBLE_STRUCT
+ || type_id == NAMED_STRUCT
+ || type_id == NAMED_COMPATIBLE_STRUCT
+ || type_id == UNKNOWN =>
+ {
+ STRUCT
+ }
+ _ if type_id == ENUM || type_id == NAMED_ENUM => ENUM,
+ _ if type_id == EXT || type_id == NAMED_EXT => EXT,
+ _ if type_id == BINARY || type_id == INT8_ARRAY || type_id == UINT8_ARRAY => BINARY,
+ _ if type_id == TypeId::INT32 as u32 || type_id == TypeId::VARINT32 as u32 => {
+ TypeId::VARINT32 as u32
+ }
+ _ if type_id == TypeId::INT64 as u32
+ || type_id == TypeId::VARINT64 as u32
+ || type_id == TypeId::TAGGED_INT64 as u32 =>
+ {
+ TypeId::VARINT64 as u32
+ }
+ _ if type_id == TypeId::UINT32 as u32 || type_id == TypeId::VAR_UINT32 as u32 => {
+ TypeId::VAR_UINT32 as u32
+ }
+ _ if type_id == TypeId::UINT64 as u32
+ || type_id == TypeId::VAR_UINT64 as u32
+ || type_id == TypeId::TAGGED_UINT64 as u32 =>
+ {
+ TypeId::VAR_UINT64 as u32
+ }
+ _ => type_id,
+ }
+}
+
+#[inline(always)]
+fn compute_field_type_fingerprint(type_id: u32, generics: &[FieldType]) -> u64 {
+ let mut hash = fnv1a_hash_u32(FNV_OFFSET_BASIS, compatible_fingerprint_type_id(type_id));
+ hash = fnv1a_hash_u32(hash, generics.len() as u32);
+ for generic in generics {
+ hash = fnv1a_hash_u64(hash, generic.compatible_fingerprint());
+ }
+ hash
+}
+
+impl std::fmt::Debug for FieldType {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("FieldType")
+ .field("type_id", &self.type_id)
+ .field("user_type_id", &self.user_type_id)
+ .field("nullable", &self.nullable)
+ .field("track_ref", &self.track_ref)
+ .field("generics", &self.generics)
+ .finish()
+ }
+}
+
#[inline(always)]
fn hash_field_type(mut hash: u64, field_type: &FieldType) -> u64 {
let type_id = normalize_type_id_for_eq(field_type.type_id);
@@ -951,25 +1022,15 @@ impl TypeMeta {
if field.field_name.is_empty() {
field.field_name = local_info.field_name.clone();
}
- // Use FieldType comparison which normalizes type IDs for cross-language
- // schema evolution (e.g., UNKNOWN=0 matches STRUCT variants)
- if field.field_type != local_info.field_type {
- if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
- eprintln!(
- "[fory-debug] field type mismatch: name={}, remote_type={:?}, local_type={:?}",
- field.field_name, field.field_type, local_info.field_type
- );
- }
- field.field_id = -1; // Type mismatch, skip
- } else {
- // Assign SORTED INDEX for matching in generated code
- field.field_id = sorted_index as i16;
- if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
- eprintln!(
- "[fory-debug] matched field: name={}, assigned_field_id={}",
- field.field_name, field.field_id
- );
- }
+ // Assign SORTED INDEX for generated code. The generated field
+ // codec inspects the remote FieldType and either consumes it or
+ // asks the caller to skip the remote payload.
+ field.field_id = sorted_index as i16;
+ if crate::util::ENABLE_FORY_DEBUG_OUTPUT {
+ eprintln!(
+ "[fory-debug] matched field: name={}, assigned_field_id={}, remote_type={:?}, local_type={:?}",
+ field.field_name, field.field_id, field.field_type, local_info.field_type
+ );
}
}
None => {
diff --git a/rust/fory-core/src/resolver/meta_resolver.rs b/rust/fory-core/src/resolver/meta_resolver.rs
index 38e07e457..43f9851f4 100644
--- a/rust/fory-core/src/resolver/meta_resolver.rs
+++ b/rust/fory-core/src/resolver/meta_resolver.rs
@@ -30,9 +30,12 @@ use std::rc::Rc;
#[derive(Default)]
pub struct MetaWriterResolver {
type_id_index_map: HashMap<std::any::TypeId, usize>,
+ type_index_index_map: Vec<usize>,
+ next_index: usize,
}
const MAX_PARSED_NUM_TYPE_DEFS: usize = 8192;
+const NO_WRITTEN_TYPE_INDEX: usize = usize::MAX;
#[allow(dead_code)]
impl MetaWriterResolver {
@@ -52,7 +55,8 @@ impl MetaWriterResolver {
}
None => {
// New type: index << 1, LSB=0, followed by TypeMeta bytes inline
- let index = self.type_id_index_map.len();
+ let index = self.next_index;
+ self.next_index += 1;
writer.write_var_u32((index as u32) << 1);
self.type_id_index_map.insert(type_id, index);
// Write TypeMeta bytes inline
@@ -63,9 +67,41 @@ impl MetaWriterResolver {
Ok(())
}
+ /// Write type meta by generated struct type index, avoiding Rust TypeId hash lookup.
+ #[inline(always)]
+ pub fn write_type_meta_fast(
+ &mut self,
+ writer: &mut Writer,
+ type_id: std::any::TypeId,
+ type_index: u32,
+ type_resolver: &TypeResolver,
+ ) -> Result<(), Error> {
+ let type_index = type_index as usize;
+ if let Some(&index) = self.type_index_index_map.get(type_index) {
+ if index != NO_WRITTEN_TYPE_INDEX {
+ writer.write_var_u32(((index as u32) << 1) | 1);
+ return Ok(());
+ }
+ }
+
+ let index = self.next_index;
+ self.next_index += 1;
+ writer.write_var_u32((index as u32) << 1);
+ if type_index >= self.type_index_index_map.len() {
+ self.type_index_index_map
+ .resize(type_index + 1, NO_WRITTEN_TYPE_INDEX);
+ }
+ self.type_index_index_map[type_index] = index;
+ let type_meta = type_resolver.get_type_meta_by_index_ref(&type_id, type_index as u32)?;
+ writer.write_bytes(type_meta.get_bytes());
+ Ok(())
+ }
+
#[inline(always)]
pub fn reset(&mut self) {
self.type_id_index_map.clear();
+ self.type_index_index_map.clear();
+ self.next_index = 0;
}
}
diff --git a/rust/fory-core/src/serializer/arc.rs b/rust/fory-core/src/serializer/arc.rs
index 2277078a3..a67746820 100644
--- a/rust/fory-core/src/serializer/arc.rs
+++ b/rust/fory-core/src/serializer/arc.rs
@@ -135,10 +135,7 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for Arc<T>
}
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/serializer/box_.rs b/rust/fory-core/src/serializer/box_.rs
index 2e5c02197..da7a378eb 100644
--- a/rust/fory-core/src/serializer/box_.rs
+++ b/rust/fory-core/src/serializer/box_.rs
@@ -59,10 +59,7 @@ impl<T: Serializer + ForyDefault> Serializer for Box<T> {
#[inline(always)]
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
#[inline(always)]
diff --git a/rust/fory-core/src/serializer/codec.rs b/rust/fory-core/src/serializer/codec.rs
new file mode 100644
index 000000000..9c6a74ee8
--- /dev/null
+++ b/rust/fory-core/src/serializer/codec.rs
@@ -0,0 +1,2547 @@
+// 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.
+
+//! Internal field codecs used by macro-generated serializers.
+//!
+//! User-owned custom serialization belongs in [`crate::Serializer`]. Codecs are
+//! Fory-owned building blocks that allow generated code to apply field-local and
+//! nested collection configuration without creating wrapper value types.
+
+use crate::context::{ReadContext, WriteContext};
+use crate::error::Error;
+use crate::meta::FieldType;
+use crate::resolver::{RefFlag, RefMode, TypeResolver};
+use crate::serializer::{ForyDefault, Serializer};
+use crate::type_id::{self, need_to_write_type_for_field, TypeId, SIZE_OF_REF_AND_TYPE, UNKNOWN};
+use std::any::Any;
+use std::collections::HashMap;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::rc::Rc;
+use std::sync::Arc;
+
+const TRACKING_REF: u8 = 0b1;
+const HAS_NULL: u8 = 0b10;
+const DECL_ELEMENT_TYPE: u8 = 0b100;
+const IS_SAME_TYPE: u8 = 0b1000;
+
+const TRACKING_KEY_REF: u8 = 0b1;
+const KEY_NULL: u8 = 0b10;
+const DECL_KEY_TYPE: u8 = 0b100;
+const TRACKING_VALUE_REF: u8 = 0b1000;
+const VALUE_NULL: u8 = 0b10000;
+const DECL_VALUE_TYPE: u8 = 0b100000;
+
+const MAX_CHUNK_SIZE: u8 = 255;
+
+#[inline(always)]
+pub fn field_ref_mode(field_type: &FieldType) -> RefMode {
+ if field_type.track_ref {
+ RefMode::Tracking
+ } else if crate::serializer::util::field_need_write_ref_into(
+ field_type.type_id,
+ field_type.nullable,
+ ) {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ }
+}
+
+#[inline(always)]
+fn field_read_type_info<T: Serializer>(context: &ReadContext, field_type: &FieldType) -> bool {
+ if context.is_compatible() {
+ crate::serializer::util::field_need_read_type_info(field_type.type_id)
+ } else {
+ T::fory_is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn field_write_type_info<T: Serializer>(context: &WriteContext) -> bool {
+ if context.is_compatible() {
+ need_to_write_type_for_field(T::fory_static_type_id())
+ } else {
+ T::fory_is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn serializer_static_field_type_id<T: Serializer>() -> u32 {
+ let type_id = T::fory_static_type_id() as u32;
+ if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+ TypeId::UNION as u32
+ } else {
+ type_id
+ }
+}
+
+#[inline(always)]
+fn serializer_ref_mode<T: Serializer, const NULLABLE: bool, const TRACK_REF: bool>() -> RefMode {
+ if TRACK_REF {
+ RefMode::Tracking
+ } else if crate::serializer::util::field_need_write_ref_into(
+ serializer_static_field_type_id::<T>(),
+ NULLABLE,
+ ) {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ }
+}
+
+#[inline(always)]
+fn serializer_read_type_info<T: Serializer>(context: &ReadContext) -> bool {
+ if context.is_compatible() {
+ crate::serializer::util::field_need_read_type_info(serializer_static_field_type_id::<T>())
+ } else {
+ T::fory_is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn codec_read_type_info<T, C>(context: &ReadContext, field_type: &FieldType) -> bool
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if context.is_compatible() {
+ field_type.type_id == type_id::UNKNOWN
+ || crate::serializer::util::field_need_read_type_info(field_type.type_id)
+ } else {
+ C::is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn codec_static_field_type_id<T, C>() -> u32
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ let type_id = C::static_type_id() as u32;
+ if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+ TypeId::UNION as u32
+ } else {
+ type_id
+ }
+}
+
+#[inline(always)]
+fn codec_read_type_info_static<T, C>(context: &ReadContext) -> bool
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if context.is_compatible() {
+ let type_id = codec_static_field_type_id::<T, C>();
+ type_id == type_id::UNKNOWN || crate::serializer::util::field_need_read_type_info(type_id)
+ } else {
+ C::is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn codec_write_type_info<T, C>(context: &WriteContext) -> bool
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if context.is_compatible() {
+ need_to_write_type_for_field(C::static_type_id())
+ } else {
+ C::is_polymorphic()
+ }
+}
+
+#[inline(always)]
+fn same_numeric_family(local: u32, remote: u32) -> bool {
+ matches!(
+ (local, remote),
+ (type_id::INT32, type_id::INT32 | type_id::VARINT32)
+ | (type_id::VARINT32, type_id::INT32 | type_id::VARINT32)
+ | (
+ type_id::INT64,
+ type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+ )
+ | (
+ type_id::VARINT64,
+ type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+ )
+ | (
+ type_id::TAGGED_INT64,
+ type_id::INT64 | type_id::VARINT64 | type_id::TAGGED_INT64
+ )
+ | (type_id::UINT32, type_id::UINT32 | type_id::VAR_UINT32)
+ | (type_id::VAR_UINT32, type_id::UINT32 | type_id::VAR_UINT32)
+ | (
+ type_id::UINT64,
+ type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+ )
+ | (
+ type_id::VAR_UINT64,
+ type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+ )
+ | (
+ type_id::TAGGED_UINT64,
+ type_id::UINT64 | type_id::VAR_UINT64 | type_id::TAGGED_UINT64
+ )
+ )
+}
+
+#[inline(always)]
+fn collection_type_with_fallback_generics(type_id: u32) -> bool {
+ type_id == type_id::LIST || type_id == type_id::SET || type_id == type_id::MAP
+}
+
+#[inline(always)]
+pub fn field_types_compatible(local: &FieldType, remote: &FieldType) -> bool {
+ if local.compatible_fingerprint() == remote.compatible_fingerprint() {
+ return true;
+ }
+ if local.type_id == remote.type_id
+ && collection_type_with_fallback_generics(local.type_id)
+ && (local.generics.is_empty() || remote.generics.is_empty())
+ {
+ return true;
+ }
+ false
+}
+
+#[inline(always)]
+fn generic_field_type<'a>(
+ field_type: &'a FieldType,
+ index: usize,
+ owner: &str,
+) -> Result<&'a FieldType, Error> {
+ field_type.generics.get(index).ok_or_else(|| {
+ Error::invalid_data(format!(
+ "{owner} field metadata is missing generic type at index {index}"
+ ))
+ })
+}
+
+#[inline(always)]
+fn field_type_for_serializer<T: Serializer>(
+ type_resolver: &TypeResolver,
+ nullable: bool,
+ track_ref: bool,
+) -> Result<FieldType, Error> {
+ let mut type_id = T::fory_get_type_id(type_resolver)? as u32;
+ let mut user_type_id = u32::MAX;
+ if !type_id::is_internal_type(type_id) {
+ let type_info = T::fory_get_type_info(type_resolver)?;
+ type_id = type_info.get_type_id() as u32;
+ user_type_id = type_info.get_user_type_id();
+ if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+ type_id = TypeId::UNION as u32;
+ user_type_id = u32::MAX;
+ }
+ } else if type_id == TypeId::TYPED_UNION as u32 || type_id == TypeId::NAMED_UNION as u32 {
+ type_id = TypeId::UNION as u32;
+ }
+ Ok(FieldType::new_with_user_type_id(
+ type_id,
+ user_type_id,
+ nullable,
+ track_ref,
+ Vec::new(),
+ ))
+}
+
+pub trait Codec<T: 'static>: 'static {
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error>;
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ std::mem::size_of::<T>()
+ }
+
+ fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error>;
+
+ fn read_field(context: &mut ReadContext) -> Result<T, Error>;
+
+ fn read_compatible(
+ context: &mut ReadContext,
+ local_field_type: &FieldType,
+ remote_field_type: &FieldType,
+ ) -> Result<Option<T>, Error> {
+ if !field_types_compatible(local_field_type, remote_field_type) {
+ return Ok(None);
+ }
+ Self::read_field_with_type(context, remote_field_type).map(Some)
+ }
+
+ fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error>;
+
+ fn read_data(context: &mut ReadContext) -> Result<T, Error>;
+
+ #[inline(always)]
+ fn read_data_with_type(
+ context: &mut ReadContext,
+ _remote_data_type: &FieldType,
+ ) -> Result<T, Error> {
+ Self::read_data(context)
+ }
+
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<T, Error>;
+
+ fn write_with_mode(
+ value: &T,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), Error>;
+
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<T, Error>;
+
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<T, Error>;
+
+ fn default_value() -> T;
+
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error>;
+
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error>;
+
+ #[inline(always)]
+ fn read_type_info_as_field_type(context: &mut ReadContext) -> Result<FieldType, Error> {
+ Self::read_type_info(context)?;
+ Self::field_type(context.get_type_resolver())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::UNKNOWN
+ }
+
+ #[inline(always)]
+ fn is_option() -> bool {
+ false
+ }
+
+ #[inline(always)]
+ fn is_none(_value: &T) -> bool {
+ false
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ false
+ }
+
+ #[inline(always)]
+ fn is_shared_ref() -> bool {
+ false
+ }
+
+ #[inline(always)]
+ fn concrete_type_id(value: &T) -> std::any::TypeId {
+ let _ = value;
+ std::any::TypeId::of::<T>()
+ }
+}
+
+pub struct SerializerCodec<T, const NULLABLE: bool, const TRACK_REF: bool>(PhantomData<T>);
+
+impl<T, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+ for SerializerCodec<T, NULLABLE, TRACK_REF>
+where
+ T: Serializer + ForyDefault,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ field_type_for_serializer::<T>(type_resolver, NULLABLE, TRACK_REF)
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write(
+ value,
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ field_write_type_info::<T>(context),
+ false,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ serializer_read_type_info::<T>(context),
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write_data_generic(value, context, false)
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ field_ref_mode(remote_field_type),
+ field_read_type_info::<T>(context, remote_field_type),
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &T,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), Error> {
+ T::fory_write(value, context, ref_mode, write_type_info, has_generics)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<T, Error> {
+ T::fory_read(context, ref_mode, read_type_info)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<T, Error> {
+ T::fory_read_with_type_info(context, ref_mode, type_info)
+ }
+
+ #[inline(always)]
+ fn default_value() -> T {
+ T::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write_type_info(context)
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ T::fory_read_type_info(context)
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ T::fory_static_type_id()
+ }
+
+ #[inline(always)]
+ fn is_option() -> bool {
+ T::fory_is_option()
+ }
+
+ #[inline(always)]
+ fn is_none(value: &T) -> bool {
+ value.fory_is_none()
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ T::fory_is_polymorphic()
+ }
+
+ #[inline(always)]
+ fn is_shared_ref() -> bool {
+ T::fory_is_shared_ref()
+ }
+
+ #[inline(always)]
+ fn concrete_type_id(value: &T) -> std::any::TypeId {
+ value.fory_concrete_type_id()
+ }
+}
+
+pub struct CollectionSerializerCodec<
+ T,
+ E,
+ EC,
+ const TYPE_ID: u8,
+ const NULLABLE: bool,
+ const TRACK_REF: bool,
+>(PhantomData<(T, E, EC)>);
+
+impl<T, E, EC, const TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+ for CollectionSerializerCodec<T, E, EC, TYPE_ID, NULLABLE, TRACK_REF>
+where
+ T: Serializer + ForyDefault,
+ E: 'static,
+ EC: Codec<E>,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ Ok(FieldType::new_with_ref(
+ TYPE_ID as u32,
+ NULLABLE,
+ TRACK_REF,
+ vec![EC::field_type(type_resolver)?],
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write(
+ value,
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ field_write_type_info::<T>(context),
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ serializer_read_type_info::<T>(context),
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write_data_generic(value, context, true)
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ field_ref_mode(remote_field_type),
+ field_read_type_info::<T>(context, remote_field_type),
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &T,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ _has_generics: bool,
+ ) -> Result<(), Error> {
+ T::fory_write(value, context, ref_mode, write_type_info, true)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<T, Error> {
+ T::fory_read(context, ref_mode, read_type_info)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<T, Error> {
+ T::fory_read_with_type_info(context, ref_mode, type_info)
+ }
+
+ #[inline(always)]
+ fn default_value() -> T {
+ T::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_u8(TYPE_ID);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let remote = context.reader.read_u8()?;
+ if remote != TYPE_ID {
+ return Err(Error::type_mismatch(TYPE_ID as u32, remote as u32));
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::try_from(TYPE_ID).unwrap_or(TypeId::UNKNOWN)
+ }
+}
+
+pub struct MapSerializerCodec<T, K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool>(
+ PhantomData<(T, K, V, KC, VC)>,
+);
+
+impl<T, K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool> Codec<T>
+ for MapSerializerCodec<T, K, V, KC, VC, NULLABLE, TRACK_REF>
+where
+ T: Serializer + ForyDefault,
+ K: 'static,
+ V: 'static,
+ KC: Codec<K>,
+ VC: Codec<V>,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ Ok(FieldType::new_with_ref(
+ TypeId::MAP as u32,
+ NULLABLE,
+ TRACK_REF,
+ vec![
+ KC::field_type(type_resolver)?,
+ VC::field_type(type_resolver)?,
+ ],
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write(
+ value,
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ field_write_type_info::<T>(context),
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ serializer_ref_mode::<T, NULLABLE, TRACK_REF>(),
+ serializer_read_type_info::<T>(context),
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(value: &T, context: &mut WriteContext) -> Result<(), Error> {
+ T::fory_write_data_generic(value, context, true)
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<T, Error> {
+ T::fory_read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<T, Error> {
+ T::fory_read(
+ context,
+ field_ref_mode(remote_field_type),
+ field_read_type_info::<T>(context, remote_field_type),
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &T,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ _has_generics: bool,
+ ) -> Result<(), Error> {
+ T::fory_write(value, context, ref_mode, write_type_info, true)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<T, Error> {
+ T::fory_read(context, ref_mode, read_type_info)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<T, Error> {
+ T::fory_read_with_type_info(context, ref_mode, type_info)
+ }
+
+ #[inline(always)]
+ fn default_value() -> T {
+ T::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_u8(TypeId::MAP as u8);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let remote = context.reader.read_u8()? as u32;
+ if remote != TypeId::MAP as u32 {
+ return Err(Error::type_mismatch(TypeId::MAP as u32, remote));
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::MAP
+ }
+}
+
+pub struct OptionCodec<T, C, const TRACK_REF: bool>(PhantomData<(T, C)>);
+
+impl<T, C, const TRACK_REF: bool> Codec<Option<T>> for OptionCodec<T, C, TRACK_REF>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ let mut field_type = C::field_type(type_resolver)?;
+ field_type.nullable = true;
+ field_type.track_ref = TRACK_REF;
+ Ok(field_type)
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ C::reserved_space() + 1
+ }
+
+ #[inline(always)]
+ fn write_field(value: &Option<T>, context: &mut WriteContext) -> Result<(), Error> {
+ Self::write_with_mode(
+ value,
+ context,
+ if TRACK_REF {
+ RefMode::Tracking
+ } else {
+ RefMode::NullOnly
+ },
+ codec_write_type_info::<T, C>(context),
+ false,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<Option<T>, Error> {
+ Self::read_with_mode(
+ context,
+ if TRACK_REF {
+ RefMode::Tracking
+ } else {
+ RefMode::NullOnly
+ },
+ codec_read_type_info_static::<T, C>(context),
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(value: &Option<T>, context: &mut WriteContext) -> Result<(), Error> {
+ let value = value.as_ref().ok_or_else(|| {
+ Error::invalid_data("Option::None cannot be written as non-null data")
+ })?;
+ C::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<Option<T>, Error> {
+ Ok(Some(C::read_data(context)?))
+ }
+
+ #[inline(always)]
+ fn read_data_with_type(
+ context: &mut ReadContext,
+ remote_data_type: &FieldType,
+ ) -> Result<Option<T>, Error> {
+ Ok(Some(C::read_data_with_type(context, remote_data_type)?))
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<Option<T>, Error> {
+ let ref_mode = field_ref_mode(remote_field_type);
+ let read_type_info = codec_read_type_info::<T, C>(context, remote_field_type);
+ if ref_mode != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(None);
+ }
+ if ref_mode == RefMode::Tracking {
+ context.reader.move_back(1);
+ return Ok(Some(C::read_with_mode(
+ context,
+ RefMode::Tracking,
+ read_type_info,
+ )?));
+ }
+ }
+ if read_type_info || C::is_polymorphic() || C::is_shared_ref() {
+ Ok(Some(C::read_with_mode(
+ context,
+ RefMode::None,
+ read_type_info,
+ )?))
+ } else {
+ Ok(Some(C::read_data_with_type(context, remote_field_type)?))
+ }
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &Option<T>,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), Error> {
+ match ref_mode {
+ RefMode::None => Self::write_data(value, context),
+ RefMode::NullOnly => {
+ if let Some(value) = value {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ C::write_with_mode(value, context, RefMode::None, write_type_info, has_generics)
+ } else {
+ context.writer.write_i8(RefFlag::Null as i8);
+ Ok(())
+ }
+ }
+ RefMode::Tracking => {
+ if let Some(value) = value {
+ C::write_with_mode(
+ value,
+ context,
+ RefMode::Tracking,
+ write_type_info,
+ has_generics,
+ )
+ } else {
+ context.writer.write_i8(RefFlag::Null as i8);
+ Ok(())
+ }
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<Option<T>, Error> {
+ match ref_mode {
+ RefMode::None => Ok(Some(C::read_with_mode(
+ context,
+ RefMode::None,
+ read_type_info,
+ )?)),
+ RefMode::NullOnly => {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(None);
+ }
+ Ok(Some(C::read_with_mode(
+ context,
+ RefMode::None,
+ read_type_info,
+ )?))
+ }
+ RefMode::Tracking => {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(None);
+ }
+ context.reader.move_back(1);
+ Ok(Some(C::read_with_mode(
+ context,
+ RefMode::Tracking,
+ read_type_info,
+ )?))
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<Option<T>, Error> {
+ match ref_mode {
+ RefMode::None => Ok(Some(C::read_with_type_info(
+ context,
+ RefMode::None,
+ type_info,
+ )?)),
+ RefMode::NullOnly => {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(None);
+ }
+ Ok(Some(C::read_with_type_info(
+ context,
+ RefMode::None,
+ type_info,
+ )?))
+ }
+ RefMode::Tracking => {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(None);
+ }
+ context.reader.move_back(1);
+ Ok(Some(C::read_with_type_info(
+ context,
+ RefMode::Tracking,
+ type_info,
+ )?))
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn default_value() -> Option<T> {
+ None
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ C::write_type_info(context)
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ C::read_type_info(context)
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ C::static_type_id()
+ }
+
+ #[inline(always)]
+ fn is_option() -> bool {
+ true
+ }
+
+ #[inline(always)]
+ fn is_none(value: &Option<T>) -> bool {
+ value.is_none()
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ C::is_polymorphic()
+ }
+
+ #[inline(always)]
+ fn is_shared_ref() -> bool {
+ C::is_shared_ref()
+ }
+}
+
+macro_rules! signed_int_codec {
+ ($name:ident, $ty:ty, $default_type:expr, $fixed_type:expr, $tagged_type:expr, $write_fixed:ident, $read_fixed:ident, $write_var:ident, $read_var:ident, $write_tagged:ident, $read_tagged:ident) => {
+ pub struct $name<const WIRE_TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool>;
+
+ impl<const WIRE_TYPE_ID: u8, const NULLABLE: bool, const TRACK_REF: bool> Codec<$ty>
+ for $name<WIRE_TYPE_ID, NULLABLE, TRACK_REF>
+ {
+ #[inline(always)]
+ fn field_type(_: &TypeResolver) -> Result<FieldType, Error> {
+ Ok(FieldType::new_with_ref(
+ WIRE_TYPE_ID as u32,
+ NULLABLE,
+ TRACK_REF,
+ Vec::new(),
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ std::mem::size_of::<$ty>() + 1
+ }
+
+ #[inline(always)]
+ fn write_field(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+ if NULLABLE {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<$ty, Error> {
+ if NULLABLE {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(<$ty as ForyDefault>::fory_default());
+ }
+ }
+ Self::read_data(context)
+ }
+
+ #[inline(always)]
+ fn write_data(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+ match WIRE_TYPE_ID as u32 {
+ x if x == $fixed_type => context.writer.$write_fixed(*value),
+ x if x == $tagged_type => context.writer.$write_tagged(*value),
+ _ => context.writer.$write_var(*value),
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<$ty, Error> {
+ match WIRE_TYPE_ID as u32 {
+ x if x == $fixed_type => context.reader.$read_fixed(),
+ x if x == $tagged_type => context.reader.$read_tagged(),
+ _ => context.reader.$read_var(),
+ }
+ }
+
+ #[inline(always)]
+ fn read_data_with_type(
+ context: &mut ReadContext,
+ remote_data_type: &FieldType,
+ ) -> Result<$ty, Error> {
+ match remote_data_type.type_id {
+ x if x == $fixed_type => context.reader.$read_fixed(),
+ x if x == $tagged_type => context.reader.$read_tagged(),
+ _ => context.reader.$read_var(),
+ }
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<$ty, Error> {
+ if field_ref_mode(remote_field_type) != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(<$ty as ForyDefault>::fory_default());
+ }
+ }
+ match remote_field_type.type_id {
+ x if x == $fixed_type => context.reader.$read_fixed(),
+ x if x == $tagged_type => context.reader.$read_tagged(),
+ _ => context.reader.$read_var(),
+ }
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &$ty,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ _has_generics: bool,
+ ) -> Result<(), Error> {
+ if ref_mode != RefMode::None {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ if write_type_info {
+ Self::write_type_info(context)?;
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<$ty, Error> {
+ if ref_mode != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(<$ty as ForyDefault>::fory_default());
+ }
+ }
+ if read_type_info {
+ let remote_field_type = Self::read_type_info_as_field_type(context)?;
+ return Self::read_data_with_type(context, &remote_field_type);
+ }
+ Self::read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ _type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<$ty, Error> {
+ Self::read_with_mode(context, ref_mode, false)
+ }
+
+ #[inline(always)]
+ fn default_value() -> $ty {
+ <$ty as ForyDefault>::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_var_u32(WIRE_TYPE_ID as u32);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let remote = context.reader.read_var_u32()?;
+ if !same_numeric_family($default_type, remote) {
+ return Err(Error::type_mismatch($default_type, remote));
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info_as_field_type(context: &mut ReadContext) -> Result<FieldType, Error> {
+ let remote = context.reader.read_var_u32()?;
+ if !same_numeric_family($default_type, remote) {
+ return Err(Error::type_mismatch($default_type, remote));
+ }
+ Ok(FieldType::new(remote, false, Vec::new()))
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::try_from(WIRE_TYPE_ID).unwrap_or(TypeId::UNKNOWN)
+ }
+ }
+ };
+}
+
+signed_int_codec!(
+ I32Codec,
+ i32,
+ type_id::VARINT32,
+ type_id::INT32,
+ UNKNOWN,
+ write_i32,
+ read_i32,
+ write_var_i32,
+ read_var_i32,
+ write_var_i32,
+ read_var_i32
+);
+signed_int_codec!(
+ I64Codec,
+ i64,
+ type_id::VARINT64,
+ type_id::INT64,
+ type_id::TAGGED_INT64,
+ write_i64,
+ read_i64,
+ write_var_i64,
+ read_var_i64,
+ write_tagged_i64,
+ read_tagged_i64
+);
+signed_int_codec!(
+ U32Codec,
+ u32,
+ type_id::VAR_UINT32,
+ type_id::UINT32,
+ UNKNOWN,
+ write_u32,
+ read_u32,
+ write_var_u32,
+ read_var_u32,
+ write_var_u32,
+ read_var_u32
+);
+signed_int_codec!(
+ U64Codec,
+ u64,
+ type_id::VAR_UINT64,
+ type_id::UINT64,
+ type_id::TAGGED_UINT64,
+ write_u64,
+ read_u64,
+ write_var_u64,
+ read_var_u64,
+ write_tagged_u64,
+ read_tagged_u64
+);
+
+pub struct VecCodec<T, C, const NULLABLE: bool, const TRACK_REF: bool>(PhantomData<(T, C)>);
+
+impl<T, C, const NULLABLE: bool, const TRACK_REF: bool> Codec<Vec<T>>
+ for VecCodec<T, C, NULLABLE, TRACK_REF>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ let element_type = C::field_type(type_resolver)?;
+ Ok(FieldType::new_with_ref(
+ TypeId::LIST as u32,
+ NULLABLE,
+ TRACK_REF,
+ vec![element_type],
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ std::mem::size_of::<u32>() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error> {
+ if NULLABLE {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<Vec<T>, Error> {
+ if NULLABLE {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(Vec::new());
+ }
+ }
+ Self::read_data(context)
+ }
+
+ fn write_data(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error> {
+ let len = value.len();
+ context.writer.write_var_u32(len as u32);
+ if len == 0 {
+ return Ok(());
+ }
+ if C::is_polymorphic() || C::is_shared_ref() {
+ return write_vec_dynamic::<T, C>(value, context);
+ }
+ let mut header = IS_SAME_TYPE;
+ let mut has_null = false;
+ if C::is_option() {
+ for item in value {
+ if C::is_none(item) {
+ has_null = true;
+ break;
+ }
+ }
+ }
+ if has_null {
+ header |= HAS_NULL;
+ }
+ if !need_to_write_type_for_field(C::static_type_id()) {
+ header |= DECL_ELEMENT_TYPE;
+ context.writer.write_u8(header);
+ } else {
+ context.writer.write_u8(header);
+ C::write_type_info(context)?;
+ }
+ context.writer.reserve(len * C::reserved_space());
+ if has_null {
+ for item in value {
+ if C::is_none(item) {
+ context.writer.write_i8(RefFlag::Null as i8);
+ continue;
+ }
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ C::write_data(item, context)?;
+ }
+ } else {
+ for item in value {
+ C::write_data(item, context)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn read_data(context: &mut ReadContext) -> Result<Vec<T>, Error> {
+ let len = context.reader.read_var_u32()?;
+ if len == 0 {
+ return Ok(Vec::new());
+ }
+ let max = context.max_collection_size();
+ if len > max {
+ return Err(Error::size_limit_exceeded(format!(
+ "Collection size {} exceeds limit {}",
+ len, max
+ )));
+ }
+ let header = context.reader.read_u8()?;
+ if C::is_polymorphic() || C::is_shared_ref() {
+ let field_type = Self::field_type(context.get_type_resolver())?;
+ return read_vec_dynamic_items::<T, C>(context, len, header, &field_type);
+ }
+ if (header & IS_SAME_TYPE) == 0 {
+ return Err(Error::type_error(
+ "Type inconsistent, target collection element type is not polymorphic",
+ ));
+ }
+ if (header & DECL_ELEMENT_TYPE) == 0 {
+ C::read_type_info(context)?;
+ }
+ let has_null = (header & HAS_NULL) != 0;
+ let mut vec = Vec::with_capacity(len as usize);
+ if has_null {
+ for _ in 0..len {
+ let flag = context.reader.read_i8()?;
+ if flag == RefFlag::Null as i8 {
+ vec.push(C::default_value());
+ } else {
+ vec.push(C::read_data(context)?);
+ }
+ }
+ } else {
+ for _ in 0..len {
+ vec.push(C::read_data(context)?);
+ }
+ }
+ Ok(vec)
+ }
+
+ fn read_data_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<Vec<T>, Error> {
+ let len = context.reader.read_var_u32()?;
+ if len == 0 {
+ return Ok(Vec::new());
+ }
+ let max = context.max_collection_size();
+ if len > max {
+ return Err(Error::size_limit_exceeded(format!(
+ "Collection size {} exceeds limit {}",
+ len, max
+ )));
+ }
+ let header = context.reader.read_u8()?;
+ let has_null = (header & HAS_NULL) != 0;
+ let is_same_type = (header & IS_SAME_TYPE) != 0;
+ let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+ if C::is_polymorphic() || C::is_shared_ref() {
+ return read_vec_dynamic_items::<T, C>(context, len, header, remote_field_type);
+ }
+ if !is_same_type {
+ return Err(Error::type_error(
+ "Type inconsistent, target collection element type is not polymorphic",
+ ));
+ }
+ let element_type = if is_declared {
+ generic_field_type(remote_field_type, 0, "list")?.clone()
+ } else {
+ C::read_type_info_as_field_type(context)?
+ };
+ let mut vec = Vec::with_capacity(len as usize);
+ if has_null {
+ for _ in 0..len {
+ let flag = context.reader.read_i8()?;
+ if flag == RefFlag::Null as i8 {
+ vec.push(C::default_value());
+ } else {
+ vec.push(C::read_data_with_type(context, &element_type)?);
+ }
+ }
+ } else {
+ for _ in 0..len {
+ vec.push(C::read_data_with_type(context, &element_type)?);
+ }
+ }
+ Ok(vec)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<Vec<T>, Error> {
+ if field_ref_mode(remote_field_type) != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(Vec::new());
+ }
+ }
+ Self::read_data_with_type(context, remote_field_type)
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &Vec<T>,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ _has_generics: bool,
+ ) -> Result<(), Error> {
+ if ref_mode != RefMode::None {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ if write_type_info {
+ Self::write_type_info(context)?;
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<Vec<T>, Error> {
+ if ref_mode != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(Vec::new());
+ }
+ }
+ if read_type_info {
+ Self::read_type_info(context)?;
+ }
+ Self::read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ _type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<Vec<T>, Error> {
+ Self::read_with_mode(context, ref_mode, false)
+ }
+
+ #[inline(always)]
+ fn default_value() -> Vec<T> {
+ Vec::new()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_u8(TypeId::LIST as u8);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let remote = context.reader.read_u8()? as u32;
+ if remote != TypeId::LIST as u32 {
+ return Err(Error::type_mismatch(TypeId::LIST as u32, remote));
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::LIST
+ }
+}
+
+fn write_vec_dynamic<T, C>(value: &Vec<T>, context: &mut WriteContext) -> Result<(), Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ let mut has_null = false;
+ let mut is_same_type = true;
+ let mut first_type_id: Option<std::any::TypeId> = None;
+ for item in value {
+ if C::is_none(item) {
+ has_null = true;
+ } else if C::is_polymorphic() && is_same_type {
+ let concrete_id = C::concrete_type_id(item);
+ if let Some(first_id) = first_type_id {
+ if first_id != concrete_id {
+ is_same_type = false;
+ }
+ } else {
+ first_type_id = Some(concrete_id);
+ }
+ }
+ }
+ if C::is_polymorphic() && is_same_type && first_type_id.is_none() {
+ is_same_type = false;
+ }
+ let mut header = 0u8;
+ if has_null {
+ header |= HAS_NULL;
+ }
+ if !need_to_write_type_for_field(C::static_type_id()) {
+ header |= DECL_ELEMENT_TYPE;
+ }
+ if is_same_type {
+ header |= IS_SAME_TYPE;
+ }
+ if C::is_shared_ref() {
+ header |= TRACKING_REF;
+ }
+ context.writer.write_u8(header);
+ if is_same_type && (header & DECL_ELEMENT_TYPE) == 0 {
+ if C::is_polymorphic() {
+ let type_id = first_type_id.ok_or_else(|| {
+ Error::type_error(
+ "Unable to determine concrete type for polymorphic collection elements",
+ )
+ })?;
+ context.write_any_type_info(C::static_type_id() as u32, type_id)?;
+ } else {
+ C::write_type_info(context)?;
+ }
+ }
+ let elem_ref_mode = if C::is_shared_ref() {
+ RefMode::Tracking
+ } else if has_null {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ };
+ if is_same_type {
+ for item in value {
+ if elem_ref_mode == RefMode::None {
+ C::write_data(item, context)?;
+ } else {
+ C::write_with_mode(item, context, elem_ref_mode, false, true)?;
+ }
+ }
+ } else {
+ for item in value {
+ C::write_with_mode(item, context, elem_ref_mode, true, true)?;
+ }
+ }
+ Ok(())
+}
+
+fn read_vec_dynamic_items<T, C>(
+ context: &mut ReadContext,
+ len: u32,
+ header: u8,
+ remote_field_type: &FieldType,
+) -> Result<Vec<T>, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ let is_track_ref = (header & TRACKING_REF) != 0;
+ let has_null = (header & HAS_NULL) != 0;
+ let is_same_type = (header & IS_SAME_TYPE) != 0;
+ let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+ let elem_ref_mode = if is_track_ref {
+ RefMode::Tracking
+ } else if has_null {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ };
+ let mut vec = Vec::with_capacity(len as usize);
+ if is_same_type {
+ if C::is_polymorphic() {
+ if is_declared {
+ return Err(Error::invalid_data(
+ "polymorphic collection element metadata must be written in the payload",
+ ));
+ }
+ let type_info = context.read_any_type_info()?;
+ for _ in 0..len {
+ vec.push(C::read_with_type_info(
+ context,
+ elem_ref_mode,
+ type_info.clone(),
+ )?);
+ }
+ } else {
+ let element_type = if is_declared {
+ generic_field_type(remote_field_type, 0, "list")?.clone()
+ } else {
+ C::read_type_info_as_field_type(context)?
+ };
+ for _ in 0..len {
+ if C::is_shared_ref() {
+ vec.push(C::read_with_mode(context, elem_ref_mode, false)?);
+ } else {
+ vec.push(C::read_data_with_type(context, &element_type)?);
+ }
+ }
+ }
+ } else {
+ for _ in 0..len {
+ vec.push(C::read_with_mode(context, elem_ref_mode, true)?);
+ }
+ }
+ Ok(vec)
+}
+
+pub struct HashMapCodec<K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool>(
+ PhantomData<(K, V, KC, VC)>,
+);
+
+impl<K, V, KC, VC, const NULLABLE: bool, const TRACK_REF: bool> Codec<HashMap<K, V>>
+ for HashMapCodec<K, V, KC, VC, NULLABLE, TRACK_REF>
+where
+ K: Eq + Hash + 'static,
+ V: 'static,
+ KC: Codec<K>,
+ VC: Codec<V>,
+{
+ #[inline(always)]
+ fn field_type(type_resolver: &TypeResolver) -> Result<FieldType, Error> {
+ Ok(FieldType::new_with_ref(
+ TypeId::MAP as u32,
+ NULLABLE,
+ TRACK_REF,
+ vec![
+ KC::field_type(type_resolver)?,
+ VC::field_type(type_resolver)?,
+ ],
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ std::mem::size_of::<u32>() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &HashMap<K, V>, context: &mut WriteContext) -> Result<(), Error> {
+ if NULLABLE {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<HashMap<K, V>, Error> {
+ if NULLABLE {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(HashMap::new());
+ }
+ }
+ Self::read_data(context)
+ }
+
+ fn write_data(value: &HashMap<K, V>, context: &mut WriteContext) -> Result<(), Error> {
+ let len = value.len();
+ context.writer.write_var_u32(len as u32);
+ if len == 0 {
+ return Ok(());
+ }
+ if KC::is_polymorphic()
+ || KC::is_shared_ref()
+ || VC::is_polymorphic()
+ || VC::is_shared_ref()
+ {
+ return write_map_dynamic::<K, V, KC, VC>(value, context);
+ }
+ let key_declared = !need_to_write_type_for_field(KC::static_type_id());
+ let value_declared = !need_to_write_type_for_field(VC::static_type_id());
+ let mut header_offset = 0;
+ let mut pair_counter: u8 = 0;
+ let mut need_write_header = true;
+
+ for (key, value) in value {
+ let key_is_none = KC::is_none(key);
+ let value_is_none = VC::is_none(value);
+ if key_is_none || value_is_none {
+ if !need_write_header && pair_counter > 0 {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ pair_counter = 0;
+ need_write_header = true;
+ }
+ if key_is_none && value_is_none {
+ context.writer.write_u8(KEY_NULL | VALUE_NULL);
+ continue;
+ }
+ if value_is_none {
+ let mut header = VALUE_NULL;
+ if KC::is_shared_ref() {
+ header |= TRACKING_KEY_REF;
+ }
+ if key_declared && !KC::is_polymorphic() {
+ header |= DECL_KEY_TYPE;
+ context.writer.write_u8(header);
+ } else {
+ context.writer.write_u8(header);
+ write_map_entry_type::<K, KC>(key, context)?;
+ }
+ write_map_entry_data::<K, KC>(key, context, KC::is_shared_ref())?;
+ continue;
+ }
+ let mut header = KEY_NULL;
+ if VC::is_shared_ref() {
+ header |= TRACKING_VALUE_REF;
+ }
+ if value_declared && !VC::is_polymorphic() {
+ header |= DECL_VALUE_TYPE;
+ context.writer.write_u8(header);
+ } else {
+ context.writer.write_u8(header);
+ write_map_entry_type::<V, VC>(value, context)?;
+ }
+ write_map_entry_data::<V, VC>(value, context, VC::is_shared_ref())?;
+ continue;
+ }
+
+ if need_write_header {
+ header_offset = context.writer.len();
+ context.writer.write_i16(-1);
+ let mut header = 0u8;
+ if KC::is_shared_ref() {
+ header |= TRACKING_KEY_REF;
+ }
+ if VC::is_shared_ref() {
+ header |= TRACKING_VALUE_REF;
+ }
+ if key_declared && !KC::is_polymorphic() {
+ header |= DECL_KEY_TYPE;
+ } else {
+ write_map_entry_type::<K, KC>(key, context)?;
+ }
+ if value_declared && !VC::is_polymorphic() {
+ header |= DECL_VALUE_TYPE;
+ } else {
+ write_map_entry_type::<V, VC>(value, context)?;
+ }
+ context.writer.set_bytes(header_offset, &[header]);
+ need_write_header = false;
+ }
+ write_map_entry_data::<K, KC>(key, context, KC::is_shared_ref())?;
+ write_map_entry_data::<V, VC>(value, context, VC::is_shared_ref())?;
+ pair_counter += 1;
+ if pair_counter == MAX_CHUNK_SIZE {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ pair_counter = 0;
+ need_write_header = true;
+ }
+ }
+ if pair_counter > 0 {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ }
+ Ok(())
+ }
+
+ fn read_data(context: &mut ReadContext) -> Result<HashMap<K, V>, Error> {
+ let len = context.reader.read_var_u32()?;
+ if len == 0 {
+ return Ok(HashMap::new());
+ }
+ let max = context.max_collection_size();
+ if len > max {
+ return Err(Error::size_limit_exceeded(format!(
+ "Map size {} exceeds limit {}",
+ len, max
+ )));
+ }
+ if KC::is_polymorphic()
+ || KC::is_shared_ref()
+ || VC::is_polymorphic()
+ || VC::is_shared_ref()
+ {
+ let field_type = Self::field_type(context.get_type_resolver())?;
+ return read_map_dynamic::<K, V, KC, VC>(context, len, &field_type);
+ }
+ read_map_static::<K, V, KC, VC>(context, len)
+ }
+
+ fn read_data_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<HashMap<K, V>, Error> {
+ let len = context.reader.read_var_u32()?;
+ if len == 0 {
+ return Ok(HashMap::new());
+ }
+ let max = context.max_collection_size();
+ if len > max {
+ return Err(Error::size_limit_exceeded(format!(
+ "Map size {} exceeds limit {}",
+ len, max
+ )));
+ }
+ if KC::is_polymorphic()
+ || KC::is_shared_ref()
+ || VC::is_polymorphic()
+ || VC::is_shared_ref()
+ {
+ return read_map_dynamic::<K, V, KC, VC>(context, len, remote_field_type);
+ }
+ let mut map = HashMap::with_capacity(len as usize);
+ let mut len_counter = 0;
+ while len_counter < len {
+ let header = context.reader.read_u8()?;
+ if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+ map.insert(KC::default_value(), VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+ let key_declared = (header & DECL_KEY_TYPE) != 0;
+ let value_declared = (header & DECL_VALUE_TYPE) != 0;
+ let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+ let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+ if header & KEY_NULL != 0 {
+ let value_type =
+ map_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+ let value = read_map_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+ map.insert(KC::default_value(), value);
+ len_counter += 1;
+ continue;
+ }
+ if header & VALUE_NULL != 0 {
+ let key_type =
+ map_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+ let key = read_map_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+ map.insert(key, VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+ let chunk_size = context.reader.read_u8()?;
+ let key_type = map_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+ let value_type =
+ map_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+ let cur_len = len_counter + chunk_size as u32;
+ if cur_len > len {
+ return Err(Error::invalid_data(format!(
+ "current length {} exceeds total length {}",
+ cur_len, len
+ )));
+ }
+ for _ in 0..chunk_size {
+ let key = read_map_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+ let value = read_map_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+ map.insert(key, value);
+ }
+ len_counter = cur_len;
+ }
+ Ok(map)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<HashMap<K, V>, Error> {
+ if field_ref_mode(remote_field_type) != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(HashMap::new());
+ }
+ }
+ Self::read_data_with_type(context, remote_field_type)
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &HashMap<K, V>,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ _has_generics: bool,
+ ) -> Result<(), Error> {
+ if ref_mode != RefMode::None {
+ context.writer.write_i8(RefFlag::NotNullValue as i8);
+ }
+ if write_type_info {
+ Self::write_type_info(context)?;
+ }
+ Self::write_data(value, context)
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<HashMap<K, V>, Error> {
+ if ref_mode != RefMode::None {
+ let ref_flag = context.reader.read_i8()?;
+ if ref_flag == RefFlag::Null as i8 {
+ return Ok(HashMap::new());
+ }
+ }
+ if read_type_info {
+ Self::read_type_info(context)?;
+ }
+ Self::read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ _type_info: std::rc::Rc<crate::TypeInfo>,
+ ) -> Result<HashMap<K, V>, Error> {
+ Self::read_with_mode(context, ref_mode, false)
+ }
+
+ #[inline(always)]
+ fn default_value() -> HashMap<K, V> {
+ HashMap::new()
+ }
+
+ #[inline(always)]
+ fn write_type_info(context: &mut WriteContext) -> Result<(), Error> {
+ context.writer.write_u8(TypeId::MAP as u8);
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(context: &mut ReadContext) -> Result<(), Error> {
+ let remote = context.reader.read_u8()? as u32;
+ if remote != TypeId::MAP as u32 {
+ return Err(Error::type_mismatch(TypeId::MAP as u32, remote));
+ }
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::MAP
+ }
+}
+
+struct MapEntryReadType {
+ field_type: Option<FieldType>,
+ type_info: Option<std::rc::Rc<crate::TypeInfo>>,
+}
+
+fn read_map_static<K, V, KC, VC>(
+ context: &mut ReadContext,
+ len: u32,
+) -> Result<HashMap<K, V>, Error>
+where
+ K: Eq + Hash + 'static,
+ V: 'static,
+ KC: Codec<K>,
+ VC: Codec<V>,
+{
+ let mut map = HashMap::with_capacity(len as usize);
+ let mut len_counter = 0u32;
+ while len_counter < len {
+ let header = context.reader.read_u8()?;
+ if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+ map.insert(KC::default_value(), VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+ let key_declared = (header & DECL_KEY_TYPE) != 0;
+ let value_declared = (header & DECL_VALUE_TYPE) != 0;
+ let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+ let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+
+ if header & KEY_NULL != 0 {
+ let value_type =
+ read_map_static_entry_type::<V, VC>(context, value_declared, track_value_ref)?;
+ let value =
+ read_map_static_entry_data::<V, VC>(context, value_type.as_ref(), track_value_ref)?;
+ map.insert(KC::default_value(), value);
+ len_counter += 1;
+ continue;
+ }
+ if header & VALUE_NULL != 0 {
+ let key_type =
+ read_map_static_entry_type::<K, KC>(context, key_declared, track_key_ref)?;
+ let key =
+ read_map_static_entry_data::<K, KC>(context, key_type.as_ref(), track_key_ref)?;
+ map.insert(key, VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+
+ let chunk_size = context.reader.read_u8()?;
+ let key_type = read_map_static_entry_type::<K, KC>(context, key_declared, track_key_ref)?;
+ let value_type =
+ read_map_static_entry_type::<V, VC>(context, value_declared, track_value_ref)?;
+ let cur_len = len_counter + chunk_size as u32;
+ if cur_len > len {
+ return Err(Error::invalid_data(format!(
+ "current length {} exceeds total length {}",
+ cur_len, len
+ )));
+ }
+ for _ in 0..chunk_size {
+ let key =
+ read_map_static_entry_data::<K, KC>(context, key_type.as_ref(), track_key_ref)?;
+ let value =
+ read_map_static_entry_data::<V, VC>(context, value_type.as_ref(), track_value_ref)?;
+ map.insert(key, value);
+ }
+ len_counter = cur_len;
+ }
+ Ok(map)
+}
+
+#[inline(always)]
+fn read_map_static_entry_type<T, C>(
+ context: &mut ReadContext,
+ declared: bool,
+ track_ref: bool,
+) -> Result<Option<FieldType>, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if declared {
+ Ok(None)
+ } else if track_ref {
+ C::read_type_info(context)?;
+ Ok(None)
+ } else {
+ C::read_type_info_as_field_type(context).map(Some)
+ }
+}
+
+#[inline(always)]
+fn read_map_static_entry_data<T, C>(
+ context: &mut ReadContext,
+ entry_type: Option<&FieldType>,
+ track_ref: bool,
+) -> Result<T, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if track_ref {
+ C::read_with_mode(context, RefMode::Tracking, false)
+ } else if let Some(entry_type) = entry_type {
+ C::read_data_with_type(context, entry_type)
+ } else {
+ C::read_data(context)
+ }
+}
+
+fn read_map_dynamic<K, V, KC, VC>(
+ context: &mut ReadContext,
+ len: u32,
+ remote_field_type: &FieldType,
+) -> Result<HashMap<K, V>, Error>
+where
+ K: Eq + Hash + 'static,
+ V: 'static,
+ KC: Codec<K>,
+ VC: Codec<V>,
+{
+ let mut map = HashMap::with_capacity(len as usize);
+ let mut len_counter = 0u32;
+ while len_counter < len {
+ let header = context.reader.read_u8()?;
+ if header & KEY_NULL != 0 && header & VALUE_NULL != 0 {
+ map.insert(KC::default_value(), VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+ let key_declared = (header & DECL_KEY_TYPE) != 0;
+ let value_declared = (header & DECL_VALUE_TYPE) != 0;
+ let track_key_ref = (header & TRACKING_KEY_REF) != 0;
+ let track_value_ref = (header & TRACKING_VALUE_REF) != 0;
+
+ if header & KEY_NULL != 0 {
+ let value_type = read_map_dynamic_entry_type::<V, VC>(
+ context,
+ value_declared,
+ remote_field_type,
+ 1,
+ )?;
+ let value =
+ read_map_dynamic_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+ map.insert(KC::default_value(), value);
+ len_counter += 1;
+ continue;
+ }
+ if header & VALUE_NULL != 0 {
+ let key_type =
+ read_map_dynamic_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+ let key = read_map_dynamic_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+ map.insert(key, VC::default_value());
+ len_counter += 1;
+ continue;
+ }
+
+ let chunk_size = context.reader.read_u8()?;
+ let key_type =
+ read_map_dynamic_entry_type::<K, KC>(context, key_declared, remote_field_type, 0)?;
+ let value_type =
+ read_map_dynamic_entry_type::<V, VC>(context, value_declared, remote_field_type, 1)?;
+ let cur_len = len_counter + chunk_size as u32;
+ if cur_len > len {
+ return Err(Error::invalid_data(format!(
+ "current length {} exceeds total length {}",
+ cur_len, len
+ )));
+ }
+ for _ in 0..chunk_size {
+ let key = read_map_dynamic_entry_data::<K, KC>(context, &key_type, track_key_ref)?;
+ let value =
+ read_map_dynamic_entry_data::<V, VC>(context, &value_type, track_value_ref)?;
+ map.insert(key, value);
+ }
+ len_counter = cur_len;
+ }
+ Ok(map)
+}
+
+#[inline(always)]
+fn read_map_dynamic_entry_type<T, C>(
+ context: &mut ReadContext,
+ declared: bool,
+ remote_field_type: &FieldType,
+ generic_index: usize,
+) -> Result<MapEntryReadType, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if declared {
+ return Ok(MapEntryReadType {
+ field_type: Some(generic_field_type(remote_field_type, generic_index, "map")?.clone()),
+ type_info: None,
+ });
+ }
+ if C::is_polymorphic() {
+ Ok(MapEntryReadType {
+ field_type: None,
+ type_info: Some(context.read_any_type_info()?),
+ })
+ } else {
+ Ok(MapEntryReadType {
+ field_type: Some(C::read_type_info_as_field_type(context)?),
+ type_info: None,
+ })
+ }
+}
+
+#[inline(always)]
+fn read_map_dynamic_entry_data<T, C>(
+ context: &mut ReadContext,
+ read_type: &MapEntryReadType,
+ track_ref: bool,
+) -> Result<T, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ let ref_mode = if track_ref {
+ RefMode::Tracking
+ } else {
+ RefMode::None
+ };
+ if let Some(type_info) = read_type.type_info.as_ref() {
+ return C::read_with_type_info(context, ref_mode, type_info.clone());
+ }
+ if track_ref {
+ return C::read_with_mode(context, ref_mode, false);
+ }
+ let field_type = read_type
+ .field_type
+ .as_ref()
+ .ok_or_else(|| Error::invalid_data("map entry field metadata is missing"))?;
+ C::read_data_with_type(context, field_type)
+}
+
+fn write_map_dynamic<K, V, KC, VC>(
+ value: &HashMap<K, V>,
+ context: &mut WriteContext,
+) -> Result<(), Error>
+where
+ K: Eq + Hash + 'static,
+ V: 'static,
+ KC: Codec<K>,
+ VC: Codec<V>,
+{
+ let key_declared = !need_to_write_type_for_field(KC::static_type_id());
+ let value_declared = !need_to_write_type_for_field(VC::static_type_id());
+ let key_is_polymorphic = KC::is_polymorphic();
+ let value_is_polymorphic = VC::is_polymorphic();
+ let key_is_shared_ref = KC::is_shared_ref();
+ let value_is_shared_ref = VC::is_shared_ref();
+ let mut current_key_type_id: Option<std::any::TypeId> = None;
+ let mut current_value_type_id: Option<std::any::TypeId> = None;
+ let mut header_offset = 0;
+ let mut pair_counter: u8 = 0;
+ let mut need_write_header = true;
+
+ for (key, value) in value {
+ let key_is_none = KC::is_none(key);
+ let value_is_none = VC::is_none(value);
+ if key_is_none || value_is_none {
+ if pair_counter > 0 {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ pair_counter = 0;
+ need_write_header = true;
+ }
+ if key_is_none && value_is_none {
+ context.writer.write_u8(KEY_NULL | VALUE_NULL);
+ continue;
+ }
+ if value_is_none {
+ let mut header = VALUE_NULL;
+ if key_is_shared_ref {
+ header |= TRACKING_KEY_REF;
+ }
+ if key_declared && !key_is_polymorphic {
+ header |= DECL_KEY_TYPE;
+ context.writer.write_u8(header);
+ } else {
+ context.writer.write_u8(header);
+ write_map_entry_type::<K, KC>(key, context)?;
+ }
+ write_map_entry_data::<K, KC>(key, context, key_is_shared_ref)?;
+ continue;
+ }
+ let mut header = KEY_NULL;
+ if value_is_shared_ref {
+ header |= TRACKING_VALUE_REF;
+ }
+ if value_declared && !value_is_polymorphic {
+ header |= DECL_VALUE_TYPE;
+ context.writer.write_u8(header);
+ } else {
+ context.writer.write_u8(header);
+ write_map_entry_type::<V, VC>(value, context)?;
+ }
+ write_map_entry_data::<V, VC>(value, context, value_is_shared_ref)?;
+ continue;
+ }
+
+ let key_type_id = if key_is_polymorphic {
+ Some(KC::concrete_type_id(key))
+ } else {
+ None
+ };
+ let value_type_id = if value_is_polymorphic {
+ Some(VC::concrete_type_id(value))
+ } else {
+ None
+ };
+ let types_changed = (key_is_polymorphic || value_is_polymorphic)
+ && (key_type_id != current_key_type_id || value_type_id != current_value_type_id);
+
+ if need_write_header || types_changed {
+ if pair_counter > 0 {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ pair_counter = 0;
+ }
+ header_offset = context.writer.len();
+ context.writer.write_i16(-1);
+ let mut header = 0u8;
+ if key_is_shared_ref {
+ header |= TRACKING_KEY_REF;
+ }
+ if value_is_shared_ref {
+ header |= TRACKING_VALUE_REF;
+ }
+ if key_declared && !key_is_polymorphic {
+ header |= DECL_KEY_TYPE;
+ } else {
+ write_map_entry_type::<K, KC>(key, context)?;
+ }
+ if value_declared && !value_is_polymorphic {
+ header |= DECL_VALUE_TYPE;
+ } else {
+ write_map_entry_type::<V, VC>(value, context)?;
+ }
+ context.writer.set_bytes(header_offset, &[header]);
+ need_write_header = false;
+ current_key_type_id = key_type_id;
+ current_value_type_id = value_type_id;
+ }
+
+ write_map_entry_data::<K, KC>(key, context, key_is_shared_ref)?;
+ write_map_entry_data::<V, VC>(value, context, value_is_shared_ref)?;
+ pair_counter += 1;
+ if pair_counter == MAX_CHUNK_SIZE {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ pair_counter = 0;
+ need_write_header = true;
+ current_key_type_id = None;
+ current_value_type_id = None;
+ }
+ }
+ if pair_counter > 0 {
+ context.writer.set_bytes(header_offset + 1, &[pair_counter]);
+ }
+ Ok(())
+}
+
+#[inline(always)]
+fn write_map_entry_type<T, C>(value: &T, context: &mut WriteContext) -> Result<(), Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if C::is_polymorphic() {
+ context.write_any_type_info(C::static_type_id() as u32, C::concrete_type_id(value))?;
+ Ok(())
+ } else {
+ C::write_type_info(context)
+ }
+}
+
+#[inline(always)]
+fn write_map_entry_data<T, C>(
+ value: &T,
+ context: &mut WriteContext,
+ track_ref: bool,
+) -> Result<(), Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if track_ref {
+ C::write_with_mode(value, context, RefMode::Tracking, false, true)
+ } else {
+ C::write_data(value, context)
+ }
+}
+
+#[inline(always)]
+fn map_entry_type<T, C>(
+ context: &mut ReadContext,
+ declared: bool,
+ remote_field_type: &FieldType,
+ generic_index: usize,
+) -> Result<FieldType, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if declared {
+ Ok(generic_field_type(remote_field_type, generic_index, "map")?.clone())
+ } else {
+ C::read_type_info_as_field_type(context)
+ }
+}
+
+#[inline(always)]
+fn read_map_entry_data<T, C>(
+ context: &mut ReadContext,
+ remote_entry_type: &FieldType,
+ track_ref: bool,
+) -> Result<T, Error>
+where
+ T: 'static,
+ C: Codec<T>,
+{
+ if track_ref {
+ C::read_with_mode(context, RefMode::Tracking, false)
+ } else {
+ C::read_data_with_type(context, remote_entry_type)
+ }
+}
+
+#[inline(always)]
+fn any_field_type<const NULLABLE: bool, const TRACK_REF: bool>() -> FieldType {
+ FieldType::new_with_ref(TypeId::UNKNOWN as u32, NULLABLE, TRACK_REF, Vec::new())
+}
+
+#[inline(always)]
+fn any_ref_mode<const NULLABLE: bool, const TRACK_REF: bool>() -> RefMode {
+ if TRACK_REF {
+ RefMode::Tracking
+ } else if NULLABLE {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ }
+}
+
+macro_rules! any_codec {
+ ($name:ident, $ty:ty) => {
+ pub struct $name<const NULLABLE: bool, const TRACK_REF: bool>;
+
+ impl<const NULLABLE: bool, const TRACK_REF: bool> Codec<$ty>
+ for $name<NULLABLE, TRACK_REF>
+ {
+ #[inline(always)]
+ fn field_type(_: &TypeResolver) -> Result<FieldType, Error> {
+ Ok(any_field_type::<NULLABLE, TRACK_REF>())
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ <$ty as Serializer>::fory_reserved_space() + SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+ <$ty as Serializer>::fory_write(
+ value,
+ context,
+ any_ref_mode::<NULLABLE, TRACK_REF>(),
+ field_write_type_info::<$ty>(context),
+ false,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(context: &mut ReadContext) -> Result<$ty, Error> {
+ <$ty as Serializer>::fory_read(
+ context,
+ any_ref_mode::<NULLABLE, TRACK_REF>(),
+ codec_read_type_info_static::<$ty, Self>(context),
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(value: &$ty, context: &mut WriteContext) -> Result<(), Error> {
+ <$ty as Serializer>::fory_write_data_generic(value, context, false)
+ }
+
+ #[inline(always)]
+ fn read_data(context: &mut ReadContext) -> Result<$ty, Error> {
+ <$ty as Serializer>::fory_read_data(context)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut ReadContext,
+ remote_field_type: &FieldType,
+ ) -> Result<$ty, Error> {
+ <$ty as Serializer>::fory_read(
+ context,
+ field_ref_mode(remote_field_type),
+ codec_read_type_info::<$ty, Self>(context, remote_field_type),
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &$ty,
+ context: &mut WriteContext,
+ ref_mode: RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), Error> {
+ <$ty as Serializer>::fory_write(
+ value,
+ context,
+ ref_mode,
+ write_type_info,
+ has_generics,
+ )
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ read_type_info: bool,
+ ) -> Result<$ty, Error> {
+ <$ty as Serializer>::fory_read(context, ref_mode, read_type_info)
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut ReadContext,
+ ref_mode: RefMode,
+ type_info: Rc<crate::TypeInfo>,
+ ) -> Result<$ty, Error> {
+ <$ty as Serializer>::fory_read_with_type_info(context, ref_mode, type_info)
+ }
+
+ #[inline(always)]
+ fn default_value() -> $ty {
+ <$ty as ForyDefault>::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(_context: &mut WriteContext) -> Result<(), Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(_context: &mut ReadContext) -> Result<(), Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> TypeId {
+ TypeId::UNKNOWN
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ true
+ }
+
+ #[inline(always)]
+ fn is_shared_ref() -> bool {
+ <$ty as Serializer>::fory_is_shared_ref()
+ }
+
+ #[inline(always)]
+ fn concrete_type_id(value: &$ty) -> std::any::TypeId {
+ <$ty as Serializer>::fory_concrete_type_id(value)
+ }
+ }
+ };
+}
+
+any_codec!(AnyBoxCodec, Box<dyn Any>);
+any_codec!(AnyRcCodec, Rc<dyn Any>);
+any_codec!(AnyArcCodec, Arc<dyn Any>);
diff --git a/rust/fory-core/src/serializer/core.rs b/rust/fory-core/src/serializer/core.rs
index 62bb62845..b91bc70b9 100644
--- a/rust/fory-core/src/serializer/core.rs
+++ b/rust/fory-core/src/serializer/core.rs
@@ -103,7 +103,7 @@ pub trait ForyDefault: Sized {
///
/// `Serializer` is the primary trait that enables types to be serialized and deserialized
/// using Fory's high-performance cross-language protocol. All types that can be serialized
-/// with Fory must implement this trait, either manually or via the `#[derive(ForyObject)]` macro.
+/// with Fory must implement this trait, either manually or via the `#[derive(ForyStruct)]` macro.
///
/// # Architecture Overview
///
@@ -173,10 +173,10 @@ pub trait ForyDefault: Sized {
///
/// # Derive Macro
///
-/// For struct types, you can use the `#[derive(ForyObject)]` macro instead of implementing manually:
+/// For struct types, you can use the `#[derive(ForyStruct)]` macro instead of implementing manually:
///
/// ```rust,ignore
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
/// struct Point {
/// x: f64,
/// y: f64,
@@ -1246,7 +1246,7 @@ pub trait Serializer: 'static {
///
/// **User types with custom serialization should NOT implement this trait.** This trait is:
///
-/// - **Only for derive-based types**: Automatically implemented by `#[derive(ForyObject)]`
+/// - **Only for derive-based types**: Automatically implemented by `#[derive(ForyStruct)]`
/// - **Internal infrastructure**: Used by Fory's struct/enum deserialization engine
/// - **Not for manual implementation**: Unless you're extending Fory's core functionality
///
@@ -1264,10 +1264,10 @@ pub trait Serializer: 'static {
///
/// # Automatic Implementation
///
-/// The `#[derive(ForyObject)]` macro automatically implements this trait for structs and enums:
+/// The `#[derive(ForyStruct)]` macro automatically implements this trait for structs and enums:
///
/// ```rust,ignore
-/// #[derive(ForyObject)]
+/// #[derive(ForyStruct)]
/// struct Point {
/// x: f64,
/// y: f64,
@@ -1288,7 +1288,7 @@ pub trait Serializer: 'static {
/// # Implementation Notes
///
/// - **Do not implement manually** for user types with custom serialization (EXT types)
-/// - **Only via derive macro**: `#[derive(ForyObject)]` is the only supported way
+/// - **Only via derive macro**: `#[derive(ForyStruct)]` is the only supported way
/// - **Internal use only**: Methods are called by Fory's serialization engine
/// - **Schema evolution**: Enables adding/removing fields without breaking compatibility
///
@@ -1314,7 +1314,7 @@ pub trait StructSerializer: Serializer + 'static {
///
/// # Implementation Notes
///
- /// - Implemented automatically by `#[derive(ForyObject)]` macro
+ /// - Implemented automatically by `#[derive(ForyStruct)]` macro
/// - Default returns empty vector (no fields)
/// - **Do not implement** for user types with custom serialization (EXT types)
#[allow(unused_variables)]
@@ -1339,7 +1339,7 @@ pub trait StructSerializer: Serializer + 'static {
///
/// # Implementation Notes
///
- /// - Implemented automatically by `#[derive(ForyObject)]` macro
+ /// - Implemented automatically by `#[derive(ForyStruct)]` macro
/// - Used for efficient type resolution
/// - **Do not implement** for user types with custom serialization (EXT types)
fn fory_type_index() -> u32 {
@@ -1389,7 +1389,7 @@ pub trait StructSerializer: Serializer + 'static {
///
/// # Implementation Notes
///
- /// - Implemented automatically by `#[derive(ForyObject)]` macro
+ /// - Implemented automatically by `#[derive(ForyStruct)]` macro
/// - Used for field ordering and compatibility
/// - Field order affects wire format
/// - **Do not implement** for user types with custom serialization (EXT types)
@@ -1426,7 +1426,7 @@ pub trait StructSerializer: Serializer + 'static {
///
/// # Implementation Notes
///
- /// - Implemented automatically by `#[derive(ForyObject)]` macro
+ /// - Implemented automatically by `#[derive(ForyStruct)]` macro
/// - Enables forward and backward compatibility
/// - Uses field names for matching when possible
/// - Falls back to field order for unnamed fields
diff --git a/rust/fory-core/src/serializer/heap.rs b/rust/fory-core/src/serializer/heap.rs
index cfbd7be30..7203c9545 100644
--- a/rust/fory-core/src/serializer/heap.rs
+++ b/rust/fory-core/src/serializer/heap.rs
@@ -34,6 +34,14 @@ impl<T: Serializer + ForyDefault + Ord> Serializer for BinaryHeap<T> {
write_collection_data(self, context, false)
}
+ fn fory_write_data_generic(
+ &self,
+ context: &mut WriteContext,
+ has_generics: bool,
+ ) -> Result<(), Error> {
+ write_collection_data(self, context, has_generics)
+ }
+
fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> {
write_collection_type_info(context, TypeId::SET as u32)
}
diff --git a/rust/fory-core/src/serializer/mod.rs b/rust/fory-core/src/serializer/mod.rs
index 1b2c97103..a51f48332 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -20,6 +20,8 @@ mod arc;
mod array;
mod bool;
mod box_;
+#[doc(hidden)]
+pub mod codec;
pub mod collection;
mod datetime;
pub mod enum_;
diff --git a/rust/fory-core/src/serializer/mutex.rs b/rust/fory-core/src/serializer/mutex.rs
index 9af0b06a2..90b69bcff 100644
--- a/rust/fory-core/src/serializer/mutex.rs
+++ b/rust/fory-core/src/serializer/mutex.rs
@@ -139,10 +139,7 @@ impl<T: Serializer + ForyDefault> Serializer for Mutex<T> {
#[inline(always)]
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
#[inline(always)]
diff --git a/rust/fory-core/src/serializer/rc.rs b/rust/fory-core/src/serializer/rc.rs
index a42e6dbeb..7c0f46861 100644
--- a/rust/fory-core/src/serializer/rc.rs
+++ b/rust/fory-core/src/serializer/rc.rs
@@ -134,10 +134,7 @@ impl<T: Serializer + ForyDefault + 'static> Serializer for Rc<T> {
}
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/serializer/refcell.rs b/rust/fory-core/src/serializer/refcell.rs
index da47ad678..3443b857f 100644
--- a/rust/fory-core/src/serializer/refcell.rs
+++ b/rust/fory-core/src/serializer/refcell.rs
@@ -137,10 +137,7 @@ impl<T: Serializer + ForyDefault> Serializer for RefCell<T> {
#[inline(always)]
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
#[inline(always)]
diff --git a/rust/fory-core/src/serializer/skip.rs b/rust/fory-core/src/serializer/skip.rs
index 9e139b0e2..c905c227a 100644
--- a/rust/fory-core/src/serializer/skip.rs
+++ b/rust/fory-core/src/serializer/skip.rs
@@ -37,13 +37,10 @@ pub fn skip_field_value(
skip_value(context, field_type, read_ref_flag, true, &None)
}
-const UNKNOWN_FIELD_TYPE: FieldType = FieldType {
- type_id: types::UNKNOWN,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![],
-};
+#[inline(always)]
+fn unknown_field_type() -> FieldType {
+ FieldType::new(types::UNKNOWN, true, Vec::new())
+}
pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<(), Error> {
// Handle ref flag first if needed
@@ -79,52 +76,26 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
// This is critical for polymorphic collections where elements are struct types.
let (field_type, type_info_opt) = match internal_id {
types::LIST | types::SET => (
- FieldType {
- type_id,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![UNKNOWN_FIELD_TYPE],
- },
+ FieldType::new(type_id, true, vec![unknown_field_type()]),
None,
),
types::MAP => (
- FieldType {
+ FieldType::new(
type_id,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![UNKNOWN_FIELD_TYPE, UNKNOWN_FIELD_TYPE],
- },
+ true,
+ vec![unknown_field_type(), unknown_field_type()],
+ ),
None,
),
types::COMPATIBLE_STRUCT | types::NAMED_COMPATIBLE_STRUCT => {
// For compatible struct types, read type meta inline using streaming protocol
let type_info = context.read_type_meta()?;
- (
- FieldType {
- type_id,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![],
- },
- Some(type_info),
- )
+ (FieldType::new(type_id, true, Vec::new()), Some(type_info))
}
types::NAMED_ENUM | types::NAMED_EXT | types::NAMED_STRUCT | types::NAMED_UNION => {
if context.is_share_meta() {
let type_info = context.read_type_meta()?;
- (
- FieldType {
- type_id,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![],
- },
- Some(type_info),
- )
+ (FieldType::new(type_id, true, Vec::new()), Some(type_info))
} else {
let namespace = context.read_meta_string()?.to_owned();
let type_name = context.read_meta_string()?.to_owned();
@@ -134,16 +105,7 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
.get_type_resolver()
.get_type_info_by_meta_string_name(rc_namespace, rc_type_name)
.ok_or_else(|| crate::Error::type_error("Name harness not found"))?;
- (
- FieldType {
- type_id,
- user_type_id: u32::MAX,
- nullable: true,
- track_ref: false,
- generics: vec![],
- },
- Some(type_info),
- )
+ (FieldType::new(type_id, true, Vec::new()), Some(type_info))
}
}
_ => {
@@ -155,13 +117,13 @@ pub fn skip_any_value(context: &mut ReadContext, read_ref_flag: bool) -> Result<
None
};
(
- FieldType {
+ FieldType::new_with_user_type_id(
type_id,
- user_type_id: _user_type_id.unwrap_or(u32::MAX),
- nullable: true,
- track_ref: false,
- generics: vec![],
- },
+ _user_type_id.unwrap_or(u32::MAX),
+ true,
+ false,
+ Vec::new(),
+ ),
type_info,
)
}
@@ -188,13 +150,13 @@ fn skip_collection(context: &mut ReadContext, field_type: &FieldType) -> Result<
let (type_info, elem_field_type);
let elem_type = if is_same_type && !is_declared {
let type_info_rc = context.read_any_type_info()?;
- elem_field_type = FieldType {
- type_id: type_info_rc.get_type_id() as u32,
- user_type_id: type_info_rc.get_user_type_id(),
- nullable: has_null,
- track_ref: false,
- generics: vec![],
- };
+ elem_field_type = FieldType::new_with_user_type_id(
+ type_info_rc.get_type_id() as u32,
+ type_info_rc.get_user_type_id(),
+ has_null,
+ false,
+ Vec::new(),
+ );
type_info = Some(type_info_rc);
&elem_field_type
} else {
@@ -237,13 +199,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
let (value_type_info, value_field_type);
let value_type = if !value_declared {
let type_info = context.read_any_type_info()?;
- value_field_type = FieldType {
- type_id: type_info.get_type_id() as u32,
- user_type_id: type_info.get_user_type_id(),
- nullable: true,
- track_ref: false,
- generics: vec![],
- };
+ value_field_type = FieldType::new_with_user_type_id(
+ type_info.get_type_id() as u32,
+ type_info.get_user_type_id(),
+ true,
+ false,
+ Vec::new(),
+ );
value_type_info = Some(type_info);
&value_field_type
} else {
@@ -262,13 +224,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
let (key_type_info, key_field_type);
let key_type = if !key_declared {
let type_info = context.read_any_type_info()?;
- key_field_type = FieldType {
- type_id: type_info.get_type_id() as u32,
- user_type_id: type_info.get_user_type_id(),
- nullable: true,
- track_ref: false,
- generics: vec![],
- };
+ key_field_type = FieldType::new_with_user_type_id(
+ type_info.get_type_id() as u32,
+ type_info.get_user_type_id(),
+ true,
+ false,
+ Vec::new(),
+ );
key_type_info = Some(type_info);
&key_field_type
} else {
@@ -290,13 +252,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
let (key_type_info, key_field_type);
let key_type = if !key_declared {
let type_info = context.read_any_type_info()?;
- key_field_type = FieldType {
- type_id: type_info.get_type_id() as u32,
- user_type_id: type_info.get_user_type_id(),
- nullable: true,
- track_ref: false,
- generics: vec![],
- };
+ key_field_type = FieldType::new_with_user_type_id(
+ type_info.get_type_id() as u32,
+ type_info.get_user_type_id(),
+ true,
+ false,
+ Vec::new(),
+ );
key_type_info = Some(type_info);
&key_field_type
} else {
@@ -308,13 +270,13 @@ fn skip_map(context: &mut ReadContext, field_type: &FieldType) -> Result<(), Err
let (value_type_info, value_field_type);
let value_type = if !value_declared {
let type_info = context.read_any_type_info()?;
- value_field_type = FieldType {
- type_id: type_info.get_type_id() as u32,
- user_type_id: type_info.get_user_type_id(),
- nullable: true,
- track_ref: false,
- generics: vec![],
- };
+ value_field_type = FieldType::new_with_user_type_id(
+ type_info.get_type_id() as u32,
+ type_info.get_user_type_id(),
+ true,
+ false,
+ Vec::new(),
+ );
value_type_info = Some(type_info);
&value_field_type
} else {
@@ -802,13 +764,7 @@ pub fn skip_enum_variant(
0b1 => {
// Unnamed variant, skip tuple data (which is serialized as a collection)
// Tuple uses collection format but doesn't write type info, so skip directly
- let field_type = FieldType {
- type_id: types::LIST,
- user_type_id: u32::MAX,
- nullable: false,
- track_ref: false,
- generics: vec![UNKNOWN_FIELD_TYPE],
- };
+ let field_type = FieldType::new(types::LIST, false, vec![unknown_field_type()]);
skip_collection(context, &field_type)
}
0b10 => {
diff --git a/rust/fory-core/src/serializer/struct_.rs b/rust/fory-core/src/serializer/struct_.rs
index d22fe1626..7981a6dba 100644
--- a/rust/fory-core/src/serializer/struct_.rs
+++ b/rust/fory-core/src/serializer/struct_.rs
@@ -47,6 +47,11 @@ pub fn write_type_info<T: Serializer>(context: &mut WriteContext) -> Result<(),
Ok(())
}
+#[inline(always)]
+pub fn write_type_info_fast<T: StructSerializer>(context: &mut WriteContext) -> Result<(), Error> {
+ context.write_struct_type_info::<T>()
+}
+
#[inline(always)]
pub fn read_type_info<T: Serializer>(context: &mut ReadContext) -> Result<(), Error> {
context.read_any_type_info()?;
diff --git a/rust/fory-core/src/serializer/trait_object.rs b/rust/fory-core/src/serializer/trait_object.rs
index bfbdfd9d0..bcba47675 100644
--- a/rust/fory-core/src/serializer/trait_object.rs
+++ b/rust/fory-core/src/serializer/trait_object.rs
@@ -64,17 +64,17 @@ macro_rules! downcast_and_serialize {
///
/// ```rust,ignore
/// use fory_core::{fory::Fory, register_trait_type, serializer::Serializer};
-/// use fory_derive::ForyObject;
+/// use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
///
/// trait Animal: Serializer {
/// fn speak(&self) -> String;
/// fn name(&self) -> &str;
/// }
///
-/// #[derive(ForyObject, Debug)]
+/// #[derive(ForyStruct, Debug)]
/// struct Dog { name: String }
///
-/// #[derive(ForyObject, Debug)]
+/// #[derive(ForyStruct, Debug)]
/// struct Cat { name: String }
///
/// impl Animal for Dog {
@@ -110,6 +110,7 @@ macro_rules! register_trait_type {
Box::new(<register_trait_type!(@first_type $($impl_type),+) as $crate::serializer::ForyDefault>::fory_default())
}
}
+ $crate::generate_box_trait_codec!($trait_name);
// 2. Auto-generate Rc wrapper type and conversions
$crate::generate_smart_pointer_wrapper!(
@@ -122,6 +123,7 @@ macro_rules! register_trait_type {
store_rc_ref,
$($impl_type),+
);
+ $crate::generate_smart_pointer_codec!(std::rc::Rc, Rc, $trait_name);
// 3. Auto-generate Arc wrapper type and conversions
$crate::generate_smart_pointer_wrapper!(
@@ -134,6 +136,7 @@ macro_rules! register_trait_type {
store_arc_ref,
$($impl_type),+
);
+ $crate::generate_smart_pointer_codec!(std::sync::Arc, Arc, $trait_name);
// 4. Serializer implementation for Box<dyn Trait> (existing functionality)
impl fory_core::Serializer for Box<dyn $trait_name> {
@@ -338,6 +341,363 @@ macro_rules! generate_smart_pointer_wrapper {
};
}
+#[macro_export]
+macro_rules! generate_smart_pointer_codec {
+ ($ptr_path:path, $ptr_name:ident, $trait_name:ident) => {
+ $crate::paste::paste! {
+ pub(crate) struct [<$trait_name $ptr_name Codec>]<const NULLABLE: bool, const TRACK_REF: bool>;
+
+ impl<const NULLABLE: bool, const TRACK_REF: bool>
+ fory_core::serializer::codec::Codec<$ptr_path<dyn $trait_name>>
+ for [<$trait_name $ptr_name Codec>]<NULLABLE, TRACK_REF>
+ {
+ #[inline(always)]
+ fn field_type(
+ _type_resolver: &fory_core::resolver::TypeResolver,
+ ) -> Result<fory_core::meta::FieldType, fory_core::Error> {
+ Ok(fory_core::meta::FieldType::new_with_ref(
+ fory_core::type_id::TypeId::UNKNOWN as u32,
+ NULLABLE,
+ TRACK_REF,
+ Vec::new(),
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_reserved_space()
+ + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(
+ value: &$ptr_path<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ) -> Result<(), fory_core::Error> {
+ let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+ <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write(
+ &wrapper,
+ context,
+ if TRACK_REF {
+ fory_core::RefMode::Tracking
+ } else if NULLABLE {
+ fory_core::RefMode::NullOnly
+ } else {
+ fory_core::RefMode::None
+ },
+ true,
+ false,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(
+ context: &mut fory_core::ReadContext,
+ ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(
+ context,
+ if TRACK_REF {
+ fory_core::RefMode::Tracking
+ } else if NULLABLE {
+ fory_core::RefMode::NullOnly
+ } else {
+ fory_core::RefMode::None
+ },
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(
+ value: &$ptr_path<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ) -> Result<(), fory_core::Error> {
+ let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+ <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write_data(
+ &wrapper,
+ context,
+ )
+ }
+
+ #[inline(always)]
+ fn read_data(
+ context: &mut fory_core::ReadContext,
+ ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(context, fory_core::RefMode::None, true)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut fory_core::ReadContext,
+ remote_field_type: &fory_core::meta::FieldType,
+ ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(
+ context,
+ fory_core::serializer::codec::field_ref_mode(remote_field_type),
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &$ptr_path<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ref_mode: fory_core::RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), fory_core::Error> {
+ let wrapper = [<$trait_name $ptr_name>]::from(value.clone());
+ <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_write(
+ &wrapper,
+ context,
+ ref_mode,
+ write_type_info,
+ has_generics,
+ )
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut fory_core::ReadContext,
+ ref_mode: fory_core::RefMode,
+ read_type_info: bool,
+ ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+ let wrapper = <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_read(
+ context,
+ ref_mode,
+ read_type_info,
+ )?;
+ Ok($ptr_path::<dyn $trait_name>::from(wrapper))
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut fory_core::ReadContext,
+ ref_mode: fory_core::RefMode,
+ type_info: std::rc::Rc<fory_core::TypeInfo>,
+ ) -> Result<$ptr_path<dyn $trait_name>, fory_core::Error> {
+ let wrapper =
+ <[<$trait_name $ptr_name>] as fory_core::Serializer>::fory_read_with_type_info(
+ context,
+ ref_mode,
+ type_info,
+ )?;
+ Ok($ptr_path::<dyn $trait_name>::from(wrapper))
+ }
+
+ #[inline(always)]
+ fn default_value() -> $ptr_path<dyn $trait_name> {
+ $ptr_path::<dyn $trait_name>::from(
+ <[<$trait_name $ptr_name>] as fory_core::ForyDefault>::fory_default(),
+ )
+ }
+
+ #[inline(always)]
+ fn write_type_info(_context: &mut fory_core::WriteContext) -> Result<(), fory_core::Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(_context: &mut fory_core::ReadContext) -> Result<(), fory_core::Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> fory_core::TypeId {
+ fory_core::TypeId::UNKNOWN
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ true
+ }
+
+ #[inline(always)]
+ fn is_shared_ref() -> bool {
+ true
+ }
+
+ #[inline(always)]
+ fn concrete_type_id(value: &$ptr_path<dyn $trait_name>) -> std::any::TypeId {
+ <dyn $trait_name as fory_core::Serializer>::as_any(&**value).type_id()
+ }
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! generate_box_trait_codec {
+ ($trait_name:ident) => {
+ $crate::paste::paste! {
+ pub(crate) struct [<$trait_name BoxCodec>]<const NULLABLE: bool, const TRACK_REF: bool>;
+
+ impl<const NULLABLE: bool, const TRACK_REF: bool>
+ fory_core::serializer::codec::Codec<Box<dyn $trait_name>>
+ for [<$trait_name BoxCodec>]<NULLABLE, TRACK_REF>
+ {
+ #[inline(always)]
+ fn field_type(
+ _type_resolver: &fory_core::resolver::TypeResolver,
+ ) -> Result<fory_core::meta::FieldType, fory_core::Error> {
+ Ok(fory_core::meta::FieldType::new_with_ref(
+ fory_core::type_id::TypeId::UNKNOWN as u32,
+ NULLABLE,
+ TRACK_REF,
+ Vec::new(),
+ ))
+ }
+
+ #[inline(always)]
+ fn reserved_space() -> usize {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_reserved_space()
+ + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+ }
+
+ #[inline(always)]
+ fn write_field(
+ value: &Box<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ) -> Result<(), fory_core::Error> {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_write(
+ value,
+ context,
+ if TRACK_REF {
+ fory_core::RefMode::Tracking
+ } else if NULLABLE {
+ fory_core::RefMode::NullOnly
+ } else {
+ fory_core::RefMode::None
+ },
+ true,
+ false,
+ )
+ }
+
+ #[inline(always)]
+ fn read_field(
+ context: &mut fory_core::ReadContext,
+ ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(
+ context,
+ if TRACK_REF {
+ fory_core::RefMode::Tracking
+ } else if NULLABLE {
+ fory_core::RefMode::NullOnly
+ } else {
+ fory_core::RefMode::None
+ },
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn write_data(
+ value: &Box<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ) -> Result<(), fory_core::Error> {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_write_data(
+ value,
+ context,
+ )
+ }
+
+ #[inline(always)]
+ fn read_data(
+ context: &mut fory_core::ReadContext,
+ ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(context, fory_core::RefMode::None, true)
+ }
+
+ #[inline(always)]
+ fn read_field_with_type(
+ context: &mut fory_core::ReadContext,
+ remote_field_type: &fory_core::meta::FieldType,
+ ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+ Self::read_with_mode(
+ context,
+ fory_core::serializer::codec::field_ref_mode(remote_field_type),
+ true,
+ )
+ }
+
+ #[inline(always)]
+ fn write_with_mode(
+ value: &Box<dyn $trait_name>,
+ context: &mut fory_core::WriteContext,
+ ref_mode: fory_core::RefMode,
+ write_type_info: bool,
+ has_generics: bool,
+ ) -> Result<(), fory_core::Error> {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_write(
+ value,
+ context,
+ ref_mode,
+ write_type_info,
+ has_generics,
+ )
+ }
+
+ #[inline(always)]
+ fn read_with_mode(
+ context: &mut fory_core::ReadContext,
+ ref_mode: fory_core::RefMode,
+ read_type_info: bool,
+ ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_read(
+ context,
+ ref_mode,
+ read_type_info,
+ )
+ }
+
+ #[inline(always)]
+ fn read_with_type_info(
+ context: &mut fory_core::ReadContext,
+ ref_mode: fory_core::RefMode,
+ type_info: std::rc::Rc<fory_core::TypeInfo>,
+ ) -> Result<Box<dyn $trait_name>, fory_core::Error> {
+ <Box<dyn $trait_name> as fory_core::Serializer>::fory_read_with_type_info(
+ context,
+ ref_mode,
+ type_info,
+ )
+ }
+
+ #[inline(always)]
+ fn default_value() -> Box<dyn $trait_name> {
+ <Box<dyn $trait_name> as fory_core::ForyDefault>::fory_default()
+ }
+
+ #[inline(always)]
+ fn write_type_info(_context: &mut fory_core::WriteContext) -> Result<(), fory_core::Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn read_type_info(_context: &mut fory_core::ReadContext) -> Result<(), fory_core::Error> {
+ Ok(())
+ }
+
+ #[inline(always)]
+ fn static_type_id() -> fory_core::TypeId {
+ fory_core::TypeId::UNKNOWN
+ }
+
+ #[inline(always)]
+ fn is_polymorphic() -> bool {
+ true
+ }
+
+ #[inline(always)]
+ fn concrete_type_id(value: &Box<dyn $trait_name>) -> std::any::TypeId {
+ <dyn $trait_name as fory_core::Serializer>::as_any(&**value).type_id()
+ }
+ }
+ }
+ };
+}
+
/// Macro to read smart pointer trait objects (`Rc<dyn Trait>,` `Arc<dyn Trait>`)
/// This macro handles ref tracking and directly constructs the trait object from concrete types
#[macro_export]
diff --git a/rust/fory-core/src/serializer/weak.rs b/rust/fory-core/src/serializer/weak.rs
index f9f4e0849..8e26dcf37 100644
--- a/rust/fory-core/src/serializer/weak.rs
+++ b/rust/fory-core/src/serializer/weak.rs
@@ -126,10 +126,7 @@ impl<T: Serializer + ForyDefault + 'static> Serializer for RcWeak<T> {
}
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
@@ -299,10 +296,7 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for ArcWeak
}
fn fory_get_type_info(type_resolver: &TypeResolver) -> Result<Rc<TypeInfo>, Error> {
- match type_resolver.get_type_info(&std::any::TypeId::of::<T>()) {
- Ok(info) => Ok(info),
- Err(e) => Err(Error::enhance_type_error::<T>(e)),
- }
+ T::fory_get_type_info(type_resolver)
}
fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<TypeId, Error> {
diff --git a/rust/fory-core/src/util/mod.rs b/rust/fory-core/src/util/mod.rs
index 071094d95..85d3be0dc 100644
--- a/rust/fory-core/src/util/mod.rs
+++ b/rust/fory-core/src/util/mod.rs
@@ -33,6 +33,6 @@ pub const EPOCH: NaiveDate = match NaiveDate::from_ymd_opt(1970, 1, 1) {
Some(epoch) => epoch,
};
-/// Global flag to check if ENABLE_FORY_DEBUG_OUTPUT environment variable is set at compile time.
-/// Set ENABLE_FORY_DEBUG_OUTPUT=1 at compile time to enable debug output.
-pub const ENABLE_FORY_DEBUG_OUTPUT: bool = option_env!("ENABLE_FORY_DEBUG_OUTPUT").is_some();
+/// Set `ENABLE_FORY_DEBUG_OUTPUT=1` at compile time to enable debug output.
+#[allow(unexpected_cfgs)]
+pub const ENABLE_FORY_DEBUG_OUTPUT: bool = cfg!(fory_debug_output);
diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs
index 4ca12cbe6..f8a726ef1 100644
--- a/rust/fory-derive/src/lib.rs
+++ b/rust/fory-derive/src/lib.rs
@@ -22,24 +22,22 @@
//!
//! ## Available Macros
//!
-//! ### `#[derive(ForyObject)]`
+//! ### `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, `#[derive(ForyUnion)]`
//!
-//! Generates object serialization code for structs and enums. This macro
-//! implements the `Serializer` trait, enabling full object graph serialization
-//! with reference handling.
+//! Generates `Serializer` implementations for structs, pure enums, and tagged
+//! unions with payload variants.
//!
//! **Supported Types:**
-//! - Structs with named fields
-//! - Structs with unnamed fields (tuple structs)
-//! - Unit structs
-//! - Enums with variants
+//! - `ForyStruct`: named, tuple, and unit structs
+//! - `ForyEnum`: pure unit enums
+//! - `ForyUnion`: enums with payload variants
//!
//! **Example:**
//! ```rust
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct};
//! use std::collections::HashMap;
//!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
//! struct Person {
//! name: String,
//! age: i32,
@@ -48,13 +46,13 @@
//! metadata: HashMap<String, String>,
//! }
//!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
//! struct Address {
//! street: String,
//! city: String,
//! }
//!
-//! #[derive(ForyObject, Debug, PartialEq, Default)]
+//! #[derive(ForyEnum, Debug, PartialEq, Default)]
//! enum Status {
//! #[default]
//! Active,
@@ -90,7 +88,7 @@
//!
//! ## Generated Code
//!
-//! ### For `#[derive(ForyObject)]`
+//! ### For `#[derive(ForyStruct)]`, `#[derive(ForyEnum)]`, and `#[derive(ForyUnion)]`
//!
//! The macro generates:
//! - `Serializer` trait implementation
@@ -116,7 +114,7 @@
//! - **`#[fory(skip)]`**: Marks an individual field (or enum variant) to be ignored by the
//! generated serializer, retaining compatibility with previous releases.
//! - **`#[fory(generate_default)]`**: Enables the macro to generate `Default` implementation.
-//! By default, `ForyObject` does NOT generate `impl Default` to avoid conflicts with existing
+//! By default, `ForyStruct` does NOT generate `impl Default` to avoid conflicts with existing
//! `Default` implementations. Use this attribute when you want the macro to generate both
//! `ForyDefault` and `Default` for you.
//!
@@ -148,9 +146,9 @@
//!
//! ```rust
//! use fory_core::{fory::Fory, error::Error};
-//! use fory_derive::ForyObject;
+//! use fory_derive::{ForyEnum, ForyStruct, ForyUnion};
//!
-//! #[derive(ForyObject, Debug, PartialEq)]
+//! #[derive(ForyStruct, Debug, PartialEq)]
//! struct MyData {
//! value: i32,
//! text: String,
@@ -182,42 +180,79 @@
use fory_row::derive_row;
use proc_macro::TokenStream;
-use syn::{parse_macro_input, spanned::Spanned, Attribute, DeriveInput, LitBool};
+use syn::{parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, LitBool};
mod fory_row;
mod object;
mod util;
-/// Derive macro for object serialization.
-///
-/// This macro generates code to implement the `Serializer` trait for the
-/// annotated type, enabling full object graph serialization with reference
-/// handling and cross-language compatibility.
-///
-/// # Example
-///
-/// ```rust
-/// use fory_derive::ForyObject;
-///
-/// #[derive(ForyObject, Debug, PartialEq)]
-/// struct Person {
-/// name: String,
-/// age: i32,
-/// address: Address,
-/// }
-///
-/// #[derive(ForyObject, Debug, PartialEq)]
-/// struct Address {
-/// street: String,
-/// city: String,
-/// }
-/// ```
-#[proc_macro_derive(ForyObject, attributes(fory))]
-pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) -> TokenStream {
+/// Derive macro for struct serialization.
+#[proc_macro_derive(ForyStruct, attributes(fory))]
+pub fn proc_macro_derive_fory_struct(input: proc_macro::TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ if !matches!(input.data, Data::Struct(_)) {
+ return syn::Error::new(
+ input.ident.span(),
+ "ForyStruct can only be derived for structs; use ForyEnum for pure enums or ForyUnion for data-carrying enums",
+ )
+ .into_compile_error()
+ .into();
+ }
+ derive_serializer(input)
+}
+
+/// Derive macro for pure enum serialization.
+#[proc_macro_derive(ForyEnum, attributes(fory))]
+pub fn proc_macro_derive_fory_enum(input: proc_macro::TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
+ let Data::Enum(data_enum) = &input.data else {
+ return syn::Error::new(input.ident.span(), "ForyEnum can only be derived for enums")
+ .into_compile_error()
+ .into();
+ };
+ if data_enum
+ .variants
+ .iter()
+ .any(|variant| !matches!(variant.fields, Fields::Unit))
+ {
+ return syn::Error::new(
+ input.ident.span(),
+ "ForyEnum is only for pure unit enums; use ForyUnion for enum variants with payloads",
+ )
+ .into_compile_error()
+ .into();
+ }
+ derive_serializer(input)
+}
+
+/// Derive macro for tagged union serialization.
+#[proc_macro_derive(ForyUnion, attributes(fory))]
+pub fn proc_macro_derive_fory_union(input: proc_macro::TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let Data::Enum(data_enum) = &input.data else {
+ return syn::Error::new(
+ input.ident.span(),
+ "ForyUnion can only be derived for enums",
+ )
+ .into_compile_error()
+ .into();
+ };
+ if data_enum
+ .variants
+ .iter()
+ .all(|variant| matches!(variant.fields, Fields::Unit))
+ {
+ return syn::Error::new(
+ input.ident.span(),
+ "ForyUnion requires at least one payload variant; use ForyEnum for pure unit enums",
+ )
+ .into_compile_error()
+ .into();
+ }
+ derive_serializer(input)
+}
- // Check if this is being applied to a trait (which is not possible with derive macros)
- // Derive macros can only be applied to structs, enums, and unions
+fn derive_serializer(input: DeriveInput) -> TokenStream {
let attrs = match parse_fory_attrs(&input.attrs) {
Ok(attrs) => attrs,
Err(err) => return err.into_compile_error().into(),
diff --git a/rust/fory-derive/src/object/derive_enum.rs b/rust/fory-derive/src/object/derive_enum.rs
index dbeaf5729..d394b5fe0 100644
--- a/rust/fory-derive/src/object/derive_enum.rs
+++ b/rust/fory-derive/src/object/derive_enum.rs
@@ -15,12 +15,11 @@
// specific language governing permissions and limitations
// under the License.
+use super::field_codec::{build_bindings, FieldBinding};
use super::util::{enum_variant_id, is_default_value_variant, is_skip_enum_variant};
use crate::object::misc;
-use crate::object::read::gen_read_field;
use crate::object::util::{get_filtered_fields_iter, get_sorted_field_names};
-use crate::object::write::gen_write_field;
-use crate::util::{extract_fields, source_fields};
+use crate::util::{extract_fields, source_fields, SourceField};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{DataEnum, Fields};
@@ -28,6 +27,175 @@ fn temp_var_name(i: usize) -> String {
format!("f{}", i)
}
+fn gen_write_named_variant_fields(
+ source_fields: &[crate::util::SourceField<'_>],
+ field_idents: &[&syn::Ident],
+) -> Vec<TokenStream> {
+ match build_bindings(source_fields) {
+ Ok(bindings) => bindings
+ .iter()
+ .zip(field_idents.iter())
+ .filter_map(|(binding, ident)| match binding {
+ FieldBinding::Codec(binding) => Some(binding.write_value_field(quote! { #ident })),
+ FieldBinding::Skipped(_) => None,
+ })
+ .collect(),
+ Err(err) => vec![err.to_compile_error()],
+ }
+}
+
+fn unnamed_source_fields(fields: &syn::FieldsUnnamed) -> Vec<SourceField<'_>> {
+ fields
+ .unnamed
+ .iter()
+ .enumerate()
+ .map(|(idx, field)| SourceField {
+ original_index: idx,
+ field,
+ field_name: idx.to_string(),
+ is_tuple_struct: true,
+ })
+ .collect()
+}
+
+fn gen_write_variant_fields(
+ source_fields: &[SourceField<'_>],
+ field_idents: &[Ident],
+) -> Vec<TokenStream> {
+ match build_bindings(source_fields) {
+ Ok(bindings) => bindings
+ .iter()
+ .zip(field_idents.iter())
+ .filter_map(|(binding, ident)| match binding {
+ FieldBinding::Codec(binding) => Some(binding.write_value_field(quote! { #ident })),
+ FieldBinding::Skipped(_) => None,
+ })
+ .collect(),
+ Err(err) => vec![err.to_compile_error()],
+ }
+}
+
+fn gen_write_variant_elements(
+ source_fields: &[SourceField<'_>],
+ field_idents: &[Ident],
+) -> Vec<TokenStream> {
+ match build_bindings(source_fields) {
+ Ok(bindings) => bindings
+ .iter()
+ .zip(field_idents.iter())
+ .filter_map(|(binding, ident)| match binding {
+ FieldBinding::Codec(binding) => Some(binding.write_value_with_mode(
+ quote! { #ident },
+ quote! { fory_core::RefMode::NullOnly },
+ quote! { true },
+ )),
+ FieldBinding::Skipped(_) => None,
+ })
+ .collect(),
+ Err(err) => vec![err.to_compile_error()],
+ }
+}
+
+fn gen_write_single_payload(source_fields: &[SourceField<'_>], value: TokenStream) -> TokenStream {
+ match build_bindings(source_fields) {
+ Ok(bindings) => match bindings.as_slice() {
+ [FieldBinding::Codec(binding)] => binding.write_value_with_mode(
+ value,
+ quote! { fory_core::RefMode::Tracking },
+ quote! { true },
+ ),
+ [FieldBinding::Skipped(_)] => {
+ quote! { compile_error!("skip is not valid for union payload fields"); }
+ }
+ _ => quote! { compile_error!("union payload helper requires exactly one field"); },
+ },
+ Err(err) => err.to_compile_error(),
+ }
+}
+
+fn gen_read_variant_fields(source_fields: &[SourceField<'_>]) -> (Vec<TokenStream>, Vec<Ident>) {
+ match build_bindings(source_fields) {
+ Ok(bindings) => {
+ let read_fields = bindings
+ .iter()
+ .map(|binding| match binding {
+ FieldBinding::Codec(binding) => binding.read_field(),
+ FieldBinding::Skipped(binding) => binding.read_default(),
+ })
+ .collect();
+ let private_idents = bindings
+ .iter()
+ .map(|binding| match binding {
+ FieldBinding::Codec(binding) => binding.private_ident.clone(),
+ FieldBinding::Skipped(binding) => binding.private_ident.clone(),
+ })
+ .collect();
+ (read_fields, private_idents)
+ }
+ Err(err) => (vec![err.to_compile_error()], Vec::new()),
+ }
+}
+
+fn gen_read_variant_elements(
+ source_fields: &[SourceField<'_>],
+) -> (Vec<TokenStream>, Vec<Ident>, usize) {
+ match build_bindings(source_fields) {
+ Ok(bindings) => {
+ let mut serialized_index = 0usize;
+ let mut read_fields = Vec::with_capacity(bindings.len());
+ let mut private_idents = Vec::with_capacity(bindings.len());
+ for binding in bindings {
+ match binding {
+ FieldBinding::Codec(binding) => {
+ let var = binding.private_ident.clone();
+ let default_expr =
+ super::field_codec::default_expr_for_type(binding.value_ty);
+ let index = serialized_index;
+ serialized_index += 1;
+ let read_value = binding.read_with_mode_expr(
+ quote! { fory_core::RefMode::NullOnly },
+ quote! { true },
+ );
+ read_fields.push(quote! {
+ let #var = if #index < len {
+ #read_value
+ } else {
+ #default_expr
+ };
+ });
+ private_idents.push(var);
+ }
+ FieldBinding::Skipped(binding) => {
+ let var = binding.private_ident;
+ let default_expr =
+ super::field_codec::default_expr_for_type(&binding.source.field.ty);
+ read_fields.push(quote! {
+ let #var = #default_expr;
+ });
+ private_idents.push(var);
+ }
+ }
+ }
+ (read_fields, private_idents, serialized_index)
+ }
+ Err(err) => (vec![err.to_compile_error()], Vec::new(), 0),
+ }
+}
+
+fn gen_read_single_payload(source_fields: &[SourceField<'_>]) -> TokenStream {
+ match build_bindings(source_fields) {
+ Ok(bindings) => match bindings.as_slice() {
+ [FieldBinding::Codec(binding)] => binding
+ .read_with_mode_expr(quote! { fory_core::RefMode::Tracking }, quote! { true }),
+ [FieldBinding::Skipped(_)] => {
+ quote! { compile_error!("skip is not valid for union payload fields") }
+ }
+ _ => quote! { compile_error!("union payload helper requires exactly one field") },
+ },
+ Err(err) => err.to_compile_error(),
+ }
+}
+
/// For Union-compatible enums with data variants, return UNION TypeId in xlang mode.
pub fn gen_actual_type_id(data_enum: &DataEnum) -> TokenStream {
let is_union_compatible = is_union_compatible_enum(data_enum);
@@ -223,12 +391,13 @@ fn xlang_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> V
}
Fields::Unnamed(fields_unnamed) => {
if is_union_compatible && fields_unnamed.unnamed.len() == 1 {
- // Union-compatible single field: write tag + value with type info (like xwriteRef)
+ let source_fields = unnamed_source_fields(fields_unnamed);
+ let write_payload =
+ gen_write_single_payload(&source_fields, quote! { value });
quote! {
Self::#ident(ref value) => {
context.writer.write_var_u32(#tag_value);
- use fory_core::serializer::Serializer;
- value.fory_write(context, fory_core::RefMode::Tracking, true, false)?;
+ #write_payload
}
}
} else {
@@ -241,14 +410,16 @@ fn xlang_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> V
}
Fields::Named(fields_named) => {
if is_union_compatible && fields_named.named.len() == 1 {
- // Union-compatible single field: write tag + value with type info (like xwriteRef)
let field_ident =
fields_named.named.first().unwrap().ident.as_ref().unwrap();
+ let fields_clone = syn::Fields::Named(fields_named.clone());
+ let source_fields = source_fields(&fields_clone);
+ let write_payload =
+ gen_write_single_payload(&source_fields, quote! { #field_ident });
quote! {
Self::#ident { ref #field_ident } => {
context.writer.write_var_u32(#tag_value);
- use fory_core::serializer::Serializer;
- #field_ident.fory_write(context, fory_core::RefMode::Tracking, true, false)?;
+ #write_payload
}
}
} else {
@@ -285,16 +456,12 @@ fn rust_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> Ve
}
}
Fields::Unnamed(fields_unnamed) => {
+ let source_fields = unnamed_source_fields(fields_unnamed);
let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
.map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
.collect();
- let write_fields: Vec<_> = fields_unnamed
- .unnamed
- .iter()
- .zip(field_idents.iter())
- .map(|(f, ident)| gen_write_field(f, ident, false))
- .collect();
+ let write_fields = gen_write_variant_fields(&source_fields, &field_idents);
quote! {
Self::#ident( #(#field_idents),* ) => {
@@ -314,11 +481,8 @@ fn rust_variant_branches(data_enum: &DataEnum, default_variant_value: u32) -> Ve
.map(|sf| sf.field.ident.as_ref().unwrap())
.collect();
- let write_fields: Vec<_> = source_fields
- .iter()
- .zip(field_idents.iter())
- .map(|(sf, ident)| gen_write_field(sf.field, ident, false))
- .collect();
+ let write_fields =
+ gen_write_named_variant_fields(&source_fields, &field_idents);
quote! {
Self::#ident { #(#field_idents),* } => {
@@ -360,11 +524,12 @@ fn rust_compatible_variant_write_branches(
}
Fields::Unnamed(fields_unnamed) => {
// For unnamed enum variants, write using collection format (same protocol as tuple)
+ let source_fields = unnamed_source_fields(fields_unnamed);
let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
.map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
.collect();
-
- let field_count = fields_unnamed.unnamed.len();
+ let write_fields = gen_write_variant_elements(&source_fields, &field_idents);
+ let field_count = write_fields.len();
quote! {
Self::#ident( #(ref #field_idents),* ) => {
@@ -373,10 +538,7 @@ fn rust_compatible_variant_write_branches(
context.writer.write_var_u32(#field_count as u32);
let header = 0u8; // No IS_SAME_TYPE flag
context.writer.write_u8(header);
- use fory_core::serializer::Serializer;
- #(
- #field_idents.fory_write(context, fory_core::RefMode::NullOnly, true, false)?;
- )*
+ #(#write_fields)*
}
}
}
@@ -384,7 +546,7 @@ fn rust_compatible_variant_write_branches(
// Generate meta type identifier for this named variant
let meta_type_ident = Ident::new(
&format!("{}_{}VariantMeta", enum_name, ident),
- proc_macro2::Span::call_site()
+ proc_macro2::Span::call_site(),
);
let fields_clone = syn::Fields::Named(fields_named.clone());
let source_fields = source_fields(&fields_clone);
@@ -393,11 +555,8 @@ fn rust_compatible_variant_write_branches(
.map(|sf| sf.field.ident.as_ref().unwrap())
.collect();
- let write_fields: Vec<_> = source_fields
- .iter()
- .zip(field_idents.iter())
- .map(|(sf, ident)| gen_write_field(sf.field, ident, false))
- .collect();
+ let write_fields =
+ gen_write_named_variant_fields(&source_fields, &field_idents);
quote! {
Self::#ident { #(#field_idents),* } => {
@@ -560,12 +719,11 @@ fn xlang_variant_read_branches(
}
Fields::Unnamed(fields_unnamed) => {
if is_union_compatible && fields_unnamed.unnamed.len() == 1 {
- // Union-compatible single field: read value with ref_info=Tracking, type_info=true
- let field_ty = &fields_unnamed.unnamed.first().unwrap().ty;
+ let source_fields = unnamed_source_fields(fields_unnamed);
+ let read_payload = gen_read_single_payload(&source_fields);
quote! {
#tag_value => {
- use fory_core::serializer::Serializer;
- let value = <#field_ty as Serializer>::fory_read(context, fory_core::RefMode::Tracking, true)?;
+ let value = #read_payload;
Ok(Self::#ident(value))
}
}
@@ -573,10 +731,7 @@ fn xlang_variant_read_branches(
let default_fields: Vec<TokenStream> = fields_unnamed
.unnamed
.iter()
- .map(|f| {
- let ty = &f.ty;
- quote! { <#ty as fory_core::ForyDefault>::fory_default() }
- })
+ .map(|f| super::field_codec::default_expr_for_type(&f.ty))
.collect();
quote! {
#tag_value => Ok(Self::#ident( #(#default_fields),* )),
@@ -585,14 +740,14 @@ fn xlang_variant_read_branches(
}
Fields::Named(fields_named) => {
if is_union_compatible && fields_named.named.len() == 1 {
- // Union-compatible single field: read value with ref_info=Tracking, type_info=true
let field = fields_named.named.first().unwrap();
let field_ident = field.ident.as_ref().unwrap();
- let field_ty = &field.ty;
+ let fields_clone = syn::Fields::Named(fields_named.clone());
+ let source_fields = source_fields(&fields_clone);
+ let read_payload = gen_read_single_payload(&source_fields);
quote! {
#tag_value => {
- use fory_core::serializer::Serializer;
- let value = <#field_ty as Serializer>::fory_read(context, fory_core::RefMode::Tracking, true)?;
+ let value = #read_payload;
Ok(Self::#ident { #field_ident: value })
}
}
@@ -602,8 +757,8 @@ fn xlang_variant_read_branches(
.iter()
.map(|f| {
let field_ident = f.ident.as_ref().unwrap();
- let ty = &f.ty;
- quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+ let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+ quote! { #field_ident: #default_expr }
})
.collect();
quote! {
@@ -638,20 +793,8 @@ fn rust_variant_read_branches(
}
}
Fields::Unnamed(fields_unnamed) => {
- let field_idents: Vec<Ident> = (0..fields_unnamed.unnamed.len())
- .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
- .collect();
-
- let read_fields: Vec<TokenStream> = fields_unnamed
- .unnamed
- .iter()
- .enumerate()
- .zip(field_idents.iter())
- .map(|((idx, f), ident)| {
- let field_name = idx.to_string();
- gen_read_field(f, ident, &field_name)
- })
- .collect();
+ let source_fields = unnamed_source_fields(fields_unnamed);
+ let (read_fields, field_idents) = gen_read_variant_fields(&source_fields);
quote! {
#tag_value => {
@@ -664,24 +807,20 @@ fn rust_variant_read_branches(
let fields_clone = syn::Fields::Named(fields_named.clone());
let source_fields = source_fields(&fields_clone);
- let field_idents: Vec<_> = source_fields
- .iter()
- .map(|sf| sf.field.ident.as_ref().unwrap())
- .collect();
-
- let read_fields: Vec<_> = source_fields
+ let (read_fields, private_idents) = gen_read_variant_fields(&source_fields);
+ let field_inits: Vec<_> = source_fields
.iter()
- .zip(field_idents.iter())
- .map(|(sf, ident)| {
- let field_name = ident.to_string();
- gen_read_field(sf.field, ident, &field_name)
+ .zip(private_idents.iter())
+ .map(|(sf, private_ident)| {
+ let field_ident = sf.field.ident.as_ref().unwrap();
+ quote! { #field_ident: #private_ident }
})
.collect();
quote! {
#tag_value => {
#(#read_fields;)*
- Ok(Self::#ident { #(#field_idents),* })
+ Ok(Self::#ident { #(#field_inits),* })
}
}
}
@@ -725,38 +864,14 @@ fn rust_compatible_variant_read_branches(
}
Fields::Unnamed(fields_unnamed) => {
// For unnamed enum variants, read using collection format (same protocol as tuple)
- let field_idents: Vec<_> = (0..fields_unnamed.unnamed.len())
- .map(|i| Ident::new(&temp_var_name(i), proc_macro2::Span::call_site()))
- .collect();
-
- let field_count = fields_unnamed.unnamed.len();
-
- let read_fields: Vec<TokenStream> = fields_unnamed
- .unnamed
- .iter()
- .enumerate()
- .map(|(i, field)| {
- let field_ident = &field_idents[i];
- let field_ty = &field.ty;
- quote! {
- let #field_ident = if #i < len {
- use fory_core::serializer::Serializer;
- <#field_ty>::fory_read(context, fory_core::RefMode::NullOnly, true)?
- } else {
- <#field_ty as fory_core::ForyDefault>::fory_default()
- }
- }
- })
- .collect();
+ let source_fields = unnamed_source_fields(fields_unnamed);
+ let (read_fields, field_idents, field_count) =
+ gen_read_variant_elements(&source_fields);
// Generate default value for this variant
- let default_fields: Vec<TokenStream> = fields_unnamed
- .unnamed
+ let default_fields: Vec<TokenStream> = source_fields
.iter()
- .map(|f| {
- let ty = &f.ty;
- quote! { <#ty as fory_core::ForyDefault>::fory_default() }
- })
+ .map(|sf| super::field_codec::default_expr_for_type(&sf.field.ty))
.collect();
let default_value = quote! { Self::#ident( #(#default_fields),* ) };
@@ -805,8 +920,8 @@ fn rust_compatible_variant_read_branches(
.iter()
.map(|f| {
let field_ident = f.ident.as_ref().unwrap();
- let ty = &f.ty;
- quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+ let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+ quote! { #field_ident: #default_expr }
})
.collect();
let default_value = quote! { Self::#ident { #(#default_fields),* } };
@@ -869,10 +984,7 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
let default_fields: Vec<TokenStream> = fields_unnamed
.unnamed
.iter()
- .map(|f| {
- let ty = &f.ty;
- quote! { <#ty as fory_core::ForyDefault>::fory_default() }
- })
+ .map(|f| super::field_codec::default_expr_for_type(&f.ty))
.collect();
quote! { Self::#default_variant_ident( #(#default_fields),* ) }
}
@@ -882,8 +994,8 @@ pub fn gen_read_data(data_enum: &DataEnum) -> TokenStream {
.iter()
.map(|f| {
let field_ident = f.ident.as_ref().unwrap();
- let ty = &f.ty;
- quote! { #field_ident: <#ty as fory_core::ForyDefault>::fory_default() }
+ let default_expr = super::field_codec::default_expr_for_type(&f.ty);
+ quote! { #field_ident: #default_expr }
})
.collect();
quote! { Self::#default_variant_ident { #(#default_fields),* } }
diff --git a/rust/fory-derive/src/object/field_codec.rs b/rust/fory-derive/src/object/field_codec.rs
new file mode 100644
index 000000000..fcd03890e
--- /dev/null
+++ b/rust/fory-derive/src/object/field_codec.rs
@@ -0,0 +1,1175 @@
+// 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.
+
+use super::field_meta::{
+ classify_field_type, extract_option_inner_type, is_option_type, parse_field_meta,
+ ForyFieldMeta, IntEncoding,
+};
+use super::read::create_private_field_name;
+use super::util::get_type_id_by_type_ast;
+use crate::util::{is_arc_dyn_trait, is_box_dyn_trait, is_rc_dyn_trait, SourceField};
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote, ToTokens};
+use syn::{GenericArgument, PathArguments, Type};
+
+pub(crate) struct ResolvedField<'a> {
+ pub source: &'a SourceField<'a>,
+ pub private_ident: syn::Ident,
+ pub dispatch: FieldDispatch,
+ pub value_ty: &'a Type,
+ pub field_id: i16,
+}
+
+pub(crate) enum FieldDispatch {
+ Codec {
+ codec_ty: TokenStream,
+ },
+ Serializer {
+ field_type: TokenStream,
+ has_generics: bool,
+ },
+}
+
+impl<'a> ResolvedField<'a> {
+ #[inline]
+ pub fn codec_call(&self) -> TokenStream {
+ match &self.dispatch {
+ FieldDispatch::Codec { codec_ty } => {
+ let value_ty = self.value_ty;
+ quote! { <#codec_ty as fory_core::serializer::codec::Codec<#value_ty>> }
+ }
+ FieldDispatch::Serializer { .. } => {
+ quote! { compile_error!("serializer-dispatched field has no codec call") }
+ }
+ }
+ }
+
+ pub fn reserved_space(&self) -> TokenStream {
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! { #call::reserved_space() }
+ }
+ FieldDispatch::Serializer { .. } => {
+ let ty = self.value_ty;
+ quote! {
+ <#ty as fory_core::Serializer>::fory_reserved_space()
+ + fory_core::type_id::SIZE_OF_REF_AND_TYPE
+ }
+ }
+ }
+ }
+
+ pub fn write_field(&self) -> TokenStream {
+ let access =
+ super::util::get_field_accessor(self.source.field, self.source.original_index, true);
+ self.write_value_field(quote! { &#access })
+ }
+
+ pub fn write_value_field(&self, value: TokenStream) -> TokenStream {
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ #call::write_field(#value, context)?;
+ }
+ }
+ FieldDispatch::Serializer { has_generics, .. } => {
+ let ty = self.value_ty;
+ if serializer_field_can_use_data_path(self.source.field) {
+ quote! {
+ <#ty as fory_core::Serializer>::fory_write_data_generic(
+ #value,
+ context,
+ #has_generics
+ )?;
+ }
+ } else {
+ let ref_mode = serializer_ref_mode_for_field(self.source.field);
+ quote! {
+ let write_type_info = if context.is_compatible() {
+ fory_core::type_id::need_to_write_type_for_field(
+ <#ty as fory_core::Serializer>::fory_static_type_id()
+ )
+ } else {
+ <#ty as fory_core::Serializer>::fory_is_polymorphic()
+ };
+ <#ty as fory_core::Serializer>::fory_write(
+ #value,
+ context,
+ #ref_mode,
+ write_type_info,
+ #has_generics
+ )?;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn write_value_with_mode(
+ &self,
+ value: TokenStream,
+ ref_mode: TokenStream,
+ write_type_info: TokenStream,
+ ) -> TokenStream {
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ #call::write_with_mode(
+ #value,
+ context,
+ #ref_mode,
+ #write_type_info,
+ false,
+ )?;
+ }
+ }
+ FieldDispatch::Serializer { has_generics, .. } => {
+ let ty = self.value_ty;
+ quote! {
+ <#ty as fory_core::Serializer>::fory_write(
+ #value,
+ context,
+ #ref_mode,
+ #write_type_info,
+ #has_generics,
+ )?;
+ }
+ }
+ }
+ }
+
+ pub fn read_field(&self) -> TokenStream {
+ let var = &self.private_ident;
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ let #var = #call::read_field(context)?;
+ }
+ }
+ FieldDispatch::Serializer { .. } => {
+ let ty = self.value_ty;
+ if serializer_field_can_use_data_path(self.source.field) {
+ quote! {
+ let #var = <#ty as fory_core::Serializer>::fory_read_data(context)?;
+ }
+ } else {
+ let ref_mode = serializer_ref_mode_for_field(self.source.field);
+ quote! {
+ let read_type_info = if context.is_compatible() {
+ fory_core::serializer::util::field_need_read_type_info(
+ <#ty as fory_core::Serializer>::fory_static_type_id() as u32
+ )
+ } else {
+ <#ty as fory_core::Serializer>::fory_is_polymorphic()
+ };
+ let #var = <#ty as fory_core::Serializer>::fory_read(
+ context,
+ #ref_mode,
+ read_type_info
+ )?;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn read_with_mode_expr(
+ &self,
+ ref_mode: TokenStream,
+ read_type_info: TokenStream,
+ ) -> TokenStream {
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ #call::read_with_mode(context, #ref_mode, #read_type_info)?
+ }
+ }
+ FieldDispatch::Serializer { .. } => {
+ let ty = self.value_ty;
+ quote! {
+ <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, #read_type_info)?
+ }
+ }
+ }
+ }
+
+ pub fn declare_compatible_var(&self) -> TokenStream {
+ let var = &self.private_ident;
+ let ty = self.value_ty;
+ quote! {
+ let mut #var: Option<#ty> = None;
+ }
+ }
+
+ pub fn assign_value(&self) -> TokenStream {
+ let var = &self.private_ident;
+ let default_expr = default_expr_for_type(self.value_ty);
+ quote! {
+ #var.unwrap_or_else(|| #default_expr)
+ }
+ }
+
+ pub fn read_compatible(&self) -> TokenStream {
+ let var = &self.private_ident;
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ let remote_field_type = &_field.field_type;
+ if let Some(value) = #call::read_compatible(
+ context,
+ local_field_type,
+ remote_field_type,
+ )? {
+ #var = Some(value);
+ } else {
+ let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
+ remote_field_type.type_id,
+ remote_field_type.nullable,
+ );
+ fory_core::serializer::skip::skip_field_value(context, remote_field_type, read_ref_flag)?;
+ }
+ }
+ }
+ FieldDispatch::Serializer { .. } => {
+ let ty = self.value_ty;
+ quote! {
+ let remote_field_type = &_field.field_type;
+ if fory_core::serializer::codec::field_types_compatible(
+ local_field_type,
+ remote_field_type,
+ ) {
+ let read_ref_mode =
+ fory_core::serializer::codec::field_ref_mode(remote_field_type);
+ let read_type_info = if context.is_compatible() {
+ fory_core::serializer::util::field_need_read_type_info(
+ remote_field_type.type_id
+ )
+ } else {
+ <#ty as fory_core::Serializer>::fory_is_polymorphic()
+ };
+ #var = Some(<#ty as fory_core::Serializer>::fory_read(
+ context,
+ read_ref_mode,
+ read_type_info,
+ )?);
+ } else {
+ let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
+ remote_field_type.type_id,
+ remote_field_type.nullable,
+ );
+ fory_core::serializer::skip::skip_field_value(context, remote_field_type, read_ref_flag)?;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn field_info(&self) -> TokenStream {
+ let field_id = self.field_id;
+ let name = &self.source.field_name;
+ match &self.dispatch {
+ FieldDispatch::Codec { .. } => {
+ let call = self.codec_call();
+ quote! {
+ fory_core::meta::FieldInfo::new_with_id(
+ #field_id,
+ #name,
+ #call::field_type(type_resolver)?
+ )
+ }
+ }
+ FieldDispatch::Serializer { field_type, .. } => {
+ quote! {
+ fory_core::meta::FieldInfo::new_with_id(
+ #field_id,
+ #name,
+ #field_type
+ )
+ }
+ }
+ }
+ }
+}
+
+pub(crate) struct SkippedField<'a> {
+ pub source: &'a SourceField<'a>,
+ pub private_ident: syn::Ident,
+}
+
+impl<'a> SkippedField<'a> {
+ pub fn read_default(&self) -> TokenStream {
+ let var = &self.private_ident;
+ let default_expr = default_expr_for_type(&self.source.field.ty);
+ quote! {
+ let #var = #default_expr;
+ }
+ }
+
+ pub fn assign_value(&self) -> TokenStream {
+ let var = &self.private_ident;
+ quote! { #var }
+ }
+}
+
+pub(crate) enum FieldBinding<'a> {
+ Codec(ResolvedField<'a>),
+ Skipped(SkippedField<'a>),
+}
+
+pub(crate) fn build_bindings<'a>(
+ source_fields: &'a [SourceField<'a>],
+) -> syn::Result<Vec<FieldBinding<'a>>> {
+ source_fields
+ .iter()
+ .map(|source| {
+ let meta = parse_field_meta(source.field)?;
+ let private_ident = create_private_field_name(source.field, source.original_index);
+ if meta.skip {
+ return Ok(FieldBinding::Skipped(SkippedField {
+ source,
+ private_ident,
+ }));
+ }
+ let type_class = classify_field_type(&source.field.ty);
+ let is_outer_option = is_option_type(&source.field.ty);
+ let nullable = meta.effective_nullable(type_class) || is_outer_option;
+ let track_ref = meta.effective_ref(type_class);
+ let field_id = if meta.uses_tag_id() {
+ meta.effective_id() as i16
+ } else {
+ -1
+ };
+ let dispatch = field_dispatch_for(&source.field.ty, &meta, nullable, track_ref)?;
+ Ok(FieldBinding::Codec(ResolvedField {
+ source,
+ private_ident,
+ dispatch,
+ value_ty: &source.field.ty,
+ field_id,
+ }))
+ })
+ .collect()
+}
+
+fn field_dispatch_for(
+ ty: &Type,
+ meta: &ForyFieldMeta,
+ nullable: bool,
+ track_ref: bool,
+) -> syn::Result<FieldDispatch> {
+ if meta.encoding.is_none()
+ && meta.list.is_none()
+ && meta.map.is_none()
+ && is_container_type(ty)
+ && !contains_custom_trait_object(ty)
+ && !contains_exact_any_object(ty)
+ {
+ return Ok(FieldDispatch::Serializer {
+ field_type: field_type_expr_for(ty, nullable, track_ref)?,
+ has_generics: true,
+ });
+ }
+ Ok(FieldDispatch::Codec {
+ codec_ty: codec_type_for(ty, meta, nullable, track_ref)?,
+ })
+}
+
+pub(crate) fn codec_type_for(
+ ty: &Type,
+ meta: &ForyFieldMeta,
+ nullable: bool,
+ track_ref: bool,
+) -> syn::Result<TokenStream> {
+ if let Some(inner) = extract_option_inner_type(ty) {
+ let inner_meta = ForyFieldMeta {
+ nullable: Some(false),
+ r#ref: None,
+ ..meta.clone()
+ };
+ let inner_codec = codec_type_for(&inner, &inner_meta, false, false)?;
+ return Ok(quote! {
+ fory_core::serializer::codec::OptionCodec<#inner, #inner_codec, #track_ref>
+ });
+ }
+
+ if let Some((name, Some(args))) = type_name_and_args(ty) {
+ if name == "Vec" {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values; use list(element(encoding = ...)) for Vec elements",
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "map(...) config is only valid for HashMap fields",
+ ));
+ }
+ let elem_ty = single_type_arg(args, ty, "Vec")?;
+ if meta.list.is_none() && is_primitive_vec_type(ty) {
+ return Ok(quote! {
+ fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref>
+ });
+ }
+ if meta.list.is_none() {
+ let elem_meta = ForyFieldMeta::default();
+ let elem_class = classify_field_type(elem_ty);
+ let elem_codec = codec_type_for(
+ elem_ty,
+ &elem_meta,
+ elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+ elem_meta.effective_ref(elem_class),
+ )?;
+ if contains_custom_trait_object(elem_ty) {
+ return Ok(quote! {
+ fory_core::serializer::codec::VecCodec<#elem_ty, #elem_codec, #nullable, #track_ref>
+ });
+ }
+ return Ok(quote! {
+ fory_core::serializer::codec::CollectionSerializerCodec<
+ #ty,
+ #elem_ty,
+ #elem_codec,
+ { fory_core::type_id::TypeId::LIST as u8 },
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ let elem_meta = meta.element_meta();
+ let elem_class = classify_field_type(elem_ty);
+ let elem_nullable = elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty);
+ let elem_track_ref = elem_meta.effective_ref(elem_class);
+ let elem_codec = codec_type_for(elem_ty, &elem_meta, elem_nullable, elem_track_ref)?;
+ return Ok(quote! {
+ fory_core::serializer::codec::VecCodec<#elem_ty, #elem_codec, #nullable, #track_ref>
+ });
+ }
+ if name == "HashMap" {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values; use map(key(...), value(...)) for HashMap entries",
+ ));
+ }
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "list(...) config is only valid for Vec fields",
+ ));
+ }
+ let (key_ty, value_ty) = two_type_args(args, ty, "HashMap")?;
+ if meta.map.is_none() {
+ let key_meta = ForyFieldMeta::default();
+ let value_meta = ForyFieldMeta::default();
+ let key_class = classify_field_type(key_ty);
+ let value_class = classify_field_type(value_ty);
+ let key_codec = codec_type_for(
+ key_ty,
+ &key_meta,
+ key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+ key_meta.effective_ref(key_class),
+ )?;
+ let value_codec = codec_type_for(
+ value_ty,
+ &value_meta,
+ value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+ value_meta.effective_ref(value_class),
+ )?;
+ if contains_custom_trait_object(key_ty)
+ || contains_custom_trait_object(value_ty)
+ || contains_exact_any_object(key_ty)
+ || contains_exact_any_object(value_ty)
+ {
+ return Ok(quote! {
+ fory_core::serializer::codec::HashMapCodec<#key_ty, #value_ty, #key_codec, #value_codec, #nullable, #track_ref>
+ });
+ }
+ return Ok(quote! {
+ fory_core::serializer::codec::MapSerializerCodec<
+ #ty,
+ #key_ty,
+ #value_ty,
+ #key_codec,
+ #value_codec,
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ let key_meta = meta.map_key_meta();
+ let value_meta = meta.map_value_meta();
+ let key_class = classify_field_type(key_ty);
+ let value_class = classify_field_type(value_ty);
+ let key_codec = codec_type_for(
+ key_ty,
+ &key_meta,
+ key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+ key_meta.effective_ref(key_class),
+ )?;
+ let value_codec = codec_type_for(
+ value_ty,
+ &value_meta,
+ value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+ value_meta.effective_ref(value_class),
+ )?;
+ return Ok(quote! {
+ fory_core::serializer::codec::HashMapCodec<#key_ty, #value_ty, #key_codec, #value_codec, #nullable, #track_ref>
+ });
+ }
+ if name == "HashSet" {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values",
+ ));
+ }
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "list(...) config is only valid for Vec fields",
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "map(...) config is only valid for HashMap fields",
+ ));
+ }
+ let elem_ty = single_type_arg(args, ty, "HashSet")?;
+ let elem_meta = ForyFieldMeta::default();
+ let elem_class = classify_field_type(elem_ty);
+ let elem_codec = codec_type_for(
+ elem_ty,
+ &elem_meta,
+ elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+ elem_meta.effective_ref(elem_class),
+ )?;
+ return Ok(quote! {
+ fory_core::serializer::codec::CollectionSerializerCodec<
+ #ty,
+ #elem_ty,
+ #elem_codec,
+ { fory_core::type_id::TypeId::SET as u8 },
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ if is_serializer_backed_collection(&name) {
+ validate_serializer_backed_collection_meta(ty, meta, &name)?;
+ let elem_ty = single_type_arg(args, ty, &name)?;
+ let elem_meta = ForyFieldMeta::default();
+ let elem_class = classify_field_type(elem_ty);
+ let elem_codec = codec_type_for(
+ elem_ty,
+ &elem_meta,
+ elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+ elem_meta.effective_ref(elem_class),
+ )?;
+ let type_id = serializer_backed_collection_type_id(&name);
+ return Ok(quote! {
+ fory_core::serializer::codec::CollectionSerializerCodec<
+ #ty,
+ #elem_ty,
+ #elem_codec,
+ #type_id,
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ if is_serializer_backed_map(&name) {
+ validate_serializer_backed_map_meta(ty, meta, &name)?;
+ let (key_ty, value_ty) = two_type_args(args, ty, &name)?;
+ let key_meta = ForyFieldMeta::default();
+ let value_meta = ForyFieldMeta::default();
+ let key_class = classify_field_type(key_ty);
+ let value_class = classify_field_type(value_ty);
+ let key_codec = codec_type_for(
+ key_ty,
+ &key_meta,
+ key_meta.effective_nullable(key_class) || is_option_type(key_ty),
+ key_meta.effective_ref(key_class),
+ )?;
+ let value_codec = codec_type_for(
+ value_ty,
+ &value_meta,
+ value_meta.effective_nullable(value_class) || is_option_type(value_ty),
+ value_meta.effective_ref(value_class),
+ )?;
+ return Ok(quote! {
+ fory_core::serializer::codec::MapSerializerCodec<
+ #ty,
+ #key_ty,
+ #value_ty,
+ #key_codec,
+ #value_codec,
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ }
+
+ if let Type::Array(array) = ty {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values",
+ ));
+ }
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "list(...) config is only valid for Vec fields",
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "map(...) config is only valid for HashMap fields",
+ ));
+ }
+ if !is_primitive_array_type(ty) {
+ let elem_ty = array.elem.as_ref();
+ let elem_meta = ForyFieldMeta::default();
+ let elem_class = classify_field_type(elem_ty);
+ let elem_codec = codec_type_for(
+ elem_ty,
+ &elem_meta,
+ elem_meta.effective_nullable(elem_class) || is_option_type(elem_ty),
+ elem_meta.effective_ref(elem_class),
+ )?;
+ return Ok(quote! {
+ fory_core::serializer::codec::CollectionSerializerCodec<
+ #ty,
+ #elem_ty,
+ #elem_codec,
+ { fory_core::type_id::TypeId::LIST as u8 },
+ #nullable,
+ #track_ref
+ >
+ });
+ }
+ }
+
+ if is_exact_any(ty, "Box") {
+ return Ok(quote! { fory_core::serializer::codec::AnyBoxCodec<#nullable, #track_ref> });
+ }
+ if is_exact_any(ty, "Rc") {
+ return Ok(quote! { fory_core::serializer::codec::AnyRcCodec<#nullable, #track_ref> });
+ }
+ if is_exact_any(ty, "Arc") {
+ return Ok(quote! { fory_core::serializer::codec::AnyArcCodec<#nullable, #track_ref> });
+ }
+
+ if let Some((_, trait_name)) = is_box_dyn_trait(ty) {
+ let codec_ident = format_ident!("{}BoxCodec", trait_name);
+ return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+ }
+ if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
+ let codec_ident = format_ident!("{}RcCodec", trait_name);
+ return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+ }
+ if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
+ let codec_ident = format_ident!("{}ArcCodec", trait_name);
+ return Ok(quote! { #codec_ident<#nullable, #track_ref> });
+ }
+
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "list(...) config is only valid for Vec fields",
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "map(...) config is only valid for HashMap fields",
+ ));
+ }
+
+ if let Some(codec) = integer_codec_type(ty, meta, nullable, track_ref) {
+ return Ok(codec);
+ }
+
+ Ok(quote! { fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref> })
+}
+
+fn integer_codec_type(
+ ty: &Type,
+ meta: &ForyFieldMeta,
+ nullable: bool,
+ track_ref: bool,
+) -> Option<TokenStream> {
+ let type_name = ty.to_token_stream().to_string().replace(' ', "");
+ let encoding = meta.encoding.unwrap_or(IntEncoding::Varint);
+ match type_name.as_str() {
+ "i32" => {
+ let wire = match encoding {
+ IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::INT32 as u8 } },
+ IntEncoding::Varint => {
+ quote! { { fory_core::type_id::TypeId::VARINT32 as u8 } }
+ }
+ IntEncoding::Tagged => {
+ return Some(quote! {
+ compile_error!("encoding = tagged is only valid for 64-bit integer fields")
+ });
+ }
+ };
+ Some(quote! { fory_core::serializer::codec::I32Codec<#wire, #nullable, #track_ref> })
+ }
+ "i64" => {
+ let wire = match encoding {
+ IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::INT64 as u8 } },
+ IntEncoding::Varint => {
+ quote! { { fory_core::type_id::TypeId::VARINT64 as u8 } }
+ }
+ IntEncoding::Tagged => {
+ quote! { { fory_core::type_id::TypeId::TAGGED_INT64 as u8 } }
+ }
+ };
+ Some(quote! { fory_core::serializer::codec::I64Codec<#wire, #nullable, #track_ref> })
+ }
+ "u32" => {
+ let wire = match encoding {
+ IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::UINT32 as u8 } },
+ IntEncoding::Varint => {
+ quote! { { fory_core::type_id::TypeId::VAR_UINT32 as u8 } }
+ }
+ IntEncoding::Tagged => {
+ return Some(quote! {
+ compile_error!("encoding = tagged is only valid for 64-bit integer fields")
+ });
+ }
+ };
+ Some(quote! { fory_core::serializer::codec::U32Codec<#wire, #nullable, #track_ref> })
+ }
+ "u64" => {
+ let wire = match encoding {
+ IntEncoding::Fixed => quote! { { fory_core::type_id::TypeId::UINT64 as u8 } },
+ IntEncoding::Varint => {
+ quote! { { fory_core::type_id::TypeId::VAR_UINT64 as u8 } }
+ }
+ IntEncoding::Tagged => {
+ quote! { { fory_core::type_id::TypeId::TAGGED_UINT64 as u8 } }
+ }
+ };
+ Some(quote! { fory_core::serializer::codec::U64Codec<#wire, #nullable, #track_ref> })
+ }
+ _ => {
+ if meta.encoding.is_some() {
+ Some(quote! {
+ compile_error!("encoding is only valid for i32, i64, u32, and u64 fields")
+ })
+ } else {
+ None
+ }
+ }
+ }
+}
+
+fn type_name_and_args(
+ ty: &Type,
+) -> Option<(
+ String,
+ Option<&syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>>,
+)> {
+ let Type::Path(type_path) = ty else {
+ return None;
+ };
+ let seg = type_path.path.segments.last()?;
+ let PathArguments::AngleBracketed(args) = &seg.arguments else {
+ return Some((seg.ident.to_string(), None));
+ };
+ Some((seg.ident.to_string(), Some(&args.args)))
+}
+
+fn single_type_arg<'a>(
+ args: &'a syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+ ty: &Type,
+ owner: &str,
+) -> syn::Result<&'a Type> {
+ args.iter()
+ .find_map(|arg| match arg {
+ GenericArgument::Type(ty) => Some(ty),
+ _ => None,
+ })
+ .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires one type argument")))
+}
+
+fn two_type_args<'a>(
+ args: &'a syn::punctuated::Punctuated<GenericArgument, syn::token::Comma>,
+ ty: &Type,
+ owner: &str,
+) -> syn::Result<(&'a Type, &'a Type)> {
+ let mut iter = args.iter().filter_map(|arg| match arg {
+ GenericArgument::Type(ty) => Some(ty),
+ _ => None,
+ });
+ let first = iter
+ .next()
+ .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires key type")))?;
+ let second = iter
+ .next()
+ .ok_or_else(|| syn::Error::new_spanned(ty, format!("{owner} requires value type")))?;
+ Ok((first, second))
+}
+
+fn is_container_type(ty: &Type) -> bool {
+ if matches!(ty, Type::Array(_) | Type::Tuple(_)) {
+ return true;
+ }
+ let Some((name, _)) = type_name_and_args(ty) else {
+ return false;
+ };
+ matches!(
+ name.as_str(),
+ "Vec"
+ | "VecDeque"
+ | "LinkedList"
+ | "HashSet"
+ | "BTreeSet"
+ | "BinaryHeap"
+ | "HashMap"
+ | "BTreeMap"
+ )
+}
+
+fn contains_custom_trait_object(ty: &Type) -> bool {
+ if !is_exact_any(ty, "Box") && is_box_dyn_trait(ty).is_some() {
+ return true;
+ }
+ if is_rc_dyn_trait(ty).is_some() || is_arc_dyn_trait(ty).is_some() {
+ return true;
+ }
+ if let Some(inner) = extract_option_inner_type(ty) {
+ return contains_custom_trait_object(&inner);
+ }
+ if let Type::Array(array) = ty {
+ return contains_custom_trait_object(array.elem.as_ref());
+ }
+ let Some((_, Some(args))) = type_name_and_args(ty) else {
+ return false;
+ };
+ args.iter().any(|arg| {
+ if let GenericArgument::Type(ty) = arg {
+ contains_custom_trait_object(ty)
+ } else {
+ false
+ }
+ })
+}
+
+fn contains_exact_any_object(ty: &Type) -> bool {
+ if is_exact_any(ty, "Box") || is_exact_any(ty, "Rc") || is_exact_any(ty, "Arc") {
+ return true;
+ }
+ if let Some(inner) = extract_option_inner_type(ty) {
+ return contains_exact_any_object(&inner);
+ }
+ if let Type::Array(array) = ty {
+ return contains_exact_any_object(array.elem.as_ref());
+ }
+ let Some((_, Some(args))) = type_name_and_args(ty) else {
+ return false;
+ };
+ args.iter().any(|arg| {
+ if let GenericArgument::Type(ty) = arg {
+ contains_exact_any_object(ty)
+ } else {
+ false
+ }
+ })
+}
+
+fn field_type_expr_for(ty: &Type, nullable: bool, track_ref: bool) -> syn::Result<TokenStream> {
+ if let Some(inner) = extract_option_inner_type(ty) {
+ return field_type_expr_for(&inner, true, track_ref);
+ }
+
+ if let Type::Array(array) = ty {
+ let type_id = get_type_id_by_type_ast(ty);
+ if fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&type_id) {
+ return Ok(field_type_literal(type_id, nullable, track_ref, Vec::new()));
+ }
+ let elem_ty = array.elem.as_ref();
+ let elem_type = nested_field_type_expr(elem_ty)?;
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::LIST as u32,
+ nullable,
+ track_ref,
+ vec![elem_type],
+ ));
+ }
+
+ if matches!(ty, Type::Tuple(_)) {
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::LIST as u32,
+ nullable,
+ track_ref,
+ vec![quote! {
+ fory_core::meta::FieldType::new(
+ fory_core::type_id::TypeId::UNKNOWN as u32,
+ true,
+ Vec::new(),
+ )
+ }],
+ ));
+ }
+
+ if let Some((name, Some(args))) = type_name_and_args(ty) {
+ match name.as_str() {
+ "Vec" => {
+ let type_id = get_type_id_by_type_ast(ty);
+ if fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&type_id) {
+ return Ok(field_type_literal(type_id, nullable, track_ref, Vec::new()));
+ }
+ let elem_ty = single_type_arg(args, ty, "Vec")?;
+ let elem_type = nested_field_type_expr(elem_ty)?;
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::LIST as u32,
+ nullable,
+ track_ref,
+ vec![elem_type],
+ ));
+ }
+ "VecDeque" | "LinkedList" => {
+ let elem_ty = single_type_arg(args, ty, &name)?;
+ let elem_type = nested_field_type_expr(elem_ty)?;
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::LIST as u32,
+ nullable,
+ track_ref,
+ vec![elem_type],
+ ));
+ }
+ "HashSet" | "BTreeSet" | "BinaryHeap" => {
+ let elem_ty = single_type_arg(args, ty, &name)?;
+ let elem_type = nested_field_type_expr(elem_ty)?;
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::SET as u32,
+ nullable,
+ track_ref,
+ vec![elem_type],
+ ));
+ }
+ "HashMap" | "BTreeMap" => {
+ let (key_ty, value_ty) = two_type_args(args, ty, &name)?;
+ let key_type = nested_field_type_expr(key_ty)?;
+ let value_type = nested_field_type_expr(value_ty)?;
+ return Ok(field_type_literal(
+ fory_core::type_id::TypeId::MAP as u32,
+ nullable,
+ track_ref,
+ vec![key_type, value_type],
+ ));
+ }
+ _ => {}
+ }
+ }
+
+ Ok(serializer_field_type_expr(ty, nullable, track_ref))
+}
+
+fn nested_field_type_expr(ty: &Type) -> syn::Result<TokenStream> {
+ let meta = ForyFieldMeta::default();
+ let class = classify_field_type(ty);
+ let nullable = meta.effective_nullable(class) || is_option_type(ty);
+ let track_ref = meta.effective_ref(class);
+ let codec_ty = codec_type_for(ty, &meta, nullable, track_ref)?;
+ Ok(quote! {
+ <#codec_ty as fory_core::serializer::codec::Codec<#ty>>::field_type(type_resolver)?
+ })
+}
+
+fn field_type_literal(
+ type_id: u32,
+ nullable: bool,
+ track_ref: bool,
+ generics: Vec<TokenStream>,
+) -> TokenStream {
+ quote! {
+ fory_core::meta::FieldType::new_with_ref(
+ #type_id,
+ #nullable,
+ #track_ref,
+ vec![#(#generics),*],
+ )
+ }
+}
+
+fn serializer_field_type_expr(ty: &Type, nullable: bool, track_ref: bool) -> TokenStream {
+ quote! {
+ <fory_core::serializer::codec::SerializerCodec<#ty, #nullable, #track_ref>
+ as fory_core::serializer::codec::Codec<#ty>>::field_type(type_resolver)?
+ }
+}
+
+fn serializer_ref_mode_for_field(field: &syn::Field) -> TokenStream {
+ let (nullable, track_ref) = serializer_field_markers(field);
+ if track_ref {
+ quote! { fory_core::RefMode::Tracking }
+ } else if nullable {
+ quote! { fory_core::RefMode::NullOnly }
+ } else {
+ quote! { fory_core::RefMode::None }
+ }
+}
+
+fn serializer_field_can_use_data_path(field: &syn::Field) -> bool {
+ let (nullable, track_ref) = serializer_field_markers(field);
+ !nullable && !track_ref
+}
+
+fn serializer_field_markers(field: &syn::Field) -> (bool, bool) {
+ let meta = parse_field_meta(field).unwrap_or_default();
+ let type_class = classify_field_type(&field.ty);
+ let nullable = meta.effective_nullable(type_class) || is_option_type(&field.ty);
+ let track_ref = meta.effective_ref(type_class);
+ (nullable, track_ref)
+}
+
+fn is_serializer_backed_collection(name: &str) -> bool {
+ matches!(name, "VecDeque" | "LinkedList" | "BTreeSet" | "BinaryHeap")
+}
+
+fn serializer_backed_collection_type_id(name: &str) -> TokenStream {
+ match name {
+ "BTreeSet" | "BinaryHeap" => quote! { { fory_core::type_id::TypeId::SET as u8 } },
+ _ => quote! { { fory_core::type_id::TypeId::LIST as u8 } },
+ }
+}
+
+fn validate_serializer_backed_collection_meta(
+ ty: &Type,
+ meta: &ForyFieldMeta,
+ name: &str,
+) -> syn::Result<()> {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values",
+ ));
+ }
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ format!("list(...) config is only valid for Vec fields, not {name}"),
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ format!("map(...) config is only valid for map fields, not {name}"),
+ ));
+ }
+ Ok(())
+}
+
+fn is_serializer_backed_map(name: &str) -> bool {
+ matches!(name, "BTreeMap")
+}
+
+fn validate_serializer_backed_map_meta(
+ ty: &Type,
+ meta: &ForyFieldMeta,
+ name: &str,
+) -> syn::Result<()> {
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ "encoding is only valid on integer values; use map(key(...), value(...)) for map entries",
+ ));
+ }
+ if meta.list.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ format!("list(...) config is only valid for list fields, not {name}"),
+ ));
+ }
+ if meta.map.is_some() {
+ return Err(syn::Error::new_spanned(
+ ty,
+ format!("map(...) config is currently supported only for HashMap fields, not {name}"),
+ ));
+ }
+ Ok(())
+}
+
+fn is_exact_any(ty: &Type, owner: &str) -> bool {
+ let Some((name, Some(args))) = type_name_and_args(ty) else {
+ return false;
+ };
+ if name != owner {
+ return false;
+ }
+ let Some(GenericArgument::Type(Type::TraitObject(trait_obj))) = args.first() else {
+ return false;
+ };
+ trait_obj.bounds.iter().any(|bound| {
+ if let syn::TypeParamBound::Trait(trait_bound) = bound {
+ trait_bound
+ .path
+ .segments
+ .last()
+ .is_some_and(|seg| seg.ident == "Any")
+ } else {
+ false
+ }
+ })
+}
+
+fn is_primitive_vec_type(ty: &Type) -> bool {
+ fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&get_type_id_by_type_ast(ty))
+}
+
+fn is_primitive_array_type(ty: &Type) -> bool {
+ fory_core::type_id::PRIMITIVE_ARRAY_TYPES.contains(&get_type_id_by_type_ast(ty))
+}
+
+pub(crate) fn default_expr_for_type(ty: &Type) -> TokenStream {
+ if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
+ let wrapper_ty = format_ident!("{}Rc", trait_name);
+ let trait_ident = format_ident!("{}", trait_name);
+ return quote! {
+ {
+ let wrapper = <#wrapper_ty as fory_core::ForyDefault>::fory_default();
+ std::rc::Rc::<dyn #trait_ident>::from(wrapper)
+ }
+ };
+ }
+ if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
+ let wrapper_ty = format_ident!("{}Arc", trait_name);
+ let trait_ident = format_ident!("{}", trait_name);
+ return quote! {
+ {
+ let wrapper = <#wrapper_ty as fory_core::ForyDefault>::fory_default();
+ std::sync::Arc::<dyn #trait_ident>::from(wrapper)
+ }
+ };
+ }
+ quote! { <#ty as fory_core::ForyDefault>::fory_default() }
+}
diff --git a/rust/fory-derive/src/object/field_meta.rs b/rust/fory-derive/src/object/field_meta.rs
index e50112efa..626153abc 100644
--- a/rust/fory-derive/src/object/field_meta.rs
+++ b/rust/fory-derive/src/object/field_meta.rs
@@ -22,16 +22,13 @@
//! - `nullable`: Whether the field can be null (default: false, except Option/RcWeak/ArcWeak)
//! - `ref`: Whether to enable reference tracking (default: false, except Rc/Arc/RcWeak/ArcWeak)
//! - `skip`: Skip this field during serialization
-//! - `compress`: For i32/u32 fields: true (VARINT32/VAR_UINT32) or false (INT32/UINT32 fixed)
-//! - `encoding`: For i32/u32: "varint", "fixed"; for u64: "varint", "fixed", "tagged"
-//! - `type_id`: Explicit type ID override (e.g., "int8_array", "uint8_array")
-//!
-//! Both `compress` and `encoding` are converted to a `type_id` internally. If both are
-//! specified, they must not conflict.
+//! - `encoding`: Integer wire encoding, one of `varint`, `fixed`, or `tagged`
+//! - `list(element(...))`: Nested list element configuration
+//! - `map(key(...), value(...))`: Nested map key/value configuration
-use fory_core::type_id::TypeId;
use quote::ToTokens;
use std::collections::HashMap;
+use syn::spanned::Spanned;
use syn::{Field, GenericArgument, PathArguments, Type};
/// Represents parsed `#[fory(...)]` field attributes
@@ -45,9 +42,31 @@ pub struct ForyFieldMeta {
pub r#ref: Option<bool>,
/// Whether to skip this field entirely
pub skip: bool,
- /// Explicit type ID for encoding or array type overrides.
- /// This is set by `compress`, `encoding`, or `type_id` attributes.
- pub type_id: Option<i16>,
+ /// Integer wire encoding selected by semantic field config.
+ pub encoding: Option<IntEncoding>,
+ /// Nested list element configuration.
+ pub list: Option<ForyListMeta>,
+ /// Nested map key/value configuration.
+ pub map: Option<ForyMapMeta>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ForyListMeta {
+ pub element: Option<Box<ForyFieldMeta>>,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct ForyMapMeta {
+ pub key: Option<Box<ForyFieldMeta>>,
+ pub value: Option<Box<ForyFieldMeta>>,
+}
+
+/// Integer wire encoding selected by `#[fory(encoding = ...)]`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum IntEncoding {
+ Varint,
+ Fixed,
+ Tagged,
}
/// Type classification for determining default nullable/ref behavior
@@ -108,154 +127,218 @@ impl ForyFieldMeta {
pub fn uses_tag_id(&self) -> bool {
self.id.is_some_and(|id| id >= 0)
}
-}
-/// Encoding specified via `compress` attribute
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum CompressEncoding {
- Varint,
- Fixed,
-}
+ pub fn element_meta(&self) -> ForyFieldMeta {
+ self.list
+ .as_ref()
+ .and_then(|list| list.element.as_ref())
+ .map(|meta| (**meta).clone())
+ .unwrap_or_default()
+ }
-/// Encoding specified via `encoding` attribute
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum ExplicitEncoding {
- Varint,
- Fixed,
- Tagged,
+ pub fn map_key_meta(&self) -> ForyFieldMeta {
+ self.map
+ .as_ref()
+ .and_then(|map| map.key.as_ref())
+ .map(|meta| (**meta).clone())
+ .unwrap_or_default()
+ }
+
+ pub fn map_value_meta(&self) -> ForyFieldMeta {
+ self.map
+ .as_ref()
+ .and_then(|map| map.value.as_ref())
+ .map(|meta| (**meta).clone())
+ .unwrap_or_default()
+ }
}
/// Parse `#[fory(...)]` attributes from a field
pub fn parse_field_meta(field: &Field) -> syn::Result<ForyFieldMeta> {
let mut meta = ForyFieldMeta::default();
- let mut compress_encoding: Option<CompressEncoding> = None;
- let mut explicit_encoding: Option<ExplicitEncoding> = None;
- let mut explicit_type_id: Option<i16> = None;
for attr in &field.attrs {
if !attr.path().is_ident("fory") {
continue;
}
- attr.parse_nested_meta(|nested| {
- if nested.path.is_ident("id") {
- let lit: syn::LitInt = nested.value()?.parse()?;
- let id: i32 = lit.base10_parse()?;
- if id < -1 {
- return Err(syn::Error::new(lit.span(), "id must be >= -1"));
- }
- meta.id = Some(id);
- } else if nested.path.is_ident("nullable") {
- let value = parse_bool_or_flag(&nested)?;
- meta.nullable = Some(value);
- } else if nested.path.is_ident("ref") {
- let value = parse_bool_or_flag(&nested)?;
- meta.r#ref = Some(value);
- } else if nested.path.is_ident("skip") {
- meta.skip = true;
- } else if nested.path.is_ident("compress") {
- let value = parse_bool_or_flag(&nested)?;
- compress_encoding = Some(if value {
- CompressEncoding::Varint
- } else {
- CompressEncoding::Fixed
- });
- } else if nested.path.is_ident("encoding") {
- let lit: syn::LitStr = nested.value()?.parse()?;
- let encoding_str = lit.value();
- explicit_encoding = Some(match encoding_str.as_str() {
- "varint" => ExplicitEncoding::Varint,
- "fixed" => ExplicitEncoding::Fixed,
- "tagged" => ExplicitEncoding::Tagged,
- _ => {
- return Err(syn::Error::new(
- lit.span(),
- "encoding must be \"varint\", \"fixed\", or \"tagged\"",
- ));
- }
- });
- } else if nested.path.is_ident("type_id") {
- let lit: syn::LitStr = nested.value()?.parse()?;
- explicit_type_id = Some(parse_type_id_tag(&lit)?);
- }
- Ok(())
- })?;
+ attr.parse_nested_meta(|nested| parse_meta_item(&mut meta, nested, true))?;
}
- if explicit_type_id.is_some() && (compress_encoding.is_some() || explicit_encoding.is_some()) {
- return Err(syn::Error::new_spanned(
- field,
- "type_id cannot be combined with compress or encoding attributes",
- ));
- }
+ Ok(meta)
+}
- // Validate that compress and encoding don't conflict if both are specified
- if let (Some(compress), Some(explicit)) = (compress_encoding, explicit_encoding) {
- let compress_implies = match compress {
- CompressEncoding::Varint => ExplicitEncoding::Varint,
- CompressEncoding::Fixed => ExplicitEncoding::Fixed,
- };
- // Only check conflict for varint/fixed (tagged is only for u64)
- if explicit != ExplicitEncoding::Tagged && compress_implies != explicit {
- let compress_str = match compress {
- CompressEncoding::Varint => "true",
- CompressEncoding::Fixed => "false",
- };
- let encoding_str = match explicit {
- ExplicitEncoding::Varint => "varint",
- ExplicitEncoding::Fixed => "fixed",
- ExplicitEncoding::Tagged => "tagged",
- };
- return Err(syn::Error::new_spanned(
- field,
- format!(
- "conflicting attributes: compress={} implies {} encoding, but encoding=\"{}\" was specified",
- compress_str,
- match compress {
- CompressEncoding::Varint => "varint",
- CompressEncoding::Fixed => "fixed",
- },
- encoding_str
- ),
+fn parse_meta_item(
+ meta: &mut ForyFieldMeta,
+ nested: syn::meta::ParseNestedMeta,
+ allow_field_keys: bool,
+) -> syn::Result<()> {
+ if nested.path.is_ident("id") {
+ if !allow_field_keys {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "id is only valid on a struct field, not inside nested list/map config",
));
}
+ let lit: syn::LitInt = nested.value()?.parse()?;
+ let id: i32 = lit.base10_parse()?;
+ if id < -1 {
+ return Err(syn::Error::new(lit.span(), "id must be >= -1"));
+ }
+ if meta.id.is_some() {
+ return Err(syn::Error::new(nested.path.span(), "duplicate id config"));
+ }
+ meta.id = Some(id);
+ } else if nested.path.is_ident("nullable") {
+ let value = parse_bool_or_flag(&nested)?;
+ if meta.nullable.is_some() {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "duplicate nullable config",
+ ));
+ }
+ meta.nullable = Some(value);
+ } else if nested.path.is_ident("ref") {
+ let value = parse_bool_or_flag(&nested)?;
+ if meta.r#ref.is_some() {
+ return Err(syn::Error::new(nested.path.span(), "duplicate ref config"));
+ }
+ meta.r#ref = Some(value);
+ } else if nested.path.is_ident("skip") {
+ if !allow_field_keys {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "skip is only valid on a struct field, not inside nested list/map config",
+ ));
+ }
+ if meta.skip {
+ return Err(syn::Error::new(nested.path.span(), "duplicate skip config"));
+ }
+ meta.skip = true;
+ } else if nested.path.is_ident("compress") {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "compress has been removed; use encoding = varint or encoding = fixed",
+ ));
+ } else if nested.path.is_ident("encoding") {
+ let encoding = parse_encoding_value(&nested)?;
+ if meta.encoding.is_some() {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "duplicate encoding config",
+ ));
+ }
+ meta.encoding = Some(encoding);
+ } else if nested.path.is_ident("list") {
+ if meta.list.is_some() {
+ return Err(syn::Error::new(nested.path.span(), "duplicate list config"));
+ }
+ parse_list_meta(meta, nested)?;
+ } else if nested.path.is_ident("map") {
+ if meta.map.is_some() {
+ return Err(syn::Error::new(nested.path.span(), "duplicate map config"));
+ }
+ parse_map_meta(meta, nested)?;
+ } else if nested.path.is_ident("type_id") {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "type_id is internal wire metadata and is not a field attribute",
+ ));
+ } else {
+ return Err(syn::Error::new(
+ nested.path.span(),
+ "unknown fory field attribute",
+ ));
}
+ Ok(())
+}
- if let Some(type_id) = explicit_type_id {
- meta.type_id = Some(type_id);
- return Ok(meta);
- }
+fn parse_list_meta(
+ meta: &mut ForyFieldMeta,
+ nested: syn::meta::ParseNestedMeta,
+) -> syn::Result<()> {
+ let mut list_meta = meta.list.clone().unwrap_or_default();
+ nested.parse_nested_meta(|item| {
+ if item.path.is_ident("element") {
+ if list_meta.element.is_some() {
+ return Err(syn::Error::new(
+ item.path.span(),
+ "duplicate list element config",
+ ));
+ }
+ let mut element = list_meta
+ .element
+ .take()
+ .map(|meta| *meta)
+ .unwrap_or_default();
+ item.parse_nested_meta(|child| parse_meta_item(&mut element, child, false))?;
+ list_meta.element = Some(Box::new(element));
+ Ok(())
+ } else {
+ Err(syn::Error::new(
+ item.path.span(),
+ "list config supports only element(...)",
+ ))
+ }
+ })?;
+ meta.list = Some(list_meta);
+ Ok(())
+}
- // Convert encoding to type_id
- // Priority: explicit_encoding > compress_encoding
- // Note: The actual type_id depends on the field type (i32, u32, u64), but we store
- // a "canonical" type_id here. The util.rs code will interpret it correctly.
- if let Some(explicit) = explicit_encoding {
- meta.type_id = Some(match explicit {
- // For varint, we use the signed variant as canonical; util.rs adjusts for unsigned
- ExplicitEncoding::Varint => TypeId::VARINT32 as i16,
- ExplicitEncoding::Fixed => TypeId::INT32 as i16,
- ExplicitEncoding::Tagged => TypeId::TAGGED_UINT64 as i16,
- });
- } else if let Some(compress) = compress_encoding {
- meta.type_id = Some(match compress {
- CompressEncoding::Varint => TypeId::VARINT32 as i16,
- CompressEncoding::Fixed => TypeId::INT32 as i16,
- });
- }
+fn parse_map_meta(meta: &mut ForyFieldMeta, nested: syn::meta::ParseNestedMeta) -> syn::Result<()> {
+ let mut map_meta = meta.map.clone().unwrap_or_default();
+ nested.parse_nested_meta(|item| {
+ if item.path.is_ident("key") {
+ if map_meta.key.is_some() {
+ return Err(syn::Error::new(
+ item.path.span(),
+ "duplicate map key config",
+ ));
+ }
+ let mut key = map_meta.key.take().map(|meta| *meta).unwrap_or_default();
+ item.parse_nested_meta(|child| parse_meta_item(&mut key, child, false))?;
+ map_meta.key = Some(Box::new(key));
+ Ok(())
+ } else if item.path.is_ident("value") {
+ if map_meta.value.is_some() {
+ return Err(syn::Error::new(
+ item.path.span(),
+ "duplicate map value config",
+ ));
+ }
+ let mut value = map_meta.value.take().map(|meta| *meta).unwrap_or_default();
+ item.parse_nested_meta(|child| parse_meta_item(&mut value, child, false))?;
+ map_meta.value = Some(Box::new(value));
+ Ok(())
+ } else {
+ Err(syn::Error::new(
+ item.path.span(),
+ "map config supports only key(...) and value(...)",
+ ))
+ }
+ })?;
+ meta.map = Some(map_meta);
+ Ok(())
+}
- Ok(meta)
+fn parse_encoding_value(meta: &syn::meta::ParseNestedMeta) -> syn::Result<IntEncoding> {
+ let value = meta.value()?;
+ if value.peek(syn::LitStr) {
+ let lit: syn::LitStr = value.parse()?;
+ return parse_encoding_name(&lit.value(), lit.span());
+ }
+ let ident: syn::Ident = value.parse()?;
+ parse_encoding_name(&ident.to_string(), ident.span())
}
-fn parse_type_id_tag(lit: &syn::LitStr) -> syn::Result<i16> {
- let value = lit.value();
- match value.as_str() {
- "int8_array" => Ok(TypeId::INT8_ARRAY as i16),
- "uint8_array" => Ok(TypeId::UINT8_ARRAY as i16),
- "union" => Ok(TypeId::UNION as i16),
+fn parse_encoding_name(value: &str, span: proc_macro2::Span) -> syn::Result<IntEncoding> {
+ match value {
+ "varint" => Ok(IntEncoding::Varint),
+ "fixed" => Ok(IntEncoding::Fixed),
+ "tagged" => Ok(IntEncoding::Tagged),
_ => Err(syn::Error::new(
- lit.span(),
- "type_id must be \"int8_array\", \"uint8_array\", or \"union\"",
+ span,
+ "encoding must be varint, fixed, or tagged",
)),
}
}
@@ -583,7 +666,9 @@ mod tests {
nullable: Some(true),
r#ref: None,
skip: false,
- type_id: None,
+ encoding: None,
+ list: None,
+ map: None,
};
assert!(meta.effective_nullable(FieldTypeClass::Primitive)); // Would be false by default
@@ -593,157 +678,241 @@ mod tests {
nullable: None,
r#ref: Some(false),
skip: false,
- type_id: None,
+ encoding: None,
+ list: None,
+ map: None,
};
assert!(!meta.effective_ref(FieldTypeClass::Rc)); // Would be true by default
}
#[test]
- fn test_parse_compress_attribute() {
- // compress=false sets type_id to INT32 (fixed encoding)
+ fn test_parse_removed_compress_attribute() {
let field: Field = parse_quote! {
#[fory(compress = false)]
value: u32
};
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
-
- // compress=true sets type_id to VARINT32 (variable encoding)
- let field: Field = parse_quote! {
- #[fory(compress = true)]
- value: u32
- };
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
-
- // Standalone compress flag should set to varint
- let field: Field = parse_quote! {
- #[fory(compress)]
- value: u32
- };
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("compress has been removed"));
}
#[test]
fn test_parse_encoding_attribute() {
- // encoding="varint" sets type_id to VARINT32
let field: Field = parse_quote! {
- #[fory(encoding = "varint")]
+ #[fory(encoding = varint)]
value: u64
};
let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+ assert_eq!(meta.encoding, Some(IntEncoding::Varint));
- // encoding="fixed" sets type_id to INT32
let field: Field = parse_quote! {
- #[fory(encoding = "fixed")]
+ #[fory(encoding = fixed)]
value: u64
};
let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+ assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
- // encoding="tagged" sets type_id to TAGGED_UINT64
let field: Field = parse_quote! {
- #[fory(encoding = "tagged")]
+ #[fory(encoding = tagged)]
value: u64
};
let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::TAGGED_UINT64 as i16));
+ assert_eq!(meta.encoding, Some(IntEncoding::Tagged));
}
#[test]
fn test_parse_encoding_for_i32_u32() {
// encoding="varint" for i32/u32
let field: Field = parse_quote! {
- #[fory(encoding = "varint")]
+ #[fory(encoding = varint)]
value: i32
};
let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+ assert_eq!(meta.encoding, Some(IntEncoding::Varint));
// encoding="fixed" for i32/u32
let field: Field = parse_quote! {
- #[fory(encoding = "fixed")]
+ #[fory(encoding = fixed)]
value: u32
};
let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+ assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
}
#[test]
- fn test_compress_encoding_no_conflict() {
- // compress=true with encoding="varint" - no conflict
+ fn test_removed_type_id_attribute() {
let field: Field = parse_quote! {
- #[fory(compress = true, encoding = "varint")]
+ #[fory(type_id = "union")]
value: i32
};
- let meta = parse_field_meta(&field);
- assert!(meta.is_ok());
- let meta = meta.unwrap();
- assert_eq!(meta.type_id, Some(TypeId::VARINT32 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("type_id is internal wire metadata"));
+ }
- // compress=false with encoding="fixed" - no conflict
+ #[test]
+ fn test_parse_combined_attributes() {
+ // nullable with encoding="tagged" (for u64)
let field: Field = parse_quote! {
- #[fory(compress = false, encoding = "fixed")]
- value: u32
+ #[fory(nullable, encoding = tagged)]
+ value: Option<u64>
+ };
+ let meta = parse_field_meta(&field).unwrap();
+ assert_eq!(meta.nullable, Some(true));
+ assert_eq!(meta.encoding, Some(IntEncoding::Tagged));
+
+ // encoding="fixed" for Option<i32>
+ let field: Field = parse_quote! {
+ #[fory(nullable, encoding = fixed)]
+ value: Option<i32>
+ };
+ let meta = parse_field_meta(&field).unwrap();
+ assert_eq!(meta.nullable, Some(true));
+ assert_eq!(meta.encoding, Some(IntEncoding::Fixed));
+ }
+
+ #[test]
+ fn test_parse_nested_list_and_map_config() {
+ let field: Field = parse_quote! {
+ #[fory(nullable, list(element(encoding = fixed)))]
+ values: Vec<Option<i32>>
+ };
+ let meta = parse_field_meta(&field).unwrap();
+ assert_eq!(meta.nullable, Some(true));
+ assert_eq!(
+ meta.list
+ .as_ref()
+ .and_then(|list| list.element.as_ref())
+ .and_then(|element| element.encoding),
+ Some(IntEncoding::Fixed)
+ );
+
+ let field: Field = parse_quote! {
+ #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))]
+ data: HashMap<Option<i32>, Option<u64>>
+ };
+ let meta = parse_field_meta(&field).unwrap();
+ let map = meta.map.as_ref().unwrap();
+ assert_eq!(
+ map.key.as_ref().and_then(|key| key.encoding),
+ Some(IntEncoding::Fixed)
+ );
+ assert_eq!(
+ map.value.as_ref().and_then(|value| value.nullable),
+ Some(true)
+ );
+ assert_eq!(
+ map.value.as_ref().and_then(|value| value.encoding),
+ Some(IntEncoding::Tagged)
+ );
+ }
+
+ #[test]
+ fn test_reject_field_only_keys_inside_nested_config() {
+ let field: Field = parse_quote! {
+ #[fory(list(element(id = 1)))]
+ values: Vec<i32>
};
- let meta = parse_field_meta(&field);
- assert!(meta.is_ok());
- let meta = meta.unwrap();
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("id is only valid on a struct field"));
}
#[test]
- fn test_compress_encoding_conflict() {
- // compress=true with encoding="fixed" - conflict!
+ fn test_reject_invalid_field_meta_grammar() {
let field: Field = parse_quote! {
- #[fory(compress = true, encoding = "fixed")]
+ #[fory(unknown = true)]
value: i32
};
- let result = parse_field_meta(&field);
- assert!(result.is_err());
- let err = result.unwrap_err();
- assert!(err.to_string().contains("conflicting"));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("unknown fory field attribute"));
- // compress=false with encoding="varint" - conflict!
let field: Field = parse_quote! {
- #[fory(compress = false, encoding = "varint")]
- value: u32
+ #[fory(encoding = zigzag)]
+ value: i64
};
- let result = parse_field_meta(&field);
- assert!(result.is_err());
- let err = result.unwrap_err();
- assert!(err.to_string().contains("conflicting"));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("encoding must be varint, fixed, or tagged"));
+
+ let field: Field = parse_quote! {
+ #[fory(map(element(encoding = fixed)))]
+ data: HashMap<i32, i32>
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("map config supports only key(...) and value(...)"));
}
#[test]
- fn test_parse_combined_attributes() {
- // nullable with compress=false
+ fn test_reject_nested_field_only_and_wire_attrs() {
let field: Field = parse_quote! {
- #[fory(nullable, compress = false)]
- value: Option<u32>
+ #[fory(map(value(skip)))]
+ data: HashMap<i32, i32>
};
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.nullable, Some(true));
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("skip is only valid on a struct field"));
- // nullable with encoding="tagged" (for u64)
let field: Field = parse_quote! {
- #[fory(nullable, encoding = "tagged")]
- value: Option<u64>
+ #[fory(map(key(type_id = 123)))]
+ data: HashMap<i32, i32>
};
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.nullable, Some(true));
- assert_eq!(meta.type_id, Some(TypeId::TAGGED_UINT64 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err
+ .to_string()
+ .contains("type_id is internal wire metadata"));
+ }
- // encoding="fixed" for Option<i32>
+ #[test]
+ fn test_reject_duplicate_field_meta_config() {
let field: Field = parse_quote! {
- #[fory(nullable, encoding = "fixed")]
+ #[fory(id = 1, id = 2)]
+ value: i32
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate id config"));
+
+ let field: Field = parse_quote! {
+ #[fory(nullable, nullable = false)]
value: Option<i32>
};
- let meta = parse_field_meta(&field).unwrap();
- assert_eq!(meta.nullable, Some(true));
- assert_eq!(meta.type_id, Some(TypeId::INT32 as i16));
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate nullable config"));
+
+ let field: Field = parse_quote! {
+ #[fory(encoding = fixed, encoding = varint)]
+ value: i32
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate encoding config"));
+ }
+
+ #[test]
+ fn test_reject_duplicate_nested_config() {
+ let field: Field = parse_quote! {
+ #[fory(list(element(encoding = fixed), element(nullable = true)))]
+ values: Vec<Option<i32>>
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate list element config"));
+
+ let field: Field = parse_quote! {
+ #[fory(map(key(encoding = fixed), key(nullable = true)))]
+ data: HashMap<Option<i32>, i32>
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate map key config"));
+
+ let field: Field = parse_quote! {
+ #[fory(map(value(encoding = fixed), value(nullable = true)))]
+ data: HashMap<i32, Option<i32>>
+ };
+ let err = parse_field_meta(&field).unwrap_err();
+ assert!(err.to_string().contains("duplicate map value config"));
}
}
diff --git a/rust/fory-derive/src/object/misc.rs b/rust/fory-derive/src/object/misc.rs
index c248b0ed4..63d730700 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -20,13 +20,8 @@ use quote::quote;
use std::sync::atomic::{AtomicU32, Ordering};
use syn::Field;
-use super::field_meta::{
- classify_field_type, extract_option_inner_type, is_option_type, parse_field_meta,
-};
-use super::util::{
- classify_trait_object_field, generic_tree_to_tokens, get_filtered_source_fields_iter,
- get_sort_fields_ts, parse_generic_tree, StructField,
-};
+use super::field_codec::{build_bindings, FieldBinding};
+use super::util::get_sort_fields_ts;
use crate::util::SourceField;
// Global type ID counter that auto-grows from 0 at macro processing time
@@ -86,188 +81,13 @@ pub fn gen_get_sorted_field_names(fields: &[&Field]) -> TokenStream {
}
pub fn gen_field_fields_info(source_fields: &[SourceField<'_>]) -> TokenStream {
- let field_infos = get_filtered_source_fields_iter(source_fields).map(|sf| {
- let field = sf.field;
- let ty = &field.ty;
- let name = &sf.field_name;
-
- // Parse field metadata for nullable/ref tracking and field ID
- let meta = parse_field_meta(field).unwrap_or_default();
- let type_class = classify_field_type(ty);
- // For nullable, check both the classified type AND whether outer type is Option
- // This handles Option<Rc<T>> correctly - classify_field_type returns Rc for track_ref,
- // but we also need to detect that the outer wrapper is Option for nullable.
- let is_outer_option = is_option_type(ty);
- let nullable = meta.effective_nullable(type_class) || is_outer_option;
- let track_ref = meta.effective_ref(type_class);
- // Only use explicit field ID when user sets #[fory(id = N)]
- // Otherwise use -1 to indicate field name encoding should be used
- let field_id = if meta.uses_tag_id() {
- meta.effective_id() as i16
- } else {
- -1i16 // Use field name encoding when no explicit ID
- };
-
- match classify_trait_object_field(ty) {
- StructField::None => {
- // Check if this is an i32/u32/u64 field (or Option<i32>/Option<u32>/Option<u64>) with encoding attributes
- // In this case, we need to generate the FieldType with the correct type ID directly
- let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
- let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
- .to_string()
- .replace(' ', "");
-
- let has_encoding =
- (inner_ty_str == "i32" || inner_ty_str == "u32" || inner_ty_str == "u64")
- && meta.type_id.is_some();
- let has_array_override = matches!(
- meta.type_id,
- Some(tid)
- if tid == fory_core::type_id::TypeId::INT8_ARRAY as i16
- || tid == fory_core::type_id::TypeId::UINT8_ARRAY as i16
- ) && (inner_ty_str == "Vec<u8>"
- || inner_ty_str == "Vec<i8>"
- || inner_ty_str.starts_with("[u8;")
- || inner_ty_str.starts_with("[i8;"));
-
- if has_encoding {
- // Generate FieldType directly with the correct type ID based on meta.type_id
- let type_id_ts = match (inner_ty_str.as_str(), meta.type_id) {
- // i32: VARINT32 (default) or INT32 (fixed)
- ("i32", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
- quote! { fory_core::type_id::TypeId::INT32 as u32 }
- }
- ("i32", _) => {
- quote! { fory_core::type_id::TypeId::VARINT32 as u32 }
- }
- // u32: VAR_UINT32 (default) or UINT32 (fixed)
- ("u32", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
- quote! { fory_core::type_id::TypeId::UINT32 as u32 }
- }
- ("u32", _) => {
- quote! { fory_core::type_id::TypeId::VAR_UINT32 as u32 }
- }
- // u64: VAR_UINT64 (default), UINT64 (fixed), or TAGGED_UINT64 (tagged)
- ("u64", Some(tid)) if tid == fory_core::type_id::TypeId::INT32 as i16 => {
- quote! { fory_core::type_id::TypeId::UINT64 as u32 }
- }
- ("u64", Some(tid))
- if tid == fory_core::type_id::TypeId::TAGGED_UINT64 as i16 =>
- {
- quote! { fory_core::type_id::TypeId::TAGGED_UINT64 as u32 }
- }
- ("u64", _) => {
- quote! { fory_core::type_id::TypeId::VAR_UINT64 as u32 }
- }
- _ => unreachable!(),
- };
-
- quote! {
- fory_core::meta::FieldInfo::new_with_id(
- #field_id,
- #name,
- fory_core::meta::FieldType {
- type_id: #type_id_ts,
- user_type_id: u32::MAX,
- nullable: #nullable,
- track_ref: #track_ref,
- generics: Vec::new()
- }
- )
- }
- } else if has_array_override {
- let type_id_ts = match meta.type_id {
- Some(tid) if tid == fory_core::type_id::TypeId::INT8_ARRAY as i16 => {
- quote! { fory_core::type_id::TypeId::INT8_ARRAY as u32 }
- }
- Some(tid) if tid == fory_core::type_id::TypeId::UINT8_ARRAY as i16 => {
- quote! { fory_core::type_id::TypeId::UINT8_ARRAY as u32 }
- }
- _ => unreachable!(),
- };
- quote! {
- fory_core::meta::FieldInfo::new_with_id(
- #field_id,
- #name,
- fory_core::meta::FieldType {
- type_id: #type_id_ts,
- user_type_id: u32::MAX,
- nullable: #nullable,
- track_ref: #track_ref,
- generics: Vec::new()
- }
- )
- }
- } else {
- let generic_tree = parse_generic_tree(ty);
- let generic_token = generic_tree_to_tokens(&generic_tree);
- quote! {
- fory_core::meta::FieldInfo::new_with_id(
- #field_id,
- #name,
- {
- let mut ft = #generic_token;
- ft.nullable = #nullable;
- ft.track_ref = #track_ref;
- ft
- }
- )
- }
- }
- }
- StructField::VecBox(_) | StructField::VecRc(_) | StructField::VecArc(_) => {
- quote! {
- fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
- type_id: fory_core::type_id::TypeId::LIST as u32,
- user_type_id: u32::MAX,
- nullable: #nullable,
- track_ref: #track_ref,
- generics: vec![fory_core::meta::FieldType {
- type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
- user_type_id: u32::MAX,
- nullable: false,
- track_ref: false,
- generics: Vec::new()
- }]
- })
- }
- }
- StructField::HashMapBox(key_ty, _)
- | StructField::HashMapRc(key_ty, _)
- | StructField::HashMapArc(key_ty, _) => {
- let key_generic_tree = parse_generic_tree(key_ty.as_ref());
- let key_generic_token = generic_tree_to_tokens(&key_generic_tree);
- quote! {
- fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
- type_id: fory_core::type_id::TypeId::MAP as u32,
- user_type_id: u32::MAX,
- nullable: #nullable,
- track_ref: #track_ref,
- generics: vec![
- #key_generic_token,
- fory_core::meta::FieldType {
- type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
- user_type_id: u32::MAX,
- nullable: false,
- track_ref: false,
- generics: Vec::new()
- }
- ]
- })
- }
- }
- _ => {
- quote! {
- fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
- type_id: fory_core::type_id::TypeId::UNKNOWN as u32,
- user_type_id: u32::MAX,
- nullable: #nullable,
- track_ref: #track_ref,
- generics: Vec::new()
- })
- }
- }
- }
+ let bindings = match build_bindings(source_fields) {
+ Ok(bindings) => bindings,
+ Err(err) => return err.to_compile_error(),
+ };
+ let field_infos = bindings.iter().filter_map(|binding| match binding {
+ FieldBinding::Codec(binding) => Some(binding.field_info()),
+ FieldBinding::Skipped(_) => None,
});
let fields: Vec<&Field> = source_fields.iter().map(|sf| sf.field).collect();
diff --git a/rust/fory-derive/src/object/mod.rs b/rust/fory-derive/src/object/mod.rs
index d546adb45..a2b0710de 100644
--- a/rust/fory-derive/src/object/mod.rs
+++ b/rust/fory-derive/src/object/mod.rs
@@ -16,6 +16,7 @@
// under the License.
mod derive_enum;
+mod field_codec;
pub(crate) mod field_meta;
mod misc;
pub(crate) mod read;
diff --git a/rust/fory-derive/src/object/read.rs b/rust/fory-derive/src/object/read.rs
index 1e37eb6ad..00b5508e5 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -19,140 +19,10 @@ use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::Field;
-use super::field_meta::{extract_option_inner_type, parse_field_meta};
-use super::util::{
- classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc,
- determine_field_ref_mode, extract_type_name, gen_struct_version_hash_ts,
- get_option_inner_primitive_name, get_primitive_reader_method_with_encoding, get_struct_name,
- is_debug_enabled, is_direct_primitive_type, is_option_encoding_primitive, is_primitive_type,
- is_skip_field, should_skip_type_info_for_field, FieldRefMode, StructField,
-};
+use super::field_codec::{build_bindings, FieldBinding};
+use super::util::{gen_struct_version_hash_ts, get_struct_name, is_debug_enabled};
use crate::util::SourceField;
-/// Check if a type is a primitive type that needs special compatible mode handling
-/// Returns the type name if it's u8, u16, u32, or u64 (or Option<u8/u16/u32/u64>)
-fn is_compatible_primitive_type(ty: &syn::Type) -> Option<&'static str> {
- let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
- let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
- .to_string()
- .replace(' ', "");
- match inner_ty_str.as_str() {
- "u8" => Some("u8"),
- "u16" => Some("u16"),
- "u32" => Some("u32"),
- "u64" => Some("u64"),
- _ => None,
- }
-}
-
-/// Check if a type is u32 or u64 (for encoding-aware reading)
-fn is_unsigned_encoding_type(ty: &syn::Type) -> Option<&'static str> {
- let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
- let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
- .to_string()
- .replace(' ', "");
- match inner_ty_str.as_str() {
- "u32" => Some("u32"),
- "u64" => Some("u64"),
- _ => None,
- }
-}
-
-/// Generate compatible mode read code for u32/u64 fields based on remote type_id
-fn gen_compatible_unsigned_read(
- unsigned_type: &str,
- var_name: &Ident,
- is_option: bool,
-) -> TokenStream {
- let read_value = if unsigned_type == "u32" {
- quote! {
- // Read u32 based on remote type_id
- match _field.field_type.type_id {
- fory_core::type_id::UINT32 => context.reader.read_u32()?,
- fory_core::type_id::VAR_UINT32 => context.reader.read_var_u32()?,
- _ => context.reader.read_var_u32()?, // Default to varint
- }
- }
- } else {
- // u64
- quote! {
- // Read u64 based on remote type_id
- match _field.field_type.type_id {
- fory_core::type_id::UINT64 => context.reader.read_u64()?,
- fory_core::type_id::VAR_UINT64 => context.reader.read_var_u64()?,
- fory_core::type_id::TAGGED_UINT64 => context.reader.read_tagged_u64()?,
- _ => context.reader.read_var_u64()?, // Default to varint
- }
- }
- };
-
- if is_option {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- if read_ref_flag {
- let ref_flag = context.reader.read_i8()?;
- if ref_flag == fory_core::RefFlag::Null as i8 {
- #var_name = Some(None);
- } else {
- #var_name = Some(Some(#read_value));
- }
- } else {
- #var_name = Some(Some(#read_value));
- }
- }
- } else {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- if read_ref_flag {
- let ref_flag = context.reader.read_i8()?;
- if ref_flag == fory_core::RefFlag::Null as i8 {
- // Remote sent null but local field is non-nullable, use default
- #var_name = 0;
- } else {
- #var_name = #read_value;
- }
- } else {
- #var_name = #read_value;
- }
- }
- }
-}
-
-/// Generate compatible mode read code for u8/u16 Option fields
-/// These need special handling because when remote field is non-nullable,
-/// Java sends just the raw bytes without a ref flag
-fn gen_compatible_primitive_option_read(prim_type: &str, var_name: &Ident) -> TokenStream {
- let read_value = match prim_type {
- "u8" => quote! { context.reader.read_u8()? },
- "u16" => quote! { context.reader.read_u16()? },
- _ => unreachable!("Only u8/u16 should use this function"),
- };
-
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- if read_ref_flag {
- let ref_flag = context.reader.read_i8()?;
- if ref_flag == fory_core::RefFlag::Null as i8 {
- #var_name = Some(None);
- } else {
- #var_name = Some(Some(#read_value));
- }
- } else {
- // Remote field is non-nullable, read raw value directly
- #var_name = Some(Some(#read_value));
- }
- }
-}
-
/// Create a private variable name for a field during deserialization.
/// For named fields: `_field_name`
/// For tuple struct fields: `_0`, `_1`, etc.
@@ -163,56 +33,16 @@ pub(crate) fn create_private_field_name(field: &Field, index: usize) -> Ident {
}
}
-fn need_declared_by_option(field: &Field) -> bool {
- let type_name = extract_type_name(&field.ty);
- type_name == "Option" || !is_primitive_type(type_name.as_str())
-}
-
-fn is_float16_like(type_name: &str) -> bool {
- type_name == "float16" || type_name == "Float16"
-}
-
-fn is_bfloat16_like(type_name: &str) -> bool {
- type_name == "bfloat16" || type_name == "BFloat16"
-}
-
pub(crate) fn declare_var(source_fields: &[SourceField<'_>]) -> Vec<TokenStream> {
- source_fields
+ let bindings = match build_bindings(source_fields) {
+ Ok(bindings) => bindings,
+ Err(err) => return vec![err.to_compile_error()],
+ };
+ bindings
.iter()
- .map(|sf| {
- let field = sf.field;
- let ty = &field.ty;
- let var_name = create_private_field_name(field, sf.original_index);
- match classify_trait_object_field(ty) {
- StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => {
- quote! {
- let mut #var_name: #ty = <#ty as fory_core::serializer::ForyDefault>::fory_default();
- }
- }
- _ => {
- if need_declared_by_option(field) {
- quote! {
- let mut #var_name: Option<#ty> = None;
- }
- } else if is_float16_like(extract_type_name(&field.ty).as_str()) {
- quote! {
- let mut #var_name: fory_core::types::float16::float16 = fory_core::types::float16::float16::ZERO;
- }
- } else if is_bfloat16_like(extract_type_name(&field.ty).as_str()) {
- quote! {
- let mut #var_name: fory_core::types::bfloat16::bfloat16 = fory_core::types::bfloat16::bfloat16::ZERO;
- }
- } else if extract_type_name(&field.ty) == "bool" {
- quote! {
- let mut #var_name: bool = false;
- }
- } else {
- quote! {
- let mut #var_name: #ty = 0 as #ty;
- }
- }
- }
- }
+ .map(|binding| match binding {
+ FieldBinding::Codec(binding) => binding.declare_compatible_var(),
+ FieldBinding::Skipped(binding) => binding.read_default(),
})
.collect()
}
@@ -222,27 +52,19 @@ pub(crate) fn assign_value(source_fields: &[SourceField<'_>]) -> Vec<TokenStream
.first()
.map(|sf| sf.is_tuple_struct)
.unwrap_or(false);
+ let bindings = match build_bindings(source_fields) {
+ Ok(bindings) => bindings,
+ Err(err) => return vec![err.to_compile_error()],
+ };
// Generate field value expressions with original index for sorting
let mut indexed: Vec<_> = source_fields
.iter()
- .map(|sf| {
- let var_name = create_private_field_name(sf.field, sf.original_index);
- let value_expr = match classify_trait_object_field(&sf.field.ty) {
- StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => {
- quote! { #var_name }
- }
- StructField::ContainsTraitObject => {
- quote! { #var_name.unwrap() }
- }
- _ => {
- if need_declared_by_option(sf.field) {
- let ty = &sf.field.ty;
- quote! { #var_name.unwrap_or_else(|| <#ty as fory_core::ForyDefault>::fory_default()) }
- } else {
- quote! { #var_name }
- }
- }
+ .zip(bindings.iter())
+ .map(|(sf, binding)| {
+ let value_expr = match binding {
+ FieldBinding::Codec(binding) => binding.assign_value(),
+ FieldBinding::Skipped(binding) => binding.assign_value(),
};
(sf.original_index, sf.field_init(value_expr))
})
@@ -256,205 +78,6 @@ pub(crate) fn assign_value(source_fields: &[SourceField<'_>]) -> Vec<TokenStream
indexed.into_iter().map(|(_, ts)| ts).collect()
}
-pub fn gen_read_field(field: &Field, private_ident: &Ident, field_name: &str) -> TokenStream {
- let ty = &field.ty;
- if is_skip_field(field) {
- return quote! {
- let #private_ident = <#ty as fory_core::ForyDefault>::fory_default();
- };
- }
-
- // Determine RefMode from field meta (respects #[fory(ref=false)] attribute)
- let ref_mode = determine_field_ref_mode(field);
- let base = match classify_trait_object_field(ty) {
- StructField::BoxDyn => {
- // Box<dyn Trait> - respect field meta for ref mode
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- }
- }
- StructField::RcDyn(trait_name) => {
- // Rc<dyn Trait> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- let #private_ident = std::rc::Rc::<dyn #trait_ident>::from(wrapper);
- }
- }
- StructField::ArcDyn(trait_name) => {
- // Arc<dyn Trait> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- let #private_ident = std::sync::Arc::<dyn #trait_ident>::from(wrapper);
- }
- }
- StructField::VecRc(trait_name) => {
- // Vec<Rc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- let #private_ident = wrapper_vec.into_iter()
- .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
- .collect();
- }
- }
- StructField::VecArc(trait_name) => {
- // Vec<Arc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- let #private_ident = wrapper_vec.into_iter()
- .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
- .collect();
- }
- }
- StructField::VecBox(_) => {
- // Vec<Box<dyn Any>> - respect field meta for ref mode
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- }
- }
- StructField::HashMapBox(_, _) => {
- // HashMap<K, Box<dyn Any>> - respect field meta for ref mode
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- }
- }
- StructField::HashMapRc(key_ty, trait_name) => {
- // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- let #private_ident = wrapper_map.into_iter()
- .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v)))
- .collect();
- }
- }
- StructField::HashMapArc(key_ty, trait_name) => {
- // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- let #private_ident = wrapper_map.into_iter()
- .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v)))
- .collect();
- }
- }
- StructField::Forward => {
- // Forward types (trait objects, forward references) - polymorphic, always need type info
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- }
- }
- _ => {
- let skip_type_info = should_skip_type_info_for_field(ty);
- let meta = parse_field_meta(field).unwrap_or_default();
-
- // Check if this is Option<u32> or Option<u64> with encoding attributes
- // These need special inline handling because the generic Option<T> serializer
- // doesn't know about field-level encoding attributes.
- if is_option_encoding_primitive(ty, &meta) {
- let inner_name = get_option_inner_primitive_name(ty).unwrap();
- let reader_method = get_primitive_reader_method_with_encoding(inner_name, &meta);
- let reader_ident = syn::Ident::new(reader_method, proc_macro2::Span::call_site());
- // For Option<primitive>, read null flag first, then value if not null
- quote! {
- let ref_flag = context.reader.read_i8()?;
- let #private_ident = if ref_flag == fory_core::RefFlag::Null as i8 {
- None
- } else {
- Some(context.reader.#reader_ident()?)
- };
- }
- }
- // Check if this is a direct primitive type that can use direct reader calls
- // Only apply when ref_mode is None (no ref tracking needed)
- else if ref_mode == FieldRefMode::None && is_direct_primitive_type(ty) {
- let type_name = extract_type_name(ty);
- if type_name == "String" {
- // String: call fory_read_data directly
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?;
- }
- } else {
- // Numeric primitives: use direct buffer methods
- // For u32/u64, consider encoding attributes
- let reader_method =
- get_primitive_reader_method_with_encoding(&type_name, &meta);
- let reader_ident =
- syn::Ident::new(reader_method, proc_macro2::Span::call_site());
- quote! {
- let #private_ident = context.reader.#reader_ident()?;
- }
- }
- } else if skip_type_info {
- // Known types (primitives, strings, collections) - skip type info at compile time
- if ref_mode == FieldRefMode::None {
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?;
- }
- } else {
- quote! {
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- }
- }
- } else {
- // Custom types (struct/enum/ext) - always need mode-dependent type info logic
- // Determine read_type_info based on mode:
- // - compatible=true: use need_to_write_type_for_field (struct types need type info)
- // - compatible=false: use fory_is_polymorphic
- // This applies regardless of ref_mode because Java always writes type info
- // for struct-type fields in compatible mode, even for non-nullable fields.
- quote! {
- let read_type_info = if context.is_compatible() {
- fory_core::type_id::need_to_write_type_for_field(
- <#ty as fory_core::Serializer>::fory_static_type_id()
- )
- } else {
- <#ty as fory_core::Serializer>::fory_is_polymorphic()
- };
- let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, read_type_info)?;
- }
- }
- }
- };
-
- if is_debug_enabled() {
- let struct_name = get_struct_name().expect("struct context not set");
- let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
- let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site());
- quote! {
- fory_core::serializer::struct_::struct_before_read_field(
- #struct_name_lit,
- #field_name_lit,
- context,
- );
- #base
- fory_core::serializer::struct_::struct_after_read_field(
- #struct_name_lit,
- #field_name_lit,
- (&#private_ident) as &dyn std::any::Any,
- context,
- );
- }
- } else {
- base
- }
-}
-
pub fn gen_read_type_info() -> TokenStream {
quote! {
fory_core::serializer::struct_::read_type_info_fast::<Self>(context)
@@ -462,11 +85,43 @@ pub fn gen_read_type_info() -> TokenStream {
}
fn get_source_fields_loop_ts(source_fields: &[SourceField<'_>]) -> TokenStream {
- let read_fields_ts: Vec<_> = source_fields
+ let bindings = match build_bindings(source_fields) {
+ Ok(bindings) => bindings,
+ Err(err) => return err.to_compile_error(),
+ };
+ let read_fields_ts: Vec<_> = bindings
.iter()
- .map(|sf| {
- let private_ident = create_private_field_name(sf.field, sf.original_index);
- gen_read_field(sf.field, &private_ident, &sf.field_name)
+ .map(|binding| match binding {
+ FieldBinding::Codec(binding) => {
+ let base = binding.read_field();
+ if is_debug_enabled() {
+ let struct_name = get_struct_name().expect("struct context not set");
+ let struct_name_lit =
+ syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
+ let field_name_lit = syn::LitStr::new(
+ &binding.source.field_name,
+ proc_macro2::Span::call_site(),
+ );
+ let private_ident = &binding.private_ident;
+ quote! {
+ fory_core::serializer::struct_::struct_before_read_field(
+ #struct_name_lit,
+ #field_name_lit,
+ context,
+ );
+ #base
+ fory_core::serializer::struct_::struct_after_read_field(
+ #struct_name_lit,
+ #field_name_lit,
+ (&#private_ident) as &dyn std::any::Any,
+ context,
+ );
+ }
+ } else {
+ base
+ }
+ }
+ FieldBinding::Skipped(binding) => binding.read_default(),
})
.collect();
quote! {
@@ -522,359 +177,6 @@ pub fn gen_read_data(source_fields: &[SourceField<'_>]) -> TokenStream {
}
}
-pub(crate) fn gen_read_compatible_match_arm_body(
- field: &Field,
- var_name: &Ident,
- field_name: &str,
-) -> TokenStream {
- let ty = &field.ty;
- let field_kind = classify_trait_object_field(ty);
- let is_skip_flag = is_skip_field(field);
- // Determine RefMode from field meta (respects #[fory(ref=false)] attribute)
- let ref_mode = determine_field_ref_mode(field);
-
- let base = if is_skip_flag {
- match field_kind {
- StructField::None => {
- let dec_by_option = need_declared_by_option(field);
- if dec_by_option {
- quote! {
- #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default());
- }
- } else {
- quote! {
- #var_name = <#ty as fory_core::ForyDefault>::fory_default();
- }
- }
- }
- _ => {
- quote! {
- #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default());
- }
- }
- }
- } else {
- match field_kind {
- StructField::BoxDyn => {
- // Box<dyn Trait> - respect field meta for ref mode
- quote! {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?);
- }
- }
- StructField::RcDyn(trait_name) => {
- // Rc<dyn Trait> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- #var_name = Some(std::rc::Rc::<dyn #trait_ident>::from(wrapper));
- }
- }
- StructField::ArcDyn(trait_name) => {
- // Arc<dyn Trait> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?;
- #var_name = Some(std::sync::Arc::<dyn #trait_ident>::from(wrapper));
- }
- }
- StructField::VecRc(trait_name) => {
- // Vec<Rc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- #var_name = Some(wrapper_vec.into_iter()
- .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w))
- .collect());
- }
- }
- StructField::VecArc(trait_name) => {
- // Vec<Arc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- #var_name = Some(wrapper_vec.into_iter()
- .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w))
- .collect());
- }
- }
- StructField::VecBox(_) => {
- // Vec<Box<dyn Any>> uses standard Vec deserialization with polymorphic elements
- // Check nullable and track_ref flags from remote field info
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- if read_ref_flag || _field.field_type.track_ref {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
- } else {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
- }
- }
- }
- StructField::HashMapBox(_, _) => {
- // HashMap<K, Box<dyn Any>> uses standard HashMap deserialization with polymorphic values
- // Check nullable and track_ref flags from remote field info
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- if read_ref_flag || _field.field_type.track_ref {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
- } else {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
- }
- }
- }
- StructField::HashMapRc(key_ty, trait_name) => {
- // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- #var_name = Some(wrapper_map.into_iter()
- .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v)))
- .collect());
- }
- }
- StructField::HashMapArc(key_ty, trait_name) => {
- // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?;
- #var_name = Some(wrapper_map.into_iter()
- .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v)))
- .collect());
- }
- }
- StructField::ContainsTraitObject => {
- // Struct containing trait objects - respect field meta for ref mode
- quote! {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?);
- }
- }
- StructField::Forward => {
- // Forward types (trait objects, forward references) - polymorphic, always need type info
- // Use remote field's track_ref flag for ref_mode
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- // Forward types are polymorphic, always read type info
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, true)?);
- }
- }
- StructField::None => {
- let skip_type_info = should_skip_type_info_for_field(ty);
- let dec_by_option = need_declared_by_option(field);
- let is_option_type = extract_option_inner_type(ty).is_some();
-
- // Check if this is a u32/u64 field that needs encoding-aware reading
- if let Some(unsigned_type) = is_unsigned_encoding_type(ty) {
- gen_compatible_unsigned_read(
- unsigned_type,
- var_name,
- is_option_type || dec_by_option,
- )
- } else if is_option_type {
- // Check if it's Option<u8> or Option<u16> which need special handling
- if let Some(prim_type) = is_compatible_primitive_type(ty) {
- if prim_type == "u8" || prim_type == "u16" {
- gen_compatible_primitive_option_read(prim_type, var_name)
- } else {
- // u32/u64 handled above
- unreachable!()
- }
- } else if skip_type_info {
- // Non-primitive Option type with skip_type_info
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- if read_ref_flag || _field.field_type.track_ref {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
- } else {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
- }
- }
- } else {
- // Non-primitive Option type without skip_type_info
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- // For ref-tracked struct types, Java writes type info after RefValue flag
- let read_type_info = fory_core::type_id::need_to_write_type_for_field(
- <#ty as fory_core::Serializer>::fory_static_type_id()
- );
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?);
- }
- }
- } else if skip_type_info {
- if dec_by_option {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- if read_ref_flag || _field.field_type.track_ref {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?);
- } else {
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?);
- }
- }
- } else {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- if read_ref_flag || _field.field_type.track_ref {
- #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?;
- } else {
- #var_name = <#ty as fory_core::Serializer>::fory_read_data(context)?;
- }
- }
- }
- } else if dec_by_option {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- // For ref-tracked struct types, Java writes type info after RefValue flag
- let read_type_info = fory_core::type_id::need_to_write_type_for_field(
- <#ty as fory_core::Serializer>::fory_static_type_id()
- );
- #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?);
- }
- } else {
- quote! {
- let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into(
- _field.field_type.type_id,
- _field.field_type.nullable,
- );
- // Use RefMode::Tracking if remote field has track_ref enabled
- let ref_mode = if _field.field_type.track_ref {
- fory_core::RefMode::Tracking
- } else if read_ref_flag {
- fory_core::RefMode::NullOnly
- } else {
- fory_core::RefMode::None
- };
- // For ref-tracked struct types, Java writes type info after RefValue flag
- let read_type_info = fory_core::type_id::need_to_write_type_for_field(
- <#ty as fory_core::Serializer>::fory_static_type_id()
- );
- #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?;
- }
- }
- }
- }
- };
-
- if is_debug_enabled() {
- let struct_name = get_struct_name().expect("struct context not set");
- let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
- let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site());
- quote! {
- fory_core::serializer::struct_::struct_before_read_field(
- #struct_name_lit,
- #field_name_lit,
- context,
- );
- #base
- fory_core::serializer::struct_::struct_after_read_field(
- #struct_name_lit,
- #field_name_lit,
- (&#var_name) as &dyn std::any::Any,
- context,
- );
- }
- } else {
- quote! {
- #base
- }
- }
-}
-
pub fn gen_read(_struct_ident: &Ident) -> TokenStream {
// Note: We use `Self` instead of `#struct_ident` to correctly handle generic types.
// When the struct has generics (e.g., LeaderId<C>), using `Self` ensures the full
@@ -950,21 +252,33 @@ pub(crate) fn gen_read_compatible_with_construction(
source_fields: &[SourceField<'_>],
variant_ident: Option<&Ident>,
) -> TokenStream {
+ let bindings = match build_bindings(source_fields) {
+ Ok(bindings) => bindings,
+ Err(err) => return err.to_compile_error(),
+ };
let declare_ts: Vec<TokenStream> = declare_var(source_fields);
let assign_ts: Vec<TokenStream> = assign_value(source_fields);
- let match_arms: Vec<TokenStream> = source_fields
+ let match_arms: Vec<TokenStream> = bindings
.iter()
+ .filter_map(|binding| match binding {
+ FieldBinding::Codec(binding) => Some(binding),
+ FieldBinding::Skipped(_) => None,
+ })
.enumerate()
- .map(|(sorted_idx, sf)| {
- let var_name = create_private_field_name(sf.field, sf.original_index);
+ .map(|(sorted_idx, binding)| {
// Use sorted index for matching. The assign_field_ids function assigns
// the sorted index to matched fields after lookup (by ID or name).
- // Fields that don't match or have type mismatches get field_id = -1.
+ // Fields that don't match get field_id = -1; codec incompatibility is
+ // handled inside the arm by reading or skipping the remote payload.
let field_id = sorted_idx as i16;
- let body = gen_read_compatible_match_arm_body(sf.field, &var_name, &sf.field_name);
+ let field_index = sorted_idx;
+ let body = binding.read_compatible();
quote! {
#field_id => {
+ let local_field_type = unsafe {
+ &(*local_fields_ptr.add(#field_index)).field_type
+ };
#body
}
}
@@ -1029,53 +343,54 @@ pub(crate) fn gen_read_compatible_with_construction(
let variant_field_remap = if let Some(variant) = variant_ident {
let variant_name = variant.to_string();
+ let enum_name = get_struct_name().expect("enum context not set");
+ let meta_type_ident = Ident::new(
+ &format!("{}_{}VariantMeta", enum_name, variant),
+ proc_macro2::Span::call_site(),
+ );
+ let variant_name_lit = syn::LitStr::new(&variant_name, proc_macro2::Span::call_site());
quote! {
- if let Ok(variants_info) =
- <Self as fory_core::StructSerializer>::fory_variants_fields_info(
- context.get_type_resolver(),
- )
- {
- if let Some((_, _, local_fields)) =
- variants_info.iter().find(|(name, _, _)| name == #variant_name)
- {
- let field_index_by_name: std::collections::HashMap<_, _> = local_fields
- .iter()
- .enumerate()
- .filter(|(_, f)| !f.field_name.is_empty())
- .map(|(i, f)| (f.field_name.clone(), (i, f)))
- .collect();
-
- let field_index_by_id: std::collections::HashMap<_, _> = local_fields
- .iter()
- .enumerate()
- .filter(|(_, f)| f.field_id >= 0)
- .map(|(i, f)| (f.field_id, (i, f)))
- .collect();
-
- for field in fields.iter_mut() {
- let local_match = if field.field_id >= 0 && field.field_name.is_empty() {
- field_index_by_id.get(&field.field_id).copied()
- } else {
- let snake_case_name =
- fory_core::util::to_snake_case(&field.field_name);
- field_index_by_name.get(&snake_case_name).copied()
- };
+ let local_variant_type_info = context
+ .get_type_resolver()
+ .get_type_info(&std::any::TypeId::of::<#meta_type_ident>())
+ .map_err(|_| fory_core::Error::type_error(
+ concat!("Local enum variant metadata not found for ", #variant_name_lit)
+ ))?;
+ let local_variant_type_meta = local_variant_type_info.get_type_meta();
+ let local_fields = local_variant_type_meta.get_field_infos();
+
+ let field_index_by_name: std::collections::HashMap<_, _> = local_fields
+ .iter()
+ .enumerate()
+ .filter(|(_, f)| !f.field_name.is_empty())
+ .map(|(i, f)| (f.field_name.clone(), (i, f)))
+ .collect();
+
+ let field_index_by_id: std::collections::HashMap<_, _> = local_fields
+ .iter()
+ .enumerate()
+ .filter(|(_, f)| f.field_id >= 0)
+ .map(|(i, f)| (f.field_id, (i, f)))
+ .collect();
+
+ for field in fields.iter_mut() {
+ let local_match = if field.field_id >= 0 && field.field_name.is_empty() {
+ field_index_by_id.get(&field.field_id).copied()
+ } else {
+ let snake_case_name =
+ fory_core::util::to_snake_case(&field.field_name);
+ field_index_by_name.get(&snake_case_name).copied()
+ };
- match local_match {
- Some((sorted_index, local_info)) => {
- if field.field_name.is_empty() {
- field.field_name = local_info.field_name.clone();
- }
- if field.field_type != local_info.field_type {
- field.field_id = -1;
- } else {
- field.field_id = sorted_index as i16;
- }
- }
- None => {
- field.field_id = -1;
- }
+ match local_match {
+ Some((sorted_index, local_info)) => {
+ if field.field_name.is_empty() {
+ field.field_name = local_info.field_name.clone();
}
+ field.field_id = sorted_index as i16;
+ }
+ None => {
+ field.field_id = -1;
}
}
}
@@ -1088,9 +403,11 @@ pub(crate) fn gen_read_compatible_with_construction(
quote! {
let mut fields = remote_meta.get_field_infos().clone();
#variant_field_remap
+ let local_fields_ptr = local_fields.as_ptr();
}
} else {
quote! {
+ let local_fields_ptr = meta.get_field_infos().as_ptr();
let fields = remote_meta.get_field_infos();
}
};
diff --git a/rust/fory-derive/src/object/serializer.rs b/rust/fory-derive/src/object/serializer.rs
index 1ee2b0d22..605c3c452 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -41,19 +41,8 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
- // Extract type parameter names from generics (e.g., "C", "T", "E")
- let type_params: std::collections::HashSet<String> = ast
- .generics
- .params
- .iter()
- .filter_map(|p| match p {
- syn::GenericParam::Type(tp) => Some(tp.ident.to_string()),
- _ => None, // Ignore lifetime and const parameters
- })
- .collect();
-
use crate::object::util::{clear_struct_context, set_struct_context};
- set_struct_context(&name.to_string(), attrs.debug_enabled, type_params);
+ set_struct_context(&name.to_string(), attrs.debug_enabled);
// Check if ForyDefault is already derived/implemented
let has_existing_default = has_existing_default(ast, "ForyDefault");
@@ -123,7 +112,6 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
) = match &ast.data {
syn::Data::Struct(s) => {
let source_fields = source_fields(&s.fields);
- let fields = extract_fields(&source_fields);
(
write::gen_write(),
write::gen_write_data(&source_fields),
@@ -132,7 +120,7 @@ pub fn derive_serializer(ast: &syn::DeriveInput, attrs: ForyAttrs) -> TokenStrea
read::gen_read_with_type_info(),
read::gen_read_data(&source_fields),
read::gen_read_type_info(),
- write::gen_reserved_space(&fields),
+ write::gen_reserved_space(&source_fields),
quote! { fory_core::TypeId::STRUCT },
)
}
@@ -284,43 +272,11 @@ fn generate_default_impl(
.map(|sf| sf.is_tuple_struct)
.unwrap_or(false);
- use super::util::{
- classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc,
- StructField,
- };
-
// Generate field initializations with original index for sorting
let mut indexed: Vec<_> = source_fields
.iter()
.map(|sf| {
- let ty = &sf.field.ty;
- let value = match classify_trait_object_field(ty) {
- StructField::RcDyn(trait_name) => {
- let types = create_wrapper_types_rc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- {
- let wrapper = #wrapper_ty::default();
- std::rc::Rc::<dyn #trait_ident>::from(wrapper)
- }
- }
- }
- StructField::ArcDyn(trait_name) => {
- let types = create_wrapper_types_arc(&trait_name);
- let wrapper_ty = types.wrapper_ty;
- let trait_ident = types.trait_ident;
- quote! {
- {
- let wrapper = #wrapper_ty::default();
- std::sync::Arc::<dyn #trait_ident>::from(wrapper)
- }
- }
- }
- _ => {
- quote! { <#ty as fory_core::ForyDefault>::fory_default() }
- }
- };
+ let value = super::field_codec::default_expr_for_type(&sf.field.ty);
(sf.original_index, sf.field_init(value))
})
.collect();
diff --git a/rust/fory-derive/src/object/util.rs b/rust/fory-derive/src/object/util.rs
index 53047951f..7cdabb2e4 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -15,16 +15,12 @@
// specific language governing permissions and limitations
// under the License.
-use crate::util::{
- detect_collection_with_trait_object, is_arc_dyn_trait, is_box_dyn_trait, is_rc_dyn_trait,
- CollectionTraitInfo,
-};
-use fory_core::type_id::{TypeId, PRIMITIVE_ARRAY_TYPE_MAP};
+use super::field_meta::IntEncoding;
+use fory_core::type_id::TypeId;
use fory_core::util::to_snake_case;
-use proc_macro2::{Ident, TokenStream};
-use quote::{format_ident, quote, ToTokens};
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
use std::cell::RefCell;
-use std::fmt;
use syn::{Field, GenericArgument, Index, PathArguments, Type};
/// Get field name for a field, handling both named and tuple struct fields.
@@ -69,24 +65,14 @@ thread_local! {
struct MacroContext {
struct_name: String,
debug_enabled: bool,
- /// Type parameter names extracted from the struct/enum generics (e.g., "C", "T", "E")
- type_params: std::collections::HashSet<String>,
}
-/// Set the macro context with struct name, debug flag, and type parameters.
-///
-/// `type_params` should contain the names of all type parameters from the struct/enum
-/// generics (e.g., for `struct Vote<C: RaftTypeConfig>`, pass `{"C"}`).
-pub(super) fn set_struct_context(
- name: &str,
- debug_enabled: bool,
- type_params: std::collections::HashSet<String>,
-) {
+/// Set the macro context with struct name and debug flag.
+pub(super) fn set_struct_context(name: &str, debug_enabled: bool) {
MACRO_CONTEXT.with(|ctx| {
*ctx.borrow_mut() = Some(MacroContext {
struct_name: name.to_string(),
debug_enabled,
- type_params,
});
});
}
@@ -110,79 +96,6 @@ pub(super) fn is_debug_enabled() -> bool {
})
}
-/// Check if a type name is a type parameter of the current struct/enum.
-pub(super) fn is_type_parameter(name: &str) -> bool {
- MACRO_CONTEXT.with(|ctx| {
- ctx.borrow()
- .as_ref()
- .map(|c| c.type_params.contains(name))
- .unwrap_or(false)
- })
-}
-
-pub(super) fn contains_trait_object(ty: &Type) -> bool {
- match ty {
- Type::TraitObject(_) => true,
- Type::Path(type_path) => {
- if is_box_dyn_trait(ty).is_some()
- || is_rc_dyn_trait(ty).is_some()
- || is_arc_dyn_trait(ty).is_some()
- {
- return true;
- }
-
- if let Some(seg) = type_path.path.segments.last() {
- if let PathArguments::AngleBracketed(args) = &seg.arguments {
- return args.args.iter().any(|arg| {
- if let GenericArgument::Type(inner_ty) = arg {
- contains_trait_object(inner_ty)
- } else {
- false
- }
- });
- }
- }
- false
- }
- _ => false,
- }
-}
-
-pub(super) struct WrapperTypes {
- pub wrapper_ty: Ident,
- pub trait_ident: Ident,
-}
-
-pub(super) fn create_wrapper_types_rc(trait_name: &str) -> WrapperTypes {
- WrapperTypes {
- wrapper_ty: format_ident!("{}Rc", trait_name),
- trait_ident: format_ident!("{}", trait_name),
- }
-}
-
-pub(super) fn create_wrapper_types_arc(trait_name: &str) -> WrapperTypes {
- WrapperTypes {
- wrapper_ty: format_ident!("{}Arc", trait_name),
- trait_ident: format_ident!("{}", trait_name),
- }
-}
-
-#[allow(dead_code)]
-pub(super) enum StructField {
- BoxDyn,
- RcDyn(String),
- ArcDyn(String),
- VecBox(String),
- VecRc(String),
- VecArc(String),
- HashMapBox(Box<Type>, String),
- HashMapRc(Box<Type>, String),
- HashMapArc(Box<Type>, String),
- ContainsTraitObject,
- Forward,
- None,
-}
-
fn is_forward_field(ty: &Type) -> bool {
let struct_name = match get_struct_name() {
Some(name) => name,
@@ -258,435 +171,6 @@ fn is_forward_field_internal(ty: &Type, struct_name: &str) -> bool {
}
}
-pub(super) fn classify_trait_object_field(ty: &Type) -> StructField {
- // Check collections FIRST - they should be handled as collections, not forward refs
- // This is important because is_forward_field would match Vec<Box<dyn Any>> as forward
- if let Some(collection_info) = detect_collection_with_trait_object(ty) {
- return match collection_info {
- CollectionTraitInfo::VecBox(t) => StructField::VecBox(t),
- CollectionTraitInfo::VecRc(t) => StructField::VecRc(t),
- CollectionTraitInfo::VecArc(t) => StructField::VecArc(t),
- CollectionTraitInfo::HashMapBox(k, t) => StructField::HashMapBox(k, t),
- CollectionTraitInfo::HashMapRc(k, t) => StructField::HashMapRc(k, t),
- CollectionTraitInfo::HashMapArc(k, t) => StructField::HashMapArc(k, t),
- };
- }
- if is_forward_field(ty) {
- return StructField::Forward;
- }
- if let Some((_, _)) = is_box_dyn_trait(ty) {
- return StructField::BoxDyn;
- }
- if let Some((_, trait_name)) = is_rc_dyn_trait(ty) {
- return StructField::RcDyn(trait_name);
- }
- if let Some((_, trait_name)) = is_arc_dyn_trait(ty) {
- return StructField::ArcDyn(trait_name);
- }
- if contains_trait_object(ty) {
- return StructField::ContainsTraitObject;
- }
- StructField::None
-}
-
-#[derive(Debug)]
-pub(super) struct TypeNode {
- /// Simple type name, used for type matching (e.g., "LeaderId", "Vec")
- pub name: String,
- /// Full type path string, used for code generation (e.g., "C::LeaderId")
- pub full_path: String,
- pub generics: Vec<TypeNode>,
- /// For arrays, store the original type string "[T; N]" to preserve length info
- pub original_type_str: Option<String>,
-}
-
-pub(super) fn try_primitive_vec_type(node: &TypeNode) -> Option<TokenStream> {
- if node.name != "Vec" {
- return None;
- }
- let child = node.generics.first()?;
- for (ty_name, type_id, _) in PRIMITIVE_ARRAY_TYPE_MAP {
- if child.name == *ty_name {
- return Some(quote! { #type_id });
- }
- }
- None
-}
-
-pub(super) fn try_vec_of_option_primitive(node: &TypeNode) -> Option<TokenStream> {
- if node.name != "Vec" {
- return None;
- }
- let child = node.generics.first()?;
- if child.name != "Option" {
- return None;
- }
- let grandchild = child.generics.first()?;
- for (ty_name, _, _) in PRIMITIVE_ARRAY_TYPE_MAP {
- if grandchild.name == *ty_name {
- return Some(quote! {
- compile_error!("Vec<Option<primitive>> is not allowed!");
- });
- }
- }
-
- None
-}
-
-impl fmt::Display for TypeNode {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- if self.name == "Tuple" {
- // Format as Rust tuple syntax: (T1, T2, T3)
- write!(
- f,
- "({})",
- self.generics
- .iter()
- .map(|g| g.to_string())
- .collect::<Vec<_>>()
- .join(", ")
- )
- } else if self.name == "Array" {
- // For arrays, use the original type string if available (e.g., "[f32; 4]")
- if let Some(ref original) = self.original_type_str {
- write!(f, "{}", original)
- } else {
- // Fallback - shouldn't happen if properly constructed
- write!(f, "Array")
- }
- } else if self.generics.is_empty() {
- // Use full_path to preserve associated type paths like C::LeaderId
- write!(f, "{}", self.full_path)
- } else {
- // Use full_path for the base type, recursively format generics
- write!(
- f,
- "{}<{}>",
- self.full_path,
- self.generics
- .iter()
- .map(|g| g.to_string())
- .collect::<Vec<_>>()
- .join(",")
- )
- }
- }
-}
-
-pub(super) fn extract_type_name(ty: &Type) -> String {
- if let Type::Path(type_path) = ty {
- type_path.path.segments.last().unwrap().ident.to_string()
- } else if matches!(ty, Type::TraitObject(_)) {
- "TraitObject".to_string()
- } else if matches!(ty, Type::Tuple(_)) {
- "Tuple".to_string()
- } else if matches!(ty, Type::Array(_)) {
- "Array".to_string()
- } else {
- quote!(#ty).to_string()
- }
-}
-
-/// Extracts the full type path string, preserving associated types like `C::LeaderId`
-pub(super) fn extract_full_type_path(ty: &Type) -> String {
- if let Type::Path(type_path) = ty {
- // Build the full path from all segments without generic arguments
- type_path
- .path
- .segments
- .iter()
- .map(|seg| seg.ident.to_string())
- .collect::<Vec<_>>()
- .join("::")
- } else if matches!(ty, Type::TraitObject(_)) {
- "TraitObject".to_string()
- } else if matches!(ty, Type::Tuple(_)) {
- "Tuple".to_string()
- } else if matches!(ty, Type::Array(_)) {
- "Array".to_string()
- } else {
- quote!(#ty).to_string()
- }
-}
-
-#[allow(dead_code)]
-pub(super) fn is_option(ty: &Type) -> bool {
- if let Type::Path(type_path) = ty {
- if let Some(seg) = type_path.path.segments.last() {
- if seg.ident == "Option" {
- return true;
- }
- }
- }
- false
-}
-
-pub(super) fn parse_generic_tree(ty: &Type) -> TypeNode {
- // Handle trait objects specially - they can't be parsed as normal types
- if matches!(ty, Type::TraitObject(_)) {
- return TypeNode {
- name: "TraitObject".to_string(),
- full_path: "TraitObject".to_string(),
- generics: vec![],
- original_type_str: None,
- };
- }
-
- // Handle tuples - make child generics empty
- if let Type::Tuple(_tuple) = ty {
- return TypeNode {
- name: "Tuple".to_string(),
- full_path: "Tuple".to_string(),
- generics: vec![],
- original_type_str: None,
- };
- }
-
- // Handle arrays - extract element type and preserve original type string
- if let Type::Array(array) = ty {
- let elem_node = parse_generic_tree(&array.elem);
- // Preserve the original type string including the length (e.g., "[f32; 4]")
- let original_type_str = quote!(#ty).to_string().replace(' ', "");
- return TypeNode {
- name: "Array".to_string(),
- full_path: "Array".to_string(),
- generics: vec![elem_node],
- original_type_str: Some(original_type_str),
- };
- }
-
- let name = extract_type_name(ty);
- let full_path = extract_full_type_path(ty);
-
- let generics = if let Type::Path(type_path) = ty {
- if let PathArguments::AngleBracketed(args) =
- &type_path.path.segments.last().unwrap().arguments
- {
- args.args
- .iter()
- .filter_map(|arg| {
- if let GenericArgument::Type(ty) = arg {
- Some(parse_generic_tree(ty))
- } else {
- None
- }
- })
- .collect()
- } else {
- vec![]
- }
- } else {
- vec![]
- };
- TypeNode {
- name,
- full_path,
- generics,
- original_type_str: None,
- }
-}
-
-pub(super) fn generic_tree_to_tokens(node: &TypeNode) -> TokenStream {
- // Special handling for type parameters (e.g., C, T, E)
- // Type parameters should use UNKNOWN type ID since they are not concrete types.
- // This prevents `C::LeaderId` generating code like `<C as Serializer>::fory_get_type_id()` which
- // would require the type parameter to implement Serializer.
- if is_type_parameter(&node.name) {
- return quote! {
- fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::UNKNOWN as u32,
- true,
- vec![]
- )
- };
- }
-
- // Special handling for tuples: always use FieldType { LIST, nullable: true, generics: vec![UNKNOWN] }
- if node.name == "Tuple" {
- return quote! {
- fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::LIST as u32,
- true,
- vec![fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::UNKNOWN as u32,
- true,
- vec![]
- )]
- )
- };
- }
-
- // Special handling for arrays: treat them as lists with element type generic
- if node.name == "Array" {
- if let Some(elem_node) = node.generics.first() {
- let elem_token = generic_tree_to_tokens(elem_node);
- // Check if element is primitive to determine the correct type ID
- let is_primitive_elem = PRIMITIVE_TYPE_NAMES.contains(&elem_node.name.as_str());
- if is_primitive_elem {
- // For primitive arrays, use primitive array type ID
- let type_id_token = match elem_node.name.as_str() {
- "bool" => quote! { fory_core::type_id::TypeId::BOOL_ARRAY as u32 },
- "i8" => quote! { fory_core::type_id::TypeId::INT8_ARRAY as u32 },
- "i16" => quote! { fory_core::type_id::TypeId::INT16_ARRAY as u32 },
- "i32" => quote! { fory_core::type_id::TypeId::INT32_ARRAY as u32 },
- "i64" => quote! { fory_core::type_id::TypeId::INT64_ARRAY as u32 },
- "i128" => quote! { fory_core::type_id::TypeId::INT128_ARRAY as u32 },
- "float16" | "Float16" => {
- quote! { fory_core::type_id::TypeId::FLOAT16_ARRAY as u32 }
- }
- "bfloat16" | "BFloat16" => {
- quote! { fory_core::type_id::TypeId::BFLOAT16_ARRAY as u32 }
- }
- "f32" => quote! { fory_core::type_id::TypeId::FLOAT32_ARRAY as u32 },
- "f64" => quote! { fory_core::type_id::TypeId::FLOAT64_ARRAY as u32 },
- "u8" => quote! { fory_core::type_id::TypeId::BINARY as u32 },
- "u16" => quote! { fory_core::type_id::TypeId::UINT16_ARRAY as u32 },
- "u32" => quote! { fory_core::type_id::TypeId::UINT32_ARRAY as u32 },
- "u64" => quote! { fory_core::type_id::TypeId::UINT64_ARRAY as u32 },
- "u128" => quote! { fory_core::type_id::TypeId::U128_ARRAY as u32 },
- _ => quote! { fory_core::type_id::TypeId::LIST as u32 },
- };
- return quote! {
- fory_core::meta::FieldType::new(
- #type_id_token,
- false,
- vec![]
- )
- };
- } else {
- // For non-primitive arrays, use LIST type ID with element type as generic
- return quote! {
- fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::LIST as u32,
- false,
- vec![#elem_token]
- )
- };
- }
- } else {
- // Array without element type info - shouldn't happen
- return quote! { compile_error!("Array missing element type"); };
- }
- }
-
- // If Option, unwrap it before generating children
- let (nullable, base_node) = if node.name == "Option" {
- if let Some(inner) = node.generics.first() {
- if inner.name == "Option" {
- return quote! { compile_error!("Nested adjacent Option is not allowed!"); };
- }
- // Special handling for Option<Tuple>
- if inner.name == "Tuple" {
- return quote! {
- fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::LIST as u32,
- true,
- vec![fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::UNKNOWN as u32,
- true,
- vec![]
- )]
- )
- };
- }
- // Unwrap Option and propagate parsing
- (true, inner)
- } else {
- return quote! { compile_error!("Missing Option inner type"); };
- }
- } else {
- (!PRIMITIVE_TYPE_NAMES.contains(&node.name.as_str()), node)
- };
-
- // If Rc or Arc, unwrap to inner type - these are reference wrappers
- // that don't add type info to the field type (handled by track_ref flag)
- let base_node = if base_node.name == "Rc" || base_node.name == "Arc" {
- if let Some(inner) = base_node.generics.first() {
- inner
- } else {
- base_node
- }
- } else {
- base_node
- };
-
- // `Vec<Option<primitive>>` rule stays as is
- if let Some(ts) = try_vec_of_option_primitive(base_node) {
- return ts;
- }
-
- // Try primitive Vec type
- let primitive_vec = try_primitive_vec_type(base_node);
-
- // Recursively generate children token streams
- let children_tokens: Vec<TokenStream> = if primitive_vec.is_none() {
- base_node
- .generics
- .iter()
- .map(generic_tree_to_tokens)
- .collect()
- } else {
- vec![]
- };
-
- // Build the syn::Type from the DISPLAY of base_node, not the original node if Option
- let ty: syn::Type = syn::parse_str(&base_node.to_string()).unwrap();
-
- // Get type ID
- let get_type_id = if let Some(ts) = primitive_vec {
- ts
- } else {
- quote! {
- <#ty as fory_core::serializer::Serializer>::fory_get_type_id(type_resolver)? as u32
- }
- };
-
- quote! {
- {
- let mut type_id = #get_type_id;
- let mut user_type_id = u32::MAX;
- let mut generics = vec![#(#children_tokens),*] as Vec<fory_core::meta::FieldType>;
- // For tuples and sets, if no generic info is available, add UNKNOWN element
- // This handles type aliases to tuples where we can't detect the tuple at macro time
- if (type_id == fory_core::type_id::TypeId::LIST as u32
- || type_id == fory_core::type_id::TypeId::SET as u32)
- && generics.is_empty() {
- generics.push(fory_core::meta::FieldType::new(
- fory_core::type_id::TypeId::UNKNOWN as u32,
- true,
- vec![]
- ));
- }
- let is_custom = !fory_core::type_id::is_internal_type(type_id);
- if is_custom {
- let type_info = <#ty as fory_core::serializer::Serializer>::fory_get_type_info(type_resolver)?;
- type_id = type_info.get_type_id() as u32;
- user_type_id = type_info.get_user_type_id();
- if type_id == fory_core::type_id::TypeId::TYPED_UNION as u32
- || type_id == fory_core::type_id::TypeId::NAMED_UNION as u32 {
- type_id = fory_core::type_id::TypeId::UNION as u32;
- user_type_id = u32::MAX;
- }
- if type_resolver.is_xlang() && generics.len() > 0 {
- return Err(fory_core::error::Error::unsupported("serialization of generic structs and enums is not supported in xlang mode"));
- } else {
- generics = vec![];
- }
- } else if type_id == fory_core::type_id::TypeId::TYPED_UNION as u32
- || type_id == fory_core::type_id::TypeId::NAMED_UNION as u32 {
- type_id = fory_core::type_id::TypeId::UNION as u32;
- }
- fory_core::meta::FieldType {
- type_id,
- user_type_id,
- nullable: #nullable,
- track_ref: false,
- generics,
- }
- }
- }
-}
-
type FieldGroup = Vec<(String, String, u32)>;
type FieldGroups = (
FieldGroup,
@@ -732,225 +216,6 @@ fn get_primitive_type_id(ty: &str) -> u32 {
}
}
-pub(super) fn is_primitive_type(ty: &str) -> bool {
- PRIMITIVE_TYPE_NAMES.contains(&ty)
-}
-
-/// Mapping of primitive type names to their writer and reader method names
-/// Order: (type_name, writer_method, reader_method)
-static PRIMITIVE_IO_METHODS: &[(&str, &str, &str)] = &[
- ("bool", "write_bool", "read_bool"),
- ("i8", "write_i8", "read_i8"),
- ("i16", "write_i16", "read_i16"),
- ("i32", "write_var_i32", "read_var_i32"),
- ("i64", "write_var_i64", "read_var_i64"),
- ("f32", "write_f32", "read_f32"),
- ("f64", "write_f64", "read_f64"),
- ("u8", "write_u8", "read_u8"),
- ("u16", "write_u16", "read_u16"),
- ("u32", "write_u32", "read_u32"),
- ("u64", "write_u64", "read_u64"),
- ("usize", "write_usize", "read_usize"),
- ("u128", "write_u128", "read_u128"),
-];
-
-/// Check if a type is a direct primitive type (numeric or String, not wrapped in Option, Vec, etc.)
-pub(super) fn is_direct_primitive_type(ty: &Type) -> bool {
- if let Type::Path(type_path) = ty {
- if let Some(seg) = type_path.path.segments.last() {
- // Check if it's a simple type path without generics
- if matches!(seg.arguments, PathArguments::None) {
- let type_name = seg.ident.to_string();
- // Check for String type
- if type_name == "String" {
- return true;
- }
- // Check for numeric primitive types
- return PRIMITIVE_IO_METHODS
- .iter()
... 4702 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org