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/29 13:32:59 UTC
(fory) branch main updated: feat(cpp): add nested field codec support (#3636)
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 112a6e4a4 feat(cpp): add nested field codec support (#3636)
112a6e4a4 is described below
commit 112a6e4a4f01ebcd8064e64175163814e78cdadb
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Wed Apr 29 21:32:55 2026 +0800
feat(cpp): add nested field codec support (#3636)
## Why?
C++ needs field metadata that can describe nested container key, value,
and element encodings so generated C++ xlang structs can match IDL and
Java metadata for maps, lists, optional wrappers, and integer encodings.
## What does this PR do?
- Embeds field metadata directly in `FORY_STRUCT` entries with
`fory::F(...)` and adds recursive `fory::T` value-node specs for scalar,
list, set, map, and inner carrier nodes.
- Make C++ struct and union serialization/type metadata paths to honor
nested specs when writing, reading, and building `FieldType` metadata,
including configured integer encodings inside lists, maps, and
optionals.
- Updates the C++ IDL generator to emit inline `FORY_STRUCT` metadata
instead of separate `FORY_FIELD_CONFIG` output and allows nested
collections for the C++ target.
- Updates C++ docs, README examples, benchmark structs, and
polymorphism/xlang guides to use the inline field configuration style.
- Adds compiler, C++ struct, C++ xlang, and Java CPP xlang coverage for
nested annotated containers in schema-consistent and compatible modes.
## Related issues
#1017
#3630
#3625
#3630
## 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?
Yes. C++ users configure field metadata inline in `FORY_STRUCT`,
including nested `fory::T` specs; `FORY_UNION` cases also require
explicit `fory::F(id)` metadata. This does not intentionally change the
binary protocol; it enables C++ to emit and read existing nested xlang
field metadata.
- [x] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
.github/workflows/ci.yml | 4 +-
benchmarks/cpp/benchmark.cc | 58 +-
benchmarks/cpp/benchmark_report.py | 30 +-
compiler/fory_compiler/cli.py | 5 +-
.../fory_compiler/frontend/proto/translator.py | 1 +
compiler/fory_compiler/generators/cpp.py | 224 +--
compiler/fory_compiler/ir/validator.py | 6 +-
.../fory_compiler/tests/test_collection_nesting.py | 11 +
.../fory_compiler/tests/test_generated_code.py | 22 +-
cpp/fory/meta/BUILD | 1 +
cpp/fory/meta/field.h | 1766 +-------------------
cpp/fory/meta/field_info.h | 319 +++-
cpp/fory/meta/field_test.cc | 431 +----
cpp/fory/serialization/BUILD | 9 -
cpp/fory/serialization/collection_serializer.h | 3 +-
cpp/fory/serialization/field_serializer_test.cc | 1211 --------------
cpp/fory/serialization/namespace_macro_test.cc | 81 +-
cpp/fory/serialization/serializer_traits.h | 15 +-
cpp/fory/serialization/skip.cc | 10 +-
.../serialization/smart_ptr_serializer_test.cc | 32 +-
cpp/fory/serialization/struct_serializer.h | 753 ++++++++-
cpp/fory/serialization/struct_test.cc | 248 ++-
cpp/fory/serialization/type_resolver.cc | 105 +-
cpp/fory/serialization/type_resolver.h | 565 +++++--
cpp/fory/serialization/union_serializer.h | 533 +++++-
cpp/fory/serialization/xlang_test_main.cc | 146 +-
docs/benchmarks/cpp/README.md | 76 +-
docs/benchmarks/cpp/mediacontent.png | Bin 52226 -> 52441 bytes
docs/benchmarks/cpp/mediacontentlist.png | Bin 57111 -> 57760 bytes
docs/benchmarks/cpp/sample.png | Bin 55251 -> 54468 bytes
docs/benchmarks/cpp/samplelist.png | Bin 53909 -> 53635 bytes
docs/benchmarks/cpp/struct.png | Bin 51352 -> 51954 bytes
docs/benchmarks/cpp/structlist.png | Bin 53820 -> 52960 bytes
docs/benchmarks/cpp/throughput.png | Bin 78902 -> 80164 bytes
docs/compiler/generated-code.md | 4 +-
docs/compiler/schema-idl.md | 7 +-
docs/guide/cpp/field-configuration.md | 576 ++-----
docs/guide/cpp/polymorphism.md | 46 +-
docs/guide/xlang/field-nullability.md | 27 +-
docs/guide/xlang/field-reference-tracking.md | 12 +-
docs/guide/xlang/field-type-meta.md | 28 +-
.../java/org/apache/fory/xlang/CPPXlangTest.java | 12 +
42 files changed, 2950 insertions(+), 4427 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index adfd5b080..3a5b8db11 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -999,7 +999,7 @@ jobs:
python:
name: Python CI
needs: changes
- if: needs.changes.outputs.python == 'true'
+ if: needs.changes.outputs.python == 'true' || needs.changes.outputs.cpp == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -1039,7 +1039,7 @@ jobs:
python_windows:
name: Python CI (${{ matrix.python-version }}, windows-2022)
needs: changes
- if: needs.changes.outputs.python == 'true'
+ if: needs.changes.outputs.python == 'true' || needs.changes.outputs.cpp == 'true'
runs-on: windows-2022
strategy:
matrix:
diff --git a/benchmarks/cpp/benchmark.cc b/benchmarks/cpp/benchmark.cc
index f1d65bff4..b9ed84e5c 100644
--- a/benchmarks/cpp/benchmark.cc
+++ b/benchmarks/cpp/benchmark.cc
@@ -49,9 +49,9 @@ struct NumericStruct {
}
MSGPACK_DEFINE_MAP(f1, f2, f3, f4, f5, f6, f7, f8);
};
-FORY_STRUCT(NumericStruct, f1, f2, f3, f4, f5, f6, f7, f8);
-FORY_FIELD_TAGS(NumericStruct, (f1, 1), (f2, 2), (f3, 3), (f4, 4), (f5, 5),
- (f6, 6), (f7, 7), (f8, 8));
+FORY_STRUCT(NumericStruct, (f1, fory::F(1)), (f2, fory::F(2)), (f3, fory::F(3)),
+ (f4, fory::F(4)), (f5, fory::F(5)), (f6, fory::F(6)),
+ (f7, fory::F(7)), (f8, fory::F(8)));
struct Sample {
int32_t int_value;
@@ -103,20 +103,17 @@ struct Sample {
int_array, long_array, float_array, double_array,
short_array, char_array, boolean_array, string);
};
-FORY_STRUCT(Sample, int_value, long_value, float_value, double_value,
- short_value, char_value, boolean_value, int_value_boxed,
- long_value_boxed, float_value_boxed, double_value_boxed,
- short_value_boxed, char_value_boxed, boolean_value_boxed, int_array,
- long_array, float_array, double_array, short_array, char_array,
- boolean_array, string);
-FORY_FIELD_TAGS(Sample, (int_value, 1), (long_value, 2), (float_value, 3),
- (double_value, 4), (short_value, 5), (char_value, 6),
- (boolean_value, 7), (int_value_boxed, 8), (long_value_boxed, 9),
- (float_value_boxed, 10), (double_value_boxed, 11),
- (short_value_boxed, 12), (char_value_boxed, 13),
- (boolean_value_boxed, 14), (int_array, 15), (long_array, 16),
- (float_array, 17), (double_array, 18), (short_array, 19),
- (char_array, 20), (boolean_array, 21), (string, 22));
+FORY_STRUCT(Sample, (int_value, fory::F(1)), (long_value, fory::F(2)),
+ (float_value, fory::F(3)), (double_value, fory::F(4)),
+ (short_value, fory::F(5)), (char_value, fory::F(6)),
+ (boolean_value, fory::F(7)), (int_value_boxed, fory::F(8)),
+ (long_value_boxed, fory::F(9)), (float_value_boxed, fory::F(10)),
+ (double_value_boxed, fory::F(11)), (short_value_boxed, fory::F(12)),
+ (char_value_boxed, fory::F(13)), (boolean_value_boxed, fory::F(14)),
+ (int_array, fory::F(15)), (long_array, fory::F(16)),
+ (float_array, fory::F(17)), (double_array, fory::F(18)),
+ (short_array, fory::F(19)), (char_array, fory::F(20)),
+ (boolean_array, fory::F(21)), (string, fory::F(22)));
// Enums for MediaContent benchmark
enum class Player : int32_t { JAVA = 0, FLASH = 1 };
@@ -150,11 +147,11 @@ struct Media {
MSGPACK_DEFINE_MAP(uri, title, width, height, format, duration, size, bitrate,
has_bitrate, persons, player, copyright);
};
-FORY_STRUCT(Media, uri, title, width, height, format, duration, size, bitrate,
- has_bitrate, persons, player, copyright);
-FORY_FIELD_TAGS(Media, (uri, 1), (title, 2), (width, 3), (height, 4),
- (format, 5), (duration, 6), (size, 7), (bitrate, 8),
- (has_bitrate, 9), (persons, 10), (player, 11), (copyright, 12));
+FORY_STRUCT(Media, (uri, fory::F(1)), (title, fory::F(2)), (width, fory::F(3)),
+ (height, fory::F(4)), (format, fory::F(5)), (duration, fory::F(6)),
+ (size, fory::F(7)), (bitrate, fory::F(8)),
+ (has_bitrate, fory::F(9)), (persons, fory::F(10)),
+ (player, fory::F(11)), (copyright, fory::F(12)));
struct Image {
std::string uri;
@@ -169,9 +166,8 @@ struct Image {
}
MSGPACK_DEFINE_MAP(uri, title, width, height, size);
};
-FORY_STRUCT(Image, uri, title, width, height, size);
-FORY_FIELD_TAGS(Image, (uri, 1), (title, 2), (width, 3), (height, 4),
- (size, 5));
+FORY_STRUCT(Image, (uri, fory::F(1)), (title, fory::F(2)), (width, fory::F(3)),
+ (height, fory::F(4)), (size, fory::F(5)));
struct MediaContent {
Media media;
@@ -182,8 +178,7 @@ struct MediaContent {
}
MSGPACK_DEFINE_MAP(media, images);
};
-FORY_STRUCT(MediaContent, media, images);
-FORY_FIELD_TAGS(MediaContent, (media, 1), (images, 2));
+FORY_STRUCT(MediaContent, (media, fory::F(1)), (images, fory::F(2)));
struct StructList {
std::vector<NumericStruct> struct_list;
@@ -193,8 +188,7 @@ struct StructList {
}
MSGPACK_DEFINE_MAP(struct_list);
};
-FORY_STRUCT(StructList, struct_list);
-FORY_FIELD_TAGS(StructList, (struct_list, 1));
+FORY_STRUCT(StructList, (struct_list, fory::F(1)));
struct SampleList {
std::vector<Sample> sample_list;
@@ -204,8 +198,7 @@ struct SampleList {
}
MSGPACK_DEFINE_MAP(sample_list);
};
-FORY_STRUCT(SampleList, sample_list);
-FORY_FIELD_TAGS(SampleList, (sample_list, 1));
+FORY_STRUCT(SampleList, (sample_list, fory::F(1)));
struct MediaContentList {
std::vector<MediaContent> media_content_list;
@@ -215,8 +208,7 @@ struct MediaContentList {
}
MSGPACK_DEFINE_MAP(media_content_list);
};
-FORY_STRUCT(MediaContentList, media_content_list);
-FORY_FIELD_TAGS(MediaContentList, (media_content_list, 1));
+FORY_STRUCT(MediaContentList, (media_content_list, fory::F(1)));
// ============================================================================
// Test data creation
diff --git a/benchmarks/cpp/benchmark_report.py b/benchmarks/cpp/benchmark_report.py
index 1480818cf..fd3944d8e 100644
--- a/benchmarks/cpp/benchmark_report.py
+++ b/benchmarks/cpp/benchmark_report.py
@@ -45,6 +45,17 @@ SERIALIZER_LABELS = {
"protobuf": "protobuf",
"msgpack": "msgpack",
}
+DATATYPE_ORDER = [
+ "struct",
+ "sample",
+ "mediacontent",
+ "structlist",
+ "samplelist",
+ "mediacontentlist",
+]
+DATATYPE_ORDER_INDEX = {
+ datatype: index for index, datatype in enumerate(DATATYPE_ORDER)
+}
# === Parse arguments ===
parser = argparse.ArgumentParser(
@@ -135,6 +146,16 @@ def format_datatype_table_label(datatype):
return datatype.capitalize()
+def ordered_datatypes(datatypes):
+ return sorted(
+ datatypes,
+ key=lambda datatype: (
+ DATATYPE_ORDER_INDEX.get(datatype, len(DATATYPE_ORDER)),
+ datatype,
+ ),
+ )
+
+
# === Read and parse benchmark JSON ===
def load_benchmark_data(json_file):
with open(json_file, "r", encoding="utf-8") as f:
@@ -243,7 +264,7 @@ def plot_datatype(ax, datatype, operation):
# === Create plots ===
plot_images = []
-datatypes = sorted(data.keys())
+datatypes = ordered_datatypes(data.keys())
operations = ["serialize", "deserialize"]
for datatype in datatypes:
@@ -343,7 +364,12 @@ for k, v in system_info.items():
md_report.append("\n## Benchmark Plots\n")
md_report.append("\nAll class-level plots below show throughput (ops/sec).\n")
plot_images_sorted = sorted(
- plot_images, key=lambda item: (0 if item[0] == "throughput" else 1, item[0])
+ plot_images,
+ key=lambda item: (
+ 0 if item[0] == "throughput" else 1,
+ DATATYPE_ORDER_INDEX.get(item[0], len(DATATYPE_ORDER)),
+ item[0],
+ ),
)
for datatype, img in plot_images_sorted:
img_filename = os.path.basename(img)
diff --git a/compiler/fory_compiler/cli.py b/compiler/fory_compiler/cli.py
index 541e100a1..c0620b854 100644
--- a/compiler/fory_compiler/cli.py
+++ b/compiler/fory_compiler/cli.py
@@ -520,7 +520,10 @@ def compile_file(
print("======================")
# Validate merged schema
- validator = SchemaValidator(schema)
+ allow_nested_collections = set(lang_output_dirs) == {"cpp"}
+ validator = SchemaValidator(
+ schema, allow_nested_collections=allow_nested_collections
+ )
if not validator.validate():
for error in validator.errors:
print(f"Error: {error}", file=sys.stderr)
diff --git a/compiler/fory_compiler/frontend/proto/translator.py b/compiler/fory_compiler/frontend/proto/translator.py
index 0bbebe8a0..f3f453721 100644
--- a/compiler/fory_compiler/frontend/proto/translator.py
+++ b/compiler/fory_compiler/frontend/proto/translator.py
@@ -297,6 +297,7 @@ class ProtoTranslator:
oneof_type_name, location=self._location(oneof.line, oneof.column)
),
number=first_case.number,
+ tag_id=first_case.number,
optional=True,
ref=False,
options={},
diff --git a/compiler/fory_compiler/generators/cpp.py b/compiler/fory_compiler/generators/cpp.py
index f1b6f4de6..66605d973 100644
--- a/compiler/fory_compiler/generators/cpp.py
+++ b/compiler/fory_compiler/generators/cpp.py
@@ -125,14 +125,6 @@ class CppGenerator(BaseGenerator):
return f"{namespace}::{qualified_name}"
return qualified_name
- def get_field_config_type(
- self,
- type_name: str,
- parent_stack: List[Message],
- ) -> str:
- """Get type name for FORY_FIELD_CONFIG."""
- return self.get_namespaced_type_name(type_name, parent_stack)
-
def is_imported_type(self, type_def: object) -> bool:
"""Return True if a type definition comes from an imported IDL file."""
if not self.schema.source_file:
@@ -252,7 +244,6 @@ class CppGenerator(BaseGenerator):
includes: Set[str] = set()
enum_macros: List[str] = []
union_macros: List[str] = []
- field_config_macros: List[str] = []
evolving_macros: List[str] = []
definition_items = self.get_definition_order()
@@ -342,7 +333,6 @@ class CppGenerator(BaseGenerator):
[],
enum_macros,
union_macros,
- field_config_macros,
evolving_macros,
"",
)
@@ -353,10 +343,6 @@ class CppGenerator(BaseGenerator):
lines.extend(union_macros)
lines.append("")
- if field_config_macros:
- lines.extend(field_config_macros)
- lines.append("")
-
if enum_macros:
lines.extend(enum_macros)
lines.append("")
@@ -658,12 +644,14 @@ class CppGenerator(BaseGenerator):
return f"std::unique_ptr<{type_name}>"
return self.generate_type(
field.field_type,
- False
- if (
- self.is_message_type(field.field_type, parent_stack)
- and (field.ref or weak_ref)
- )
- else field.optional,
+ (
+ False
+ if (
+ self.is_message_type(field.field_type, parent_stack)
+ and (field.ref or weak_ref)
+ )
+ else field.optional
+ ),
field.ref,
field.element_optional,
field.element_ref,
@@ -878,7 +866,6 @@ class CppGenerator(BaseGenerator):
parent_stack: List[Message],
enum_macros: List[str],
union_macros: List[str],
- field_config_macros: List[str],
evolving_macros: List[str],
indent: str,
) -> List[str]:
@@ -908,7 +895,6 @@ class CppGenerator(BaseGenerator):
lineage,
enum_macros,
union_macros,
- field_config_macros,
evolving_macros,
body_indent,
)
@@ -959,13 +945,7 @@ class CppGenerator(BaseGenerator):
lines.append("")
lines.append(f"{body_indent}public:")
field_members = ", ".join(
- self.get_field_member_name(f) for f in message.fields
- )
- field_config_type_name = self.get_field_config_type(
- message.name, parent_stack
- )
- field_config_macros.append(
- self.generate_field_config_macro(message, field_config_type_name)
+ self.get_field_macro_entry(f) for f in message.fields
)
lines.append(
f"{body_indent}FORY_STRUCT({struct_type_name}, {field_members});"
@@ -1144,7 +1124,7 @@ class CppGenerator(BaseGenerator):
case_ctor = self.to_snake_case(field.name)
meta = self.get_union_field_meta(field)
suffix = "," if index + 1 < len(union.fields) else ""
- lines.append(f" ({case_type}, {case_ctor}, {meta}){suffix}")
+ lines.append(f" ({case_ctor}, {case_type}, {meta}){suffix}")
lines.append(");")
return lines
@@ -1539,39 +1519,40 @@ class CppGenerator(BaseGenerator):
return "void*"
- def generate_field_config_macro(
- self,
- message: Message,
- qualified_name: str,
- ) -> str:
- """Generate FORY_FIELD_CONFIG macro for a message."""
- entries = []
- for field in message.fields:
- field_name = self.get_field_member_name(field)
- meta = self.get_field_meta(field)
- entries.append(f"({field_name}, {meta})")
- joined = ", ".join(entries)
- return f"FORY_FIELD_CONFIG({qualified_name}, {joined});"
+ def get_field_macro_entry(self, field: Field) -> str:
+ """Build one FORY_STRUCT field entry."""
+ return f"({self.get_field_member_name(field)}, {self.get_field_meta(field)})"
def get_field_meta(self, field: Field) -> str:
"""Build FieldMeta expression for a field."""
- meta = "fory::F()"
+ if field.tag_id is not None:
+ meta = f"fory::F({field.tag_id})"
+ else:
+ meta = "fory::F()"
is_any = (
isinstance(field.field_type, PrimitiveType)
and field.field_type.kind == PrimitiveKind.ANY
)
- if field.tag_id is not None:
- meta += f".id({field.tag_id})"
if field.optional or is_any:
meta += ".nullable()"
if field.ref:
meta += ".ref()"
- encoding = self.get_encoding_config(field.field_type)
- if encoding:
- meta += encoding
- array_type = self.get_array_type_config(field)
- if array_type:
- meta += array_type
+ spec = self.get_field_type_spec(field)
+ if spec:
+ if field.optional or field.ref:
+ meta += f".inner({spec})"
+ elif spec.startswith("fory::T::list("):
+ meta += f".list({spec[len('fory::T::list(') : -1]})"
+ elif spec.startswith("fory::T::set("):
+ meta += f".set({spec[len('fory::T::set(') : -1]})"
+ elif spec.startswith("fory::T::map("):
+ meta += f".map({spec[len('fory::T::map(') : -1]})"
+ elif spec.endswith(".fixed()"):
+ meta += ".fixed()"
+ elif spec.endswith(".varint()"):
+ meta += ".varint()"
+ elif spec.endswith(".tagged()"):
+ meta += ".tagged()"
return meta
def get_union_field_meta(self, field: Field) -> str:
@@ -1585,52 +1566,105 @@ class CppGenerator(BaseGenerator):
meta += ".nullable()"
if field.ref:
meta += ".ref()"
- encoding = self.get_encoding_config(field.field_type)
- if encoding:
- meta += encoding
- array_type = self.get_array_type_config(field)
- if array_type:
- meta += array_type
+ spec = self.get_field_type_spec(field)
+ if spec:
+ if field.optional or field.ref:
+ meta += f".inner({spec})"
+ elif spec.startswith("fory::T::list("):
+ meta += f".list({spec[len('fory::T::list(') : -1]})"
+ elif spec.startswith("fory::T::set("):
+ meta += f".set({spec[len('fory::T::set(') : -1]})"
+ elif spec.startswith("fory::T::map("):
+ meta += f".map({spec[len('fory::T::map(') : -1]})"
+ elif spec.endswith(".fixed()"):
+ meta += ".fixed()"
+ elif spec.endswith(".varint()"):
+ meta += ".varint()"
+ elif spec.endswith(".tagged()"):
+ meta += ".tagged()"
return meta
- def get_encoding_config(self, field_type: FieldType) -> str:
- """Return encoding config for primitive types."""
- kind = None
- if isinstance(field_type, PrimitiveType):
- kind = field_type.kind
- elif isinstance(field_type, ListType) and isinstance(
- field_type.element_type, PrimitiveType
- ):
- kind = field_type.element_type.kind
- if kind is None:
- return ""
- if kind in (
- PrimitiveKind.INT32,
- PrimitiveKind.INT64,
- PrimitiveKind.UINT32,
- PrimitiveKind.UINT64,
- ):
- return ".fixed()"
- if kind in (PrimitiveKind.VAR_UINT32, PrimitiveKind.VAR_UINT64):
- return ".varint()"
- if kind in (PrimitiveKind.TAGGED_INT64, PrimitiveKind.TAGGED_UINT64):
- return ".tagged()"
- return ""
+ def get_field_type_spec(self, field: Field) -> str:
+ """Return the T-node spec for a field when generated metadata needs it."""
+ return self.get_type_spec(
+ field.field_type,
+ field.element_optional,
+ field.element_ref,
+ )
- def get_array_type_config(self, field: Field) -> str:
- """Return array type override for int8/uint8 arrays."""
- if not isinstance(field.field_type, ListType):
- return ""
- if field.element_optional or field.element_ref:
- return ""
- element_type = field.field_type.element_type
- if not isinstance(element_type, PrimitiveType):
- return ""
- if element_type.kind == PrimitiveKind.INT8:
- return ".int8_array()"
- if element_type.kind == PrimitiveKind.UINT8:
- return ".uint8_array()"
- return ""
+ def get_type_spec(
+ self,
+ field_type: FieldType,
+ optional: bool = False,
+ ref: bool = False,
+ ) -> str:
+ """Return a recursive fory::T spec for generated C++ metadata."""
+ if isinstance(field_type, PrimitiveType):
+ spec = self.get_primitive_type_spec(field_type.kind)
+ elif isinstance(field_type, ListType):
+ if (
+ isinstance(field_type.element_type, PrimitiveType)
+ and not field_type.element_optional
+ and not field_type.element_ref
+ and field_type.element_type.kind
+ not in (PrimitiveKind.INT8, PrimitiveKind.UINT8)
+ ):
+ spec = ""
+ else:
+ element = self.get_type_spec(
+ field_type.element_type,
+ field_type.element_optional,
+ field_type.element_ref,
+ )
+ if not element:
+ element = "fory::FieldNodeSpec{}"
+ spec = f"fory::T::list({element})"
+ elif isinstance(field_type, MapType):
+ key = self.get_type_spec(field_type.key_type)
+ element = self.get_type_spec(
+ field_type.value_type,
+ field_type.value_optional,
+ field_type.value_ref,
+ )
+ if key or element:
+ if not key:
+ key = "fory::FieldNodeSpec{}"
+ if not element:
+ element = "fory::FieldNodeSpec{}"
+ spec = f"fory::T::map({key}, {element})"
+ else:
+ spec = ""
+ else:
+ spec = ""
+
+ if (optional or ref) and spec:
+ return f"fory::T::inner({spec})"
+ return spec
+
+ def get_primitive_type_spec(self, kind: PrimitiveKind) -> str:
+ """Return a scalar T-node spec for primitive encoding metadata."""
+ typed = {
+ PrimitiveKind.BOOL: "fory::T::boolean()",
+ PrimitiveKind.INT8: "fory::T::int8()",
+ PrimitiveKind.INT16: "fory::T::int16()",
+ PrimitiveKind.INT32: "fory::T::int32().fixed()",
+ PrimitiveKind.VARINT32: "fory::T::int32().varint()",
+ PrimitiveKind.INT64: "fory::T::int64().fixed()",
+ PrimitiveKind.VARINT64: "fory::T::int64().varint()",
+ PrimitiveKind.TAGGED_INT64: "fory::T::int64().tagged()",
+ PrimitiveKind.UINT8: "fory::T::uint8()",
+ PrimitiveKind.UINT16: "fory::T::uint16()",
+ PrimitiveKind.UINT32: "fory::T::uint32().fixed()",
+ PrimitiveKind.VAR_UINT32: "fory::T::uint32().varint()",
+ PrimitiveKind.UINT64: "fory::T::uint64().fixed()",
+ PrimitiveKind.VAR_UINT64: "fory::T::uint64().varint()",
+ PrimitiveKind.TAGGED_UINT64: "fory::T::uint64().tagged()",
+ PrimitiveKind.FLOAT16: "fory::T::float16()",
+ PrimitiveKind.FLOAT32: "fory::T::float32()",
+ PrimitiveKind.FLOAT64: "fory::T::float64()",
+ PrimitiveKind.STRING: "fory::T::string()",
+ }
+ return typed.get(kind, "")
def generate_type(
self,
diff --git a/compiler/fory_compiler/ir/validator.py b/compiler/fory_compiler/ir/validator.py
index acd774177..0553c7524 100644
--- a/compiler/fory_compiler/ir/validator.py
+++ b/compiler/fory_compiler/ir/validator.py
@@ -54,8 +54,9 @@ class ValidationIssue:
class SchemaValidator:
"""Validates a Fory IR schema."""
- def __init__(self, schema: Schema):
+ def __init__(self, schema: Schema, allow_nested_collections: bool = False):
self.schema = schema
+ self.allow_nested_collections = allow_nested_collections
self.errors: List[ValidationIssue] = []
self.warnings: List[ValidationIssue] = []
@@ -67,7 +68,8 @@ class SchemaValidator:
self._check_messages()
self._check_type_references()
self._check_services()
- self._check_collection_nesting()
+ if not self.allow_nested_collections:
+ self._check_collection_nesting()
self._check_ref_rules()
self._check_weak_refs()
return not self.errors
diff --git a/compiler/fory_compiler/tests/test_collection_nesting.py b/compiler/fory_compiler/tests/test_collection_nesting.py
index 881451aeb..efb1ee622 100644
--- a/compiler/fory_compiler/tests/test_collection_nesting.py
+++ b/compiler/fory_compiler/tests/test_collection_nesting.py
@@ -79,3 +79,14 @@ def test_map_with_list_key_rejected():
}
"""
assert_nested_collections_rejected(source)
+
+
+def test_nested_collections_allowed_for_capable_targets():
+ source = """
+ message Foo {
+ map<string, list<int32>> values = 1;
+ }
+ """
+ schema = parse_schema(source)
+ validator = SchemaValidator(schema, allow_nested_collections=True)
+ assert validator.validate(), validator.errors
diff --git a/compiler/fory_compiler/tests/test_generated_code.py b/compiler/fory_compiler/tests/test_generated_code.py
index f35d24742..7b1510e88 100644
--- a/compiler/fory_compiler/tests/test_generated_code.py
+++ b/compiler/fory_compiler/tests/test_generated_code.py
@@ -596,6 +596,26 @@ def test_java_nested_integer_annotations_in_generic_containers():
)
+def test_cpp_nested_integer_specs_in_generic_containers():
+ schema = parse_fdl(
+ dedent(
+ """
+ package gen;
+
+ message NestedIntegerSpecs {
+ map<fixed_uint32, list<optional tagged_uint64>> values = 1;
+ }
+ """
+ )
+ )
+ cpp_output = render_files(generate_files(schema, CppGenerator))
+ assert (
+ "FORY_STRUCT(NestedIntegerSpecs, "
+ "(values_, fory::F(1).map(fory::T::uint32().fixed(), "
+ "fory::T::list(fory::T::inner(fory::T::uint64().tagged())))));" in cpp_output
+ )
+
+
def test_cpp_generator_supports_decimal_fields_and_unions():
schema = parse_fdl(
dedent(
@@ -618,7 +638,7 @@ def test_cpp_generator_supports_decimal_fields_and_unions():
assert '#include "fory/serialization/decimal_serializers.h"' in cpp_output
assert "const fory::serialization::Decimal& amount() const" in cpp_output
assert "std::variant<fory::serialization::Decimal, Money> value_" in cpp_output
- assert "(fory::serialization::Decimal, amount, fory::F(1))" in cpp_output
+ assert "(amount, fory::serialization::Decimal, fory::F(1))" in cpp_output
def test_java_enum_generation_uses_fory_enum_ids():
diff --git a/cpp/fory/meta/BUILD b/cpp/fory/meta/BUILD
index d5f02247f..0b2b64a61 100644
--- a/cpp/fory/meta/BUILD
+++ b/cpp/fory/meta/BUILD
@@ -64,5 +64,6 @@ cc_test(
deps = [
":fory_meta",
"@googletest//:gtest",
+ "@googletest//:gtest_main",
],
)
diff --git a/cpp/fory/meta/field.h b/cpp/fory/meta/field.h
index a7e23d9b2..faad1c2e7 100644
--- a/cpp/fory/meta/field.h
+++ b/cpp/fory/meta/field.h
@@ -20,16 +20,13 @@
#pragma once
#include "fory/meta/field_info.h"
-#include "fory/meta/preprocessor.h"
#include "fory/type/type.h"
-#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>
-#include <utility>
namespace fory {
@@ -37,372 +34,53 @@ namespace serialization {
template <typename T> class SharedWeak;
} // namespace serialization
-// ============================================================================
-// Field Option Tags
-// ============================================================================
-
-/// Tag to mark a shared_ptr/SharedWeak/unique_ptr field as nullable.
-/// Only valid for std::shared_ptr, SharedWeak, and std::unique_ptr types.
-/// For nullable primitives/strings, use std::optional<T> instead.
-struct nullable {};
-
-/// Tag to explicitly mark a pointer field as non-nullable.
-/// Useful for future pointer types (e.g., weak_ptr) that might be nullable by
-/// default. For shared_ptr/SharedWeak/unique_ptr, non-nullable is already the
-/// default.
-struct not_null {};
-
-/// Tag to enable reference tracking for shared_ptr/SharedWeak fields.
-/// Only valid for std::shared_ptr or SharedWeak types (requires shared
-/// ownership for ref tracking).
-struct ref {};
-
-/// Template tag to control dynamic type dispatch for smart pointer fields.
-/// - `dynamic<true>`: Force type info to be written (enable runtime subtype
-/// support)
-/// - `dynamic<false>`: skip type info (use declared type directly)
-///
-/// By default, Fory auto-detects polymorphism via `std::is_polymorphic<T>`.
-/// Use this tag to override the default behavior.
-///
-/// Example:
-/// fory::field<std::shared_ptr<Base>, 0, fory::dynamic<false>> ptr;
-template <bool V> struct dynamic : std::bool_constant<V> {};
-
namespace detail {
-// ============================================================================
-// Type Traits for Smart Pointers and Optional
-// ============================================================================
-
template <typename T>
using FieldInfo = decltype(::fory::meta::fory_field_info(std::declval<T>()));
-inline constexpr size_t k_invalid_field_index = static_cast<size_t>(-1);
-
-template <typename T> constexpr size_t field_index(std::string_view name) {
- constexpr auto names = FieldInfo<T>::Names;
- for (size_t i = 0; i < names.size(); ++i) {
- if (names[i] == name) {
- return i;
- }
- }
- return k_invalid_field_index;
-}
-
-template <typename T, size_t Index, typename Enable = void> struct FieldTypeAt;
-
-template <typename T, size_t Index>
-struct FieldTypeAt<T, Index, std::enable_if_t<Index != k_invalid_field_index>> {
- using PtrsType = typename FieldInfo<T>::PtrsType;
- using PtrT = std::tuple_element_t<Index, PtrsType>;
- using type = ::fory::meta::RemoveMemberPointerCVRefT<PtrT>;
-};
-
-template <typename T, size_t Index>
-struct FieldTypeAt<T, Index, std::enable_if_t<Index == k_invalid_field_index>> {
- static_assert(Index != k_invalid_field_index,
- "Unknown field name in FORY_FIELD_TAGS");
-};
-
template <typename T> struct is_shared_ptr : std::false_type {};
-
template <typename T>
struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
-
template <typename T>
inline constexpr bool is_shared_ptr_v = is_shared_ptr<T>::value;
template <typename T> struct is_shared_weak : std::false_type {};
-
template <typename T>
struct is_shared_weak<serialization::SharedWeak<T>> : std::true_type {};
-
template <typename T>
inline constexpr bool is_shared_weak_v = is_shared_weak<T>::value;
template <typename T> struct is_unique_ptr : std::false_type {};
-
template <typename T, typename D>
struct is_unique_ptr<std::unique_ptr<T, D>> : std::true_type {};
-
template <typename T>
inline constexpr bool is_unique_ptr_v = is_unique_ptr<T>::value;
template <typename T> struct is_optional : std::false_type {};
-
template <typename T> struct is_optional<std::optional<T>> : std::true_type {};
-
template <typename T>
inline constexpr bool is_optional_v = is_optional<T>::value;
-/// Helper to check if type is shared_ptr/SharedWeak or unique_ptr
template <typename T>
inline constexpr bool is_smart_ptr_v =
is_shared_ptr_v<T> || is_shared_weak_v<T> || is_unique_ptr_v<T>;
-// ============================================================================
-// Option Tag Detection
-// ============================================================================
-
-/// Check if a specific tag type is present in the Options pack
-template <typename Tag, typename... Options>
-inline constexpr bool has_option_v = (std::is_same_v<Tag, Options> || ...);
-
-/// Check if a type is a dynamic<V> tag
-template <typename T> struct is_dynamic_tag : std::false_type {};
-template <bool V> struct is_dynamic_tag<dynamic<V>> : std::true_type {};
template <typename T>
-inline constexpr bool is_dynamic_tag_v = is_dynamic_tag<T>::value;
-
-/// Check if any dynamic<V> tag is present in Options pack
-template <typename... Options>
-inline constexpr bool has_dynamic_option_v = (is_dynamic_tag_v<Options> || ...);
-
-/// Extract the dynamic value from Options pack (default = -1 for AUTO)
-/// Returns: 1 for dynamic<true>, 0 for dynamic<false>, -1 for AUTO (not
-/// specified)
-template <typename... Options> struct get_dynamic_value {
- static constexpr int value = -1; // AUTO
-};
-template <bool V, typename... Rest>
-struct get_dynamic_value<dynamic<V>, Rest...> {
- static constexpr int value = V ? 1 : 0;
-};
-template <typename First, typename... Rest>
-struct get_dynamic_value<First, Rest...> {
- static constexpr int value = get_dynamic_value<Rest...>::value;
-};
-template <typename... Options>
-inline constexpr int get_dynamic_value_v = get_dynamic_value<Options...>::value;
-
-// ============================================================================
-// Field Tag Entry for FORY_FIELD_TAGS Macro
-// ============================================================================
-
-/// Compile-time field tag metadata entry
-/// Dynamic: -1 = AUTO (use std::is_polymorphic), 0 = FALSE (not dynamic), 1 =
-/// TRUE (dynamic)
-template <int16_t Id, bool Nullable, bool Ref, int Dynamic = -1>
-struct FieldTagEntry {
- static constexpr int16_t id = Id;
- static constexpr bool is_nullable = Nullable;
- static constexpr bool track_ref = Ref;
- static constexpr int dynamic_value = Dynamic;
-};
-
-struct FieldTagEntryWithName {
- const char *name;
- int16_t id;
- bool is_nullable;
- bool track_ref;
- int dynamic_value;
-};
-
-template <typename Entry>
-constexpr FieldTagEntryWithName make_field_tag_entry(const char *name) {
- return FieldTagEntryWithName{name, Entry::id, Entry::is_nullable,
- Entry::track_ref, Entry::dynamic_value};
-}
-
-/// Default: no field tags defined for type T (legacy specialization path)
-template <typename T> struct ForyFieldTagsImpl {
- static constexpr bool has_tags = false;
-};
+using MemberFieldConfigDescriptor =
+ decltype(T::fory_field_config(std::declval<meta::Identity<T>>()));
template <typename T>
-using AdlFieldTagsDescriptor =
- decltype(fory_field_tags(std::declval<meta::Identity<T>>()));
+using AdlFieldConfigDescriptor =
+ decltype(fory_field_config(std::declval<meta::Identity<T>>()));
template <typename T, typename = void>
-struct HasAdlFieldTags : std::false_type {};
+struct HasMemberFieldConfig : std::false_type {};
template <typename T>
-struct HasAdlFieldTags<T, std::void_t<AdlFieldTagsDescriptor<T>>>
+struct HasMemberFieldConfig<T, std::void_t<MemberFieldConfigDescriptor<T>>>
: std::true_type {};
-template <typename T, typename Enable = void> struct FieldTagsInfo {
- static constexpr bool has_tags = false;
- static constexpr size_t field_count = 0;
- static inline constexpr auto entries = std::tuple<>{};
- using Entries = std::decay_t<decltype(entries)>;
- static constexpr bool use_index = true;
-};
-
-template <typename T>
-struct FieldTagsInfo<T, std::enable_if_t<HasAdlFieldTags<T>::value>> {
- using Descriptor = AdlFieldTagsDescriptor<T>;
- static constexpr bool has_tags = Descriptor::has_tags;
- static inline constexpr auto entries = Descriptor::entries;
- using Entries = std::decay_t<decltype(entries)>;
- static constexpr size_t field_count = std::tuple_size_v<Entries>;
- static constexpr bool use_index = false;
-};
-
-template <typename T>
-struct FieldTagsInfo<T, std::enable_if_t<!HasAdlFieldTags<T>::value &&
- ForyFieldTagsImpl<T>::has_tags>> {
- static constexpr bool has_tags = true;
- static constexpr size_t field_count = ForyFieldTagsImpl<T>::field_count;
- using Entries = typename ForyFieldTagsImpl<T>::Entries;
- static inline constexpr auto entries = Entries{};
- static constexpr bool use_index = true;
-};
-
-template <typename T>
-inline constexpr bool has_field_tags_v = FieldTagsInfo<T>::has_tags;
-
-} // namespace detail
-
-// ============================================================================
-// Field Encoding Types for Unsigned Integers
-// ============================================================================
-
-/// Encoding strategies for integer fields
-enum class Encoding {
- Default = 0, // Use type's default encoding
- Varint = 1, // Variable-length encoding (smaller values use fewer bytes)
- Fixed = 2, // Fixed-size encoding (always uses full type width)
- Tagged = 3 // Tagged encoding (uses tag byte + value)
-};
-
-// ============================================================================
-// FieldMeta - Compile-time Field Configuration with Builder Pattern
-// ============================================================================
-
-/// Compile-time field metadata with fluent builder API.
-/// Supports both:
-/// - Simple: F(0) - just field ID
-/// - Full: F(0).nullable().varint().compress(false).dynamic(false)
-struct FieldMeta {
- int16_t id_ = -1;
- bool nullable_ = false;
- bool ref_ = false;
- int dynamic_ = -1; // -1 = AUTO, 0 = FALSE, 1 = TRUE
- Encoding encoding_ = Encoding::Default;
- bool compress_ = true;
- int16_t type_id_override_ = -1; // -1 = unset
-
- // Builder methods - each returns a modified copy
- constexpr FieldMeta id(int16_t v) const {
- auto c = *this;
- c.id_ = v;
- return c;
- }
- constexpr FieldMeta nullable(bool v = true) const {
- auto c = *this;
- c.nullable_ = v;
- return c;
- }
- constexpr FieldMeta ref(bool v = true) const {
- auto c = *this;
- c.ref_ = v;
- return c;
- }
- /// Set dynamic type dispatch: true = write type info, false = skip type info
- constexpr FieldMeta dynamic(bool v) const {
- auto c = *this;
- c.dynamic_ = v ? 1 : 0;
- return c;
- }
- constexpr FieldMeta encoding(Encoding v) const {
- auto c = *this;
- c.encoding_ = v;
- return c;
- }
- constexpr FieldMeta compress(bool v) const {
- auto c = *this;
- c.compress_ = v;
- return c;
- }
- constexpr FieldMeta type_id(TypeId v) const {
- auto c = *this;
- c.type_id_override_ = static_cast<int16_t>(v);
- return c;
- }
- constexpr FieldMeta int8_array() const { return type_id(TypeId::INT8_ARRAY); }
- constexpr FieldMeta uint8_array() const {
- return type_id(TypeId::UINT8_ARRAY);
- }
-
- // Convenience shortcuts for common encodings
- constexpr FieldMeta varint() const { return encoding(Encoding::Varint); }
- constexpr FieldMeta fixed() const { return encoding(Encoding::Fixed); }
- constexpr FieldMeta tagged() const { return encoding(Encoding::Tagged); }
-};
-
-/// Short factory functions for FieldMeta - use F() as a builder or F(id) for
-/// tag
-constexpr FieldMeta F() { return FieldMeta{}; }
-constexpr FieldMeta F(int16_t id) { return FieldMeta{}.id(id); }
-
-namespace detail {
-
-// ============================================================================
-// Config Normalization - Handle both integer IDs and FieldMeta
-// ============================================================================
-
-/// Normalize configuration: convert integer to FieldMeta, pass FieldMeta
-/// through
-template <typename T> constexpr auto normalize_config(T &&v) {
- if constexpr (std::is_integral_v<std::decay_t<T>>) {
- // Old syntax: just an integer ID
- return FieldMeta{}.id(static_cast<int16_t>(v));
- } else if constexpr (std::is_same_v<std::decay_t<T>, FieldMeta>) {
- // New syntax: already a FieldMeta
- return v;
- } else {
- static_assert(
- std::is_integral_v<std::decay_t<T>> ||
- std::is_same_v<std::decay_t<T>, FieldMeta>,
- "Field config must be an integer ID or FieldMeta (use F(id)...)");
- return FieldMeta{};
- }
-}
-
-/// Apply tag to FieldMeta
-constexpr FieldMeta apply_tag(FieldMeta m, nullable) { return m.nullable(); }
-constexpr FieldMeta apply_tag(FieldMeta m, not_null) {
- return m.nullable(false);
-}
-constexpr FieldMeta apply_tag(FieldMeta m, ref) { return m.ref(); }
-template <bool V> constexpr FieldMeta apply_tag(FieldMeta m, dynamic<V>) {
- return m.dynamic(V);
-}
-
-/// Fold multiple tags onto a base config
-template <typename... Tags>
-constexpr FieldMeta apply_tags(FieldMeta base, Tags... tags) {
- ((base = apply_tag(base, tags)), ...);
- return base;
-}
-
-// ============================================================================
-// FieldEntry - Stores Field Configuration Metadata
-// ============================================================================
-
-/// Field entry that stores name and configuration metadata
-struct FieldEntry {
- const char *name; // Field name for debugging
- FieldMeta meta; // Field configuration
-
- constexpr FieldEntry(const char *n, FieldMeta m) : name(n), meta(m) {}
-};
-
-/// Create a FieldEntry
-constexpr auto make_field_entry(const char *name, FieldMeta meta) {
- return FieldEntry{name, meta};
-}
-
-/// Default: no field config defined for type T
-template <typename T> struct ForyFieldConfigImpl {
- static constexpr bool has_config = false;
-};
-
-template <typename T>
-using AdlFieldConfigDescriptor =
- decltype(fory_field_config(std::declval<meta::Identity<T>>()));
-
template <typename T, typename = void>
struct HasAdlFieldConfig : std::false_type {};
@@ -417,35 +95,36 @@ template <typename T, typename Enable = void> struct FieldConfigInfo {
};
template <typename T>
-struct FieldConfigInfo<T, std::enable_if_t<HasAdlFieldConfig<T>::value>> {
- using Descriptor = AdlFieldConfigDescriptor<T>;
+struct FieldConfigInfo<T, std::enable_if_t<HasMemberFieldConfig<T>::value>> {
+ using Descriptor = MemberFieldConfigDescriptor<T>;
static constexpr bool has_config = Descriptor::has_config;
static constexpr size_t field_count = Descriptor::field_count;
static inline constexpr auto entries = Descriptor::entries;
};
template <typename T>
-struct FieldConfigInfo<T,
- std::enable_if_t<!HasAdlFieldConfig<T>::value &&
- ForyFieldConfigImpl<T>::has_config>> {
- static constexpr bool has_config = true;
- static constexpr size_t field_count = ForyFieldConfigImpl<T>::field_count;
- static inline constexpr auto entries = ForyFieldConfigImpl<T>::entries;
+struct FieldConfigInfo<T, std::enable_if_t<!HasMemberFieldConfig<T>::value &&
+ HasAdlFieldConfig<T>::value>> {
+ using Descriptor = AdlFieldConfigDescriptor<T>;
+ static constexpr bool has_config = Descriptor::has_config;
+ static constexpr size_t field_count = Descriptor::field_count;
+ static inline constexpr auto entries = Descriptor::entries;
};
template <typename T>
inline constexpr bool has_field_config_v = FieldConfigInfo<T>::has_config;
-/// Helper to get field encoding from FieldConfigInfo
template <typename T, size_t Index, typename = void>
struct GetFieldConfigEntry {
static constexpr Encoding encoding = Encoding::Default;
static constexpr int16_t id = -1;
static constexpr bool nullable = false;
static constexpr bool ref = false;
- static constexpr int dynamic_value = -1; // AUTO
+ static constexpr int dynamic_value = -1;
static constexpr bool compress = true;
static constexpr int16_t type_id_override = -1;
+ static constexpr bool has_id = false;
+ static constexpr FieldNodeSpec spec = FieldNodeSpec{};
static constexpr bool has_entry = false;
};
@@ -478,1450 +157,45 @@ public:
static constexpr int dynamic_value = entry.meta.dynamic_;
static constexpr bool compress = entry.meta.compress_;
static constexpr int16_t type_id_override = entry.meta.type_id_override_;
+ static constexpr bool has_id = entry.meta.has_id_;
+ static constexpr FieldNodeSpec spec = entry.meta.spec_;
static constexpr bool has_entry = entry.name[0] != '\0';
};
} // namespace detail
-// ============================================================================
-// fory::field<T, Id, Options...> Template
-// ============================================================================
-
-/// Field wrapper template that provides compile-time field metadata.
-///
-/// Usage:
-/// struct Person {
-/// fory::field<std::string, 0> name; // non-nullable
-/// fory::field<int32_t, 1> age; // non-nullable
-/// fory::field<std::optional<std::string>, 2> nickname; // inherently
-/// nullable fory::field<std::shared_ptr<Person>, 3> parent; //
-/// non-nullable fory::field<std::shared_ptr<Person>, 4, fory::nullable>
-/// guardian; fory::field<std::shared_ptr<Node>, 5, fory::ref> node;
-/// fory::field<std::shared_ptr<Node>, 6, fory::nullable, fory::ref> link;
-/// };
-///
-/// Template Parameters:
-/// T - The underlying field type
-/// Id - The field tag ID (int16_t) for compact serialization
-/// Options - Optional tags: fory::nullable, fory::ref
-///
-/// Type Rules:
-/// - Primitives/strings: No options allowed (use std::optional for nullable)
-/// - std::optional<T>: Inherently nullable, no options needed
-/// - std::shared_ptr<T>: Can use nullable and/or ref
-/// - SharedWeak<T>: Can use nullable and/or ref
-/// - std::unique_ptr<T>: Can use nullable only (no ref - exclusive
-/// ownership)
-template <typename T, int16_t Id, typename... Options> class field {
- // Validate: nullable and not_null are mutually exclusive
- static_assert(!(detail::has_option_v<nullable, Options...> &&
- detail::has_option_v<not_null, Options...>),
- "fory::nullable and fory::not_null are mutually exclusive.");
-
- // Validate: nullable only for smart pointers
- static_assert(!detail::has_option_v<nullable, Options...> ||
- detail::is_smart_ptr_v<T>,
- "fory::nullable is only valid for shared_ptr/SharedWeak/"
- "unique_ptr. "
- "Use std::optional<T> for nullable primitives/strings.");
-
- // Validate: not_null only for smart pointers (for now)
- static_assert(!detail::has_option_v<not_null, Options...> ||
- detail::is_smart_ptr_v<T>,
- "fory::not_null is only valid for pointer types.");
-
- // Validate: ref only for shared_ptr/SharedWeak
- static_assert(!detail::has_option_v<ref, Options...> ||
- detail::is_shared_ptr_v<T> || detail::is_shared_weak_v<T>,
- "fory::ref is only valid for shared_ptr/SharedWeak "
- "(reference tracking requires shared ownership).");
-
- // Validate: dynamic<V> only for smart pointers
- static_assert(!detail::has_dynamic_option_v<Options...> ||
- detail::is_smart_ptr_v<T>,
- "fory::dynamic<V> is only valid for shared_ptr/SharedWeak/"
- "unique_ptr.");
-
- // Validate: no options for optional (inherently nullable)
- static_assert(!detail::is_optional_v<T> || sizeof...(Options) == 0,
- "std::optional<T> is inherently nullable. No options allowed.");
-
- // Validate: no options for non-smart-pointer types
- static_assert(detail::is_smart_ptr_v<T> || detail::is_optional_v<T> ||
- sizeof...(Options) == 0,
- "Options are only valid for shared_ptr/SharedWeak/unique_ptr "
- "fields. "
- "Use std::optional<T> for nullable primitives/strings.");
-
-public:
- using value_type = T;
- static constexpr int16_t tag_id = Id;
-
- /// Field is nullable if:
- /// - It's std::optional (inherently nullable), OR
- /// - It's a smart pointer with fory::nullable option
- static constexpr bool is_nullable =
- detail::is_optional_v<T> ||
- (detail::is_smart_ptr_v<T> && detail::has_option_v<nullable, Options...>);
-
- /// Reference tracking is enabled if:
- /// - It's std::shared_ptr or SharedWeak (default)
- /// - Or explicitly marked with fory::ref
- static constexpr bool track_ref = detail::is_shared_ptr_v<T> ||
- detail::is_shared_weak_v<T> ||
- detail::has_option_v<ref, Options...>;
-
- /// Dynamic type dispatch control:
- /// - -1 (AUTO): Use std::is_polymorphic<T> to decide
- /// - 0 (FALSE): skip type info, use declared type directly
- /// - 1 (TRUE): write type info, enable runtime subtype support
- static constexpr int dynamic_value = detail::get_dynamic_value_v<Options...>;
-
- T value{};
-
- // Default constructor
- field() = default;
-
- // Value constructors
- field(const T &v) : value(v) {}
- field(T &&v) : value(std::move(v)) {}
-
- // copy and move constructors
- field(const field &) = default;
- field(field &&) = default;
-
- // copy and move assignment
- field &operator=(const field &) = default;
- field &operator=(field &&) = default;
-
- // Value assignment
- field &operator=(const T &v) {
- value = v;
- return *this;
- }
- field &operator=(T &&v) {
- value = std::move(v);
- return *this;
- }
-
- // Implicit conversions to underlying type
- operator T &() { return value; }
- operator const T &() const { return value; }
-
- // Pointer-like access for smart pointers
- T *operator->() { return &value; }
- const T *operator->() const { return &value; }
-
- // Dereference operators
- T &operator*() { return value; }
- const T &operator*() const { return value; }
-
- // get underlying value
- T &get() { return value; }
- const T &get() const { return value; }
-};
-
-// ============================================================================
-// Type Traits for fory::field Detection
-// ============================================================================
-
-/// Check if a type is a fory::field wrapper
template <typename T> struct is_fory_field : std::false_type {};
-
-template <typename T, int16_t Id, typename... Options>
-struct is_fory_field<field<T, Id, Options...>> : std::true_type {};
-
template <typename T>
inline constexpr bool is_fory_field_v = is_fory_field<T>::value;
-/// unwrap fory::field to get the underlying type
template <typename T> struct unwrap_field {
using type = T;
};
-
-template <typename T, int16_t Id, typename... Options>
-struct unwrap_field<field<T, Id, Options...>> {
- using type = T;
-};
-
template <typename T> using unwrap_field_t = typename unwrap_field<T>::type;
-/// get tag ID from field type (returns -1 if not a fory::field)
template <typename T> struct field_tag_id {
static constexpr int16_t value = -1;
};
-
-template <typename T, int16_t Id, typename... Options>
-struct field_tag_id<field<T, Id, Options...>> {
- static constexpr int16_t value = Id;
-};
-
template <typename T>
inline constexpr int16_t field_tag_id_v = field_tag_id<T>::value;
-/// Determines whether a field is nullable and requires a RefFlag byte.
-///
-/// This mirrors Rust's `field_need_write_ref_into(type_id, nullable)` in
-/// rust/fory-core/src/serializer/util.rs and determines whether the writer
-/// emits a `RefFlag` byte before the field's value payload.
-///
-/// Per the xlang protocol:
-/// - Non-nullable types (nullable=false) skip the ref flag entirely
-/// - Nullable types (nullable=true) write a ref flag to indicate null vs
-/// non-null
-///
-/// For non-field types, std::optional is considered nullable.
-/// For fory::field types, uses the explicit nullable option if provided.
template <typename T> struct field_is_nullable {
static constexpr bool value = detail::is_optional_v<T>;
};
-
-template <typename T, int16_t Id, typename... Options>
-struct field_is_nullable<field<T, Id, Options...>> {
- static constexpr bool value = field<T, Id, Options...>::is_nullable;
-};
-
template <typename T>
inline constexpr bool field_is_nullable_v = field_is_nullable<T>::value;
-/// get track_ref from field type
template <typename T> struct field_track_ref {
static constexpr bool value =
detail::is_shared_ptr_v<T> || detail::is_shared_weak_v<T>;
};
-
-template <typename T, int16_t Id, typename... Options>
-struct field_track_ref<field<T, Id, Options...>> {
- static constexpr bool value = field<T, Id, Options...>::track_ref;
-};
-
template <typename T>
inline constexpr bool field_track_ref_v = field_track_ref<T>::value;
-/// get dynamic_value from field type (-1 = AUTO, 0 = FALSE, 1 = TRUE)
template <typename T> struct field_dynamic_value {
- static constexpr int value = -1; // AUTO
+ static constexpr int value = -1;
};
-
-template <typename T, int16_t Id, typename... Options>
-struct field_dynamic_value<field<T, Id, Options...>> {
- static constexpr int value = field<T, Id, Options...>::dynamic_value;
-};
-
template <typename T>
inline constexpr int field_dynamic_value_v = field_dynamic_value<T>::value;
-// ============================================================================
-// FORY_FIELD_TAGS Macro Support
-// ============================================================================
-
-namespace detail {
-
-// Helper to parse field tag entry from macro arguments
-// Supports: (field, id), (field, id, nullable), (field, id, ref),
-// (field, id, nullable, ref), (field, id, dynamic<false>), etc.
-template <typename FieldType, int16_t Id, typename... Options>
-struct ParseFieldTagEntry {
- static constexpr bool is_nullable =
- is_optional_v<FieldType> ||
- (is_smart_ptr_v<FieldType> && has_option_v<nullable, Options...>);
-
- static constexpr bool track_ref = is_shared_ptr_v<FieldType> ||
- is_shared_weak_v<FieldType> ||
- has_option_v<ref, Options...>;
-
- static constexpr int dynamic_value = get_dynamic_value_v<Options...>;
-
- // Compile-time validation
- static_assert(!has_option_v<nullable, Options...> ||
- is_smart_ptr_v<FieldType>,
- "fory::nullable is only valid for shared_ptr/SharedWeak/"
- "unique_ptr");
-
- static_assert(!has_option_v<ref, Options...> || is_shared_ptr_v<FieldType> ||
- is_shared_weak_v<FieldType>,
- "fory::ref is only valid for shared_ptr/SharedWeak");
-
- static_assert(!has_dynamic_option_v<Options...> || is_smart_ptr_v<FieldType>,
- "fory::dynamic<V> is only valid for shared_ptr/SharedWeak/"
- "unique_ptr");
-
- using type = FieldTagEntry<Id, is_nullable, track_ref, dynamic_value>;
-};
-
-/// get field tag entry by index from FieldTagsInfo
-template <typename T, size_t Index, typename = void> struct GetFieldTagEntry {
- static constexpr int16_t id = -1;
- static constexpr bool is_nullable = false;
- static constexpr bool track_ref = false;
- static constexpr int dynamic_value = -1; // AUTO
- static constexpr bool has_entry = false;
-};
-
-template <typename T, size_t Index>
-struct GetFieldTagEntry<
- T, Index,
- std::enable_if_t<FieldTagsInfo<T>::has_tags &&
- (Index < FieldTagsInfo<T>::field_count) &&
- FieldTagsInfo<T>::use_index>> {
- using Entry = std::tuple_element_t<Index, typename FieldTagsInfo<T>::Entries>;
- static constexpr int16_t id = Entry::id;
- static constexpr bool is_nullable = Entry::is_nullable;
- static constexpr bool track_ref = Entry::track_ref;
- static constexpr int dynamic_value = Entry::dynamic_value;
- static constexpr bool has_entry = true;
-};
-
-template <typename T, size_t Index>
-struct GetFieldTagEntry<T, Index,
- std::enable_if_t<FieldTagsInfo<T>::has_tags &&
- !FieldTagsInfo<T>::use_index>> {
-private:
- static constexpr std::string_view field_name = FieldInfo<T>::Names[Index];
-
- template <size_t I = 0> static constexpr FieldTagEntryWithName find_entry() {
- if constexpr (I >= std::tuple_size_v<typename FieldTagsInfo<T>::Entries>) {
- return FieldTagEntryWithName{"", -1, false, false, -1};
- } else {
- constexpr auto entry = std::get<I>(FieldTagsInfo<T>::entries);
- if (std::string_view{entry.name} == field_name) {
- return entry;
- }
- return find_entry<I + 1>();
- }
- }
-
- static constexpr FieldTagEntryWithName entry = find_entry<>();
-
-public:
- static constexpr int16_t id = entry.id;
- static constexpr bool is_nullable = entry.is_nullable;
- static constexpr bool track_ref = entry.track_ref;
- static constexpr int dynamic_value = entry.dynamic_value;
- static constexpr bool has_entry = entry.name[0] != '\0';
-};
-
-} // namespace detail
-
} // namespace fory
-
-// ============================================================================
-// FORY_FIELD_TAGS Macro Implementation
-// ============================================================================
-
-// Helper macros to extract parts from (field, id, ...) tuples
-#define FORY_FT_FIELD(tuple) FORY_FT_FIELD_IMPL tuple
-#define FORY_FT_FIELD_IMPL(field, ...) field
-
-// Stringify field name
-#define FORY_FT_STRINGIFY(x) FORY_FT_STRINGIFY_I(x)
-#define FORY_FT_STRINGIFY_I(x) #x
-
-#define FORY_FT_ID(tuple) FORY_FT_ID_IMPL tuple
-#define FORY_FT_ID_IMPL(field, id, ...) id
-
-// get options from tuple
-#define FORY_FT_GET_OPT1(tuple) FORY_FT_GET_OPT1_IMPL tuple
-#define FORY_FT_GET_OPT1_IMPL(f, i, o1, ...) o1
-#define FORY_FT_GET_OPT2(tuple) FORY_FT_GET_OPT2_IMPL tuple
-#define FORY_FT_GET_OPT2_IMPL(f, i, o1, o2, ...) o2
-#define FORY_FT_GET_OPT3(tuple) FORY_FT_GET_OPT3_IMPL tuple
-#define FORY_FT_GET_OPT3_IMPL(f, i, o1, o2, o3, ...) o3
-
-// Detect number of elements in tuple: 2, 3, 4, or 5
-#define FORY_FT_TUPLE_SIZE(tuple) FORY_FT_TUPLE_SIZE_IMPL tuple
-#define FORY_FT_TUPLE_SIZE_IMPL(...) \
- FORY_FT_TUPLE_SIZE_SELECT(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
-#define FORY_FT_TUPLE_SIZE_SELECT(_1, _2, _3, _4, _5, N, ...) N
-
-// Create FieldTagEntry based on tuple size using indirect call pattern
-// This pattern ensures the concatenated macro name is properly rescanned
-#define FORY_FT_MAKE_ENTRY(Type, tuple) \
- FORY_FT_MAKE_ENTRY_I(Type, tuple, FORY_FT_TUPLE_SIZE(tuple))
-#define FORY_FT_MAKE_ENTRY_I(Type, tuple, size) \
- FORY_FT_MAKE_ENTRY_II(Type, tuple, size)
-#define FORY_FT_MAKE_ENTRY_II(Type, tuple, size) \
- FORY_FT_MAKE_ENTRY_##size(Type, tuple)
-
-#define FORY_FT_FIELD_INDEX(Type, tuple) \
- ::fory::detail::field_index<Type>( \
- std::string_view{FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple))})
-
-#define FORY_FT_FIELD_TYPE(Type, tuple) \
- typename ::fory::detail::FieldTypeAt<Type, \
- FORY_FT_FIELD_INDEX(Type, tuple)>::type
-
-#define FORY_FT_MAKE_ENTRY_2(Type, tuple) \
- ::fory::detail::make_field_tag_entry< \
- typename ::fory::detail::ParseFieldTagEntry< \
- FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple)>::type>( \
- FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
-
-#define FORY_FT_MAKE_ENTRY_3(Type, tuple) \
- ::fory::detail::make_field_tag_entry< \
- typename ::fory::detail::ParseFieldTagEntry< \
- FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple), \
- ::fory::FORY_FT_GET_OPT1(tuple)>::type>( \
- FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
-
-#define FORY_FT_MAKE_ENTRY_4(Type, tuple) \
- ::fory::detail::make_field_tag_entry< \
- typename ::fory::detail::ParseFieldTagEntry< \
- FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple), \
- ::fory::FORY_FT_GET_OPT1(tuple), \
- ::fory::FORY_FT_GET_OPT2(tuple)>::type>( \
- FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
-
-#define FORY_FT_MAKE_ENTRY_5(Type, tuple) \
- ::fory::detail::make_field_tag_entry< \
- typename ::fory::detail::ParseFieldTagEntry< \
- FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple), \
- ::fory::FORY_FT_GET_OPT1(tuple), ::fory::FORY_FT_GET_OPT2(tuple), \
- ::fory::FORY_FT_GET_OPT3(tuple)>::type>( \
- FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
-
-// Main macro: FORY_FIELD_TAGS(Type, (field1, id1), (field2, id2, nullable),...)
-#define FORY_FT_DESCRIPTOR_NAME(line) \
- FORY_PP_CONCAT(ForyFieldTagsDescriptor_, line)
-#define FORY_FIELD_TAGS(Type, ...) \
- FORY_FIELD_TAGS_IMPL(__LINE__, Type, __VA_ARGS__)
-#define FORY_FIELD_TAGS_IMPL(line, Type, ...) \
- struct FORY_FT_DESCRIPTOR_NAME(line) { \
- static constexpr bool has_tags = true; \
- static inline constexpr auto entries = \
- std::make_tuple(FORY_FT_ENTRIES(Type, __VA_ARGS__)); \
- using Entries = std::decay_t<decltype(entries)>; \
- static constexpr size_t field_count = std::tuple_size_v<Entries>; \
- }; \
- constexpr auto fory_field_tags(::fory::meta::Identity<Type>) { \
- return FORY_FT_DESCRIPTOR_NAME(line){}; \
- } \
- static_assert(true)
-
-// Helper to generate entries tuple content using indirect expansion pattern
-// This ensures FORY_PP_NARG is fully expanded before concatenation
-#define FORY_FT_ENTRIES(Type, ...) \
- FORY_FT_ENTRIES_I(Type, FORY_PP_NARG(__VA_ARGS__), __VA_ARGS__)
-#define FORY_FT_ENTRIES_I(Type, N, ...) FORY_FT_ENTRIES_II(Type, N, __VA_ARGS__)
-#define FORY_FT_ENTRIES_II(Type, N, ...) FORY_FT_ENTRIES_##N(Type, __VA_ARGS__)
-
-// Generate entries for 1-64 fields
-#define FORY_FT_ENTRIES_1(T, _1) FORY_FT_MAKE_ENTRY(T, _1)
-#define FORY_FT_ENTRIES_2(T, _1, _2) \
- FORY_FT_MAKE_ENTRY(T, _1), FORY_FT_MAKE_ENTRY(T, _2)
-#define FORY_FT_ENTRIES_3(T, _1, _2, _3) \
- FORY_FT_ENTRIES_2(T, _1, _2), FORY_FT_MAKE_ENTRY(T, _3)
-#define FORY_FT_ENTRIES_4(T, _1, _2, _3, _4) \
- FORY_FT_ENTRIES_3(T, _1, _2, _3), FORY_FT_MAKE_ENTRY(T, _4)
-#define FORY_FT_ENTRIES_5(T, _1, _2, _3, _4, _5) \
- FORY_FT_ENTRIES_4(T, _1, _2, _3, _4), FORY_FT_MAKE_ENTRY(T, _5)
-#define FORY_FT_ENTRIES_6(T, _1, _2, _3, _4, _5, _6) \
- FORY_FT_ENTRIES_5(T, _1, _2, _3, _4, _5), FORY_FT_MAKE_ENTRY(T, _6)
-#define FORY_FT_ENTRIES_7(T, _1, _2, _3, _4, _5, _6, _7) \
- FORY_FT_ENTRIES_6(T, _1, _2, _3, _4, _5, _6), FORY_FT_MAKE_ENTRY(T, _7)
-#define FORY_FT_ENTRIES_8(T, _1, _2, _3, _4, _5, _6, _7, _8) \
- FORY_FT_ENTRIES_7(T, _1, _2, _3, _4, _5, _6, _7), FORY_FT_MAKE_ENTRY(T, _8)
-#define FORY_FT_ENTRIES_9(T, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
- FORY_FT_ENTRIES_8(T, _1, _2, _3, _4, _5, _6, _7, _8), \
- FORY_FT_MAKE_ENTRY(T, _9)
-#define FORY_FT_ENTRIES_10(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
- FORY_FT_ENTRIES_9(T, _1, _2, _3, _4, _5, _6, _7, _8, _9), \
- FORY_FT_MAKE_ENTRY(T, _10)
-#define FORY_FT_ENTRIES_11(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
- FORY_FT_ENTRIES_10(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10), \
- FORY_FT_MAKE_ENTRY(T, _11)
-#define FORY_FT_ENTRIES_12(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12) \
- FORY_FT_ENTRIES_11(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11), \
- FORY_FT_MAKE_ENTRY(T, _12)
-#define FORY_FT_ENTRIES_13(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13) \
- FORY_FT_ENTRIES_12(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12), \
- FORY_FT_MAKE_ENTRY(T, _13)
-#define FORY_FT_ENTRIES_14(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14) \
- FORY_FT_ENTRIES_13(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13), \
- FORY_FT_MAKE_ENTRY(T, _14)
-#define FORY_FT_ENTRIES_15(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15) \
- FORY_FT_ENTRIES_14(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14), \
- FORY_FT_MAKE_ENTRY(T, _15)
-#define FORY_FT_ENTRIES_16(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16) \
- FORY_FT_ENTRIES_15(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15), \
- FORY_FT_MAKE_ENTRY(T, _16)
-#define FORY_FT_ENTRIES_17(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17) \
- FORY_FT_ENTRIES_16(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16), \
- FORY_FT_MAKE_ENTRY(T, _17)
-#define FORY_FT_ENTRIES_18(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18) \
- FORY_FT_ENTRIES_17(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17), \
- FORY_FT_MAKE_ENTRY(T, _18)
-#define FORY_FT_ENTRIES_19(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19) \
- FORY_FT_ENTRIES_18(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18), \
- FORY_FT_MAKE_ENTRY(T, _19)
-#define FORY_FT_ENTRIES_20(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20) \
- FORY_FT_ENTRIES_19(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19), \
- FORY_FT_MAKE_ENTRY(T, _20)
-#define FORY_FT_ENTRIES_21(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \
- FORY_FT_ENTRIES_20(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20), \
- FORY_FT_MAKE_ENTRY(T, _21)
-#define FORY_FT_ENTRIES_22(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22) \
- FORY_FT_ENTRIES_21(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21), \
- FORY_FT_MAKE_ENTRY(T, _22)
-#define FORY_FT_ENTRIES_23(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23) \
- FORY_FT_ENTRIES_22(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22), \
- FORY_FT_MAKE_ENTRY(T, _23)
-#define FORY_FT_ENTRIES_24(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24) \
- FORY_FT_ENTRIES_23(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23), \
- FORY_FT_MAKE_ENTRY(T, _24)
-#define FORY_FT_ENTRIES_25(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25) \
- FORY_FT_ENTRIES_24(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24), \
- FORY_FT_MAKE_ENTRY(T, _25)
-#define FORY_FT_ENTRIES_26(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26) \
- FORY_FT_ENTRIES_25(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25), \
- FORY_FT_MAKE_ENTRY(T, _26)
-#define FORY_FT_ENTRIES_27(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27) \
- FORY_FT_ENTRIES_26(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26), \
- FORY_FT_MAKE_ENTRY(T, _27)
-#define FORY_FT_ENTRIES_28(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28) \
- FORY_FT_ENTRIES_27(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27), \
- FORY_FT_MAKE_ENTRY(T, _28)
-#define FORY_FT_ENTRIES_29(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29) \
- FORY_FT_ENTRIES_28(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28), \
- FORY_FT_MAKE_ENTRY(T, _29)
-#define FORY_FT_ENTRIES_30(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30) \
- FORY_FT_ENTRIES_29(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29), \
- FORY_FT_MAKE_ENTRY(T, _30)
-#define FORY_FT_ENTRIES_31(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31) \
- FORY_FT_ENTRIES_30(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30), \
- FORY_FT_MAKE_ENTRY(T, _31)
-#define FORY_FT_ENTRIES_32(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32) \
- FORY_FT_ENTRIES_31(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31), \
- FORY_FT_MAKE_ENTRY(T, _32)
-#define FORY_FT_ENTRIES_33(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33) \
- FORY_FT_ENTRIES_32(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32), \
- FORY_FT_MAKE_ENTRY(T, _33)
-#define FORY_FT_ENTRIES_34(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34) \
- FORY_FT_ENTRIES_33(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33), \
- FORY_FT_MAKE_ENTRY(T, _34)
-#define FORY_FT_ENTRIES_35(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35) \
- FORY_FT_ENTRIES_34(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34), \
- FORY_FT_MAKE_ENTRY(T, _35)
-#define FORY_FT_ENTRIES_36(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36) \
- FORY_FT_ENTRIES_35(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35), \
- FORY_FT_MAKE_ENTRY(T, _36)
-#define FORY_FT_ENTRIES_37(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37) \
- FORY_FT_ENTRIES_36(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36), \
- FORY_FT_MAKE_ENTRY(T, _37)
-#define FORY_FT_ENTRIES_38(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38) \
- FORY_FT_ENTRIES_37(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37), \
- FORY_FT_MAKE_ENTRY(T, _38)
-#define FORY_FT_ENTRIES_39(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39) \
- FORY_FT_ENTRIES_38(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38), \
- FORY_FT_MAKE_ENTRY(T, _39)
-#define FORY_FT_ENTRIES_40(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40) \
- FORY_FT_ENTRIES_39(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39), \
- FORY_FT_MAKE_ENTRY(T, _40)
-#define FORY_FT_ENTRIES_41(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41) \
- FORY_FT_ENTRIES_40(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40), \
- FORY_FT_MAKE_ENTRY(T, _41)
-#define FORY_FT_ENTRIES_42( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42) \
- FORY_FT_ENTRIES_41(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41), \
- FORY_FT_MAKE_ENTRY(T, _42)
-#define FORY_FT_ENTRIES_43( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43) \
- FORY_FT_ENTRIES_42(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42), \
- FORY_FT_MAKE_ENTRY(T, _43)
-#define FORY_FT_ENTRIES_44( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44) \
- FORY_FT_ENTRIES_43(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43), \
- FORY_FT_MAKE_ENTRY(T, _44)
-#define FORY_FT_ENTRIES_45( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45) \
- FORY_FT_ENTRIES_44(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44), \
- FORY_FT_MAKE_ENTRY(T, _45)
-#define FORY_FT_ENTRIES_46( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46) \
- FORY_FT_ENTRIES_45(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45), \
- FORY_FT_MAKE_ENTRY(T, _46)
-#define FORY_FT_ENTRIES_47(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47) \
- FORY_FT_ENTRIES_46(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46), \
- FORY_FT_MAKE_ENTRY(T, _47)
-#define FORY_FT_ENTRIES_48(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48) \
- FORY_FT_ENTRIES_47(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47), \
- FORY_FT_MAKE_ENTRY(T, _48)
-#define FORY_FT_ENTRIES_49(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49) \
- FORY_FT_ENTRIES_48(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48), \
- FORY_FT_MAKE_ENTRY(T, _49)
-#define FORY_FT_ENTRIES_50(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49, _50) \
- FORY_FT_ENTRIES_49(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49), \
- FORY_FT_MAKE_ENTRY(T, _50)
-#define FORY_FT_ENTRIES_51(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49, _50, _51) \
- FORY_FT_ENTRIES_50(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50), \
- FORY_FT_MAKE_ENTRY(T, _51)
-#define FORY_FT_ENTRIES_52( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52) \
- FORY_FT_ENTRIES_51(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51), \
- FORY_FT_MAKE_ENTRY(T, _52)
-#define FORY_FT_ENTRIES_53( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53) \
- FORY_FT_ENTRIES_52(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52), \
- FORY_FT_MAKE_ENTRY(T, _53)
-#define FORY_FT_ENTRIES_54( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54) \
- FORY_FT_ENTRIES_53(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53), \
- FORY_FT_MAKE_ENTRY(T, _54)
-#define FORY_FT_ENTRIES_55( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55) \
- FORY_FT_ENTRIES_54(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54), \
- FORY_FT_MAKE_ENTRY(T, _55)
-#define FORY_FT_ENTRIES_56( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56) \
- FORY_FT_ENTRIES_55(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55), \
- FORY_FT_MAKE_ENTRY(T, _56)
-#define FORY_FT_ENTRIES_57( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57) \
- FORY_FT_ENTRIES_56(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56), \
- FORY_FT_MAKE_ENTRY(T, _57)
-#define FORY_FT_ENTRIES_58( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58) \
- FORY_FT_ENTRIES_57( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \
- _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
- _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \
- _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57), \
- FORY_FT_MAKE_ENTRY(T, _58)
-#define FORY_FT_ENTRIES_59( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59) \
- FORY_FT_ENTRIES_58(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58), \
- FORY_FT_MAKE_ENTRY(T, _59)
-#define FORY_FT_ENTRIES_60( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60) \
- FORY_FT_ENTRIES_59(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59), \
- FORY_FT_MAKE_ENTRY(T, _60)
-#define FORY_FT_ENTRIES_61( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61) \
- FORY_FT_ENTRIES_60(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60), \
- FORY_FT_MAKE_ENTRY(T, _61)
-#define FORY_FT_ENTRIES_62( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62) \
- FORY_FT_ENTRIES_61(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61), \
- FORY_FT_MAKE_ENTRY(T, _62)
-#define FORY_FT_ENTRIES_63( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62, _63) \
- FORY_FT_ENTRIES_62(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61, _62), \
- FORY_FT_MAKE_ENTRY(T, _63)
-#define FORY_FT_ENTRIES_64( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62, _63, _64) \
- FORY_FT_ENTRIES_63(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61, _62, _63), \
- FORY_FT_MAKE_ENTRY(T, _64)
-
-// ============================================================================
-// FORY_FIELD_CONFIG Macro - New Syntax with Member Pointer Verification
-// ============================================================================
-//
-// Usage:
-// FORY_FIELD_CONFIG(MyStruct,
-// (field1, fory::F(0)), // Simple: just ID
-// (field2, fory::F(1).nullable()), // With nullable
-// (field3, fory::F(2).varint()), // With encoding
-// (field4, fory::F(3).nullable().ref()), // Multiple options
-// (field5, 4) // Backward compatible: integer
-// ID
-// );
-//
-// This macro:
-// 1. Verifies field names exist at compile time via member pointers
-// 2. Supports both integer IDs (old) and F(id).xxx() builder (new)
-// 3. Stores configuration in a constexpr tuple for efficient access
-
-// Helper to stringify field name
-#define FORY_FC_STRINGIFY(x) FORY_FC_STRINGIFY_I(x)
-#define FORY_FC_STRINGIFY_I(x) #x
-
-// Extract field name (first element of tuple)
-#define FORY_FC_NAME(tuple) FORY_FC_NAME_IMPL tuple
-#define FORY_FC_NAME_IMPL(name, ...) name
-
-// Extract config (second element of tuple)
-#define FORY_FC_CONFIG(tuple) FORY_FC_CONFIG_IMPL tuple
-#define FORY_FC_CONFIG_IMPL(name, config, ...) config
-
-// Create a FieldEntry with member pointer verification
-#define FORY_FC_MAKE_ENTRY(Type, tuple) \
- ::fory::detail::make_field_entry( \
- FORY_FC_STRINGIFY(FORY_FC_NAME(tuple)), \
- ::fory::detail::normalize_config(FORY_FC_CONFIG(tuple)))
-
-// Generate entries using indirect expansion
-#define FORY_FC_ENTRIES(Type, ...) \
- FORY_FC_ENTRIES_I(Type, FORY_PP_NARG(__VA_ARGS__), __VA_ARGS__)
-#define FORY_FC_ENTRIES_I(Type, N, ...) FORY_FC_ENTRIES_II(Type, N, __VA_ARGS__)
-#define FORY_FC_ENTRIES_II(Type, N, ...) FORY_FC_ENTRIES_##N(Type, __VA_ARGS__)
-
-// Generate entries for 1-64 fields
-#define FORY_FC_ENTRIES_1(T, _1) FORY_FC_MAKE_ENTRY(T, _1)
-#define FORY_FC_ENTRIES_2(T, _1, _2) \
- FORY_FC_MAKE_ENTRY(T, _1), FORY_FC_MAKE_ENTRY(T, _2)
-#define FORY_FC_ENTRIES_3(T, _1, _2, _3) \
- FORY_FC_ENTRIES_2(T, _1, _2), FORY_FC_MAKE_ENTRY(T, _3)
-#define FORY_FC_ENTRIES_4(T, _1, _2, _3, _4) \
- FORY_FC_ENTRIES_3(T, _1, _2, _3), FORY_FC_MAKE_ENTRY(T, _4)
-#define FORY_FC_ENTRIES_5(T, _1, _2, _3, _4, _5) \
- FORY_FC_ENTRIES_4(T, _1, _2, _3, _4), FORY_FC_MAKE_ENTRY(T, _5)
-#define FORY_FC_ENTRIES_6(T, _1, _2, _3, _4, _5, _6) \
- FORY_FC_ENTRIES_5(T, _1, _2, _3, _4, _5), FORY_FC_MAKE_ENTRY(T, _6)
-#define FORY_FC_ENTRIES_7(T, _1, _2, _3, _4, _5, _6, _7) \
- FORY_FC_ENTRIES_6(T, _1, _2, _3, _4, _5, _6), FORY_FC_MAKE_ENTRY(T, _7)
-#define FORY_FC_ENTRIES_8(T, _1, _2, _3, _4, _5, _6, _7, _8) \
- FORY_FC_ENTRIES_7(T, _1, _2, _3, _4, _5, _6, _7), FORY_FC_MAKE_ENTRY(T, _8)
-#define FORY_FC_ENTRIES_9(T, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
- FORY_FC_ENTRIES_8(T, _1, _2, _3, _4, _5, _6, _7, _8), \
- FORY_FC_MAKE_ENTRY(T, _9)
-#define FORY_FC_ENTRIES_10(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
- FORY_FC_ENTRIES_9(T, _1, _2, _3, _4, _5, _6, _7, _8, _9), \
- FORY_FC_MAKE_ENTRY(T, _10)
-#define FORY_FC_ENTRIES_11(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
- FORY_FC_ENTRIES_10(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10), \
- FORY_FC_MAKE_ENTRY(T, _11)
-#define FORY_FC_ENTRIES_12(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12) \
- FORY_FC_ENTRIES_11(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11), \
- FORY_FC_MAKE_ENTRY(T, _12)
-#define FORY_FC_ENTRIES_13(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13) \
- FORY_FC_ENTRIES_12(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12), \
- FORY_FC_MAKE_ENTRY(T, _13)
-#define FORY_FC_ENTRIES_14(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14) \
- FORY_FC_ENTRIES_13(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13), \
- FORY_FC_MAKE_ENTRY(T, _14)
-#define FORY_FC_ENTRIES_15(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15) \
- FORY_FC_ENTRIES_14(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14), \
- FORY_FC_MAKE_ENTRY(T, _15)
-#define FORY_FC_ENTRIES_16(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16) \
- FORY_FC_ENTRIES_15(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15), \
- FORY_FC_MAKE_ENTRY(T, _16)
-#define FORY_FC_ENTRIES_17(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17) \
- FORY_FC_ENTRIES_16(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16), \
- FORY_FC_MAKE_ENTRY(T, _17)
-#define FORY_FC_ENTRIES_18(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18) \
- FORY_FC_ENTRIES_17(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17), \
- FORY_FC_MAKE_ENTRY(T, _18)
-#define FORY_FC_ENTRIES_19(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19) \
- FORY_FC_ENTRIES_18(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18), \
- FORY_FC_MAKE_ENTRY(T, _19)
-#define FORY_FC_ENTRIES_20(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20) \
- FORY_FC_ENTRIES_19(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19), \
- FORY_FC_MAKE_ENTRY(T, _20)
-#define FORY_FC_ENTRIES_21(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21) \
- FORY_FC_ENTRIES_20(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20), \
- FORY_FC_MAKE_ENTRY(T, _21)
-#define FORY_FC_ENTRIES_22(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22) \
- FORY_FC_ENTRIES_21(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21), \
- FORY_FC_MAKE_ENTRY(T, _22)
-#define FORY_FC_ENTRIES_23(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23) \
- FORY_FC_ENTRIES_22(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22), \
- FORY_FC_MAKE_ENTRY(T, _23)
-#define FORY_FC_ENTRIES_24(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24) \
- FORY_FC_ENTRIES_23(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23), \
- FORY_FC_MAKE_ENTRY(T, _24)
-#define FORY_FC_ENTRIES_25(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25) \
- FORY_FC_ENTRIES_24(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24), \
- FORY_FC_MAKE_ENTRY(T, _25)
-#define FORY_FC_ENTRIES_26(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26) \
- FORY_FC_ENTRIES_25(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25), \
- FORY_FC_MAKE_ENTRY(T, _26)
-#define FORY_FC_ENTRIES_27(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27) \
- FORY_FC_ENTRIES_26(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26), \
- FORY_FC_MAKE_ENTRY(T, _27)
-#define FORY_FC_ENTRIES_28(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28) \
- FORY_FC_ENTRIES_27(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27), \
- FORY_FC_MAKE_ENTRY(T, _28)
-#define FORY_FC_ENTRIES_29(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29) \
- FORY_FC_ENTRIES_28(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28), \
- FORY_FC_MAKE_ENTRY(T, _29)
-#define FORY_FC_ENTRIES_30(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30) \
- FORY_FC_ENTRIES_29(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29), \
- FORY_FC_MAKE_ENTRY(T, _30)
-#define FORY_FC_ENTRIES_31(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31) \
- FORY_FC_ENTRIES_30(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30), \
- FORY_FC_MAKE_ENTRY(T, _31)
-#define FORY_FC_ENTRIES_32(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32) \
- FORY_FC_ENTRIES_31(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31), \
- FORY_FC_MAKE_ENTRY(T, _32)
-#define FORY_FC_ENTRIES_33(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33) \
- FORY_FC_ENTRIES_32(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32), \
- FORY_FC_MAKE_ENTRY(T, _33)
-#define FORY_FC_ENTRIES_34(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34) \
- FORY_FC_ENTRIES_33(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33), \
- FORY_FC_MAKE_ENTRY(T, _34)
-#define FORY_FC_ENTRIES_35(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35) \
- FORY_FC_ENTRIES_34(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34), \
- FORY_FC_MAKE_ENTRY(T, _35)
-#define FORY_FC_ENTRIES_36(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36) \
- FORY_FC_ENTRIES_35(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35), \
- FORY_FC_MAKE_ENTRY(T, _36)
-#define FORY_FC_ENTRIES_37(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37) \
- FORY_FC_ENTRIES_36(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36), \
- FORY_FC_MAKE_ENTRY(T, _37)
-#define FORY_FC_ENTRIES_38(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38) \
- FORY_FC_ENTRIES_37(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37), \
- FORY_FC_MAKE_ENTRY(T, _38)
-#define FORY_FC_ENTRIES_39(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39) \
- FORY_FC_ENTRIES_38(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38), \
- FORY_FC_MAKE_ENTRY(T, _39)
-#define FORY_FC_ENTRIES_40(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40) \
- FORY_FC_ENTRIES_39(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39), \
- FORY_FC_MAKE_ENTRY(T, _40)
-#define FORY_FC_ENTRIES_41(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41) \
- FORY_FC_ENTRIES_40(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40), \
- FORY_FC_MAKE_ENTRY(T, _41)
-#define FORY_FC_ENTRIES_42( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42) \
- FORY_FC_ENTRIES_41(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41), \
- FORY_FC_MAKE_ENTRY(T, _42)
-#define FORY_FC_ENTRIES_43( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43) \
- FORY_FC_ENTRIES_42(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42), \
- FORY_FC_MAKE_ENTRY(T, _43)
-#define FORY_FC_ENTRIES_44( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44) \
- FORY_FC_ENTRIES_43(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43), \
- FORY_FC_MAKE_ENTRY(T, _44)
-#define FORY_FC_ENTRIES_45( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45) \
- FORY_FC_ENTRIES_44(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44), \
- FORY_FC_MAKE_ENTRY(T, _45)
-#define FORY_FC_ENTRIES_46( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46) \
- FORY_FC_ENTRIES_45(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45), \
- FORY_FC_MAKE_ENTRY(T, _46)
-#define FORY_FC_ENTRIES_47(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47) \
- FORY_FC_ENTRIES_46(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46), \
- FORY_FC_MAKE_ENTRY(T, _47)
-#define FORY_FC_ENTRIES_48(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48) \
- FORY_FC_ENTRIES_47(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47), \
- FORY_FC_MAKE_ENTRY(T, _48)
-#define FORY_FC_ENTRIES_49(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49) \
- FORY_FC_ENTRIES_48(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48), \
- FORY_FC_MAKE_ENTRY(T, _49)
-#define FORY_FC_ENTRIES_50(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49, _50) \
- FORY_FC_ENTRIES_49(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49), \
- FORY_FC_MAKE_ENTRY(T, _50)
-#define FORY_FC_ENTRIES_51(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, \
- _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, \
- _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, \
- _42, _43, _44, _45, _46, _47, _48, _49, _50, _51) \
- FORY_FC_ENTRIES_50(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50), \
- FORY_FC_MAKE_ENTRY(T, _51)
-#define FORY_FC_ENTRIES_52( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52) \
- FORY_FC_ENTRIES_51(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51), \
- FORY_FC_MAKE_ENTRY(T, _52)
-#define FORY_FC_ENTRIES_53( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53) \
- FORY_FC_ENTRIES_52(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52), \
- FORY_FC_MAKE_ENTRY(T, _53)
-#define FORY_FC_ENTRIES_54( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54) \
- FORY_FC_ENTRIES_53(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53), \
- FORY_FC_MAKE_ENTRY(T, _54)
-#define FORY_FC_ENTRIES_55( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55) \
- FORY_FC_ENTRIES_54(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54), \
- FORY_FC_MAKE_ENTRY(T, _55)
-#define FORY_FC_ENTRIES_56( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56) \
- FORY_FC_ENTRIES_55(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55), \
- FORY_FC_MAKE_ENTRY(T, _56)
-#define FORY_FC_ENTRIES_57( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57) \
- FORY_FC_ENTRIES_56(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56), \
- FORY_FC_MAKE_ENTRY(T, _57)
-#define FORY_FC_ENTRIES_58( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58) \
- FORY_FC_ENTRIES_57( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \
- _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
- _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \
- _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57), \
- FORY_FC_MAKE_ENTRY(T, _58)
-#define FORY_FC_ENTRIES_59( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59) \
- FORY_FC_ENTRIES_58(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58), \
- FORY_FC_MAKE_ENTRY(T, _59)
-#define FORY_FC_ENTRIES_60( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60) \
- FORY_FC_ENTRIES_59(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59), \
- FORY_FC_MAKE_ENTRY(T, _60)
-#define FORY_FC_ENTRIES_61( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61) \
- FORY_FC_ENTRIES_60(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60), \
- FORY_FC_MAKE_ENTRY(T, _61)
-#define FORY_FC_ENTRIES_62( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62) \
- FORY_FC_ENTRIES_61(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61), \
- FORY_FC_MAKE_ENTRY(T, _62)
-#define FORY_FC_ENTRIES_63( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62, _63) \
- FORY_FC_ENTRIES_62(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61, _62), \
- FORY_FC_MAKE_ENTRY(T, _63)
-#define FORY_FC_ENTRIES_64( \
- T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \
- _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, \
- _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, \
- _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, \
- _62, _63, _64) \
- FORY_FC_ENTRIES_63(T, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \
- _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, \
- _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, \
- _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, \
- _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, \
- _57, _58, _59, _60, _61, _62, _63), \
- FORY_FC_MAKE_ENTRY(T, _64)
-
-// Main FORY_FIELD_CONFIG macro
-// Creates a constexpr tuple of FieldEntry objects with member pointer
-// verification. Descriptor name uses a unique line-based token.
-#define FORY_FC_DESCRIPTOR_NAME(line) \
- FORY_PP_CONCAT(ForyFieldConfigDescriptor_, line)
-#define FORY_FIELD_CONFIG(Type, ...) \
- FORY_FIELD_CONFIG_IMPL(__LINE__, Type, __VA_ARGS__)
-#define FORY_FIELD_CONFIG_IMPL(line, Type, ...) \
- struct FORY_FC_DESCRIPTOR_NAME(line) { \
- static constexpr bool has_config = true; \
- static inline constexpr auto entries = \
- std::make_tuple(FORY_FC_ENTRIES(Type, __VA_ARGS__)); \
- static constexpr size_t field_count = \
- std::tuple_size_v<std::decay_t<decltype(entries)>>; \
- }; \
- constexpr auto fory_field_config(::fory::meta::Identity<Type>) { \
- return FORY_FC_DESCRIPTOR_NAME(line){}; \
- } \
- static_assert(true)
diff --git a/cpp/fory/meta/field_info.h b/cpp/fory/meta/field_info.h
index f49cfddfe..b5476b559 100644
--- a/cpp/fory/meta/field_info.h
+++ b/cpp/fory/meta/field_info.h
@@ -22,6 +22,7 @@
#include "fory/meta/preprocessor.h"
#include "fory/meta/type_traits.h"
#include <array>
+#include <cstdint>
#include <string_view>
#include <tuple>
#include <type_traits>
@@ -29,6 +30,277 @@
namespace fory {
+/// Encoding strategies for integer field nodes.
+enum class Encoding { Default = 0, Varint = 1, Fixed = 2, Tagged = 3 };
+
+enum class FieldNodeKind {
+ Default = 0,
+ Scalar = 1,
+ List = 2,
+ Set = 3,
+ Map = 4,
+ Inner = 5
+};
+
+enum class FieldScalarKind {
+ Inferred = 0,
+ Bool,
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ UInt8,
+ UInt16,
+ UInt32,
+ UInt64,
+ Float16,
+ BFloat16,
+ Float32,
+ Float64,
+ String
+};
+
+struct FieldNodeSpec {
+ static constexpr uint8_t kMaxNodes = 16;
+
+ FieldNodeKind kind_[kMaxNodes]{};
+ Encoding encoding_[kMaxNodes]{};
+ FieldScalarKind scalar_[kMaxNodes]{};
+ int8_t child0_[kMaxNodes]{};
+ int8_t child1_[kMaxNodes]{};
+ uint8_t size_ = 1;
+
+ constexpr FieldNodeSpec() {
+ for (uint8_t i = 0; i < kMaxNodes; ++i) {
+ kind_[i] = FieldNodeKind::Default;
+ encoding_[i] = Encoding::Default;
+ scalar_[i] = FieldScalarKind::Inferred;
+ child0_[i] = -1;
+ child1_[i] = -1;
+ }
+ }
+
+ static constexpr FieldNodeSpec
+ scalar(FieldScalarKind scalar_kind = FieldScalarKind::Inferred) {
+ FieldNodeSpec spec;
+ spec.kind_[0] = FieldNodeKind::Scalar;
+ spec.scalar_[0] = scalar_kind;
+ return spec;
+ }
+
+ constexpr int8_t append_tree(const FieldNodeSpec &child,
+ int8_t child_index = 0) {
+ const int8_t dest = static_cast<int8_t>(size_++);
+ kind_[dest] = child.kind_[child_index];
+ encoding_[dest] = child.encoding_[child_index];
+ scalar_[dest] = child.scalar_[child_index];
+ if (child.child0_[child_index] >= 0) {
+ child0_[dest] = append_tree(child, child.child0_[child_index]);
+ }
+ if (child.child1_[child_index] >= 0) {
+ child1_[dest] = append_tree(child, child.child1_[child_index]);
+ }
+ return dest;
+ }
+
+ constexpr FieldNodeSpec with_encoding(Encoding encoding) const {
+ auto copy = *this;
+ copy.encoding_[0] = encoding;
+ if (copy.kind_[0] == FieldNodeKind::Default) {
+ copy.kind_[0] = FieldNodeKind::Scalar;
+ }
+ return copy;
+ }
+
+ constexpr FieldNodeSpec fixed() const {
+ return with_encoding(Encoding::Fixed);
+ }
+ constexpr FieldNodeSpec varint() const {
+ return with_encoding(Encoding::Varint);
+ }
+ constexpr FieldNodeSpec tagged() const {
+ return with_encoding(Encoding::Tagged);
+ }
+
+ constexpr FieldNodeSpec list(FieldNodeSpec elem) const {
+ auto copy = *this;
+ copy.size_ = 1;
+ copy.kind_[0] = FieldNodeKind::List;
+ copy.child0_[0] = copy.append_tree(elem);
+ copy.child1_[0] = -1;
+ copy.encoding_[0] = Encoding::Default;
+ return copy;
+ }
+
+ constexpr FieldNodeSpec set(FieldNodeSpec elem) const {
+ auto copy = *this;
+ copy.size_ = 1;
+ copy.kind_[0] = FieldNodeKind::Set;
+ copy.child0_[0] = copy.append_tree(elem);
+ copy.child1_[0] = -1;
+ copy.encoding_[0] = Encoding::Default;
+ return copy;
+ }
+
+ constexpr FieldNodeSpec map() const {
+ auto copy = *this;
+ copy.size_ = 1;
+ copy.kind_[0] = FieldNodeKind::Map;
+ copy.child0_[0] = -1;
+ copy.child1_[0] = -1;
+ copy.encoding_[0] = Encoding::Default;
+ return copy;
+ }
+
+ constexpr FieldNodeSpec map(FieldNodeSpec key, FieldNodeSpec value) const {
+ return map().key(key).value(value);
+ }
+
+ constexpr FieldNodeSpec key(FieldNodeSpec key_spec) const {
+ auto copy = *this;
+ copy.kind_[0] = FieldNodeKind::Map;
+ copy.child0_[0] = copy.append_tree(key_spec);
+ return copy;
+ }
+
+ constexpr FieldNodeSpec value(FieldNodeSpec value_spec) const {
+ auto copy = *this;
+ copy.kind_[0] = FieldNodeKind::Map;
+ copy.child1_[0] = copy.append_tree(value_spec);
+ return copy;
+ }
+
+ constexpr FieldNodeSpec inner(FieldNodeSpec child) const {
+ auto copy = *this;
+ copy.size_ = 1;
+ copy.kind_[0] = FieldNodeKind::Inner;
+ copy.child0_[0] = copy.append_tree(child);
+ copy.child1_[0] = -1;
+ copy.encoding_[0] = Encoding::Default;
+ return copy;
+ }
+};
+
+struct FieldMeta {
+ int16_t id_ = -1;
+ bool has_id_ = false;
+ bool nullable_ = false;
+ bool ref_ = false;
+ int dynamic_ = -1;
+ FieldNodeSpec spec_{};
+ Encoding encoding_ = Encoding::Default;
+ bool compress_ = true;
+ int16_t type_id_override_ = -1;
+
+ constexpr FieldMeta id(int16_t v) const {
+ auto copy = *this;
+ copy.id_ = v;
+ copy.has_id_ = true;
+ return copy;
+ }
+ constexpr FieldMeta nullable(bool v = true) const {
+ auto copy = *this;
+ copy.nullable_ = v;
+ return copy;
+ }
+ constexpr FieldMeta ref(bool v = true) const {
+ auto copy = *this;
+ copy.ref_ = v;
+ return copy;
+ }
+ constexpr FieldMeta dynamic(bool v) const {
+ auto copy = *this;
+ copy.dynamic_ = v ? 1 : 0;
+ return copy;
+ }
+ constexpr FieldMeta with_spec(FieldNodeSpec spec) const {
+ auto copy = *this;
+ copy.spec_ = spec;
+ copy.encoding_ = spec.encoding_[0];
+ return copy;
+ }
+ constexpr FieldMeta fixed() const { return with_spec(spec_.fixed()); }
+ constexpr FieldMeta varint() const { return with_spec(spec_.varint()); }
+ constexpr FieldMeta tagged() const { return with_spec(spec_.tagged()); }
+ constexpr FieldMeta list(FieldNodeSpec elem) const {
+ return with_spec(spec_.list(elem));
+ }
+ constexpr FieldMeta set(FieldNodeSpec elem) const {
+ return with_spec(spec_.set(elem));
+ }
+ constexpr FieldMeta map() const { return with_spec(spec_.map()); }
+ constexpr FieldMeta map(FieldNodeSpec key, FieldNodeSpec value) const {
+ return with_spec(spec_.map(key, value));
+ }
+ constexpr FieldMeta key(FieldNodeSpec key_spec) const {
+ return with_spec(spec_.key(key_spec));
+ }
+ constexpr FieldMeta value(FieldNodeSpec value_spec) const {
+ return with_spec(spec_.value(value_spec));
+ }
+ constexpr FieldMeta inner(FieldNodeSpec child) const {
+ return with_spec(spec_.inner(child));
+ }
+};
+
+constexpr FieldMeta F() { return FieldMeta{}; }
+constexpr FieldMeta F(int16_t id) { return FieldMeta{}.id(id); }
+
+namespace T {
+
+constexpr FieldNodeSpec
+scalar(FieldScalarKind kind = FieldScalarKind::Inferred) {
+ return FieldNodeSpec::scalar(kind);
+}
+constexpr FieldNodeSpec fixed() { return scalar().fixed(); }
+constexpr FieldNodeSpec varint() { return scalar().varint(); }
+constexpr FieldNodeSpec tagged() { return scalar().tagged(); }
+constexpr FieldNodeSpec list(FieldNodeSpec elem) {
+ return FieldNodeSpec{}.list(elem);
+}
+constexpr FieldNodeSpec set(FieldNodeSpec elem) {
+ return FieldNodeSpec{}.set(elem);
+}
+constexpr FieldNodeSpec map() { return FieldNodeSpec{}.map(); }
+constexpr FieldNodeSpec map(FieldNodeSpec key, FieldNodeSpec value) {
+ return FieldNodeSpec{}.map(key, value);
+}
+constexpr FieldNodeSpec inner(FieldNodeSpec child) {
+ return FieldNodeSpec{}.inner(child);
+}
+
+constexpr FieldNodeSpec boolean() { return scalar(FieldScalarKind::Bool); }
+constexpr FieldNodeSpec int8() { return scalar(FieldScalarKind::Int8); }
+constexpr FieldNodeSpec int16() { return scalar(FieldScalarKind::Int16); }
+constexpr FieldNodeSpec int32() { return scalar(FieldScalarKind::Int32); }
+constexpr FieldNodeSpec int64() { return scalar(FieldScalarKind::Int64); }
+constexpr FieldNodeSpec uint8() { return scalar(FieldScalarKind::UInt8); }
+constexpr FieldNodeSpec uint16() { return scalar(FieldScalarKind::UInt16); }
+constexpr FieldNodeSpec uint32() { return scalar(FieldScalarKind::UInt32); }
+constexpr FieldNodeSpec uint64() { return scalar(FieldScalarKind::UInt64); }
+constexpr FieldNodeSpec float16() { return scalar(FieldScalarKind::Float16); }
+constexpr FieldNodeSpec bfloat16() { return scalar(FieldScalarKind::BFloat16); }
+constexpr FieldNodeSpec float32() { return scalar(FieldScalarKind::Float32); }
+constexpr FieldNodeSpec float64() { return scalar(FieldScalarKind::Float64); }
+constexpr FieldNodeSpec string() { return scalar(FieldScalarKind::String); }
+
+} // namespace T
+
+namespace detail {
+
+struct FieldEntry {
+ const char *name;
+ FieldMeta meta;
+
+ constexpr FieldEntry(const char *n, FieldMeta m) : name(n), meta(m) {}
+};
+
+constexpr auto make_field_entry(const char *name, FieldMeta meta) {
+ return FieldEntry{name, meta};
+}
+
+} // namespace detail
+
namespace meta {
template <typename T> struct Identity {
@@ -214,15 +486,40 @@ constexpr auto concat_tuples_from_tuple(const Tuple &tuple) {
#define FORY_BASE_TYPE(arg) FORY_PP_TUPLE_SECOND(arg)
+#define FORY_PP_IS_CONFIG(arg) \
+ FORY_PP_IS_CONFIG_IMPL(FORY_PP_IS_PAREN(arg), arg)
+#define FORY_PP_IS_CONFIG_IMPL(is_paren, arg) \
+ FORY_PP_CONCAT(FORY_PP_IS_CONFIG_IMPL_, is_paren)(arg)
+#define FORY_PP_IS_CONFIG_IMPL_0(arg) 0
+#define FORY_PP_IS_CONFIG_IMPL_1(arg) FORY_PP_NOT(FORY_PP_IS_BASE(arg))
+
+#define FORY_FIELD_ARG_NAME(arg) \
+ FORY_PP_IF(FORY_PP_IS_CONFIG(arg)) \
+ (FORY_PP_TUPLE_FIRST(arg), arg)
+
+#define FORY_FIELD_ARG_META(arg) \
+ FORY_PP_IF(FORY_PP_IS_CONFIG(arg)) \
+ (FORY_PP_TUPLE_SECOND(arg), ::fory::F())
+
#define FORY_FIELD_INFO_NAMES_FIELD(field) #field,
+#define FORY_FIELD_INFO_NAMES_ARG(arg) FORY_FIELD_INFO_NAMES_FIELD(arg)
#define FORY_FIELD_INFO_NAMES_FUNC(arg) \
FORY_PP_IF(FORY_PP_IS_BASE(arg)) \
- (FORY_PP_EMPTY(), FORY_FIELD_INFO_NAMES_FIELD(arg))
+ (FORY_PP_EMPTY(), FORY_FIELD_INFO_NAMES_ARG(FORY_FIELD_ARG_NAME(arg)))
#define FORY_FIELD_INFO_PTRS_FIELD(type, field) &type::field,
+#define FORY_FIELD_INFO_PTRS_ARG(type, field) \
+ FORY_FIELD_INFO_PTRS_FIELD(type, field)
#define FORY_FIELD_INFO_PTRS_FUNC(type, arg) \
FORY_PP_IF(FORY_PP_IS_BASE(arg)) \
- (FORY_PP_EMPTY(), FORY_FIELD_INFO_PTRS_FIELD(type, arg))
+ (FORY_PP_EMPTY(), FORY_FIELD_INFO_PTRS_ARG(type, FORY_FIELD_ARG_NAME(arg)))
+
+#define FORY_FIELD_INFO_CONFIG_ENTRY(arg) \
+ ::fory::detail::make_field_entry( \
+ FORY_PP_STRINGIFY(FORY_FIELD_ARG_NAME(arg)), FORY_FIELD_ARG_META(arg)),
+#define FORY_FIELD_INFO_CONFIG_FUNC(arg) \
+ FORY_PP_IF(FORY_PP_IS_BASE(arg)) \
+ (FORY_PP_EMPTY(), FORY_FIELD_INFO_CONFIG_ENTRY(arg))
#define FORY_BASE_NAMES_ARG(arg) \
FORY_PP_IF(FORY_PP_IS_BASE(arg)) \
@@ -268,6 +565,13 @@ constexpr auto concat_tuples_from_tuple(const Tuple &tuple) {
static inline constexpr std::array<std::string_view, FieldSize> \
FieldNames = { \
FORY_PP_FOREACH(FORY_FIELD_INFO_NAMES_FUNC, __VA_ARGS__)}; \
+ static inline constexpr auto FieldConfigEntries = \
+ std::tuple{FORY_PP_FOREACH(FORY_FIELD_INFO_CONFIG_FUNC, __VA_ARGS__)}; \
+ static constexpr bool has_config = true; \
+ static inline constexpr auto entries = FieldConfigEntries; \
+ using FieldConfigEntriesType = std::decay_t<decltype(entries)>; \
+ [[maybe_unused]] static constexpr size_t field_count = \
+ std::tuple_size_v<FieldConfigEntriesType>; \
static inline constexpr auto Names = \
fory::meta::concat_arrays(BaseNames, FieldNames); \
using BasePtrsType = decltype(fory::meta::concat_tuples_from_tuple( \
@@ -303,6 +607,10 @@ constexpr auto concat_tuples_from_tuple(const Tuple &tuple) {
const ::fory::meta::Identity<type> &) noexcept { \
return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \
} \
+ [[maybe_unused]] inline static constexpr auto fory_field_config( \
+ const ::fory::meta::Identity<type> &) noexcept { \
+ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \
+ } \
[[maybe_unused]] inline static constexpr std::true_type fory_struct_marker( \
const ::fory::meta::Identity<type> &) noexcept { \
return {}; \
@@ -314,6 +622,9 @@ constexpr auto concat_tuples_from_tuple(const Tuple &tuple) {
static inline constexpr size_t Size = 0; \
static inline constexpr std::string_view Name = #type; \
static inline constexpr std::array<std::string_view, Size> Names = {}; \
+ static constexpr bool has_config = false; \
+ static inline constexpr auto entries = std::tuple{}; \
+ [[maybe_unused]] static constexpr size_t field_count = 0; \
using PtrsType = decltype(std::tuple{}); \
static constexpr PtrsType ptrs() { return {}; } \
static const PtrsType &ptrs_ref() { \
@@ -332,6 +643,10 @@ constexpr auto concat_tuples_from_tuple(const Tuple &tuple) {
const ::fory::meta::Identity<type> &) noexcept { \
return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \
} \
+ [[maybe_unused]] inline static constexpr auto fory_field_config( \
+ const ::fory::meta::Identity<type> &) noexcept { \
+ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \
+ } \
[[maybe_unused]] inline static constexpr std::true_type fory_struct_marker( \
const ::fory::meta::Identity<type> &) noexcept { \
return {}; \
diff --git a/cpp/fory/meta/field_test.cc b/cpp/fory/meta/field_test.cc
index f8c92af56..24285a133 100644
--- a/cpp/fory/meta/field_test.cc
+++ b/cpp/fory/meta/field_test.cc
@@ -21,396 +21,121 @@
#include "fory/meta/field.h"
#include "fory/meta/field_info.h"
+#include <map>
#include <memory>
#include <optional>
#include <string>
+#include <vector>
-namespace fory {
+namespace fory::test {
-namespace test {
+namespace T = fory::T;
-// ============================================================================
-// Type Traits Tests
-// ============================================================================
-
-TEST(FieldTraits, IsSharedPtr) {
+TEST(FieldTraits, CarrierTraits) {
static_assert(!detail::is_shared_ptr_v<int>);
- static_assert(!detail::is_shared_ptr_v<std::string>);
- static_assert(!detail::is_shared_ptr_v<std::unique_ptr<int>>);
static_assert(detail::is_shared_ptr_v<std::shared_ptr<int>>);
- static_assert(detail::is_shared_ptr_v<std::shared_ptr<std::string>>);
-}
-
-TEST(FieldTraits, IsUniquePtr) {
static_assert(!detail::is_unique_ptr_v<int>);
- static_assert(!detail::is_unique_ptr_v<std::string>);
- static_assert(!detail::is_unique_ptr_v<std::shared_ptr<int>>);
static_assert(detail::is_unique_ptr_v<std::unique_ptr<int>>);
- static_assert(detail::is_unique_ptr_v<std::unique_ptr<std::string>>);
-}
-
-TEST(FieldTraits, IsOptional) {
static_assert(!detail::is_optional_v<int>);
- static_assert(!detail::is_optional_v<std::string>);
- static_assert(!detail::is_optional_v<std::shared_ptr<int>>);
static_assert(detail::is_optional_v<std::optional<int>>);
- static_assert(detail::is_optional_v<std::optional<std::string>>);
-}
-
-TEST(FieldTraits, IsSmartPtr) {
- static_assert(!detail::is_smart_ptr_v<int>);
- static_assert(!detail::is_smart_ptr_v<std::string>);
static_assert(!detail::is_smart_ptr_v<std::optional<int>>);
static_assert(detail::is_smart_ptr_v<std::shared_ptr<int>>);
static_assert(detail::is_smart_ptr_v<std::unique_ptr<int>>);
}
-// ============================================================================
-// fory::field<> Basic Tests
-// ============================================================================
-
-TEST(Field, BasicPrimitive) {
- using FieldType = field<int32_t, 0>;
- static_assert(FieldType::tag_id == 0);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == false);
+TEST(FieldBuilder, ScalarEncodingSpec) {
+ constexpr auto fixed = fory::F(7).fixed();
+ static_assert(fixed.has_id_);
+ static_assert(fixed.id_ == 7);
+ static_assert(fixed.encoding_ == Encoding::Fixed);
+ static_assert(fixed.spec_.encoding_[0] == Encoding::Fixed);
- FieldType f;
- f = 42;
- EXPECT_EQ(f.value, 42);
-
- // Implicit conversion
- int32_t val = f;
- EXPECT_EQ(val, 42);
+ constexpr auto tagged = T::uint64().tagged();
+ static_assert(tagged.scalar_[0] == FieldScalarKind::UInt64);
+ static_assert(tagged.encoding_[0] == Encoding::Tagged);
}
-TEST(Field, BasicString) {
- using FieldType = field<std::string, 1>;
- static_assert(FieldType::tag_id == 1);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == false);
-
- FieldType f;
- f = "hello";
- EXPECT_EQ(f.value, "hello");
+TEST(FieldBuilder, NestedSpecComposition) {
+ constexpr auto spec = fory::F().map(T::varint(), T::list(T::tagged()));
+ static_assert(!spec.has_id_);
+ static_assert(spec.spec_.kind_[0] == FieldNodeKind::Map);
+ static_assert(spec.spec_.kind_[1] == FieldNodeKind::Scalar);
+ static_assert(spec.spec_.encoding_[1] == Encoding::Varint);
+ static_assert(spec.spec_.kind_[2] == FieldNodeKind::List);
+ static_assert(spec.spec_.encoding_[3] == Encoding::Tagged);
}
-TEST(Field, OptionalField) {
- // std::optional is inherently nullable
- using FieldType = field<std::optional<int32_t>, 2>;
- static_assert(FieldType::tag_id == 2);
- static_assert(FieldType::is_nullable == true);
- static_assert(FieldType::track_ref == false);
+TEST(FieldBuilder, ChainMapPartialOverrides) {
+ constexpr auto key_only = fory::F().map().key(T::varint());
+ static_assert(key_only.spec_.kind_[0] == FieldNodeKind::Map);
+ static_assert(key_only.spec_.child0_[0] == 1);
+ static_assert(key_only.spec_.child1_[0] == -1);
+ static_assert(key_only.spec_.encoding_[1] == Encoding::Varint);
- FieldType f;
- f = std::optional<int32_t>(123);
- EXPECT_TRUE(f.value.has_value());
- EXPECT_EQ(*f.value, 123);
+ constexpr auto value_only = fory::F().map().value(T::list(T::tagged()));
+ static_assert(value_only.spec_.child0_[0] == -1);
+ static_assert(value_only.spec_.child1_[0] == 1);
+ static_assert(value_only.spec_.kind_[1] == FieldNodeKind::List);
+ static_assert(value_only.spec_.encoding_[2] == Encoding::Tagged);
}
-TEST(Field, SharedPtrNonNullable) {
- // shared_ptr is non-nullable by default
- using FieldType = field<std::shared_ptr<int32_t>, 3>;
- static_assert(FieldType::tag_id == 3);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == true);
-
- FieldType f;
- f = std::make_shared<int32_t>(99);
- EXPECT_NE(f.value, nullptr);
- EXPECT_EQ(*f.value, 99);
-}
-
-TEST(Field, SharedPtrNullable) {
- // shared_ptr with nullable option
- using FieldType = field<std::shared_ptr<int32_t>, 4, nullable>;
- static_assert(FieldType::tag_id == 4);
- static_assert(FieldType::is_nullable == true);
- static_assert(FieldType::track_ref == true);
-
- FieldType f;
- EXPECT_EQ(f.value, nullptr); // Default is null
-}
-
-TEST(Field, SharedPtrWithRef) {
- // shared_ptr with ref tracking
- using FieldType = field<std::shared_ptr<int32_t>, 5, ref>;
- static_assert(FieldType::tag_id == 5);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == true);
-}
-
-TEST(Field, SharedPtrNullableWithRef) {
- // shared_ptr with both nullable and ref
- using FieldType = field<std::shared_ptr<int32_t>, 6, nullable, ref>;
- static_assert(FieldType::tag_id == 6);
- static_assert(FieldType::is_nullable == true);
- static_assert(FieldType::track_ref == true);
-}
+struct NameModeStruct {
+ uint32_t id;
+ std::optional<std::vector<uint64_t>> values;
-TEST(Field, UniquePtrNonNullable) {
- // unique_ptr is non-nullable by default
- using FieldType = field<std::unique_ptr<int32_t>, 7>;
- static_assert(FieldType::tag_id == 7);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == false); // ref not valid for unique_ptr
-}
-
-TEST(Field, UniquePtrNullable) {
- // unique_ptr with nullable option
- using FieldType = field<std::unique_ptr<int32_t>, 8, nullable>;
- static_assert(FieldType::tag_id == 8);
- static_assert(FieldType::is_nullable == true);
- static_assert(FieldType::track_ref == false);
-}
-
-TEST(Field, SharedPtrNotNull) {
- // shared_ptr with not_null option (explicit non-nullable)
- using FieldType = field<std::shared_ptr<int32_t>, 9, not_null>;
- static_assert(FieldType::tag_id == 9);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == true);
-}
-
-TEST(Field, SharedPtrNotNullWithRef) {
- // shared_ptr with not_null and ref options
- using FieldType = field<std::shared_ptr<int32_t>, 10, not_null, ref>;
- static_assert(FieldType::tag_id == 10);
- static_assert(FieldType::is_nullable == false);
- static_assert(FieldType::track_ref == true);
-}
-
-// ============================================================================
-// fory::field<> Type Traits Tests
-// ============================================================================
-
-TEST(FieldTraits, IsForyField) {
- static_assert(!is_fory_field_v<int>);
- static_assert(!is_fory_field_v<std::string>);
- static_assert(!is_fory_field_v<std::shared_ptr<int>>);
- static_assert(is_fory_field_v<field<int, 0>>);
- static_assert(is_fory_field_v<field<std::string, 1>>);
- static_assert(is_fory_field_v<field<std::shared_ptr<int>, 2, nullable>>);
-}
-
-TEST(FieldTraits, UnwrapField) {
- static_assert(std::is_same_v<unwrap_field_t<int>, int>);
- static_assert(std::is_same_v<unwrap_field_t<std::string>, std::string>);
- static_assert(std::is_same_v<unwrap_field_t<field<int, 0>>, int>);
- static_assert(
- std::is_same_v<unwrap_field_t<field<std::string, 1>>, std::string>);
- static_assert(
- std::is_same_v<unwrap_field_t<field<std::shared_ptr<int>, 2, nullable>>,
- std::shared_ptr<int>>);
-}
-
-TEST(FieldTraits, FieldTagId) {
- static_assert(field_tag_id_v<int> == -1);
- static_assert(field_tag_id_v<field<int, 0>> == 0);
- static_assert(field_tag_id_v<field<std::string, 42>> == 42);
-}
-
-TEST(FieldTraits, FieldIsNullable) {
- static_assert(field_is_nullable_v<int> == false);
- static_assert(field_is_nullable_v<std::optional<int>> == true);
- static_assert(field_is_nullable_v<field<int, 0>> == false);
- static_assert(field_is_nullable_v<field<std::optional<int>, 1>> == true);
- static_assert(field_is_nullable_v<field<std::shared_ptr<int>, 2>> == false);
- static_assert(field_is_nullable_v<field<std::shared_ptr<int>, 3, nullable>> ==
- true);
-}
-
-TEST(FieldTraits, FieldTrackRef) {
- static_assert(field_track_ref_v<int> == false);
- static_assert(field_track_ref_v<std::shared_ptr<int>> == true);
- static_assert(field_track_ref_v<field<int, 0>> == false);
- static_assert(field_track_ref_v<field<std::shared_ptr<int>, 1>> == true);
- static_assert(field_track_ref_v<field<std::shared_ptr<int>, 2, ref>> == true);
- static_assert(
- field_track_ref_v<field<std::shared_ptr<int>, 3, nullable, ref>> == true);
-}
-
-// ============================================================================
-// Struct with fory::field<> members
-// ============================================================================
-
-struct Person {
- field<std::string, 0> name;
- field<int32_t, 1> age;
- field<std::optional<std::string>, 2> nickname;
- field<std::shared_ptr<Person>, 3, ref> parent;
- field<std::shared_ptr<Person>, 4, nullable> guardian;
- FORY_STRUCT(Person, name, age, nickname, parent, guardian);
+ FORY_STRUCT(NameModeStruct, id,
+ (values, fory::F().inner(T::list(T::tagged()))));
};
-TEST(FieldStruct, BasicUsage) {
- Person p;
- p.name = "Alice";
- p.age = 30;
- p.nickname = std::optional<std::string>("Ali");
- p.parent = nullptr;
- p.guardian = nullptr;
-
- EXPECT_EQ(p.name.value, "Alice");
- EXPECT_EQ(p.age.value, 30);
- EXPECT_TRUE(p.nickname.value.has_value());
- EXPECT_EQ(*p.nickname.value, "Ali");
- EXPECT_EQ(p.parent.value, nullptr);
- EXPECT_EQ(p.guardian.value, nullptr);
-}
-
-TEST(FieldStruct, FieldInfo) {
- Person p;
- constexpr auto info = meta::fory_field_info(p);
-
- static_assert(info.Size == 5);
- static_assert(info.Name == "Person");
- static_assert(info.Names[0] == "name");
- static_assert(info.Names[1] == "age");
- static_assert(info.Names[2] == "nickname");
- static_assert(info.Names[3] == "parent");
- static_assert(info.Names[4] == "guardian");
-}
-
-} // namespace test
-
-} // namespace fory
-
-// ============================================================================
-// FORY_FIELD_TAGS Macro Tests
-// ============================================================================
-
-namespace field_tags_test {
+struct IdModeStruct {
+ uint32_t id;
+ std::map<uint32_t, std::vector<int64_t>> values;
-// Test struct with pure C++ types (no fory::field wrappers)
-struct Document {
- std::string title;
- int32_t version;
- std::optional<std::string> description;
- std::shared_ptr<Document> author;
- std::shared_ptr<Document> reviewer;
- std::shared_ptr<Document> parent;
- std::unique_ptr<std::string> metadata;
- FORY_STRUCT(Document, title, version, description, author, reviewer, parent,
- metadata);
+ FORY_STRUCT(IdModeStruct, (id, fory::F(0).varint()),
+ (values, fory::F(1).map(T::varint(), T::list(T::tagged()))));
};
-// Test struct with nullable + ref combined
-struct Node {
- std::string name;
- std::shared_ptr<Node> left;
- std::shared_ptr<Node> right;
- FORY_STRUCT(Node, name, left, right);
+struct NamespaceConfigStruct {
+ uint32_t id;
+ uint64_t timestamp;
};
-// Test with single field
-struct SingleField {
- int32_t value;
- FORY_STRUCT(SingleField, value);
-};
-
-// Define field tags in the same namespace as the types.
-FORY_FIELD_TAGS(Document, (title, 0), // string: non-nullable
- (version, 1), // int: non-nullable
- (description, 2), // optional: inherently nullable
- (author, 3), // shared_ptr: non-nullable (default)
- (reviewer, 4, nullable), // shared_ptr: nullable
- (parent, 5, ref), // shared_ptr: non-nullable, ref
- (metadata, 6, nullable)); // unique_ptr: nullable
-
-FORY_FIELD_TAGS(Node, (name, 0), (left, 1, nullable, ref),
- (right, 2, nullable, ref));
-
-FORY_FIELD_TAGS(SingleField, (value, 0));
+FORY_STRUCT(NamespaceConfigStruct, (id, fory::F(3).varint()),
+ (timestamp, fory::F(4).tagged()));
-} // namespace field_tags_test
-
-namespace fory {
-namespace test {
-
-using field_tags_test::Document;
-using field_tags_test::Node;
-using field_tags_test::SingleField;
-
-TEST(FieldTags, HasTags) {
- static_assert(detail::has_field_tags_v<Document> == true);
- static_assert(detail::has_field_tags_v<Person> == false); // Uses fory::field
- static_assert(detail::has_field_tags_v<int> == false);
-}
-
-TEST(FieldTags, FieldCount) {
- static_assert(detail::FieldTagsInfo<Document>::field_count == 7);
-}
-
-TEST(FieldTags, TagIds) {
- // Check tag IDs
- static_assert(detail::GetFieldTagEntry<Document, 0>::id == 0);
- static_assert(detail::GetFieldTagEntry<Document, 1>::id == 1);
- static_assert(detail::GetFieldTagEntry<Document, 2>::id == 2);
- static_assert(detail::GetFieldTagEntry<Document, 3>::id == 3);
- static_assert(detail::GetFieldTagEntry<Document, 4>::id == 4);
- static_assert(detail::GetFieldTagEntry<Document, 5>::id == 5);
- static_assert(detail::GetFieldTagEntry<Document, 6>::id == 6);
-}
-
-TEST(FieldTags, Nullability) {
- // title (string): non-nullable
- static_assert(detail::GetFieldTagEntry<Document, 0>::is_nullable == false);
- // version (int): non-nullable
- static_assert(detail::GetFieldTagEntry<Document, 1>::is_nullable == false);
- // description (optional): inherently nullable
- static_assert(detail::GetFieldTagEntry<Document, 2>::is_nullable == true);
- // author (shared_ptr): non-nullable (default)
- static_assert(detail::GetFieldTagEntry<Document, 3>::is_nullable == false);
- // reviewer (shared_ptr, nullable): nullable
- static_assert(detail::GetFieldTagEntry<Document, 4>::is_nullable == true);
- // parent (shared_ptr, ref): non-nullable
- static_assert(detail::GetFieldTagEntry<Document, 5>::is_nullable == false);
- // metadata (unique_ptr, nullable): nullable
- static_assert(detail::GetFieldTagEntry<Document, 6>::is_nullable == true);
+TEST(FieldConfig, NameModeEntries) {
+ static_assert(detail::has_field_config_v<NameModeStruct>);
+ static_assert(!detail::GetFieldConfigEntry<NameModeStruct, 0>::has_id);
+ static_assert(!detail::GetFieldConfigEntry<NameModeStruct, 1>::has_id);
+ static_assert(detail::GetFieldConfigEntry<NameModeStruct, 1>::spec.kind_[0] ==
+ FieldNodeKind::Inner);
}
-TEST(FieldTags, RefTracking) {
- // shared_ptr fields track refs by default
- static_assert(detail::GetFieldTagEntry<Document, 0>::track_ref == false);
- static_assert(detail::GetFieldTagEntry<Document, 1>::track_ref == false);
- static_assert(detail::GetFieldTagEntry<Document, 2>::track_ref == false);
- static_assert(detail::GetFieldTagEntry<Document, 3>::track_ref == true);
- static_assert(detail::GetFieldTagEntry<Document, 4>::track_ref == true);
- static_assert(detail::GetFieldTagEntry<Document, 5>::track_ref == true);
- static_assert(detail::GetFieldTagEntry<Document, 6>::track_ref == false);
+TEST(FieldConfig, IdModeEntries) {
+ static_assert(detail::has_field_config_v<IdModeStruct>);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 0>::has_id);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 0>::id == 0);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 0>::encoding ==
+ Encoding::Varint);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 1>::has_id);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 1>::id == 1);
+ static_assert(detail::GetFieldConfigEntry<IdModeStruct, 1>::spec.kind_[0] ==
+ FieldNodeKind::Map);
}
-TEST(FieldTags, NullableWithRef) {
- // name: non-nullable, no ref
- static_assert(detail::GetFieldTagEntry<Node, 0>::id == 0);
- static_assert(detail::GetFieldTagEntry<Node, 0>::is_nullable == false);
- static_assert(detail::GetFieldTagEntry<Node, 0>::track_ref == false);
-
- // left: nullable + ref
- static_assert(detail::GetFieldTagEntry<Node, 1>::id == 1);
- static_assert(detail::GetFieldTagEntry<Node, 1>::is_nullable == true);
- static_assert(detail::GetFieldTagEntry<Node, 1>::track_ref == true);
-
- // right: nullable + ref
- static_assert(detail::GetFieldTagEntry<Node, 2>::id == 2);
- static_assert(detail::GetFieldTagEntry<Node, 2>::is_nullable == true);
- static_assert(detail::GetFieldTagEntry<Node, 2>::track_ref == true);
-}
-
-TEST(FieldTags, SingleField) {
- static_assert(detail::has_field_tags_v<SingleField> == true);
- static_assert(detail::FieldTagsInfo<SingleField>::field_count == 1);
- static_assert(detail::GetFieldTagEntry<SingleField, 0>::id == 0);
- static_assert(detail::GetFieldTagEntry<SingleField, 0>::is_nullable == false);
- static_assert(detail::GetFieldTagEntry<SingleField, 0>::track_ref == false);
+TEST(FieldConfig, NamespaceScopeEntries) {
+ static_assert(detail::has_field_config_v<NamespaceConfigStruct>);
+ static_assert(
+ detail::GetFieldConfigEntry<NamespaceConfigStruct, 0>::has_entry);
+ static_assert(detail::GetFieldConfigEntry<NamespaceConfigStruct, 0>::has_id);
+ static_assert(detail::GetFieldConfigEntry<NamespaceConfigStruct, 0>::id == 3);
+ static_assert(
+ detail::GetFieldConfigEntry<NamespaceConfigStruct, 0>::encoding ==
+ Encoding::Varint);
+ static_assert(detail::GetFieldConfigEntry<NamespaceConfigStruct, 1>::has_id);
+ static_assert(detail::GetFieldConfigEntry<NamespaceConfigStruct, 1>::id == 4);
+ static_assert(detail::GetFieldConfigEntry<NamespaceConfigStruct, 1>::spec
+ .encoding_[0] == Encoding::Tagged);
}
-} // namespace test
-
-} // namespace fory
-
-int main(int argc, char **argv) {
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
+} // namespace fory::test
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index 2308a59bf..87b3ede02 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -146,15 +146,6 @@ cc_test(
],
)
-cc_test(
- name = "field_serializer_test",
- srcs = ["field_serializer_test.cc"],
- deps = [
- ":fory_serialization",
- "@googletest//:gtest",
- ],
-)
-
cc_test(
name = "namespace_macro_test",
srcs = ["namespace_macro_test.cc"],
diff --git a/cpp/fory/serialization/collection_serializer.h b/cpp/fory/serialization/collection_serializer.h
index 0dadca10f..dfbd74478 100644
--- a/cpp/fory/serialization/collection_serializer.h
+++ b/cpp/fory/serialization/collection_serializer.h
@@ -631,7 +631,8 @@ struct Serializer<
if (total_bytes_u32 % sizeof(T) != 0) {
ctx.set_error(Error::invalid_data(
- "Vector byte size not aligned with element size"));
+ "Vector byte size " + std::to_string(total_bytes_u32) +
+ " not aligned with element size " + std::to_string(sizeof(T))));
return std::vector<T, Alloc>();
}
std::vector<T, Alloc> result(elem_count);
diff --git a/cpp/fory/serialization/field_serializer_test.cc b/cpp/fory/serialization/field_serializer_test.cc
deleted file mode 100644
index 72ca3ff68..000000000
--- a/cpp/fory/serialization/field_serializer_test.cc
+++ /dev/null
@@ -1,1211 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Serialization tests for fory::field<> template.
- *
- * Tests struct serialization with fory::field<> members including:
- * - Primitive fields with tag IDs
- * - String fields with tag IDs
- * - Optional fields (inherently nullable)
- * - Smart pointer fields with nullable/ref options
- * - Nested structs with field metadata
- * - Reference tracking with fory::ref
- */
-
-#include "fory/meta/field.h"
-#include "fory/serialization/fory.h"
-#include "gtest/gtest.h"
-#include <cstdint>
-#include <memory>
-#include <optional>
-#include <string>
-#include <vector>
-
-// ============================================================================
-// Struct definitions with fory::field<> members
-// ============================================================================
-
-// Basic struct with primitive fields
-struct FieldPerson {
- fory::field<std::string, 0> name;
- fory::field<int32_t, 1> age;
- fory::field<double, 2> score;
- fory::field<bool, 3> active;
-
- bool operator==(const FieldPerson &other) const {
- return name.value == other.name.value && age.value == other.age.value &&
- score.value == other.score.value &&
- active.value == other.active.value;
- }
- FORY_STRUCT(FieldPerson, name, age, score, active);
-};
-
-// Struct with optional fields
-struct FieldOptionalData {
- fory::field<std::string, 0> required_name;
- fory::field<std::optional<int32_t>, 1> optional_age;
- fory::field<std::optional<std::string>, 2> optional_email;
-
- bool operator==(const FieldOptionalData &other) const {
- return required_name.value == other.required_name.value &&
- optional_age.value == other.optional_age.value &&
- optional_email.value == other.optional_email.value;
- }
- FORY_STRUCT(FieldOptionalData, required_name, optional_age, optional_email);
-};
-
-// Struct with shared_ptr fields (non-nullable by default)
-struct FieldSharedPtrHolder {
- fory::field<std::shared_ptr<int32_t>, 0> value;
- fory::field<std::shared_ptr<std::string>, 1> text;
-
- bool operator==(const FieldSharedPtrHolder &other) const {
- if (static_cast<bool>(value.value) != static_cast<bool>(other.value.value))
- return false;
- if (static_cast<bool>(text.value) != static_cast<bool>(other.text.value))
- return false;
- if (value.value && *value.value != *other.value.value)
- return false;
- if (text.value && *text.value != *other.text.value)
- return false;
- return true;
- }
- FORY_STRUCT(FieldSharedPtrHolder, value, text);
-};
-
-// Struct with nullable shared_ptr fields
-struct FieldNullableSharedPtr {
- fory::field<std::shared_ptr<int32_t>, 0, fory::nullable> nullable_value;
- fory::field<std::shared_ptr<std::string>, 1, fory::nullable> nullable_text;
-
- bool operator==(const FieldNullableSharedPtr &other) const {
- if (static_cast<bool>(nullable_value.value) !=
- static_cast<bool>(other.nullable_value.value))
- return false;
- if (static_cast<bool>(nullable_text.value) !=
- static_cast<bool>(other.nullable_text.value))
- return false;
- if (nullable_value.value &&
- *nullable_value.value != *other.nullable_value.value)
- return false;
- if (nullable_text.value &&
- *nullable_text.value != *other.nullable_text.value)
- return false;
- return true;
- }
- FORY_STRUCT(FieldNullableSharedPtr, nullable_value, nullable_text);
-};
-
-// Struct with unique_ptr fields
-struct FieldUniquePtrHolder {
- fory::field<std::unique_ptr<int32_t>, 0> value;
- fory::field<std::unique_ptr<int32_t>, 1, fory::nullable> nullable_value;
- FORY_STRUCT(FieldUniquePtrHolder, value, nullable_value);
-};
-
-// Nested struct for reference tracking tests
-struct FieldNode {
- fory::field<int32_t, 0> id;
- fory::field<std::string, 1> name;
-
- bool operator==(const FieldNode &other) const {
- return id.value == other.id.value && name.value == other.name.value;
- }
- FORY_STRUCT(FieldNode, id, name);
-};
-
-// Struct with ref tracking for shared_ptr
-struct FieldRefTrackingHolder {
- fory::field<std::shared_ptr<FieldNode>, 0, fory::ref> first;
- fory::field<std::shared_ptr<FieldNode>, 1, fory::ref> second;
- FORY_STRUCT(FieldRefTrackingHolder, first, second);
-};
-
-// Struct with nullable + ref
-struct FieldNullableRefHolder {
- fory::field<std::shared_ptr<FieldNode>, 0, fory::nullable, fory::ref> node;
- FORY_STRUCT(FieldNullableRefHolder, node);
-};
-
-// Struct with not_null + ref
-struct FieldNotNullRefHolder {
- fory::field<std::shared_ptr<FieldNode>, 0, fory::not_null, fory::ref> node;
- FORY_STRUCT(FieldNotNullRefHolder, node);
-};
-
-// Struct with vector of field-wrapped structs
-struct FieldVectorHolder {
- fory::field<std::vector<FieldNode>, 0> nodes;
-
- bool operator==(const FieldVectorHolder &other) const {
- return nodes.value == other.nodes.value;
- }
- FORY_STRUCT(FieldVectorHolder, nodes);
-};
-
-// Mixed struct: some fields with fory::field, some without
-struct MixedFieldStruct {
- fory::field<std::string, 0> field_name;
- int32_t plain_age; // Not wrapped
- fory::field<double, 2> field_score;
-
- bool operator==(const MixedFieldStruct &other) const {
- return field_name.value == other.field_name.value &&
- plain_age == other.plain_age &&
- field_score.value == other.field_score.value;
- }
- FORY_STRUCT(MixedFieldStruct, field_name, plain_age, field_score);
-};
-
-// ============================================================================
-// Test Implementation
-// ============================================================================
-
-namespace fory {
-namespace serialization {
-namespace test {
-
-inline void register_field_test_types(Fory &fory) {
- uint32_t type_id = 500; // Start from 500 to avoid conflicts
-
- fory.register_struct<FieldPerson>(type_id++);
- fory.register_struct<FieldOptionalData>(type_id++);
- fory.register_struct<FieldSharedPtrHolder>(type_id++);
- fory.register_struct<FieldNullableSharedPtr>(type_id++);
- fory.register_struct<FieldUniquePtrHolder>(type_id++);
- fory.register_struct<FieldNode>(type_id++);
- fory.register_struct<FieldRefTrackingHolder>(type_id++);
- fory.register_struct<FieldNullableRefHolder>(type_id++);
- fory.register_struct<FieldNotNullRefHolder>(type_id++);
- fory.register_struct<FieldVectorHolder>(type_id++);
- fory.register_struct<MixedFieldStruct>(type_id++);
-}
-
-Fory create_fory(bool track_ref = true) {
- return Fory::builder().xlang(true).track_ref(track_ref).build();
-}
-
-// ============================================================================
-// Primitive Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, PrimitiveFieldsRoundTrip) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldPerson original;
- original.name = "Alice";
- original.age = 30;
- original.score = 95.5;
- original.active = true;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result =
- fory.deserialize<FieldPerson>(bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, PrimitiveFieldsEdgeCases) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldPerson original;
- original.name = "";
- original.age = -1;
- original.score = 0.0;
- original.active = false;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result =
- fory.deserialize<FieldPerson>(bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-// ============================================================================
-// Optional Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, OptionalFieldsAllSet) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldOptionalData original;
- original.required_name = "Bob";
- original.optional_age = 25;
- original.optional_email = "bob@example.com";
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldOptionalData>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, OptionalFieldsAllEmpty) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldOptionalData original;
- original.required_name = "Charlie";
- original.optional_age = std::nullopt;
- original.optional_email = std::nullopt;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldOptionalData>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, OptionalFieldsMixed) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldOptionalData original;
- original.required_name = "Diana";
- original.optional_age = 35;
- original.optional_email = std::nullopt;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldOptionalData>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-// ============================================================================
-// Shared Pointer Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, SharedPtrFieldsNonNullable) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldSharedPtrHolder original;
- original.value = std::make_shared<int32_t>(42);
- original.text = std::make_shared<std::string>("hello");
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldSharedPtrHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, NullableSharedPtrWithValues) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldNullableSharedPtr original;
- original.nullable_value = std::make_shared<int32_t>(99);
- original.nullable_text = std::make_shared<std::string>("world");
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNullableSharedPtr>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, NullableSharedPtrWithNulls) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldNullableSharedPtr original;
- original.nullable_value = nullptr;
- original.nullable_text = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNullableSharedPtr>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, NullableSharedPtrMixed) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldNullableSharedPtr original;
- original.nullable_value = std::make_shared<int32_t>(123);
- original.nullable_text = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNullableSharedPtr>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-// ============================================================================
-// Unique Pointer Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, UniquePtrFieldWithValue) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldUniquePtrHolder original;
- original.value = std::make_unique<int32_t>(2025);
- original.nullable_value = std::make_unique<int32_t>(1234);
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldUniquePtrHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- ASSERT_TRUE(deser_result.value().value.value);
- EXPECT_EQ(*deser_result.value().value.value, 2025);
- ASSERT_TRUE(deser_result.value().nullable_value.value);
- EXPECT_EQ(*deser_result.value().nullable_value.value, 1234);
-}
-
-TEST(FieldSerializerTest, UniquePtrNullableFieldNull) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldUniquePtrHolder original;
- original.value = std::make_unique<int32_t>(999);
- original.nullable_value = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldUniquePtrHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- ASSERT_TRUE(deser_result.value().value.value);
- EXPECT_EQ(*deser_result.value().value.value, 999);
- EXPECT_EQ(deser_result.value().nullable_value.value, nullptr);
-}
-
-// ============================================================================
-// Reference Tracking Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, RefTrackingSameObject) {
- auto fory = create_fory(true);
- register_field_test_types(fory);
-
- auto shared_node = std::make_shared<FieldNode>();
- shared_node->id = 42;
- shared_node->name = "shared";
-
- FieldRefTrackingHolder original;
- original.first = shared_node;
- original.second = shared_node; // Same object
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldRefTrackingHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.first.value);
- ASSERT_TRUE(result.second.value);
- EXPECT_EQ(result.first.value->id.value, 42);
- EXPECT_EQ(result.first.value->name.value, "shared");
- EXPECT_EQ(result.second.value->id.value, 42);
- EXPECT_EQ(result.second.value->name.value, "shared");
-
- // Reference tracking should preserve shared_ptr aliasing
- EXPECT_EQ(result.first.value, result.second.value)
- << "Reference tracking should preserve shared_ptr aliasing";
-}
-
-TEST(FieldSerializerTest, RefTrackingDifferentObjects) {
- auto fory = create_fory(true);
- register_field_test_types(fory);
-
- FieldRefTrackingHolder original;
- original.first = std::make_shared<FieldNode>();
- original.first.value->id = 1;
- original.first.value->name = "first";
- original.second = std::make_shared<FieldNode>();
- original.second.value->id = 2;
- original.second.value->name = "second";
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldRefTrackingHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.first.value);
- ASSERT_TRUE(result.second.value);
- EXPECT_EQ(result.first.value->id.value, 1);
- EXPECT_EQ(result.first.value->name.value, "first");
- EXPECT_EQ(result.second.value->id.value, 2);
- EXPECT_EQ(result.second.value->name.value, "second");
-
- // Different objects should not share
- EXPECT_NE(result.first.value, result.second.value);
-}
-
-TEST(FieldSerializerTest, NullableRefWithValue) {
- auto fory = create_fory(true);
- register_field_test_types(fory);
-
- FieldNullableRefHolder original;
- original.node = std::make_shared<FieldNode>();
- original.node.value->id = 100;
- original.node.value->name = "nullable_ref";
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNullableRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.node.value);
- EXPECT_EQ(result.node.value->id.value, 100);
- EXPECT_EQ(result.node.value->name.value, "nullable_ref");
-}
-
-TEST(FieldSerializerTest, NullableRefWithNull) {
- auto fory = create_fory(true);
- register_field_test_types(fory);
-
- FieldNullableRefHolder original;
- original.node = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNullableRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(deser_result.value().node.value, nullptr);
-}
-
-TEST(FieldSerializerTest, NotNullRefWithValue) {
- auto fory = create_fory(true);
- register_field_test_types(fory);
-
- FieldNotNullRefHolder original;
- original.node = std::make_shared<FieldNode>();
- original.node.value->id = 200;
- original.node.value->name = "not_null_ref";
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldNotNullRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.node.value);
- EXPECT_EQ(result.node.value->id.value, 200);
- EXPECT_EQ(result.node.value->name.value, "not_null_ref");
-}
-
-// ============================================================================
-// Container Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, VectorOfFieldStructs) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldVectorHolder original;
- for (int i = 0; i < 5; ++i) {
- FieldNode node;
- node.id = i;
- node.name = "node_" + std::to_string(i);
- original.nodes.value.push_back(node);
- }
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldVectorHolder>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldSerializerTest, EmptyVectorField) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- FieldVectorHolder original;
- // Empty vector
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<FieldVectorHolder>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-// ============================================================================
-// Mixed Field Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, MixedFieldStruct) {
- auto fory = create_fory();
- register_field_test_types(fory);
-
- MixedFieldStruct original;
- original.field_name = "mixed";
- original.plain_age = 42;
- original.field_score = 88.5;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<MixedFieldStruct>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-// ============================================================================
-// Field Metadata Compile-time Tests
-// ============================================================================
-
-TEST(FieldSerializerTest, FieldMetadataCompileTime) {
- // Verify compile-time field metadata extraction
- using PersonType = FieldPerson;
-
- // Check that field types are correctly detected
- static_assert(is_fory_field_v<decltype(PersonType::name)>);
- static_assert(is_fory_field_v<decltype(PersonType::age)>);
- static_assert(is_fory_field_v<decltype(PersonType::score)>);
- static_assert(is_fory_field_v<decltype(PersonType::active)>);
-
- // Check tag IDs
- static_assert(decltype(PersonType::name)::tag_id == 0);
- static_assert(decltype(PersonType::age)::tag_id == 1);
- static_assert(decltype(PersonType::score)::tag_id == 2);
- static_assert(decltype(PersonType::active)::tag_id == 3);
-
- // Check nullability
- static_assert(!decltype(PersonType::name)::is_nullable);
- static_assert(!decltype(PersonType::age)::is_nullable);
-
- // Optional fields are inherently nullable
- static_assert(decltype(FieldOptionalData::optional_age)::is_nullable);
- static_assert(decltype(FieldOptionalData::optional_email)::is_nullable);
-
- // Nullable shared_ptr
- static_assert(decltype(FieldNullableSharedPtr::nullable_value)::is_nullable);
- static_assert(!decltype(FieldSharedPtrHolder::value)::is_nullable);
-
- // Ref tracking
- static_assert(decltype(FieldRefTrackingHolder::first)::track_ref);
- static_assert(decltype(FieldSharedPtrHolder::value)::track_ref);
-
- // not_null doesn't change is_nullable for already non-nullable
- static_assert(!decltype(FieldNotNullRefHolder::node)::is_nullable);
- static_assert(decltype(FieldNotNullRefHolder::node)::track_ref);
-}
-
-} // namespace test
-} // namespace serialization
-} // namespace fory
-
-// ============================================================================
-// FORY_FIELD_TAGS Serialization Tests
-// FORY_FIELD_TAGS remains namespace-scope, FORY_STRUCT is declared in-class
-// ============================================================================
-
-// Simple helper struct for testing FORY_FIELD_TAGS
-struct TagsTestData {
- std::string content;
- int32_t value;
-
- bool operator==(const TagsTestData &other) const {
- return content == other.content && value == other.value;
- }
- FORY_STRUCT(TagsTestData, content, value);
-};
-
-FORY_FIELD_TAGS(TagsTestData, (content, 0), (value, 1));
-
-// Pure C++ struct with FORY_FIELD_TAGS metadata (non-invasive)
-struct TagsTestDocument {
- std::string title;
- int32_t version;
- std::optional<std::string> description;
- std::shared_ptr<TagsTestData> data;
- std::shared_ptr<TagsTestData> optional_data;
-
- bool operator==(const TagsTestDocument &other) const {
- bool data_eq = static_cast<bool>(data) == static_cast<bool>(other.data);
- if (data_eq && data && other.data) {
- data_eq = (*data == *other.data);
- }
- bool opt_data_eq = static_cast<bool>(optional_data) ==
- static_cast<bool>(other.optional_data);
- if (opt_data_eq && optional_data && other.optional_data) {
- opt_data_eq = (*optional_data == *other.optional_data);
- }
- return title == other.title && version == other.version &&
- description == other.description && data_eq && opt_data_eq;
- }
- FORY_STRUCT(TagsTestDocument, title, version, description, data,
- optional_data);
-};
-
-FORY_FIELD_TAGS(TagsTestDocument, (title, 0), // string: non-nullable
- (version, 1), // int: non-nullable
- (description, 2), // optional: inherently nullable
- (data, 3), // shared_ptr: non-nullable (default)
- (optional_data, 4, nullable)); // shared_ptr: nullable
-
-// Struct for testing FORY_FIELD_TAGS with ref tracking
-struct TagsRefNode {
- std::string name;
- int32_t id;
-
- bool operator==(const TagsRefNode &other) const {
- return name == other.name && id == other.id;
- }
- FORY_STRUCT(TagsRefNode, name, id);
-};
-
-FORY_FIELD_TAGS(TagsRefNode, (name, 0), (id, 1));
-
-// Struct with ref tracking via FORY_FIELD_TAGS
-struct TagsRefHolder {
- std::shared_ptr<TagsRefNode> first;
- std::shared_ptr<TagsRefNode> second;
- FORY_STRUCT(TagsRefHolder, first, second);
-};
-
-FORY_FIELD_TAGS(TagsRefHolder, (first, 0, ref), (second, 1, ref));
-
-// Struct with nullable + ref via FORY_FIELD_TAGS
-struct TagsNullableRefHolder {
- std::shared_ptr<TagsRefNode> required_node;
- std::shared_ptr<TagsRefNode> optional_node;
- FORY_STRUCT(TagsNullableRefHolder, required_node, optional_node);
-};
-
-FORY_FIELD_TAGS(TagsNullableRefHolder, (required_node, 0, ref),
- (optional_node, 1, nullable, ref));
-
-// Tree-like struct with self-referential nullable ref pointers
-struct TagsTreeNode {
- std::string value;
- std::shared_ptr<TagsTreeNode> left;
- std::shared_ptr<TagsTreeNode> right;
- FORY_STRUCT(TagsTreeNode, value, left, right);
-};
-
-FORY_FIELD_TAGS(TagsTreeNode, (value, 0), (left, 1, nullable, ref),
- (right, 2, nullable, ref));
-
-namespace fory {
-namespace serialization {
-namespace test {
-
-TEST(FieldTagsSerializerTest, BasicTagsDocument) {
- auto fory =
- Fory::builder().xlang(true).track_ref(false).compatible(false).build();
- fory.register_struct<TagsTestData>(200);
- fory.register_struct<TagsTestDocument>(201);
-
- TagsTestDocument original;
- original.title = "My Document";
- original.version = 1;
- original.description = "A test document";
- original.data = std::make_shared<TagsTestData>();
- original.data->content = "data content";
- original.data->value = 42;
- original.optional_data = nullptr; // nullable
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsTestDocument>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldTagsSerializerTest, TagsDocumentWithNullableSet) {
- auto fory =
- Fory::builder().xlang(true).track_ref(false).compatible(false).build();
- fory.register_struct<TagsTestData>(200);
- fory.register_struct<TagsTestDocument>(201);
-
- TagsTestDocument original;
- original.title = "Doc with optional";
- original.version = 2;
- original.description = std::nullopt;
- original.data = std::make_shared<TagsTestData>();
- original.data->content = "main data";
- original.data->value = 100;
- original.optional_data = std::make_shared<TagsTestData>();
- original.optional_data->content = "optional data";
- original.optional_data->value = 999;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsTestDocument>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- EXPECT_EQ(original, deser_result.value());
-}
-
-TEST(FieldTagsSerializerTest, TagsMetadataCompileTime) {
- // Verify that FORY_FIELD_TAGS metadata is correctly accessed
- using DocHelpers = detail::CompileTimeFieldHelpers<TagsTestDocument>;
- using DataHelpers = detail::CompileTimeFieldHelpers<TagsTestData>;
-
- // Check tag IDs via GetFieldTagEntry for TagsTestData
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestData, 0>::id == 0);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestData, 1>::id == 1);
-
- // Check tag IDs via GetFieldTagEntry for TagsTestDocument
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestDocument, 0>::id == 0);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestDocument, 1>::id == 1);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestDocument, 2>::id == 2);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestDocument, 3>::id == 3);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTestDocument, 4>::id == 4);
-
- // Check nullability via GetFieldTagEntry
- // title (string): non-nullable
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTestDocument, 0>::is_nullable ==
- false);
- // version (int): non-nullable
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTestDocument, 1>::is_nullable ==
- false);
- // description (optional): inherently nullable
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTestDocument, 2>::is_nullable ==
- true);
- // data (shared_ptr): non-nullable (default)
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTestDocument, 3>::is_nullable ==
- false);
- // optional_data (shared_ptr, nullable): nullable
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTestDocument, 4>::is_nullable ==
- true);
-
- // Verify CompileTimeFieldHelpers uses the tags correctly
- static_assert(DocHelpers::template field_nullable<0>() == false);
- static_assert(DocHelpers::template field_nullable<1>() == false);
- static_assert(DocHelpers::template field_nullable<2>() == true);
- static_assert(DocHelpers::template field_nullable<3>() == false);
- static_assert(DocHelpers::template field_nullable<4>() == true);
-
- // Check tag IDs via CompileTimeFieldHelpers
- static_assert(DocHelpers::template field_tag_id<0>() == 0);
- static_assert(DocHelpers::template field_tag_id<1>() == 1);
- static_assert(DocHelpers::template field_tag_id<2>() == 2);
- static_assert(DocHelpers::template field_tag_id<3>() == 3);
- static_assert(DocHelpers::template field_tag_id<4>() == 4);
-
- // Verify TagsTestData helpers
- static_assert(DataHelpers::template field_nullable<0>() == false);
- static_assert(DataHelpers::template field_nullable<1>() == false);
- static_assert(DataHelpers::template field_tag_id<0>() == 0);
- static_assert(DataHelpers::template field_tag_id<1>() == 1);
-}
-
-// ============================================================================
-// FORY_FIELD_TAGS Reference Tracking Tests
-// ============================================================================
-
-TEST(FieldTagsSerializerTest, TagsRefTrackingSameObject) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsRefNode>(300);
- fory.register_struct<TagsRefHolder>(301);
-
- // Create a shared node that will be referenced by both fields
- auto shared_node = std::make_shared<TagsRefNode>();
- shared_node->name = "shared";
- shared_node->id = 42;
-
- TagsRefHolder original;
- original.first = shared_node;
- original.second = shared_node; // Same object
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsRefHolder>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.first);
- ASSERT_TRUE(result.second);
- EXPECT_EQ(result.first->name, "shared");
- EXPECT_EQ(result.first->id, 42);
- EXPECT_EQ(result.second->name, "shared");
- EXPECT_EQ(result.second->id, 42);
-
- // Reference tracking should preserve shared_ptr aliasing
- EXPECT_EQ(result.first, result.second)
- << "FORY_FIELD_TAGS with ref should preserve shared_ptr aliasing";
-}
-
-TEST(FieldTagsSerializerTest, TagsRefTrackingDifferentObjects) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsRefNode>(300);
- fory.register_struct<TagsRefHolder>(301);
-
- TagsRefHolder original;
- original.first = std::make_shared<TagsRefNode>();
- original.first->name = "first";
- original.first->id = 1;
- original.second = std::make_shared<TagsRefNode>();
- original.second->name = "second";
- original.second->id = 2;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsRefHolder>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.first);
- ASSERT_TRUE(result.second);
- EXPECT_EQ(result.first->name, "first");
- EXPECT_EQ(result.first->id, 1);
- EXPECT_EQ(result.second->name, "second");
- EXPECT_EQ(result.second->id, 2);
-
- // Different objects should not share
- EXPECT_NE(result.first, result.second);
-}
-
-TEST(FieldTagsSerializerTest, TagsNullableRefWithValue) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsRefNode>(300);
- fory.register_struct<TagsNullableRefHolder>(302);
-
- TagsNullableRefHolder original;
- original.required_node = std::make_shared<TagsRefNode>();
- original.required_node->name = "required";
- original.required_node->id = 100;
- original.optional_node = std::make_shared<TagsRefNode>();
- original.optional_node->name = "optional";
- original.optional_node->id = 200;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsNullableRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.required_node);
- ASSERT_TRUE(result.optional_node);
- EXPECT_EQ(result.required_node->name, "required");
- EXPECT_EQ(result.required_node->id, 100);
- EXPECT_EQ(result.optional_node->name, "optional");
- EXPECT_EQ(result.optional_node->id, 200);
-}
-
-TEST(FieldTagsSerializerTest, TagsNullableRefWithNull) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsRefNode>(300);
- fory.register_struct<TagsNullableRefHolder>(302);
-
- TagsNullableRefHolder original;
- original.required_node = std::make_shared<TagsRefNode>();
- original.required_node->name = "required";
- original.required_node->id = 100;
- original.optional_node = nullptr; // nullable field set to null
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsNullableRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.required_node);
- EXPECT_EQ(result.required_node->name, "required");
- EXPECT_EQ(result.required_node->id, 100);
- EXPECT_EQ(result.optional_node, nullptr);
-}
-
-TEST(FieldTagsSerializerTest, TagsNullableRefSharedObject) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsRefNode>(300);
- fory.register_struct<TagsNullableRefHolder>(302);
-
- // Both fields point to the same object
- auto shared_node = std::make_shared<TagsRefNode>();
- shared_node->name = "shared_nullable";
- shared_node->id = 999;
-
- TagsNullableRefHolder original;
- original.required_node = shared_node;
- original.optional_node = shared_node;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsNullableRefHolder>(
- bytes_result->data(), bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- ASSERT_TRUE(result.required_node);
- ASSERT_TRUE(result.optional_node);
- EXPECT_EQ(result.required_node->name, "shared_nullable");
- EXPECT_EQ(result.required_node->id, 999);
-
- // Both should point to the same deserialized object
- EXPECT_EQ(result.required_node, result.optional_node)
- << "Nullable ref fields should also preserve shared_ptr aliasing";
-}
-
-TEST(FieldTagsSerializerTest, TagsTreeNodeSerialization) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsTreeNode>(303);
-
- // Build a simple tree:
- // root
- // / \
- // left right
- // / \
- // ll lr
- TagsTreeNode original;
- original.value = "root";
- original.left = std::make_shared<TagsTreeNode>();
- original.left->value = "left";
- original.left->left = std::make_shared<TagsTreeNode>();
- original.left->left->value = "ll";
- original.left->left->left = nullptr;
- original.left->left->right = nullptr;
- original.left->right = std::make_shared<TagsTreeNode>();
- original.left->right->value = "lr";
- original.left->right->left = nullptr;
- original.left->right->right = nullptr;
- original.right = std::make_shared<TagsTreeNode>();
- original.right->value = "right";
- original.right->left = nullptr;
- original.right->right = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsTreeNode>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- EXPECT_EQ(result.value, "root");
- ASSERT_TRUE(result.left);
- EXPECT_EQ(result.left->value, "left");
- ASSERT_TRUE(result.left->left);
- EXPECT_EQ(result.left->left->value, "ll");
- EXPECT_EQ(result.left->left->left, nullptr);
- EXPECT_EQ(result.left->left->right, nullptr);
- ASSERT_TRUE(result.left->right);
- EXPECT_EQ(result.left->right->value, "lr");
- EXPECT_EQ(result.left->right->left, nullptr);
- EXPECT_EQ(result.left->right->right, nullptr);
- ASSERT_TRUE(result.right);
- EXPECT_EQ(result.right->value, "right");
- EXPECT_EQ(result.right->left, nullptr);
- EXPECT_EQ(result.right->right, nullptr);
-}
-
-TEST(FieldTagsSerializerTest, TagsTreeNodeWithSharedSubtree) {
- auto fory =
- Fory::builder().xlang(true).track_ref(true).compatible(false).build();
- fory.register_struct<TagsTreeNode>(303);
-
- // Build a DAG (tree with shared subtree):
- // root
- // / \
- // left right
- // \ /
- // shared
- auto shared = std::make_shared<TagsTreeNode>();
- shared->value = "shared_subtree";
- shared->left = nullptr;
- shared->right = nullptr;
-
- TagsTreeNode original;
- original.value = "root";
- original.left = std::make_shared<TagsTreeNode>();
- original.left->value = "left";
- original.left->left = nullptr;
- original.left->right = shared; // left's right points to shared
- original.right = std::make_shared<TagsTreeNode>();
- original.right->value = "right";
- original.right->left = shared; // right's left points to same shared
- original.right->right = nullptr;
-
- auto bytes_result = fory.serialize(original);
- ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
-
- auto deser_result = fory.deserialize<TagsTreeNode>(bytes_result->data(),
- bytes_result->size());
- ASSERT_TRUE(deser_result.ok()) << deser_result.error().to_string();
-
- auto &result = deser_result.value();
- EXPECT_EQ(result.value, "root");
- ASSERT_TRUE(result.left);
- ASSERT_TRUE(result.right);
- ASSERT_TRUE(result.left->right);
- ASSERT_TRUE(result.right->left);
-
- // The shared subtree should still be the same object after deserialization
- EXPECT_EQ(result.left->right, result.right->left)
- << "Tree nodes with shared subtrees should preserve sharing";
- EXPECT_EQ(result.left->right->value, "shared_subtree");
-}
-
-TEST(FieldTagsSerializerTest, TagsRefMetadataCompileTime) {
- // Verify that FORY_FIELD_TAGS with ref option is correctly parsed
- using RefHolderHelpers = detail::CompileTimeFieldHelpers<TagsRefHolder>;
- using NullableRefHelpers =
- detail::CompileTimeFieldHelpers<TagsNullableRefHolder>;
- using TreeHelpers = detail::CompileTimeFieldHelpers<TagsTreeNode>;
-
- // TagsRefHolder: (first, 0, ref), (second, 1, ref)
- static_assert(::fory::detail::GetFieldTagEntry<TagsRefHolder, 0>::id == 0);
- static_assert(::fory::detail::GetFieldTagEntry<TagsRefHolder, 1>::id == 1);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsRefHolder, 0>::is_nullable == false);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsRefHolder, 1>::is_nullable == false);
- static_assert(::fory::detail::GetFieldTagEntry<TagsRefHolder, 0>::track_ref ==
- true);
- static_assert(::fory::detail::GetFieldTagEntry<TagsRefHolder, 1>::track_ref ==
- true);
-
- // TagsNullableRefHolder: (required_node, 0, ref), (optional_node, 1,
- // nullable, ref)
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 0>::id == 0);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 1>::id == 1);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 0>::is_nullable ==
- false);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 1>::is_nullable ==
- true);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 0>::track_ref ==
- true);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsNullableRefHolder, 1>::track_ref ==
- true);
-
- // TagsTreeNode: (value, 0), (left, 1, nullable, ref), (right, 2, nullable,
- // ref)
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 0>::id == 0);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 1>::id == 1);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 2>::id == 2);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTreeNode, 0>::is_nullable == false);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTreeNode, 1>::is_nullable == true);
- static_assert(
- ::fory::detail::GetFieldTagEntry<TagsTreeNode, 2>::is_nullable == true);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 0>::track_ref ==
- false);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 1>::track_ref ==
- true);
- static_assert(::fory::detail::GetFieldTagEntry<TagsTreeNode, 2>::track_ref ==
- true);
-
- // Verify CompileTimeFieldHelpers uses the tags correctly
- static_assert(RefHolderHelpers::template field_track_ref<0>() == true);
- static_assert(RefHolderHelpers::template field_track_ref<1>() == true);
- static_assert(NullableRefHelpers::template field_track_ref<0>() == true);
- static_assert(NullableRefHelpers::template field_track_ref<1>() == true);
- static_assert(TreeHelpers::template field_track_ref<0>() == false);
- static_assert(TreeHelpers::template field_track_ref<1>() == true);
- static_assert(TreeHelpers::template field_track_ref<2>() == true);
-}
-
-} // namespace test
-} // namespace serialization
-} // namespace fory
-
-int main(int argc, char **argv) {
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
diff --git a/cpp/fory/serialization/namespace_macro_test.cc b/cpp/fory/serialization/namespace_macro_test.cc
index a508f57c6..155e951a6 100644
--- a/cpp/fory/serialization/namespace_macro_test.cc
+++ b/cpp/fory/serialization/namespace_macro_test.cc
@@ -25,10 +25,12 @@
#include "gtest/gtest.h"
#include <cstdint>
+#include <map>
#include <optional>
#include <string>
#include <utility>
#include <variant>
+#include <vector>
namespace macro_test {
@@ -47,7 +49,7 @@ private:
int32_t id_ = 0;
public:
- FORY_STRUCT(Configured, id_);
+ FORY_STRUCT(Configured, (id_, fory::F(1).varint()));
};
class OptionalHolder final {
@@ -64,7 +66,7 @@ private:
std::optional<std::string> name_;
public:
- FORY_STRUCT(OptionalHolder, name_);
+ FORY_STRUCT(OptionalHolder, (name_, fory::F(1)));
};
class Choice final {
@@ -103,6 +105,32 @@ private:
: value_(tag, std::forward<Args>(args)...) {}
};
+class NestedChoice final {
+public:
+ using Values = std::map<uint32_t, std::vector<uint64_t>>;
+
+ NestedChoice() = default;
+
+ static NestedChoice values(Values value) {
+ return NestedChoice(std::move(value));
+ }
+
+ uint32_t fory_case_id() const noexcept { return 1; }
+
+ template <class Visitor> decltype(auto) visit(Visitor &&vis) const {
+ return std::forward<Visitor>(vis)(values_);
+ }
+
+ bool operator==(const NestedChoice &other) const {
+ return values_ == other.values_;
+ }
+
+private:
+ Values values_;
+
+ explicit NestedChoice(Values value) : values_(std::move(value)) {}
+};
+
class Partial final {
public:
Partial() = default;
@@ -115,7 +143,8 @@ private:
int64_t count_ = 0;
public:
- FORY_STRUCT(Partial, id_, name_, count_);
+ FORY_STRUCT(Partial, (id_, fory::F(5)), (name_, fory::F(6)),
+ (count_, fory::F(7).varint()));
};
class EnumContainer final {
@@ -125,13 +154,12 @@ public:
FORY_ENUM(EnumContainer::Kind, Alpha, Beta);
-FORY_FIELD_CONFIG(Configured, (id_, fory::F().id(1).varint()));
-FORY_FIELD_TAGS(OptionalHolder, (name_, 1));
-FORY_FIELD_CONFIG(Partial, (count_, fory::F().id(7).varint()));
-FORY_FIELD_TAGS(Partial, (id_, 5));
-
-FORY_UNION(Choice, (std::string, text, fory::F(1)),
- (int32_t, number, fory::F(2).varint()));
+FORY_UNION(Choice, (text, std::string, fory::F(1)),
+ (number, int32_t, fory::F(2).varint()));
+FORY_UNION(NestedChoice,
+ (values, NestedChoice::Values,
+ fory::F(1).map(fory::T::uint32().fixed(),
+ fory::T::list(fory::T::uint64().tagged()))));
} // namespace macro_test
@@ -139,7 +167,7 @@ namespace fory {
namespace serialization {
namespace test {
-TEST(NamespaceMacros, FieldConfigAndTagsInNamespace) {
+TEST(NamespaceMacros, FieldConfigInNamespace) {
static_assert(::fory::detail::has_field_config_v<macro_test::Configured>);
static_assert(
::fory::detail::GetFieldConfigEntry<macro_test::Configured, 0>::id == 1);
@@ -147,20 +175,20 @@ TEST(NamespaceMacros, FieldConfigAndTagsInNamespace) {
0>::encoding ==
::fory::Encoding::Varint);
- static_assert(::fory::detail::has_field_tags_v<macro_test::OptionalHolder>);
- static_assert(::fory::detail::GetFieldTagEntry<macro_test::OptionalHolder,
- 0>::is_nullable);
+ static_assert(
+ ::fory::detail::GetFieldConfigEntry<macro_test::OptionalHolder, 0>::id ==
+ 1);
+ static_assert(::fory::serialization::detail::CompileTimeFieldHelpers<
+ macro_test::OptionalHolder>::field_nullable<0>());
- static_assert(::fory::detail::GetFieldTagEntry<macro_test::Partial, 0>::id ==
- 5);
static_assert(
- !::fory::detail::GetFieldTagEntry<macro_test::Partial, 1>::has_entry);
+ ::fory::detail::GetFieldConfigEntry<macro_test::Partial, 0>::id == 5);
+ static_assert(
+ ::fory::detail::GetFieldConfigEntry<macro_test::Partial, 1>::id == 6);
static_assert(::fory::serialization::detail::CompileTimeFieldHelpers<
macro_test::Partial>::field_nullable<1>());
static_assert(
::fory::detail::GetFieldConfigEntry<macro_test::Partial, 2>::id == 7);
- static_assert(
- ::fory::detail::GetFieldConfigEntry<macro_test::Partial, 0>::id == -1);
}
TEST(NamespaceMacros, UnionInNamespace) {
@@ -176,6 +204,21 @@ TEST(NamespaceMacros, UnionInNamespace) {
EXPECT_EQ(macro_test::Choice::text("hello"), decoded.value());
}
+TEST(NamespaceMacros, NestedUnionPayloadSpec) {
+ auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ ASSERT_TRUE(fory.register_union<macro_test::NestedChoice>(1002).ok());
+
+ macro_test::NestedChoice::Values values = {{4000000000u, {7u, 1000000000u}},
+ {3u, {42u}}};
+ auto bytes = fory.serialize(macro_test::NestedChoice::values(values));
+ ASSERT_TRUE(bytes.ok()) << bytes.error().to_string();
+
+ auto decoded =
+ fory.deserialize<macro_test::NestedChoice>(bytes->data(), bytes->size());
+ ASSERT_TRUE(decoded.ok()) << decoded.error().to_string();
+ EXPECT_EQ(macro_test::NestedChoice::values(values), decoded.value());
+}
+
TEST(NamespaceMacros, EnumInAndOutOfClass) {
static_assert(::fory::meta::EnumInfo<macro_test::LocalEnum>::defined);
static_assert(::fory::meta::EnumInfo<macro_test::LocalEnum>::size == 2);
diff --git a/cpp/fory/serialization/serializer_traits.h b/cpp/fory/serialization/serializer_traits.h
index 98c476f7a..ad07c4aa6 100644
--- a/cpp/fory/serialization/serializer_traits.h
+++ b/cpp/fory/serialization/serializer_traits.h
@@ -373,8 +373,19 @@ inline constexpr bool is_shared_ref_v = is_shared_ref<T>::value;
// Element Type Extraction
// ============================================================================
-/// get element type for containers (reuse meta::GetValueType)
-template <typename T> using element_type_t = typename meta::GetValueType<T>;
+/// get element type for containers.
+template <typename T, typename = void> struct element_type_impl {
+ using type = typename meta::GetValueType<T>;
+};
+
+template <typename T>
+struct element_type_impl<T, std::void_t<typename T::value_type>> {
+ using type = typename T::value_type;
+};
+
+template <typename T>
+using element_type_t = typename element_type_impl<
+ std::remove_cv_t<std::remove_reference_t<T>>>::type;
/// get key type for map-like containers
template <typename T, typename = void> struct key_type_impl {};
diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc
index 9835e0b45..d146c9395 100644
--- a/cpp/fory/serialization/skip.cc
+++ b/cpp/fory/serialization/skip.cc
@@ -82,7 +82,7 @@ void skip_list(ReadContext &ctx, const FieldType &field_type) {
elem_type = field_type.generics[0];
} else {
// Unknown element type, need to read type info for each element
- elem_type.type_id = 0; // Unknown
+ elem_type.set_type_id(0); // Unknown
elem_type.nullable = false;
}
@@ -135,8 +135,8 @@ void skip_map(ReadContext &ctx, const FieldType &field_type) {
value_type = field_type.generics[1];
} else {
// Unknown types
- key_type.type_id = 0;
- value_type.type_id = 0;
+ key_type.set_type_id(0);
+ value_type.set_type_id(0);
}
uint64_t read_count = 0;
@@ -423,7 +423,7 @@ void skip_unknown(ReadContext &ctx) {
// For non-struct types (primitives, arrays, maps, etc.),
// recursively call skip_field_value with the actual type
FieldType actual_field_type;
- actual_field_type.type_id = type_info->type_id;
+ actual_field_type.set_type_id(type_info->type_id);
actual_field_type.nullable = false;
skip_field_value(ctx, actual_field_type, RefMode::None);
return;
@@ -469,7 +469,7 @@ void skip_union(ReadContext &ctx) {
// skip the alternative's value
FieldType alt_field_type;
- alt_field_type.type_id = type_info->type_id;
+ alt_field_type.set_type_id(type_info->type_id);
alt_field_type.nullable = false;
skip_field_value(ctx, alt_field_type, RefMode::None);
}
diff --git a/cpp/fory/serialization/smart_ptr_serializer_test.cc b/cpp/fory/serialization/smart_ptr_serializer_test.cc
index 1c8b105f6..971ea2966 100644
--- a/cpp/fory/serialization/smart_ptr_serializer_test.cc
+++ b/cpp/fory/serialization/smart_ptr_serializer_test.cc
@@ -437,7 +437,7 @@ TEST(SmartPtrSerializerTest, MaxDynDepthDefault) {
} // namespace
// ============================================================================
-// Monomorphic field tests (fory::field<> style)
+// Monomorphic field tests
// ============================================================================
namespace {
@@ -450,21 +450,17 @@ struct PolymorphicBaseForMono {
FORY_STRUCT(PolymorphicBaseForMono, value, data);
};
-// Holder with non-dynamic field using fory::field<>
struct NonDynamicFieldHolder {
- // Field marked as dynamic<false> - no dynamic type dispatch, always
- // PolymorphicBaseForMono
- fory::field<std::shared_ptr<PolymorphicBaseForMono>, 0, fory::nullable,
- fory::dynamic<false>>
- ptr;
- FORY_STRUCT(NonDynamicFieldHolder, ptr);
+ std::shared_ptr<PolymorphicBaseForMono> ptr;
+ FORY_STRUCT(NonDynamicFieldHolder,
+ (ptr, fory::F().nullable().dynamic(false)));
};
-TEST(SmartPtrSerializerTest, NonDynamicFieldWithForyField) {
+TEST(SmartPtrSerializerTest, NonDynamicFieldConfig) {
NonDynamicFieldHolder original;
- original.ptr.value = std::make_shared<PolymorphicBaseForMono>();
- original.ptr.value->value = 42;
- original.ptr.value->data = "test data";
+ original.ptr = std::make_shared<PolymorphicBaseForMono>();
+ original.ptr->value = 42;
+ original.ptr->data = "test data";
auto fory = Fory::builder().track_ref(false).build();
fory.register_struct<NonDynamicFieldHolder>(400);
@@ -479,15 +475,15 @@ TEST(SmartPtrSerializerTest, NonDynamicFieldWithForyField) {
<< deserialize_result.error().to_string();
auto deserialized = std::move(deserialize_result).value();
- ASSERT_TRUE(deserialized.ptr.value);
- EXPECT_EQ(deserialized.ptr.value->value, 42);
- EXPECT_EQ(deserialized.ptr.value->data, "test data");
- EXPECT_EQ(deserialized.ptr.value->name(), "PolymorphicBaseForMono");
+ ASSERT_TRUE(deserialized.ptr);
+ EXPECT_EQ(deserialized.ptr->value, 42);
+ EXPECT_EQ(deserialized.ptr->data, "test data");
+ EXPECT_EQ(deserialized.ptr->name(), "PolymorphicBaseForMono");
}
TEST(SmartPtrSerializerTest, NonDynamicFieldNullValue) {
NonDynamicFieldHolder original;
- original.ptr.value = nullptr;
+ original.ptr = nullptr;
auto fory = Fory::builder().track_ref(false).build();
fory.register_struct<NonDynamicFieldHolder>(404);
@@ -502,7 +498,7 @@ TEST(SmartPtrSerializerTest, NonDynamicFieldNullValue) {
<< deserialize_result.error().to_string();
auto deserialized = std::move(deserialize_result).value();
- EXPECT_FALSE(deserialized.ptr.value);
+ EXPECT_FALSE(deserialized.ptr);
}
} // namespace
diff --git a/cpp/fory/serialization/struct_serializer.h b/cpp/fory/serialization/struct_serializer.h
index cdd4a5f40..88a6de6d2 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -24,6 +24,8 @@
#include "fory/meta/field_info.h"
#include "fory/meta/preprocessor.h"
#include "fory/meta/type_traits.h"
+#include "fory/serialization/collection_serializer.h"
+#include "fory/serialization/map_serializer.h"
#include "fory/serialization/serializer.h"
#include "fory/serialization/serializer_traits.h"
#include "fory/serialization/skip.h"
@@ -34,6 +36,7 @@
#include <limits>
#include <memory>
#include <numeric>
+#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
@@ -476,6 +479,587 @@ void for_each_index(std::index_sequence<Indices...>, Func &&func) {
(func(std::integral_constant<size_t, Indices>{}), ...);
}
+template <typename StructT, size_t Index, int8_t NodeIndex = 0>
+constexpr FieldNodeKind configured_node_kind() {
+ constexpr auto spec =
+ ::fory::detail::GetFieldConfigEntry<StructT, Index>::spec;
+ return NodeIndex >= 0 ? spec.kind_[NodeIndex] : FieldNodeKind::Default;
+}
+
+template <typename StructT, size_t Index, int8_t NodeIndex = 0>
+constexpr Encoding configured_node_encoding() {
+ constexpr auto spec =
+ ::fory::detail::GetFieldConfigEntry<StructT, Index>::spec;
+ return NodeIndex >= 0 ? spec.encoding_[NodeIndex] : Encoding::Default;
+}
+
+template <typename StructT, size_t Index, int8_t NodeIndex = 0>
+constexpr bool configured_node_has_override() {
+ constexpr auto spec =
+ ::fory::detail::GetFieldConfigEntry<StructT, Index>::spec;
+ return NodeIndex >= 0 &&
+ (spec.kind_[NodeIndex] != FieldNodeKind::Default ||
+ spec.encoding_[NodeIndex] != Encoding::Default ||
+ spec.scalar_[NodeIndex] != FieldScalarKind::Inferred);
+}
+
+template <typename StructT, size_t Index, int8_t NodeIndex = 0>
+constexpr FieldScalarKind configured_node_scalar() {
+ constexpr auto spec =
+ ::fory::detail::GetFieldConfigEntry<StructT, Index>::spec;
+ return NodeIndex >= 0 ? spec.scalar_[NodeIndex] : FieldScalarKind::Inferred;
+}
+
+template <typename FieldType, FieldScalarKind ScalarKind>
+constexpr bool configured_scalar_kind_matches() {
+ using Decayed = decay_t<FieldType>;
+ if constexpr (ScalarKind == FieldScalarKind::Inferred) {
+ return true;
+ } else if constexpr (ScalarKind == FieldScalarKind::Bool) {
+ return std::is_same_v<Decayed, bool>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Int8) {
+ return std::is_same_v<Decayed, int8_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Int16) {
+ return std::is_same_v<Decayed, int16_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Int32) {
+ return std::is_same_v<Decayed, int32_t> || std::is_same_v<Decayed, int>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Int64) {
+ return std::is_same_v<Decayed, int64_t> ||
+ std::is_same_v<Decayed, long long>;
+ } else if constexpr (ScalarKind == FieldScalarKind::UInt8) {
+ return std::is_same_v<Decayed, uint8_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::UInt16) {
+ return std::is_same_v<Decayed, uint16_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::UInt32) {
+ return std::is_same_v<Decayed, uint32_t> ||
+ std::is_same_v<Decayed, unsigned int>;
+ } else if constexpr (ScalarKind == FieldScalarKind::UInt64) {
+ return std::is_same_v<Decayed, uint64_t> ||
+ std::is_same_v<Decayed, unsigned long long>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Float16) {
+ return std::is_same_v<Decayed, float16_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::BFloat16) {
+ return std::is_same_v<Decayed, bfloat16_t>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Float32) {
+ return std::is_same_v<Decayed, float>;
+ } else if constexpr (ScalarKind == FieldScalarKind::Float64) {
+ return std::is_same_v<Decayed, double>;
+ } else if constexpr (ScalarKind == FieldScalarKind::String) {
+ return std::is_same_v<Decayed, std::string>;
+ } else {
+ return false;
+ }
+}
+
+template <typename StructT, size_t Index, int8_t NodeIndex, int ChildSlot>
+constexpr int8_t configured_node_child() {
+ constexpr auto spec =
+ ::fory::detail::GetFieldConfigEntry<StructT, Index>::spec;
+ if constexpr (ChildSlot == 0) {
+ return NodeIndex >= 0 ? spec.child0_[NodeIndex] : -1;
+ } else {
+ return NodeIndex >= 0 ? spec.child1_[NodeIndex] : -1;
+ }
+}
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+constexpr bool configured_vector_primitive_array_spec() {
+ if constexpr (!is_vector_v<ValueType>) {
+ return false;
+ } else {
+ using Element = element_type_t<ValueType>;
+ if constexpr (!std::is_same_v<decay_t<Element>, int8_t> &&
+ !std::is_same_v<decay_t<Element>, uint8_t>) {
+ return false;
+ } else {
+ constexpr FieldNodeKind kind =
+ configured_node_kind<StructT, Index, NodeIndex>();
+ constexpr int8_t child =
+ configured_node_child<StructT, Index, NodeIndex, 0>();
+ if constexpr (kind != FieldNodeKind::List || child < 0) {
+ return false;
+ } else if constexpr (configured_node_kind<StructT, Index, child>() !=
+ FieldNodeKind::Scalar ||
+ configured_node_encoding<StructT, Index, child>() !=
+ Encoding::Default) {
+ return false;
+ } else if constexpr (std::is_same_v<decay_t<Element>, int8_t>) {
+ return configured_node_scalar<StructT, Index, child>() ==
+ FieldScalarKind::Int8;
+ } else {
+ return configured_node_scalar<StructT, Index, child>() ==
+ FieldScalarKind::UInt8;
+ }
+ }
+ }
+}
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+constexpr uint32_t configured_vector_primitive_array_type_id() {
+ if constexpr (!configured_vector_primitive_array_spec<ValueType, StructT,
+ Index, NodeIndex>()) {
+ return 0;
+ } else {
+ using Element = element_type_t<ValueType>;
+ if constexpr (std::is_same_v<decay_t<Element>, int8_t>) {
+ return static_cast<uint32_t>(TypeId::INT8_ARRAY);
+ } else {
+ return static_cast<uint32_t>(TypeId::UINT8_ARRAY);
+ }
+ }
+}
+
+template <typename FieldType, typename StructT, size_t Index, int8_t NodeIndex>
+constexpr uint32_t configured_scalar_type_id() {
+ constexpr Encoding enc =
+ configured_node_encoding<StructT, Index, NodeIndex>();
+ using Decayed = decay_t<FieldType>;
+ if constexpr (std::is_same_v<Decayed, bool>) {
+ return static_cast<uint32_t>(TypeId::BOOL);
+ } else if constexpr (std::is_same_v<Decayed, int8_t>) {
+ return static_cast<uint32_t>(TypeId::INT8);
+ } else if constexpr (std::is_same_v<Decayed, uint8_t>) {
+ return static_cast<uint32_t>(TypeId::UINT8);
+ } else if constexpr (std::is_same_v<Decayed, int16_t>) {
+ return static_cast<uint32_t>(TypeId::INT16);
+ } else if constexpr (std::is_same_v<Decayed, uint16_t>) {
+ return static_cast<uint32_t>(TypeId::UINT16);
+ } else if constexpr (std::is_same_v<Decayed, int32_t> ||
+ std::is_same_v<Decayed, int>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<uint32_t>(TypeId::INT32);
+ }
+ return static_cast<uint32_t>(TypeId::VARINT32);
+ } else if constexpr (std::is_same_v<Decayed, uint32_t> ||
+ std::is_same_v<Decayed, unsigned int>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<uint32_t>(TypeId::VAR_UINT32);
+ }
+ return static_cast<uint32_t>(TypeId::UINT32);
+ } else if constexpr (std::is_same_v<Decayed, int64_t> ||
+ std::is_same_v<Decayed, long long>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<uint32_t>(TypeId::INT64);
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<uint32_t>(TypeId::TAGGED_INT64);
+ }
+ return static_cast<uint32_t>(TypeId::VARINT64);
+ } else if constexpr (std::is_same_v<Decayed, uint64_t> ||
+ std::is_same_v<Decayed, unsigned long long>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<uint32_t>(TypeId::VAR_UINT64);
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<uint32_t>(TypeId::TAGGED_UINT64);
+ }
+ return static_cast<uint32_t>(TypeId::UINT64);
+ } else if constexpr (std::is_same_v<Decayed, float16_t>) {
+ return static_cast<uint32_t>(TypeId::FLOAT16);
+ } else if constexpr (std::is_same_v<Decayed, bfloat16_t>) {
+ return static_cast<uint32_t>(TypeId::BFLOAT16);
+ } else if constexpr (std::is_same_v<Decayed, float>) {
+ return static_cast<uint32_t>(TypeId::FLOAT32);
+ } else if constexpr (std::is_same_v<Decayed, double>) {
+ return static_cast<uint32_t>(TypeId::FLOAT64);
+ } else if constexpr (std::is_same_v<Decayed, std::string>) {
+ return static_cast<uint32_t>(TypeId::STRING);
+ }
+ return 0;
+}
+
+template <typename FieldType, typename StructT, size_t Index, int8_t NodeIndex>
+constexpr uint32_t configured_effective_type_id() {
+ constexpr FieldNodeKind kind =
+ configured_node_kind<StructT, Index, NodeIndex>();
+ if constexpr (is_optional_v<FieldType>) {
+ using Inner = typename decay_t<FieldType>::value_type;
+ constexpr int8_t child =
+ kind == FieldNodeKind::Inner
+ ? configured_node_child<StructT, Index, NodeIndex, 0>()
+ : NodeIndex;
+ return configured_effective_type_id<Inner, StructT, Index, child>();
+ } else {
+ constexpr uint32_t vector_array_tid =
+ configured_vector_primitive_array_type_id<FieldType, StructT, Index,
+ NodeIndex>();
+ if constexpr (vector_array_tid != 0) {
+ return vector_array_tid;
+ } else if constexpr (kind == FieldNodeKind::List) {
+ return static_cast<uint32_t>(TypeId::LIST);
+ } else if constexpr (kind == FieldNodeKind::Set) {
+ return static_cast<uint32_t>(TypeId::SET);
+ } else if constexpr (kind == FieldNodeKind::Map) {
+ return static_cast<uint32_t>(TypeId::MAP);
+ } else if constexpr (kind == FieldNodeKind::Scalar ||
+ configured_node_encoding<StructT, Index,
+ NodeIndex>() !=
+ Encoding::Default) {
+ return configured_scalar_type_id<FieldType, StructT, Index, NodeIndex>();
+ }
+ return 0;
+ }
+}
+
+template <typename FieldType, typename StructT, size_t Index, int8_t NodeIndex>
+FORY_ALWAYS_INLINE void write_configured_scalar(const FieldType &value,
+ WriteContext &ctx) {
+ constexpr Encoding enc =
+ configured_node_encoding<StructT, Index, NodeIndex>();
+ if constexpr (is_configurable_int_v<FieldType>) {
+ if constexpr (std::is_same_v<FieldType, uint32_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ ctx.write_var_uint32(value);
+ } else {
+ ctx.buffer().write_int32(static_cast<int32_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ ctx.write_var_uint64(value);
+ } else if constexpr (enc == Encoding::Tagged) {
+ ctx.write_tagged_uint64(value);
+ } else {
+ ctx.buffer().write_int64(static_cast<int64_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, int32_t> ||
+ std::is_same_v<FieldType, int>) {
+ if constexpr (enc == Encoding::Fixed) {
+ ctx.buffer().write_int32(static_cast<int32_t>(value));
+ } else {
+ ctx.write_var_int32(static_cast<int32_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, int64_t> ||
+ std::is_same_v<FieldType, long long>) {
+ if constexpr (enc == Encoding::Fixed) {
+ ctx.buffer().write_int64(static_cast<int64_t>(value));
+ } else if constexpr (enc == Encoding::Tagged) {
+ ctx.write_tagged_int64(static_cast<int64_t>(value));
+ } else {
+ ctx.write_var_int64(static_cast<int64_t>(value));
+ }
+ } else {
+ Serializer<FieldType>::write_data(value, ctx);
+ }
+ } else {
+ Serializer<FieldType>::write_data(value, ctx);
+ }
+}
+
+template <typename FieldType, typename StructT, size_t Index, int8_t NodeIndex>
+FORY_ALWAYS_INLINE FieldType read_configured_scalar(ReadContext &ctx) {
+ if constexpr (is_configurable_int_v<FieldType>) {
+ constexpr Encoding enc =
+ configured_node_encoding<StructT, Index, NodeIndex>();
+ if constexpr (std::is_same_v<FieldType, uint32_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<FieldType>(ctx.read_tagged_uint64(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, int32_t> ||
+ std::is_same_v<FieldType, int>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_var_int32(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, int64_t> ||
+ std::is_same_v<FieldType, long long>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<FieldType>(ctx.read_int64(ctx.error()));
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<FieldType>(ctx.read_tagged_int64(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_var_int64(ctx.error()));
+ } else {
+ return Serializer<FieldType>::read_data(ctx);
+ }
+ } else {
+ return Serializer<FieldType>::read_data(ctx);
+ }
+}
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+void write_configured_value(const ValueType &value, WriteContext &ctx,
+ RefMode ref_mode, bool write_type,
+ bool has_generics);
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+ValueType read_configured_value(ReadContext &ctx, RefMode ref_mode,
+ bool read_type);
+
+template <typename Container, typename StructT, size_t Index, int8_t NodeIndex,
+ int8_t ElemNode>
+void write_configured_list_data(const Container &coll, WriteContext &ctx) {
+ using Elem = element_type_t<Container>;
+ ctx.write_var_uint32(static_cast<uint32_t>(coll.size()));
+ if (coll.empty()) {
+ return;
+ }
+ ctx.write_uint8(COLL_DECL_ELEMENT_TYPE | COLL_IS_SAME_TYPE);
+ for (const auto &elem : coll) {
+ if constexpr (ElemNode >= 0) {
+ write_configured_value<Elem, StructT, Index, ElemNode>(
+ elem, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Elem>::write_data(elem, ctx);
+ }
+ }
+}
+
+template <typename Container, typename StructT, size_t Index, int8_t NodeIndex,
+ int8_t ElemNode>
+Container read_configured_list_data(ReadContext &ctx) {
+ using Elem = element_type_t<Container>;
+ uint32_t length = ctx.read_var_uint32(ctx.error());
+ Container result;
+ if constexpr (has_reserve_v<Container>) {
+ result.reserve(length);
+ }
+ if (FORY_PREDICT_FALSE(ctx.has_error()) || length == 0) {
+ return result;
+ }
+ uint8_t bitmap = ctx.read_uint8(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ const bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0;
+ const bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0;
+ if (is_same_type && !is_decl_type) {
+ (void)ctx.read_any_type_info(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ }
+ for (uint32_t i = 0; i < length; ++i) {
+ if constexpr (ElemNode >= 0) {
+ auto elem = read_configured_value<Elem, StructT, Index, ElemNode>(
+ ctx, RefMode::None, false);
+ collection_insert(result, std::move(elem));
+ } else {
+ auto elem = Serializer<Elem>::read_data(ctx);
+ collection_insert(result, std::move(elem));
+ }
+ }
+ return result;
+}
+
+template <typename MapType, typename StructT, size_t Index, int8_t KeyNode,
+ int8_t ValueNode>
+void write_configured_map_data(const MapType &map, WriteContext &ctx) {
+ using Key = key_type_t<MapType>;
+ using Value = mapped_type_t<MapType>;
+ ctx.write_var_uint32(static_cast<uint32_t>(map.size()));
+ if (map.empty()) {
+ return;
+ }
+ size_t header_offset = 0;
+ uint8_t pair_counter = 0;
+ bool need_write_header = true;
+ for (const auto &[key, value] : map) {
+ if (need_write_header) {
+ ctx.enter_flush_barrier();
+ header_offset = ctx.buffer().writer_index();
+ ctx.write_uint16(0);
+ ctx.buffer().unsafe_put_byte(
+ header_offset, static_cast<uint8_t>(DECL_KEY_TYPE | DECL_VALUE_TYPE));
+ need_write_header = false;
+ }
+ if constexpr (KeyNode >= 0) {
+ write_configured_value<Key, StructT, Index, KeyNode>(
+ key, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Key>::write_data(key, ctx);
+ }
+ if constexpr (ValueNode >= 0) {
+ write_configured_value<Value, StructT, Index, ValueNode>(
+ value, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Value>::write_data(value, ctx);
+ }
+ ++pair_counter;
+ if (pair_counter == MAX_CHUNK_SIZE) {
+ write_chunk_size(ctx, header_offset, pair_counter);
+ ctx.exit_flush_barrier();
+ ctx.try_flush();
+ pair_counter = 0;
+ need_write_header = true;
+ }
+ }
+ if (pair_counter > 0) {
+ write_chunk_size(ctx, header_offset, pair_counter);
+ ctx.exit_flush_barrier();
+ ctx.try_flush();
+ }
+}
+
+template <typename MapType, typename StructT, size_t Index, int8_t KeyNode,
+ int8_t ValueNode>
+MapType read_configured_map_data(ReadContext &ctx) {
+ using Key = key_type_t<MapType>;
+ using Value = mapped_type_t<MapType>;
+ uint32_t length = ctx.read_var_uint32(ctx.error());
+ MapType result;
+ MapReserver<MapType>::reserve(result, length);
+ uint32_t read_count = 0;
+ while (read_count < length && !ctx.has_error()) {
+ uint8_t header = ctx.read_uint8(ctx.error());
+ uint8_t chunk_size = ctx.read_uint8(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ const bool key_decl = (header & DECL_KEY_TYPE) != 0;
+ const bool value_decl = (header & DECL_VALUE_TYPE) != 0;
+ if (!key_decl) {
+ (void)ctx.read_any_type_info(ctx.error());
+ }
+ if (!value_decl) {
+ (void)ctx.read_any_type_info(ctx.error());
+ }
+ for (uint8_t i = 0; i < chunk_size && read_count < length; ++i) {
+ Key key = [&]() {
+ if constexpr (KeyNode >= 0) {
+ return read_configured_value<Key, StructT, Index, KeyNode>(
+ ctx, RefMode::None, false);
+ } else {
+ return Serializer<Key>::read_data(ctx);
+ }
+ }();
+ Value value = [&]() {
+ if constexpr (ValueNode >= 0) {
+ return read_configured_value<Value, StructT, Index, ValueNode>(
+ ctx, RefMode::None, false);
+ } else {
+ return Serializer<Value>::read_data(ctx);
+ }
+ }();
+ result.emplace(std::move(key), std::move(value));
+ ++read_count;
+ }
+ }
+ return result;
+}
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+void write_configured_value(const ValueType &value, WriteContext &ctx,
+ RefMode ref_mode, bool write_type,
+ bool has_generics) {
+ constexpr FieldNodeKind kind =
+ configured_node_kind<StructT, Index, NodeIndex>();
+ constexpr FieldScalarKind scalar_kind =
+ configured_node_scalar<StructT, Index, NodeIndex>();
+ static_assert(configured_scalar_kind_matches<ValueType, scalar_kind>(),
+ "fory::T typed scalar spec does not match the C++ field type");
+ if constexpr (is_optional_v<ValueType>) {
+ using Inner = typename ValueType::value_type;
+ if (!value.has_value()) {
+ if (ref_mode != RefMode::None) {
+ ctx.write_int8(NULL_FLAG);
+ }
+ return;
+ }
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t child =
+ kind == FieldNodeKind::Inner
+ ? configured_node_child<StructT, Index, NodeIndex, 0>()
+ : NodeIndex;
+ write_configured_value<Inner, StructT, Index, child>(
+ *value, ctx, RefMode::None, false, has_generics);
+ } else if constexpr ((is_vector_v<ValueType> || is_list_v<ValueType> ||
+ is_deque_v<ValueType> || is_set_like_v<ValueType>) &&
+ (kind == FieldNodeKind::List ||
+ kind == FieldNodeKind::Set)) {
+ if constexpr (configured_vector_primitive_array_spec<ValueType, StructT,
+ Index, NodeIndex>()) {
+ Serializer<ValueType>::write(value, ctx, ref_mode, false, has_generics);
+ } else {
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t child =
+ configured_node_child<StructT, Index, NodeIndex, 0>();
+ write_configured_list_data<ValueType, StructT, Index, NodeIndex, child>(
+ value, ctx);
+ }
+ } else if constexpr (is_map_like_v<ValueType> && kind == FieldNodeKind::Map) {
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t key_child =
+ configured_node_child<StructT, Index, NodeIndex, 0>();
+ constexpr int8_t value_child =
+ configured_node_child<StructT, Index, NodeIndex, 1>();
+ write_configured_map_data<ValueType, StructT, Index, key_child,
+ value_child>(value, ctx);
+ } else if constexpr (kind == FieldNodeKind::Scalar ||
+ configured_node_encoding<StructT, Index, NodeIndex>() !=
+ Encoding::Default) {
+ write_not_null_ref_flag(ctx, ref_mode);
+ write_configured_scalar<ValueType, StructT, Index, NodeIndex>(value, ctx);
+ } else {
+ Serializer<ValueType>::write(value, ctx, ref_mode, write_type,
+ has_generics);
+ }
+}
+
+template <typename ValueType, typename StructT, size_t Index, int8_t NodeIndex>
+ValueType read_configured_value(ReadContext &ctx, RefMode ref_mode,
+ bool read_type) {
+ constexpr FieldNodeKind kind =
+ configured_node_kind<StructT, Index, NodeIndex>();
+ constexpr FieldScalarKind scalar_kind =
+ configured_node_scalar<StructT, Index, NodeIndex>();
+ static_assert(configured_scalar_kind_matches<ValueType, scalar_kind>(),
+ "fory::T typed scalar spec does not match the C++ field type");
+ if constexpr (is_optional_v<ValueType>) {
+ using Inner = typename ValueType::value_type;
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return std::nullopt;
+ }
+ constexpr int8_t child =
+ kind == FieldNodeKind::Inner
+ ? configured_node_child<StructT, Index, NodeIndex, 0>()
+ : NodeIndex;
+ Inner inner = read_configured_value<Inner, StructT, Index, child>(
+ ctx, RefMode::None, false);
+ return ValueType{std::move(inner)};
+ } else if constexpr ((is_vector_v<ValueType> || is_list_v<ValueType> ||
+ is_deque_v<ValueType> || is_set_like_v<ValueType>) &&
+ (kind == FieldNodeKind::List ||
+ kind == FieldNodeKind::Set)) {
+ if constexpr (configured_vector_primitive_array_spec<ValueType, StructT,
+ Index, NodeIndex>()) {
+ return Serializer<ValueType>::read(ctx, ref_mode, false);
+ } else {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ constexpr int8_t child =
+ configured_node_child<StructT, Index, NodeIndex, 0>();
+ return read_configured_list_data<ValueType, StructT, Index, NodeIndex,
+ child>(ctx);
+ }
+ } else if constexpr (is_map_like_v<ValueType> && kind == FieldNodeKind::Map) {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ constexpr int8_t key_child =
+ configured_node_child<StructT, Index, NodeIndex, 0>();
+ constexpr int8_t value_child =
+ configured_node_child<StructT, Index, NodeIndex, 1>();
+ return read_configured_map_data<ValueType, StructT, Index, key_child,
+ value_child>(ctx);
+ } else if constexpr (kind == FieldNodeKind::Scalar ||
+ configured_node_encoding<StructT, Index, NodeIndex>() !=
+ Encoding::Default) {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ return read_configured_scalar<ValueType, StructT, Index, NodeIndex>(ctx);
+ } else {
+ return Serializer<ValueType>::read(ctx, ref_mode, read_type);
+ }
+}
+
template <typename T, typename Func, size_t... Indices>
void dispatch_field_index_impl(size_t target_index, Func &&func,
std::index_sequence<Indices...>, bool &handled) {
@@ -509,17 +1093,47 @@ template <typename T> struct CompileTimeFieldHelpers {
static inline constexpr auto ptrs = FieldDescriptor::ptrs();
using FieldPtrs = decltype(ptrs);
+ template <size_t... Indices>
+ static constexpr bool any_field_has_id(std::index_sequence<Indices...>) {
+ if constexpr (sizeof...(Indices) == 0) {
+ return false;
+ } else {
+ return ((::fory::detail::GetFieldConfigEntry<T, Indices>::has_id) || ...);
+ }
+ }
+
+ template <size_t... Indices>
+ static constexpr bool all_fields_have_id(std::index_sequence<Indices...>) {
+ if constexpr (sizeof...(Indices) == 0) {
+ return true;
+ } else {
+ return ((::fory::detail::GetFieldConfigEntry<T, Indices>::has_id) && ...);
+ }
+ }
+
+ static constexpr bool any_id_mode_field =
+ any_field_has_id(std::make_index_sequence<FieldCount>{});
+ static constexpr bool all_id_mode_fields =
+ all_fields_have_id(std::make_index_sequence<FieldCount>{});
+
+ static_assert(!any_id_mode_field || all_id_mode_fields,
+ "FORY_STRUCT must use exactly one identity mode: either all "
+ "fields use fory::F(id), or no fields use ids.");
+
template <size_t Index> static constexpr uint32_t field_type_id() {
if constexpr (FieldCount == 0) {
return 0;
} else {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
- // unwrap fory::field<> to get the actual type for serialization
using FieldType = unwrap_field_t<RawFieldType>;
- // Check for encoding override from FORY_FIELD_CONFIG
if constexpr (::fory::detail::has_field_config_v<T>) {
+ constexpr uint32_t effective_tid =
+ configured_effective_type_id<FieldType, T, Index, 0>();
+ if constexpr (effective_tid != 0) {
+ return effective_tid;
+ }
constexpr uint32_t unsigned_tid =
compute_unsigned_type_id<FieldType, T, Index>();
if constexpr (unsigned_tid != 0) {
@@ -541,11 +1155,8 @@ template <typename T> struct CompileTimeFieldHelpers {
}
/// Returns true if the field at Index is nullable for fingerprint
- /// computation. This checks:
- /// 1. If the field is a fory::field<>, use its is_nullable metadata
- /// 2. Else if FORY_FIELD_TAGS is defined, use that metadata
- /// 3. Otherwise, use xlang defaults: only std::optional is nullable
- /// (For xlang: nullable=false by default, except for Optional types)
+ /// computation. Configured fields may opt in to nullable behavior; otherwise
+ /// xlang defaults make only std::optional nullable.
template <size_t Index> static constexpr bool field_nullable() {
if constexpr (FieldCount == 0) {
return false;
@@ -553,19 +1164,9 @@ template <typename T> struct CompileTimeFieldHelpers {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
- // If it's a fory::field<> wrapper, use its metadata
if constexpr (is_fory_field_v<RawFieldType>) {
return RawFieldType::is_nullable;
- }
- // Else if FORY_FIELD_TAGS is defined, use that metadata
- else if constexpr (::fory::detail::has_field_tags_v<T>) {
- if constexpr (::fory::detail::GetFieldTagEntry<T, Index>::has_entry) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::is_nullable;
- }
- return field_is_nullable_v<RawFieldType>;
- }
- // Else if FORY_FIELD_CONFIG is defined, use nullable from config
- else if constexpr (::fory::detail::has_field_config_v<T>) {
+ } else if constexpr (::fory::detail::has_field_config_v<T>) {
if constexpr (::fory::detail::GetFieldConfigEntry<T,
Index>::has_entry &&
::fory::detail::GetFieldConfigEntry<T, Index>::nullable) {
@@ -599,14 +1200,9 @@ template <typename T> struct CompileTimeFieldHelpers {
return config_id;
}
}
- // If it's a fory::field<> wrapper, use its tag_id
if constexpr (is_fory_field_v<RawFieldType>) {
return RawFieldType::tag_id;
}
- // Else if FORY_FIELD_TAGS is defined, use that metadata
- else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::id;
- }
// No tag ID defined
else {
return -1;
@@ -633,16 +1229,9 @@ template <typename T> struct CompileTimeFieldHelpers {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
- // If it's a fory::field<> wrapper, use its track_ref metadata
if constexpr (is_fory_field_v<RawFieldType>) {
return RawFieldType::track_ref;
- }
- // Else if FORY_FIELD_TAGS is defined, use that metadata
- else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::track_ref;
- }
- // Else if FORY_FIELD_CONFIG is defined, use ref from config
- else if constexpr (::fory::detail::has_field_config_v<T>) {
+ } else if constexpr (::fory::detail::has_field_config_v<T>) {
if constexpr (::fory::detail::GetFieldConfigEntry<T,
Index>::has_entry &&
::fory::detail::GetFieldConfigEntry<T, Index>::ref) {
@@ -668,16 +1257,9 @@ template <typename T> struct CompileTimeFieldHelpers {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
- // If it's a fory::field<> wrapper, use its dynamic_value metadata
if constexpr (is_fory_field_v<RawFieldType>) {
return RawFieldType::dynamic_value;
- }
- // Else if FORY_FIELD_TAGS is defined, use that metadata
- else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::dynamic_value;
- }
- // Else if FORY_FIELD_CONFIG is defined, use dynamic_value from config
- else if constexpr (::fory::detail::has_field_config_v<T>) {
+ } else if constexpr (::fory::detail::has_field_config_v<T>) {
constexpr int dynamic_value =
::fory::detail::GetFieldConfigEntry<T, Index>::dynamic_value;
if constexpr (dynamic_value != -1) {
@@ -733,7 +1315,7 @@ template <typename T> struct CompileTimeFieldHelpers {
!any_field_needs_type_info_in_compatible(
std::make_index_sequence<FieldCount>{});
- /// get the underlying field type (unwraps fory::field<> if present)
+ /// get the underlying field type.
template <size_t Index> struct UnwrappedFieldTypeHelper {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
@@ -1635,7 +2217,7 @@ FORY_ALWAYS_INLINE void write_single_fixed_field(const T &obj, Buffer &buffer,
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
- // get the actual value (unwrap fory::field<> if needed)
+ // get the actual field value.
const FieldType &field_value = [&]() -> const FieldType & {
if constexpr (is_fory_field_v<RawFieldType>) {
return (obj.*field_ptr).value;
@@ -1677,7 +2259,7 @@ FORY_ALWAYS_INLINE void write_single_varint_field(const T &obj, Buffer &buffer,
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
- // get the actual value (unwrap fory::field<> if needed)
+ // get the actual field value.
const FieldType &field_value = [&]() -> const FieldType & {
if constexpr (is_fory_field_v<RawFieldType>) {
return (obj.*field_ptr).value;
@@ -1720,7 +2302,7 @@ write_single_remaining_field(const T &obj, Buffer &buffer, uint32_t &offset) {
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using FieldType = unwrap_field_t<RawFieldType>;
- // get the actual value (unwrap fory::field<> if needed)
+ // get the actual field value.
const FieldType &field_value = [&]() -> const FieldType & {
if constexpr (is_fory_field_v<RawFieldType>) {
return (obj.*field_ptr).value;
@@ -1803,10 +2385,9 @@ void write_single_field(const T &obj, WriteContext &ctx,
const auto field_ptr = std::get<Index>(field_ptrs);
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
- // unwrap fory::field<> to get the actual type for serialization
using FieldType = unwrap_field_t<RawFieldType>;
- // get the actual value (unwrap fory::field<> if needed)
+ // get the actual field value.
const auto &raw_field_ref = obj.*field_ptr;
const FieldType &field_value = [&]() -> const FieldType & {
if constexpr (is_fory_field_v<RawFieldType>) {
@@ -1819,12 +2400,28 @@ void write_single_field(const T &obj, WriteContext &ctx,
constexpr TypeId field_type_id = Serializer<FieldType>::type_id;
constexpr bool is_primitive_field = is_primitive_type_id(field_type_id);
- // get field metadata from fory::field<> or FORY_FIELD_TAGS or defaults
+ // get field metadata from the effective field spec or defaults.
constexpr bool is_nullable = Helpers::template field_nullable<Index>();
constexpr bool track_ref = Helpers::template field_track_ref<Index>();
// Some wrapper types always require ref/null flags in the wire format.
constexpr bool field_type_is_nullable = is_nullable_v<FieldType>;
+ if constexpr (configured_node_has_override<T, Index>()) {
+ constexpr RefMode field_ref_mode =
+ make_ref_mode(is_nullable || field_type_is_nullable, track_ref);
+ constexpr bool is_struct = is_struct_type(field_type_id);
+ constexpr bool is_ext = is_ext_type(field_type_id);
+ constexpr bool is_polymorphic = field_type_id == TypeId::UNKNOWN;
+ constexpr int dynamic_val = Helpers::template field_dynamic_value<Index>();
+ constexpr bool polymorphic_write_type =
+ (dynamic_val == 1) || (dynamic_val == -1 && is_polymorphic);
+ bool write_type = polymorphic_write_type ||
+ ((is_struct || is_ext) && ctx.is_compatible());
+ write_configured_value<FieldType, T, Index, 0>(
+ field_value, ctx, field_ref_mode, write_type, has_generics);
+ return;
+ }
+
// Special handling for std::optional<uint32_t/uint64_t> with encoding config
// This must come BEFORE the general primitive check because optional requires
// ref metadata but we want to use encoding-specific serialization.
@@ -2229,7 +2826,6 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
const auto field_ptr = std::get<Index>(field_ptrs);
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
- // unwrap fory::field<> to get the actual type for deserialization
using FieldType = unwrap_field_t<RawFieldType>;
// In non-compatible mode, no type info for fields except for polymorphic
@@ -2257,7 +2853,7 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
(dynamic_val == -1 && is_polymorphic_field) ||
((is_struct_field || is_ext_field) && ctx.is_compatible());
- // get field metadata from fory::field<> or FORY_FIELD_TAGS or defaults
+ // get field metadata from the effective field spec or defaults.
constexpr bool is_nullable = Helpers::template field_nullable<Index>();
constexpr bool track_ref = Helpers::template field_track_ref<Index>();
@@ -2271,6 +2867,18 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
// Per xlang protocol: non-nullable fields skip ref flag entirely
constexpr RefMode field_ref_mode =
make_ref_mode(is_nullable || field_type_is_nullable, track_ref);
+
+ if constexpr (configured_node_has_override<T, Index>()) {
+ FieldType result = read_configured_value<FieldType, T, Index, 0>(
+ ctx, field_ref_mode, read_type);
+ if constexpr (is_fory_field_v<RawFieldType>) {
+ (obj.*field_ptr).value = std::move(result);
+ } else {
+ obj.*field_ptr = std::move(result);
+ }
+ return;
+ }
+
// OPTIMIZATION: For raw primitive fields (not wrappers like optional,
// shared_ptr) that don't need ref metadata, bypass Serializer<T>::read
// and use direct buffer reads with Error&.
@@ -2283,7 +2891,7 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
}
return read_primitive_field_direct<FieldType>(ctx, ctx.error());
};
- // Assign to field (handle fory::field<> wrapper if needed)
+ // Assign to field.
if constexpr (is_fory_field_v<RawFieldType>) {
(obj.*field_ptr).value = read_value();
} else {
@@ -2382,7 +2990,7 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
obj.*field_ptr = std::optional<InnerType>(value);
}
} else {
- // Assign to field (handle fory::field<> wrapper if needed)
+ // Assign to field.
FieldType result =
Serializer<FieldType>::read(ctx, field_ref_mode, read_type);
if constexpr (is_fory_field_v<RawFieldType>) {
@@ -2396,19 +3004,19 @@ void read_single_field_by_index(T &obj, ReadContext &ctx) {
/// Helper to read a single field by index in compatible mode using
/// remote field metadata to decide reference flag presence.
-/// @param remote_type_id The type_id from the remote schema (for encoding)
+/// @param remote_field_type The field type tree from the remote schema.
template <size_t Index, typename T>
void read_single_field_by_index_compatible(T &obj, ReadContext &ctx,
- RefMode remote_ref_mode,
- uint32_t remote_type_id) {
+ const FieldType &remote_field_type) {
using Helpers = CompileTimeFieldHelpers<T>;
const auto field_info = fory_field_info(obj);
const auto &field_ptrs = decltype(field_info)::ptrs_ref();
const auto field_ptr = std::get<Index>(field_ptrs);
using RawFieldType =
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
- // unwrap fory::field<> to get the actual type for deserialization
using FieldType = unwrap_field_t<RawFieldType>;
+ const RefMode remote_ref_mode = remote_field_type.ref_mode;
+ const uint32_t remote_type_id = remote_field_type.type_id;
constexpr TypeId field_type_id = Serializer<FieldType>::type_id;
// Check if field is a struct type - use type_id to handle shared_ptr<Struct>
@@ -2527,6 +3135,17 @@ void read_single_field_by_index_compatible(T &obj, ReadContext &ctx,
}
}
+ if constexpr (configured_node_has_override<T, Index>()) {
+ FieldType result = read_configured_value<FieldType, T, Index, 0>(
+ ctx, remote_ref_mode, read_type);
+ if constexpr (is_fory_field_v<RawFieldType>) {
+ (obj.*field_ptr).value = std::move(result);
+ } else {
+ obj.*field_ptr = std::move(result);
+ }
+ return;
+ }
+
// For non-primitive types, use the standard serializer path
FieldType result =
Serializer<FieldType>::read(ctx, remote_ref_mode, read_type);
@@ -2540,11 +3159,13 @@ void read_single_field_by_index_compatible(T &obj, ReadContext &ctx,
/// Helper to dispatch field reading by field_id in compatible mode.
/// Uses fold expression with short-circuit to avoid lambda overhead.
/// Sets handled=true if field was matched.
-/// @param remote_type_id The type_id from the remote schema (for encoding)
+/// @param remote_field_type The field type tree from the remote schema.
template <typename T, size_t... Indices>
-FORY_ALWAYS_INLINE void dispatch_compatible_field_read_impl(
- T &obj, ReadContext &ctx, int16_t field_id, RefMode remote_ref_mode,
- uint32_t remote_type_id, bool &handled, std::index_sequence<Indices...>) {
+FORY_ALWAYS_INLINE void
+dispatch_compatible_field_read_impl(T &obj, ReadContext &ctx, int16_t field_id,
+ const FieldType &remote_field_type,
+ bool &handled,
+ std::index_sequence<Indices...>) {
using Helpers = CompileTimeFieldHelpers<T>;
// Short-circuit fold: stops at first match
@@ -2552,8 +3173,8 @@ FORY_ALWAYS_INLINE void dispatch_compatible_field_read_impl(
(void)((static_cast<int16_t>(Indices) == field_id
? (handled = true,
read_single_field_by_index_compatible<
- Helpers::sorted_indices[Indices]>(
- obj, ctx, remote_ref_mode, remote_type_id),
+ Helpers::sorted_indices[Indices]>(obj, ctx,
+ remote_field_type),
true)
: false) ||
...);
@@ -2667,7 +3288,7 @@ FORY_ALWAYS_INLINE void read_single_fixed_field(T &obj, Buffer &buffer,
using FieldType = unwrap_field_t<RawFieldType>;
FieldType result =
read_fixed_primitive_at<FieldType>(buffer, base_offset + field_offset);
- // Assign to field (handle fory::field<> wrapper if needed)
+ // Assign to field.
if constexpr (is_fory_field_v<RawFieldType>) {
(obj.*field_ptr).value = result;
} else {
@@ -2753,7 +3374,7 @@ FORY_ALWAYS_INLINE void read_single_varint_field(T &obj, Buffer &buffer,
result = read_varint_at<FieldType>(buffer, offset);
}
- // Assign to field (handle fory::field<> wrapper if needed)
+ // Assign to field.
if constexpr (is_fory_field_v<RawFieldType>) {
(obj.*field_ptr).value = result;
} else {
@@ -2926,11 +3547,11 @@ void read_struct_fields_compatible(T &obj, ReadContext &ctx,
// Dispatch to the correct local field by field_id
// Uses fold expression with short-circuit - no lambda overhead
- // Pass remote type_id for correct encoding in compatible mode
+ // Pass remote field type for correct encoding and ref metadata.
bool handled = false;
- dispatch_compatible_field_read_impl<T>(
- obj, ctx, field_id, remote_ref_mode, remote_field.field_type.type_id,
- handled, std::index_sequence<Indices...>{});
+ dispatch_compatible_field_read_impl<T>(obj, ctx, field_id,
+ remote_field.field_type, handled,
+ std::index_sequence<Indices...>{});
if (!handled) {
// Shouldn't happen if TypeMeta::assign_field_ids worked correctly
diff --git a/cpp/fory/serialization/struct_test.cc b/cpp/fory/serialization/struct_test.cc
index 7330ee6bc..83ab3e0c0 100644
--- a/cpp/fory/serialization/struct_test.cc
+++ b/cpp/fory/serialization/struct_test.cc
@@ -88,24 +88,10 @@ struct FieldConfigTaggedStruct {
bool operator==(const FieldConfigTaggedStruct &other) const {
return a == other.a && b == other.b && c == other.c;
}
- FORY_STRUCT(FieldConfigTaggedStruct, a, b, c);
+ FORY_STRUCT(FieldConfigTaggedStruct, (a, fory::F(1)), (b, fory::F(2)),
+ (c, fory::F(3)));
};
-struct FieldTagsTaggedStruct {
- int32_t a;
- int64_t b;
- std::string c;
-
- bool operator==(const FieldTagsTaggedStruct &other) const {
- return a == other.a && b == other.b && c == other.c;
- }
- FORY_STRUCT(FieldTagsTaggedStruct, a, b, c);
-};
-
-FORY_FIELD_CONFIG(FieldConfigTaggedStruct, (a, fory::F().id(1)),
- (b, fory::F().id(2)), (c, fory::F().id(3)));
-FORY_FIELD_TAGS(FieldTagsTaggedStruct, (a, 1), (b, 2), (c, 3));
-
class PrivateFieldsStruct {
public:
PrivateFieldsStruct() = default;
@@ -242,6 +228,15 @@ struct VectorStruct {
FORY_STRUCT(VectorStruct, numbers, strings, points);
};
+struct VectorBoolStruct {
+ std::vector<bool> flags;
+
+ bool operator==(const VectorBoolStruct &other) const {
+ return flags == other.flags;
+ }
+ FORY_STRUCT(VectorBoolStruct, flags);
+};
+
struct NamedItem {
int32_t id;
std::string name;
@@ -274,6 +269,41 @@ struct NestedContainerStruct {
FORY_STRUCT(NestedContainerStruct, matrix, grouped_numbers);
};
+namespace T = fory::T;
+
+struct NestedAnnotatedStruct {
+ std::map<uint32_t, std::vector<int64_t>> map;
+
+ bool operator==(const NestedAnnotatedStruct &other) const {
+ return map == other.map;
+ }
+ FORY_STRUCT(NestedAnnotatedStruct,
+ (map,
+ fory::F().map().key(T::varint()).value(T::list(T::tagged()))));
+};
+
+struct PartialMapAnnotatedStruct {
+ std::map<uint32_t, std::vector<int64_t>> key_only;
+ std::map<uint32_t, std::vector<int64_t>> value_only;
+
+ bool operator==(const PartialMapAnnotatedStruct &other) const {
+ return key_only == other.key_only && value_only == other.value_only;
+ }
+ FORY_STRUCT(PartialMapAnnotatedStruct,
+ (key_only, fory::F().map().key(T::varint())),
+ (value_only, fory::F().map().value(T::list(T::tagged()))));
+};
+
+struct OptionalNestedAnnotatedStruct {
+ std::optional<std::vector<uint32_t>> values;
+
+ bool operator==(const OptionalNestedAnnotatedStruct &other) const {
+ return values == other.values;
+ }
+ FORY_STRUCT(OptionalNestedAnnotatedStruct,
+ (values, fory::F().inner(T::list(T::varint()))));
+};
+
// Optional fields
struct OptionalFieldsStruct {
std::string name;
@@ -439,8 +469,12 @@ inline void register_all_test_types(Fory &fory) {
fory.register_struct<BoundingBox>(type_id++);
fory.register_struct<Scene>(type_id++);
fory.register_struct<VectorStruct>(type_id++);
+ fory.register_struct<VectorBoolStruct>(type_id++);
fory.register_struct<MapStruct>(type_id++);
fory.register_struct<NestedContainerStruct>(type_id++);
+ fory.register_struct<NestedAnnotatedStruct>(type_id++);
+ fory.register_struct<PartialMapAnnotatedStruct>(type_id++);
+ fory.register_struct<OptionalNestedAnnotatedStruct>(type_id++);
fory.register_struct<OptionalFieldsStruct>(type_id++);
fory.register_struct<EnumStruct>(type_id++);
fory.register_struct<UserProfile>(type_id++);
@@ -455,6 +489,19 @@ inline void register_all_test_types(Fory &fory) {
fory.register_enum<Status>(type_id++);
}
+inline FieldType make_test_field_type(TypeId type_id,
+ std::vector<FieldType> generics = {}) {
+ return FieldType(static_cast<uint32_t>(type_id), false, false,
+ std::move(generics));
+}
+
+inline FieldInfo make_test_field_info(std::string name, int16_t field_id,
+ FieldType field_type) {
+ FieldInfo info(std::move(name), std::move(field_type));
+ info.field_id = field_id;
+ return info;
+}
+
template <typename T> void test_roundtrip(const T &original) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
register_all_test_types(fory);
@@ -500,28 +547,6 @@ TEST(StructComprehensiveTest, ManyFieldsStruct) {
-9223372036854775807LL - 1, -1.0f, -1.0, ""});
}
-TEST(StructComprehensiveTest, FieldTagsMatchFieldConfigSize) {
- FieldConfigTaggedStruct config_obj{1, 2, "config"};
- FieldTagsTaggedStruct tags_obj{1, 2, "config"};
-
- auto fory_config =
- Fory::builder().xlang(true).compatible(true).track_ref(false).build();
- auto fory_tags =
- Fory::builder().xlang(true).compatible(true).track_ref(false).build();
-
- ASSERT_TRUE(fory_config.register_struct<FieldConfigTaggedStruct>(101).ok());
- ASSERT_TRUE(fory_tags.register_struct<FieldTagsTaggedStruct>(101).ok());
-
- auto config_bytes = fory_config.serialize(config_obj);
- ASSERT_TRUE(config_bytes.ok())
- << "Serialization failed: " << config_bytes.error().to_string();
- auto tags_bytes = fory_tags.serialize(tags_obj);
- ASSERT_TRUE(tags_bytes.ok())
- << "Serialization failed: " << tags_bytes.error().to_string();
-
- EXPECT_EQ(config_bytes->size(), tags_bytes->size());
-}
-
TEST(StructComprehensiveTest, PrivateFieldsStruct) {
test_roundtrip(PrivateFieldsStruct{42, "secret", {1, 2, 3}});
}
@@ -582,6 +607,10 @@ TEST(StructComprehensiveTest, VectorStructMultiple) {
test_roundtrip(VectorStruct{{1, 2, 3}, {"foo", "bar"}, {{0, 0}, {10, 10}}});
}
+TEST(StructComprehensiveTest, VectorBoolStruct) {
+ test_roundtrip(VectorBoolStruct{{true, false, false, true}});
+}
+
TEST(StructComprehensiveTest, NamedStructElementTypeInfo) {
std::vector<NamedItem> items{{1, "alpha"}, {2, "beta"}};
@@ -615,6 +644,149 @@ TEST(StructComprehensiveTest, NestedContainers) {
test_roundtrip(NestedContainerStruct{{{1, 2}, {3, 4}}, {{"a", {10, 20}}}});
}
+TEST(StructComprehensiveTest, NestedAnnotatedContainers) {
+ NestedAnnotatedStruct obj{{{1, {10, -20, 30}}, {2, {40, 50}}}};
+ test_roundtrip(obj);
+
+ auto fory =
+ Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+ ASSERT_TRUE(fory.register_struct<NestedAnnotatedStruct>(601).ok());
+ ASSERT_TRUE(fory.serialize(obj).ok());
+ TypeMeta meta =
+ fory.type_resolver().clone_struct_meta<NestedAnnotatedStruct>();
+ const auto &fields = meta.get_field_infos();
+ ASSERT_EQ(fields.size(), 1);
+ const auto &field_type = fields[0].field_type;
+ ASSERT_EQ(field_type.type_id, static_cast<uint32_t>(TypeId::MAP));
+ ASSERT_EQ(field_type.generics.size(), 2);
+ EXPECT_EQ(field_type.generics[0].type_id,
+ static_cast<uint32_t>(TypeId::VAR_UINT32));
+ ASSERT_EQ(field_type.generics[1].type_id,
+ static_cast<uint32_t>(TypeId::LIST));
+ ASSERT_EQ(field_type.generics[1].generics.size(), 1);
+ EXPECT_EQ(field_type.generics[1].generics[0].type_id,
+ static_cast<uint32_t>(TypeId::TAGGED_INT64));
+}
+
+TEST(StructComprehensiveTest, PartialMapAnnotations) {
+ PartialMapAnnotatedStruct obj{
+ {{1, {10, 20}}, {2, {30}}},
+ {{3, {40, -50}}, {4, {60}}},
+ };
+ test_roundtrip(obj);
+
+ auto fory =
+ Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+ ASSERT_TRUE(fory.register_struct<PartialMapAnnotatedStruct>(602).ok());
+ ASSERT_TRUE(fory.serialize(obj).ok());
+ TypeMeta meta =
+ fory.type_resolver().clone_struct_meta<PartialMapAnnotatedStruct>();
+ const auto &fields = meta.get_field_infos();
+ ASSERT_EQ(fields.size(), 2);
+ const FieldInfo *key_only = nullptr;
+ const FieldInfo *value_only = nullptr;
+ for (const auto &field : fields) {
+ if (field.field_name == "key_only") {
+ key_only = &field;
+ } else if (field.field_name == "value_only") {
+ value_only = &field;
+ }
+ }
+ ASSERT_NE(key_only, nullptr);
+ ASSERT_NE(value_only, nullptr);
+ EXPECT_EQ(key_only->field_type.generics[0].type_id,
+ static_cast<uint32_t>(TypeId::VAR_UINT32));
+ EXPECT_EQ(value_only->field_type.generics[1].generics[0].type_id,
+ static_cast<uint32_t>(TypeId::TAGGED_INT64));
+}
+
+TEST(StructComprehensiveTest, OptionalNestedAnnotation) {
+ test_roundtrip(
+ OptionalNestedAnnotatedStruct{{std::vector<uint32_t>{1, 2, 3}}});
+ test_roundtrip(OptionalNestedAnnotatedStruct{std::nullopt});
+
+ auto fory =
+ Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+ ASSERT_TRUE(fory.register_struct<OptionalNestedAnnotatedStruct>(603).ok());
+ ASSERT_TRUE(
+ fory.serialize(OptionalNestedAnnotatedStruct{{std::vector<uint32_t>{1}}})
+ .ok());
+ TypeMeta meta =
+ fory.type_resolver().clone_struct_meta<OptionalNestedAnnotatedStruct>();
+ const auto &fields = meta.get_field_infos();
+ ASSERT_EQ(fields.size(), 1);
+ EXPECT_TRUE(fields[0].field_type.nullable);
+ ASSERT_EQ(fields[0].field_type.type_id, static_cast<uint32_t>(TypeId::LIST));
+ ASSERT_EQ(fields[0].field_type.generics.size(), 1);
+ EXPECT_EQ(fields[0].field_type.generics[0].type_id,
+ static_cast<uint32_t>(TypeId::VAR_UINT32));
+}
+
+TEST(StructComprehensiveTest,
+ FieldTypeCompatibleFingerprintNormalizesEncoding) {
+ FieldType fixed_i32 = make_test_field_type(TypeId::INT32);
+ FieldType var_i32 = make_test_field_type(TypeId::VARINT32);
+ EXPECT_TRUE(field_types_compatible(fixed_i32, var_i32));
+ EXPECT_EQ(fixed_i32.compatible_fingerprint, var_i32.compatible_fingerprint);
+
+ FieldType fixed_list =
+ make_test_field_type(TypeId::LIST, {make_test_field_type(TypeId::INT32)});
+ FieldType var_list = make_test_field_type(
+ TypeId::LIST, {make_test_field_type(TypeId::VARINT32)});
+ EXPECT_TRUE(field_types_compatible(fixed_list, var_list));
+
+ FieldType int64_list = make_test_field_type(
+ TypeId::LIST, {make_test_field_type(TypeId::VARINT64)});
+ EXPECT_FALSE(field_types_compatible(fixed_list, int64_list));
+
+ EXPECT_TRUE(
+ field_types_compatible(make_test_field_type(TypeId::BINARY),
+ make_test_field_type(TypeId::UINT8_ARRAY)));
+}
+
+TEST(StructComprehensiveTest,
+ AssignFieldIdsRejectsIncompatibleTaggedNestedTypes) {
+ TypeMeta local_type;
+ local_type.field_infos = {make_test_field_info(
+ "items", 7,
+ make_test_field_type(TypeId::LIST,
+ {make_test_field_type(TypeId::VAR_UINT32)}))};
+
+ std::vector<FieldInfo> incompatible_remote = {make_test_field_info(
+ "items", 7,
+ make_test_field_type(TypeId::MAP,
+ {make_test_field_type(TypeId::VAR_UINT32),
+ make_test_field_type(TypeId::VAR_UINT32)}))};
+ TypeMeta::assign_field_ids(&local_type, incompatible_remote);
+ EXPECT_EQ(incompatible_remote[0].field_id, -1);
+
+ std::vector<FieldInfo> compatible_remote = {make_test_field_info(
+ "items", 7,
+ make_test_field_type(TypeId::LIST,
+ {make_test_field_type(TypeId::UINT32)}))};
+ TypeMeta::assign_field_ids(&local_type, compatible_remote);
+ EXPECT_EQ(compatible_remote[0].field_id, 0);
+
+ TypeMeta name_mode_local;
+ name_mode_local.field_infos = {make_test_field_info(
+ "items", -1,
+ make_test_field_type(TypeId::LIST,
+ {make_test_field_type(TypeId::VAR_UINT32)}))};
+ std::vector<FieldInfo> mixed_mode_remote = {make_test_field_info(
+ "items", 7,
+ make_test_field_type(TypeId::LIST,
+ {make_test_field_type(TypeId::UINT32)}))};
+ TypeMeta::assign_field_ids(&name_mode_local, mixed_mode_remote);
+ EXPECT_EQ(mixed_mode_remote[0].field_id, -1);
+
+ std::vector<FieldInfo> name_remote = {make_test_field_info(
+ "items", -1,
+ make_test_field_type(TypeId::LIST,
+ {make_test_field_type(TypeId::UINT32)}))};
+ TypeMeta::assign_field_ids(&local_type, name_remote);
+ EXPECT_EQ(name_remote[0].field_id, -1);
+}
+
TEST(StructComprehensiveTest, OptionalFieldsAllEmpty) {
test_roundtrip(
OptionalFieldsStruct{"John", std::nullopt, std::nullopt, std::nullopt});
diff --git a/cpp/fory/serialization/type_resolver.cc b/cpp/fory/serialization/type_resolver.cc
index b17f959fb..563afb6b6 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -112,12 +112,12 @@ Result<FieldType, Error> FieldType::read_from(Buffer &buffer, bool read_flag,
if (tid == static_cast<uint32_t>(TypeId::LIST) ||
tid == static_cast<uint32_t>(TypeId::SET)) {
FORY_TRY(generic, FieldType::read_from(buffer, true, false));
- ft.generics.push_back(std::move(generic));
+ ft.add_generic(std::move(generic));
} else if (tid == static_cast<uint32_t>(TypeId::MAP)) {
FORY_TRY(key, FieldType::read_from(buffer, true, false));
FORY_TRY(val, FieldType::read_from(buffer, true, false));
- ft.generics.push_back(std::move(key));
- ft.generics.push_back(std::move(val));
+ ft.add_generic(std::move(key));
+ ft.add_generic(std::move(val));
}
return ft;
@@ -778,8 +778,6 @@ bool is_compress(uint32_t type_id) {
type_id == static_cast<uint32_t>(TypeId::TAGGED_UINT64);
}
-bool field_type_needs_user_type_id(uint32_t) { return false; }
-
std::string field_sort_key(const FieldInfo &field) {
if (field.field_id >= 0) {
return std::to_string(field.field_id);
@@ -949,80 +947,42 @@ void TypeMeta::assign_field_ids(const TypeMeta *local_type,
local_field_id_map.emplace(local_fields[i].field_id, i);
}
}
+ const bool local_uses_field_ids = !local_field_id_map.empty();
+ bool remote_uses_field_ids = false;
+ for (const auto &remote_field : remote_fields) {
+ if (remote_field.field_id >= 0) {
+ remote_uses_field_ids = true;
+ break;
+ }
+ }
+
+ if (local_uses_field_ids || remote_uses_field_ids) {
+ for (auto &remote_field : remote_fields) {
+ size_t local_index = static_cast<size_t>(-1);
+ if (local_uses_field_ids && remote_field.field_id >= 0) {
+ auto id_it = local_field_id_map.find(remote_field.field_id);
+ if (id_it != local_field_id_map.end() &&
+ field_types_compatible(local_fields[id_it->second].field_type,
+ remote_field.field_type)) {
+ local_index = id_it->second;
+ }
+ }
+ remote_field.field_id = local_index == static_cast<size_t>(-1)
+ ? -1
+ : static_cast<int16_t>(local_index);
+ }
+ return;
+ }
// Track which local fields have already been matched so that each
// local field is bound to at most one remote field when we fall
// back to type-based matching.
std::vector<bool> used(local_fields.size(), false);
- // Normalize user-defined type IDs so that different encodings of the
- // same logical category (STRUCT vs COMPATIBLE_STRUCT, ENUM vs
- // NAMED_ENUM, EXT vs NAMED_EXT, and their ID-based variants) are
- // treated as equal for schema-evolution matching.
- auto normalize_type_id = [](uint32_t tid) {
- switch (static_cast<TypeId>(tid)) {
- case TypeId::STRUCT:
- case TypeId::COMPATIBLE_STRUCT:
- case TypeId::NAMED_STRUCT:
- case TypeId::NAMED_COMPATIBLE_STRUCT:
- case TypeId::UNKNOWN:
- // UNKNOWN is used for polymorphic types (e.g., Java interfaces) and
- // should match STRUCT for cross-language schema evolution.
- return static_cast<uint32_t>(TypeId::STRUCT);
- case TypeId::ENUM:
- case TypeId::NAMED_ENUM:
- return static_cast<uint32_t>(TypeId::ENUM);
- case TypeId::EXT:
- case TypeId::NAMED_EXT:
- return static_cast<uint32_t>(TypeId::EXT);
- case TypeId::BINARY:
- case TypeId::INT8_ARRAY:
- case TypeId::UINT8_ARRAY:
- return static_cast<uint32_t>(TypeId::BINARY);
- default:
- return tid;
- }
- };
-
- // Recursive logical type comparison that ignores language-specific
- // encoding details (such as embedded user IDs or named vs unnamed
- // variants) while still matching full generic structure.
- std::function<bool(const FieldType &, const FieldType &)> types_match;
- types_match = [&](const FieldType &a, const FieldType &b) -> bool {
- if (normalize_type_id(a.type_id) != normalize_type_id(b.type_id)) {
- return false;
- }
- if (field_type_needs_user_type_id(a.type_id) ||
- field_type_needs_user_type_id(b.type_id)) {
- if ((a.user_type_id != kInvalidUserTypeId ||
- b.user_type_id != kInvalidUserTypeId) &&
- a.user_type_id != b.user_type_id) {
- return false;
- }
- }
- if (a.generics.size() != b.generics.size()) {
- return false;
- }
- for (size_t i = 0; i < a.generics.size(); ++i) {
- if (!types_match(a.generics[i], b.generics[i])) {
- return false;
- }
- }
- return true;
- };
-
// For each remote field, assign field ID (sorted index in local schema)
for (auto &remote_field : remote_fields) {
size_t local_index = static_cast<size_t>(-1);
- // 0) If remote field carries a tag ID, map directly by ID.
- if (remote_field.field_id >= 0) {
- auto id_it = local_field_id_map.find(remote_field.field_id);
- if (id_it != local_field_id_map.end()) {
- local_index = id_it->second;
- }
- }
-
// 1) Try exact name + type match first (fast path for same-language
// schemas and most C++-only cases).
if (local_index == static_cast<size_t>(-1)) {
@@ -1030,7 +990,8 @@ void TypeMeta::assign_field_ids(const TypeMeta *local_type,
if (it != local_field_index_map.end()) {
size_t idx = it->second;
const FieldInfo &local_field = local_fields[idx];
- if (types_match(remote_field.field_type, local_field.field_type)) {
+ if (field_types_compatible(local_field.field_type,
+ remote_field.field_type)) {
local_index = idx;
}
}
@@ -1043,7 +1004,8 @@ void TypeMeta::assign_field_ids(const TypeMeta *local_type,
if (used[i]) {
continue;
}
- if (types_match(remote_field.field_type, local_fields[i].field_type)) {
+ if (field_types_compatible(local_fields[i].field_type,
+ remote_field.field_type)) {
local_index = i;
break;
}
@@ -1208,7 +1170,6 @@ std::string TypeMeta::compute_struct_fingerprint(
}
fingerprint.append(std::to_string(effective_type_id));
fingerprint.push_back(',');
- // Use field-level ref tracking flag from FORY_FIELD_TAGS or fory::field<>
fingerprint.push_back(fi.field_type.track_ref ? '1' : '0');
fingerprint.push_back(',');
fingerprint.append(fi.field_type.nullable ? "1;" : "0;");
diff --git a/cpp/fory/serialization/type_resolver.h b/cpp/fory/serialization/type_resolver.h
index e5c746781..c03970f98 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -24,7 +24,6 @@
#include <array>
#include <cstdint>
#include <deque>
-#include <functional>
#include <list>
#include <map>
#include <memory>
@@ -110,17 +109,40 @@ public:
bool track_ref;
RefMode ref_mode; // Precomputed from nullable and track_ref
std::vector<FieldType> generics;
+ uint64_t compatible_fingerprint;
+
+ static uint64_t
+ compute_compatible_fingerprint(uint32_t tid,
+ const std::vector<FieldType> &generic_types);
FieldType()
: type_id(0), user_type_id(kInvalidUserTypeId), nullable(false),
- track_ref(false), ref_mode(RefMode::None) {}
+ track_ref(false), ref_mode(RefMode::None), generics(),
+ compatible_fingerprint(
+ compute_compatible_fingerprint(type_id, generics)) {}
FieldType(uint32_t tid, bool null, bool ref_track = false,
std::vector<FieldType> gens = {},
uint32_t user_tid = kInvalidUserTypeId)
: type_id(tid), user_type_id(user_tid), nullable(null),
track_ref(ref_track), ref_mode(make_ref_mode(null, ref_track)),
- generics(std::move(gens)) {}
+ generics(std::move(gens)),
+ compatible_fingerprint(
+ compute_compatible_fingerprint(type_id, generics)) {}
+
+ void refresh_compatible_fingerprint() {
+ compatible_fingerprint = compute_compatible_fingerprint(type_id, generics);
+ }
+
+ void set_type_id(uint32_t tid) {
+ type_id = tid;
+ refresh_compatible_fingerprint();
+ }
+
+ void add_generic(FieldType generic) {
+ generics.push_back(std::move(generic));
+ refresh_compatible_fingerprint();
+ }
/// write field type to buffer
/// @param buffer Target buffer
@@ -147,6 +169,70 @@ public:
bool operator!=(const FieldType &other) const { return !(*this == other); }
};
+inline uint32_t compatible_fingerprint_type_id(uint32_t type_id) {
+ switch (static_cast<TypeId>(type_id)) {
+ case TypeId::STRUCT:
+ case TypeId::COMPATIBLE_STRUCT:
+ case TypeId::NAMED_STRUCT:
+ case TypeId::NAMED_COMPATIBLE_STRUCT:
+ case TypeId::UNKNOWN:
+ return static_cast<uint32_t>(TypeId::STRUCT);
+ case TypeId::ENUM:
+ case TypeId::NAMED_ENUM:
+ return static_cast<uint32_t>(TypeId::ENUM);
+ case TypeId::EXT:
+ case TypeId::NAMED_EXT:
+ return static_cast<uint32_t>(TypeId::EXT);
+ case TypeId::BINARY:
+ case TypeId::INT8_ARRAY:
+ case TypeId::UINT8_ARRAY:
+ return static_cast<uint32_t>(TypeId::BINARY);
+ case TypeId::INT32:
+ case TypeId::VARINT32:
+ return static_cast<uint32_t>(TypeId::VARINT32);
+ case TypeId::INT64:
+ case TypeId::VARINT64:
+ case TypeId::TAGGED_INT64:
+ return static_cast<uint32_t>(TypeId::VARINT64);
+ case TypeId::UINT32:
+ case TypeId::VAR_UINT32:
+ return static_cast<uint32_t>(TypeId::VAR_UINT32);
+ case TypeId::UINT64:
+ case TypeId::VAR_UINT64:
+ case TypeId::TAGGED_UINT64:
+ return static_cast<uint32_t>(TypeId::VAR_UINT64);
+ default:
+ return type_id;
+ }
+}
+
+inline uint64_t FieldType::compute_compatible_fingerprint(
+ uint32_t tid, const std::vector<FieldType> &generic_types) {
+ uint64_t hash = 14695981039346656037ULL;
+ hash = fnv1a_64_combine(hash, compatible_fingerprint_type_id(tid));
+ hash = fnv1a_64_combine(hash, static_cast<uint64_t>(generic_types.size()));
+ for (const auto &generic : generic_types) {
+ hash = fnv1a_64_combine(hash, generic.compatible_fingerprint);
+ }
+ return hash;
+}
+
+inline bool collection_type_allows_empty_generic_fallback(uint32_t type_id) {
+ return type_id == static_cast<uint32_t>(TypeId::LIST) ||
+ type_id == static_cast<uint32_t>(TypeId::SET) ||
+ type_id == static_cast<uint32_t>(TypeId::MAP);
+}
+
+inline bool field_types_compatible(const FieldType &local,
+ const FieldType &remote) {
+ if (local.compatible_fingerprint == remote.compatible_fingerprint) {
+ return true;
+ }
+ return local.type_id == remote.type_id &&
+ collection_type_allows_empty_generic_fallback(local.type_id) &&
+ (local.generics.empty() || remote.generics.empty());
+}
+
// ============================================================================
// FieldInfo - Field metadata (name, type, id)
// ============================================================================
@@ -377,7 +463,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_vector_v<decay_t<T>>>> {
if constexpr (vec_type_id == TypeId::LIST) {
FieldType elem = FieldTypeBuilder<Element>::build(false);
FieldType ft(to_type_id(vec_type_id), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else {
return FieldType(to_type_id(vec_type_id), nullable);
@@ -392,7 +478,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_list_v<decay_t<T>>>> {
static FieldType build(bool nullable) {
FieldType elem = FieldTypeBuilder<Element>::build(false);
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
}
};
@@ -404,7 +490,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_deque_v<decay_t<T>>>> {
static FieldType build(bool nullable) {
FieldType elem = FieldTypeBuilder<Element>::build(false);
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
}
};
@@ -416,7 +502,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_forward_list_v<decay_t<T>>>> {
static FieldType build(bool nullable) {
FieldType elem = FieldTypeBuilder<Element>::build(false);
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
}
};
@@ -428,7 +514,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_set_like_v<decay_t<T>>>> {
static FieldType build(bool nullable) {
FieldType elem = FieldTypeBuilder<Element>::build(false);
FieldType ft(to_type_id(Serializer<Set>::type_id), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
}
};
@@ -442,8 +528,8 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_map_like_v<decay_t<T>>>> {
FieldType key_ft = FieldTypeBuilder<Key>::build(false);
FieldType value_ft = FieldTypeBuilder<Value>::build(false);
FieldType ft(to_type_id(Serializer<Map>::type_id), nullable);
- ft.generics.push_back(std::move(key_ft));
- ft.generics.push_back(std::move(value_ft));
+ ft.add_generic(std::move(key_ft));
+ ft.add_generic(std::move(value_ft));
return ft;
}
};
@@ -463,7 +549,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_tuple_v<decay_t<T>>>> {
template <size_t... Is>
static void add_element_types(FieldType &ft, std::index_sequence<Is...>) {
- (ft.generics.push_back(
+ (ft.add_generic(
FieldTypeBuilder<std::tuple_element_t<Is, Tuple>>::build(false)),
...);
}
@@ -475,8 +561,7 @@ struct FieldTypeBuilder<T, std::enable_if_t<is_tuple_v<decay_t<T>>>> {
add_element_types(ft, std::make_index_sequence<tuple_size>{});
} else {
// Empty tuple: use UNKNOWN as stub element type for schema encoding
- ft.generics.push_back(
- FieldType(static_cast<uint32_t>(TypeId::UNKNOWN), false));
+ ft.add_generic(FieldType(static_cast<uint32_t>(TypeId::UNKNOWN), false));
}
return ft;
}
@@ -510,19 +595,200 @@ struct FieldTypeBuilder<
}
};
-inline bool field_type_needs_user_type_id(uint32_t) { return false; }
-
template <typename T>
Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
bool nullable);
+inline bool field_node_has_override(const FieldNodeSpec &spec,
+ int8_t node_index) {
+ return node_index >= 0 &&
+ (spec.kind_[node_index] != FieldNodeKind::Default ||
+ spec.encoding_[node_index] != Encoding::Default ||
+ spec.scalar_[node_index] != FieldScalarKind::Inferred);
+}
+
+template <typename Element>
+TypeId vector_type_id_for_spec(const FieldNodeSpec &spec, int8_t node_index,
+ TypeId default_type_id) {
+ if (node_index < 0 || spec.kind_[node_index] != FieldNodeKind::List) {
+ return default_type_id;
+ }
+ const int8_t child = spec.child0_[node_index];
+ if (child < 0 || spec.kind_[child] != FieldNodeKind::Scalar ||
+ spec.encoding_[child] != Encoding::Default) {
+ return TypeId::LIST;
+ }
+ if constexpr (std::is_same_v<decay_t<Element>, int8_t>) {
+ if (spec.scalar_[child] == FieldScalarKind::Int8) {
+ return TypeId::INT8_ARRAY;
+ }
+ } else if constexpr (std::is_same_v<decay_t<Element>, uint8_t>) {
+ if (spec.scalar_[child] == FieldScalarKind::UInt8) {
+ return TypeId::UINT8_ARRAY;
+ }
+ }
+ return TypeId::LIST;
+}
+
+template <typename FieldT>
+void apply_integer_encoding(FieldType &ft, const FieldNodeSpec &spec,
+ int8_t node_index) {
+ using Decayed = decay_t<FieldT>;
+ const Encoding enc =
+ node_index >= 0 ? spec.encoding_[node_index] : Encoding::Default;
+ if (enc == Encoding::Default) {
+ return;
+ }
+ if constexpr (std::is_same_v<Decayed, uint8_t>) {
+ ft.set_type_id(static_cast<uint32_t>(TypeId::UINT8));
+ } else if constexpr (std::is_same_v<Decayed, uint16_t>) {
+ ft.set_type_id(static_cast<uint32_t>(TypeId::UINT16));
+ } else if constexpr (std::is_same_v<Decayed, uint32_t>) {
+ ft.set_type_id(static_cast<uint32_t>(
+ enc == Encoding::Varint ? TypeId::VAR_UINT32 : TypeId::UINT32));
+ } else if constexpr (std::is_same_v<Decayed, uint64_t>) {
+ ft.set_type_id(static_cast<uint32_t>(enc == Encoding::Varint
+ ? TypeId::VAR_UINT64
+ : (enc == Encoding::Tagged
+ ? TypeId::TAGGED_UINT64
+ : TypeId::UINT64)));
+ } else if constexpr (std::is_same_v<Decayed, int32_t> ||
+ std::is_same_v<Decayed, int>) {
+ ft.set_type_id(static_cast<uint32_t>(
+ enc == Encoding::Fixed ? TypeId::INT32 : TypeId::VARINT32));
+ } else if constexpr (std::is_same_v<Decayed, int64_t> ||
+ std::is_same_v<Decayed, long long>) {
+ ft.set_type_id(static_cast<uint32_t>(
+ enc == Encoding::Fixed ? TypeId::INT64
+ : (enc == Encoding::Tagged ? TypeId::TAGGED_INT64
+ : TypeId::VARINT64)));
+ }
+}
+
+template <typename T>
+FieldType build_field_type_with_spec(bool nullable, const FieldNodeSpec &spec,
+ int8_t node_index = 0) {
+ using Decayed = decay_t<T>;
+ const FieldNodeKind node_kind =
+ node_index >= 0 ? spec.kind_[node_index] : FieldNodeKind::Default;
+
+ if constexpr (is_optional_v<Decayed>) {
+ using Inner = typename Decayed::value_type;
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FieldType inner = build_field_type_with_spec<Inner>(true, spec, child);
+ inner.nullable = true;
+ return inner;
+ } else if constexpr (is_shared_ptr_v<Decayed>) {
+ using Inner = typename Decayed::element_type;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FieldType inner = build_field_type_with_spec<Inner>(true, spec, child);
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (::fory::detail::is_shared_weak_v<Decayed>) {
+ using Inner = nullable_element_t<Decayed>;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FieldType inner = build_field_type_with_spec<Inner>(true, spec, child);
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (is_unique_ptr_v<Decayed>) {
+ using Inner = typename Decayed::element_type;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FieldType inner = build_field_type_with_spec<Inner>(true, spec, child);
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (is_vector_v<Decayed>) {
+ using Element = element_type_t<Decayed>;
+ constexpr TypeId default_type_id = Serializer<Decayed>::type_id;
+ const bool force_list = node_kind == FieldNodeKind::List;
+ const TypeId type_id =
+ vector_type_id_for_spec<Element>(spec, node_index, default_type_id);
+ if (type_id == TypeId::LIST) {
+ const int8_t child = force_list ? spec.child0_[node_index] : -1;
+ FieldType elem =
+ field_node_has_override(spec, child)
+ ? build_field_type_with_spec<Element>(false, spec, child)
+ : FieldTypeBuilder<Element>::build(false);
+ FieldType ft(to_type_id(TypeId::LIST), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ }
+ return FieldType(to_type_id(type_id), nullable);
+ } else if constexpr (is_list_v<Decayed> || is_deque_v<Decayed> ||
+ is_forward_list_v<Decayed>) {
+ using Element = typename Decayed::value_type;
+ const int8_t child =
+ node_kind == FieldNodeKind::List ? spec.child0_[node_index] : -1;
+ FieldType elem =
+ field_node_has_override(spec, child)
+ ? build_field_type_with_spec<Element>(false, spec, child)
+ : FieldTypeBuilder<Element>::build(false);
+ FieldType ft(to_type_id(TypeId::LIST), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ } else if constexpr (is_set_like_v<Decayed>) {
+ using Element = element_type_t<Decayed>;
+ const int8_t child =
+ node_kind == FieldNodeKind::Set ? spec.child0_[node_index] : -1;
+ FieldType elem =
+ field_node_has_override(spec, child)
+ ? build_field_type_with_spec<Element>(false, spec, child)
+ : FieldTypeBuilder<Element>::build(false);
+ FieldType ft(to_type_id(Serializer<Decayed>::type_id), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ } else if constexpr (is_map_like_v<Decayed>) {
+ using Key = key_type_t<Decayed>;
+ using Value = mapped_type_t<Decayed>;
+ const int8_t key_child =
+ node_kind == FieldNodeKind::Map ? spec.child0_[node_index] : -1;
+ const int8_t value_child =
+ node_kind == FieldNodeKind::Map ? spec.child1_[node_index] : -1;
+ FieldType key_ft =
+ field_node_has_override(spec, key_child)
+ ? build_field_type_with_spec<Key>(false, spec, key_child)
+ : FieldTypeBuilder<Key>::build(false);
+ FieldType value_ft =
+ field_node_has_override(spec, value_child)
+ ? build_field_type_with_spec<Value>(false, spec, value_child)
+ : FieldTypeBuilder<Value>::build(false);
+ FieldType ft(to_type_id(Serializer<Decayed>::type_id), nullable);
+ ft.add_generic(std::move(key_ft));
+ ft.add_generic(std::move(value_ft));
+ return ft;
+ } else {
+ FieldType ft = FieldTypeBuilder<Decayed>::build(nullable);
+ apply_integer_encoding<Decayed>(ft, spec, node_index);
+ return ft;
+ }
+}
+
template <typename Tuple, size_t Index>
Result<void, Error> add_tuple_element_types(TypeResolver &resolver,
FieldType &ft) {
if constexpr (Index < std::tuple_size_v<Tuple>) {
using Elem = std::tuple_element_t<Index, Tuple>;
FORY_TRY(elem_ft, build_field_type_with_resolver<Elem>(resolver, false));
- ft.generics.push_back(std::move(elem_ft));
+ ft.add_generic(std::move(elem_ft));
return add_tuple_element_types<Tuple, Index + 1>(resolver, ft);
} else {
return Result<void, Error>();
@@ -572,7 +838,7 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
if constexpr (vec_type_id == TypeId::LIST) {
FORY_TRY(elem, build_field_type_with_resolver<Element>(resolver, false));
FieldType ft(to_type_id(vec_type_id), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else {
return FieldType(to_type_id(vec_type_id), nullable);
@@ -581,26 +847,26 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
using Element = typename Decayed::value_type;
FORY_TRY(elem, build_field_type_with_resolver<Element>(resolver, false));
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else if constexpr (is_deque_v<Decayed>) {
using Element = typename Decayed::value_type;
FORY_TRY(elem, build_field_type_with_resolver<Element>(resolver, false));
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else if constexpr (is_forward_list_v<Decayed>) {
using Element = typename Decayed::value_type;
FORY_TRY(elem, build_field_type_with_resolver<Element>(resolver, false));
FieldType ft(to_type_id(TypeId::LIST), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else if constexpr (is_set_like_v<Decayed>) {
using Set = Decayed;
using Element = element_type_t<Set>;
FORY_TRY(elem, build_field_type_with_resolver<Element>(resolver, false));
FieldType ft(to_type_id(Serializer<Set>::type_id), nullable);
- ft.generics.push_back(std::move(elem));
+ ft.add_generic(std::move(elem));
return ft;
} else if constexpr (is_map_like_v<Decayed>) {
using Map = Decayed;
@@ -609,8 +875,8 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
FORY_TRY(key_ft, build_field_type_with_resolver<Key>(resolver, false));
FORY_TRY(val_ft, build_field_type_with_resolver<Value>(resolver, false));
FieldType ft(to_type_id(Serializer<Map>::type_id), nullable);
- ft.generics.push_back(std::move(key_ft));
- ft.generics.push_back(std::move(val_ft));
+ ft.add_generic(std::move(key_ft));
+ ft.add_generic(std::move(val_ft));
return ft;
} else if constexpr (is_string_view_v<Decayed>) {
using StringT = std::basic_string<typename Decayed::value_type,
@@ -622,8 +888,7 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
if constexpr (tuple_size > 0) {
FORY_RETURN_IF_ERROR((add_tuple_element_types<Decayed, 0>(resolver, ft)));
} else {
- ft.generics.push_back(
- FieldType(static_cast<uint32_t>(TypeId::UNKNOWN), false));
+ ft.add_generic(FieldType(static_cast<uint32_t>(TypeId::UNKNOWN), false));
}
return ft;
} else {
@@ -636,7 +901,7 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
if (resolved_type_id == static_cast<uint32_t>(TypeId::NAMED_ENUM)) {
resolved_type_id = static_cast<uint32_t>(TypeId::ENUM);
}
- ft.type_id = resolved_type_id;
+ ft.set_type_id(resolved_type_id);
}
} else {
FORY_TRY(info, get_type_info_with_resolver<Decayed>(resolver));
@@ -652,27 +917,149 @@ Result<FieldType, Error> build_field_type_with_resolver(TypeResolver &resolver,
default:
break;
}
- ft.type_id = resolved_type_id;
+ ft.set_type_id(resolved_type_id);
}
}
return ft;
}
}
+template <typename T>
+Result<FieldType, Error>
+build_field_type_with_resolver_and_spec(TypeResolver &resolver, bool nullable,
+ const FieldNodeSpec &spec,
+ int8_t node_index = 0) {
+ using Decayed = decay_t<T>;
+ const FieldNodeKind node_kind =
+ node_index >= 0 ? spec.kind_[node_index] : FieldNodeKind::Default;
+
+ if constexpr (is_optional_v<Decayed>) {
+ using Inner = typename Decayed::value_type;
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FORY_TRY(inner, (build_field_type_with_resolver_and_spec<Inner>(
+ resolver, true, spec, child)));
+ inner.nullable = true;
+ return inner;
+ } else if constexpr (is_shared_ptr_v<Decayed>) {
+ using Inner = typename Decayed::element_type;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FORY_TRY(inner, (build_field_type_with_resolver_and_spec<Inner>(
+ resolver, true, spec, child)));
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (::fory::detail::is_shared_weak_v<Decayed>) {
+ using Inner = nullable_element_t<Decayed>;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FORY_TRY(inner, (build_field_type_with_resolver_and_spec<Inner>(
+ resolver, true, spec, child)));
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (is_unique_ptr_v<Decayed>) {
+ using Inner = typename Decayed::element_type;
+ if constexpr (std::is_polymorphic_v<Inner>) {
+ return FieldType(to_type_id(TypeId::UNKNOWN), true);
+ } else {
+ const int8_t child = node_kind == FieldNodeKind::Inner
+ ? spec.child0_[node_index]
+ : node_index;
+ FORY_TRY(inner, (build_field_type_with_resolver_and_spec<Inner>(
+ resolver, true, spec, child)));
+ inner.nullable = true;
+ return inner;
+ }
+ } else if constexpr (is_vector_v<Decayed>) {
+ using Element = element_type_t<Decayed>;
+ constexpr TypeId default_type_id = Serializer<Decayed>::type_id;
+ const bool force_list = node_kind == FieldNodeKind::List;
+ const TypeId type_id =
+ vector_type_id_for_spec<Element>(spec, node_index, default_type_id);
+ if (type_id == TypeId::LIST) {
+ const int8_t child = force_list ? spec.child0_[node_index] : -1;
+ FORY_TRY(elem, (field_node_has_override(spec, child)
+ ? build_field_type_with_resolver_and_spec<Element>(
+ resolver, false, spec, child)
+ : build_field_type_with_resolver<Element>(resolver,
+ false)));
+ FieldType ft(to_type_id(TypeId::LIST), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ }
+ return FieldType(to_type_id(type_id), nullable);
+ } else if constexpr (is_list_v<Decayed> || is_deque_v<Decayed> ||
+ is_forward_list_v<Decayed>) {
+ using Element = typename Decayed::value_type;
+ const int8_t child =
+ node_kind == FieldNodeKind::List ? spec.child0_[node_index] : -1;
+ FORY_TRY(elem,
+ (field_node_has_override(spec, child)
+ ? build_field_type_with_resolver_and_spec<Element>(
+ resolver, false, spec, child)
+ : build_field_type_with_resolver<Element>(resolver, false)));
+ FieldType ft(to_type_id(TypeId::LIST), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ } else if constexpr (is_set_like_v<Decayed>) {
+ using Element = element_type_t<Decayed>;
+ const int8_t child =
+ node_kind == FieldNodeKind::Set ? spec.child0_[node_index] : -1;
+ FORY_TRY(elem,
+ (field_node_has_override(spec, child)
+ ? build_field_type_with_resolver_and_spec<Element>(
+ resolver, false, spec, child)
+ : build_field_type_with_resolver<Element>(resolver, false)));
+ FieldType ft(to_type_id(Serializer<Decayed>::type_id), nullable);
+ ft.add_generic(std::move(elem));
+ return ft;
+ } else if constexpr (is_map_like_v<Decayed>) {
+ using Key = key_type_t<Decayed>;
+ using Value = mapped_type_t<Decayed>;
+ const int8_t key_child =
+ node_kind == FieldNodeKind::Map ? spec.child0_[node_index] : -1;
+ const int8_t value_child =
+ node_kind == FieldNodeKind::Map ? spec.child1_[node_index] : -1;
+ FORY_TRY(key_ft,
+ (field_node_has_override(spec, key_child)
+ ? build_field_type_with_resolver_and_spec<Key>(
+ resolver, false, spec, key_child)
+ : build_field_type_with_resolver<Key>(resolver, false)));
+ FORY_TRY(value_ft,
+ (field_node_has_override(spec, value_child)
+ ? build_field_type_with_resolver_and_spec<Value>(
+ resolver, false, spec, value_child)
+ : build_field_type_with_resolver<Value>(resolver, false)));
+ FieldType ft(to_type_id(Serializer<Decayed>::type_id), nullable);
+ ft.add_generic(std::move(key_ft));
+ ft.add_generic(std::move(value_ft));
+ return ft;
+ } else {
+ FORY_TRY(ft, (build_field_type_with_resolver<Decayed>(resolver, nullable)));
+ apply_integer_encoding<Decayed>(ft, spec, node_index);
+ return ft;
+ }
+}
+
// Helper template functions to compute is_nullable and track_ref at compile
// time. These replace constexpr lambdas which have issues on MSVC.
template <typename ActualFieldType, typename T, size_t Index,
typename UnwrappedFieldType>
constexpr bool compute_is_nullable() {
- if constexpr (is_fory_field_v<ActualFieldType>) {
- return ActualFieldType::is_nullable;
- } else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::is_nullable;
- } else if constexpr (::fory::detail::has_field_config_v<T> &&
- ::fory::detail::GetFieldConfigEntry<T,
- Index>::has_entry &&
- ::fory::detail::GetFieldConfigEntry<T,
- Index>::nullable) {
+ if constexpr (::fory::detail::has_field_config_v<T> &&
+ ::fory::detail::GetFieldConfigEntry<T, Index>::has_entry &&
+ ::fory::detail::GetFieldConfigEntry<T, Index>::nullable) {
return true;
} else {
// Default: nullable if std::optional or smart pointers.
@@ -684,14 +1071,9 @@ constexpr bool compute_is_nullable() {
template <typename ActualFieldType, typename T, size_t Index>
constexpr bool compute_track_ref() {
- if constexpr (is_fory_field_v<ActualFieldType>) {
- return ActualFieldType::track_ref;
- } else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::track_ref;
- } else if constexpr (::fory::detail::has_field_config_v<T> &&
- ::fory::detail::GetFieldConfigEntry<T,
- Index>::has_entry &&
- ::fory::detail::GetFieldConfigEntry<T, Index>::ref) {
+ if constexpr (::fory::detail::has_field_config_v<T> &&
+ ::fory::detail::GetFieldConfigEntry<T, Index>::has_entry &&
+ ::fory::detail::GetFieldConfigEntry<T, Index>::ref) {
return true;
} else {
using UnwrappedFieldType = fory::unwrap_field_t<ActualFieldType>;
@@ -709,15 +1091,6 @@ constexpr int16_t compute_field_id() {
return config_id;
}
}
- if constexpr (is_fory_field_v<ActualFieldType>) {
- return field_tag_id_v<ActualFieldType>;
- }
- if constexpr (::fory::detail::has_field_tags_v<T>) {
- constexpr int16_t tag_id = ::fory::detail::GetFieldTagEntry<T, Index>::id;
- if constexpr (tag_id >= 0) {
- return tag_id;
- }
- }
return -1;
}
@@ -741,10 +1114,10 @@ struct unwrap_optional_inner<T, std::enable_if_t<is_optional_v<decay_t<T>>>> {
template <typename T>
using unwrap_optional_inner_t = typename unwrap_optional_inner<T>::type;
-// Helper to compute the correct type_id for unsigned types based on encoding
+// Helper to compute the correct type_id for unsigned types based on the
+// effective field spec.
template <typename FieldT, typename StructT, size_t Index>
constexpr uint32_t compute_unsigned_type_id() {
- // For unsigned types, check if FORY_FIELD_CONFIG specifies an encoding
if constexpr (::fory::detail::has_field_config_v<StructT>) {
constexpr auto enc =
::fory::detail::GetFieldConfigEntry<StructT, Index>::encoding;
@@ -770,7 +1143,7 @@ constexpr uint32_t compute_unsigned_type_id() {
}
}
}
- // Not an unsigned type with field config, use default
+ // Not an unsigned type with configured encoding; use the type default.
return 0;
}
@@ -817,42 +1190,17 @@ template <typename T, size_t Index> struct FieldInfoBuilder {
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using ActualFieldType =
std::remove_cv_t<std::remove_reference_t<RawFieldType>>;
- // unwrap fory::field<> to get the underlying type for FieldTypeBuilder
using UnwrappedFieldType = fory::unwrap_field_t<ActualFieldType>;
- // get nullable and track_ref from field tags (FORY_FIELD_TAGS or
- // fory::field<>)
constexpr bool is_nullable =
compute_is_nullable<ActualFieldType, T, Index, UnwrappedFieldType>();
constexpr bool track_ref = compute_track_ref<ActualFieldType, T, Index>();
constexpr int16_t field_id = compute_field_id<ActualFieldType, T, Index>();
- FieldType field_type = FieldTypeBuilder<UnwrappedFieldType>::build(false);
-
- // Override type_id for unsigned types based on encoding from
- // FORY_FIELD_CONFIG
- using InnerType = unwrap_optional_inner_t<UnwrappedFieldType>;
- constexpr uint32_t unsigned_tid =
- compute_unsigned_type_id<UnwrappedFieldType, T, Index>();
- if constexpr (unsigned_tid != 0 && is_unsigned_integer_v<InnerType>) {
- field_type.type_id = unsigned_tid;
- }
-
- // Override type_id for signed types based on encoding from
- // FORY_FIELD_CONFIG
- constexpr uint32_t signed_tid =
- compute_signed_type_id<UnwrappedFieldType, T, Index>();
- if constexpr (signed_tid != 0) {
- field_type.type_id = signed_tid;
- }
-
- if constexpr (::fory::detail::has_field_config_v<T>) {
- constexpr int16_t override_id =
- ::fory::detail::GetFieldConfigEntry<T, Index>::type_id_override;
- if constexpr (override_id >= 0) {
- field_type.type_id = static_cast<uint32_t>(override_id);
- }
- }
+ constexpr FieldNodeSpec spec =
+ ::fory::detail::GetFieldConfigEntry<T, Index>::spec;
+ FieldType field_type =
+ build_field_type_with_spec<UnwrappedFieldType>(false, spec);
// Override nullable and track_ref from field-level metadata
field_type.nullable = is_nullable;
@@ -862,9 +1210,9 @@ template <typename T, size_t Index> struct FieldInfoBuilder {
// DEBUG: Print field info for debugging fingerprint mismatch
std::cerr << "[xlang][debug] FieldInfoBuilder T=" << typeid(T).name()
<< " Index=" << Index << " field=" << field_name
- << " type_id=" << field_type.type_id << " has_tags="
- << ::fory::detail::has_field_tags_v<T> << " is_nullable="
- << is_nullable << " track_ref=" << track_ref << std::endl;
+ << " type_id=" << field_type.type_id
+ << " is_nullable=" << is_nullable << " track_ref=" << track_ref
+ << std::endl;
#endif
FieldInfo info(std::move(field_name), std::move(field_type));
info.field_id = field_id;
@@ -889,43 +1237,18 @@ template <typename T, size_t Index> struct FieldInfoBuilder {
typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
using ActualFieldType =
std::remove_cv_t<std::remove_reference_t<RawFieldType>>;
- // unwrap fory::field<> to get the underlying type for FieldTypeBuilder
using UnwrappedFieldType = fory::unwrap_field_t<ActualFieldType>;
- // get nullable and track_ref from field tags (FORY_FIELD_TAGS or
- // fory::field<>)
constexpr bool is_nullable =
compute_is_nullable<ActualFieldType, T, Index, UnwrappedFieldType>();
constexpr bool track_ref = compute_track_ref<ActualFieldType, T, Index>();
constexpr int16_t field_id = compute_field_id<ActualFieldType, T, Index>();
- FORY_TRY(field_type, build_field_type_with_resolver<UnwrappedFieldType>(
- resolver, false));
-
- // Override type_id for unsigned types based on encoding from
- // FORY_FIELD_CONFIG
- using InnerType = unwrap_optional_inner_t<UnwrappedFieldType>;
- constexpr uint32_t unsigned_tid =
- compute_unsigned_type_id<UnwrappedFieldType, T, Index>();
- if constexpr (unsigned_tid != 0 && is_unsigned_integer_v<InnerType>) {
- field_type.type_id = unsigned_tid;
- }
-
- // Override type_id for signed types based on encoding from
- // FORY_FIELD_CONFIG
- constexpr uint32_t signed_tid =
- compute_signed_type_id<UnwrappedFieldType, T, Index>();
- if constexpr (signed_tid != 0) {
- field_type.type_id = signed_tid;
- }
-
- if constexpr (::fory::detail::has_field_config_v<T>) {
- constexpr int16_t override_id =
- ::fory::detail::GetFieldConfigEntry<T, Index>::type_id_override;
- if constexpr (override_id >= 0) {
- field_type.type_id = static_cast<uint32_t>(override_id);
- }
- }
+ constexpr FieldNodeSpec spec =
+ ::fory::detail::GetFieldConfigEntry<T, Index>::spec;
+ FORY_TRY(field_type,
+ (build_field_type_with_resolver_and_spec<UnwrappedFieldType>(
+ resolver, false, spec)));
// Override nullable and track_ref from field-level metadata
field_type.nullable = is_nullable;
@@ -935,9 +1258,9 @@ template <typename T, size_t Index> struct FieldInfoBuilder {
// DEBUG: Print field info for debugging fingerprint mismatch
std::cerr << "[xlang][debug] FieldInfoBuilder T=" << typeid(T).name()
<< " Index=" << Index << " field=" << field_name
- << " type_id=" << field_type.type_id << " has_tags="
- << ::fory::detail::has_field_tags_v<T> << " is_nullable="
- << is_nullable << " track_ref=" << track_ref << std::endl;
+ << " type_id=" << field_type.type_id
+ << " is_nullable=" << is_nullable << " track_ref=" << track_ref
+ << std::endl;
#endif
FieldInfo info(std::move(field_name), std::move(field_type));
info.field_id = field_id;
diff --git a/cpp/fory/serialization/union_serializer.h b/cpp/fory/serialization/union_serializer.h
index c90d3d0d2..7d5820f4f 100644
--- a/cpp/fory/serialization/union_serializer.h
+++ b/cpp/fory/serialization/union_serializer.h
@@ -25,6 +25,7 @@
#include "fory/serialization/serializer.h"
#include "fory/serialization/serializer_traits.h"
#include "fory/serialization/skip.h"
+#include "fory/serialization/struct_serializer.h"
#include "fory/type/type.h"
#include "fory/util/error.h"
@@ -175,6 +176,78 @@ template <typename T>
using union_unwrap_optional_inner_t =
typename union_unwrap_optional_inner<T>::type;
+template <typename UnionT, size_t Index> struct UnionCaseSpecProvider {
+ static inline constexpr FieldNodeSpec spec =
+ UnionInfo<UnionT>::template Meta<Index>::value.spec_;
+};
+
+template <typename SpecProvider, int8_t NodeIndex = 0>
+constexpr FieldNodeKind union_node_kind() {
+ constexpr auto spec = SpecProvider::spec;
+ return NodeIndex >= 0 ? spec.kind_[NodeIndex] : FieldNodeKind::Default;
+}
+
+template <typename SpecProvider, int8_t NodeIndex = 0>
+constexpr Encoding union_node_encoding() {
+ constexpr auto spec = SpecProvider::spec;
+ return NodeIndex >= 0 ? spec.encoding_[NodeIndex] : Encoding::Default;
+}
+
+template <typename SpecProvider, int8_t NodeIndex = 0>
+constexpr bool union_node_has_override() {
+ constexpr auto spec = SpecProvider::spec;
+ return NodeIndex >= 0 &&
+ (spec.kind_[NodeIndex] != FieldNodeKind::Default ||
+ spec.encoding_[NodeIndex] != Encoding::Default ||
+ spec.scalar_[NodeIndex] != FieldScalarKind::Inferred);
+}
+
+template <typename SpecProvider, int8_t NodeIndex = 0>
+constexpr FieldScalarKind union_node_scalar() {
+ constexpr auto spec = SpecProvider::spec;
+ return NodeIndex >= 0 ? spec.scalar_[NodeIndex] : FieldScalarKind::Inferred;
+}
+
+template <typename SpecProvider, int8_t NodeIndex, int ChildSlot>
+constexpr int8_t union_node_child() {
+ constexpr auto spec = SpecProvider::spec;
+ if constexpr (ChildSlot == 0) {
+ return NodeIndex >= 0 ? spec.child0_[NodeIndex] : -1;
+ } else {
+ return NodeIndex >= 0 ? spec.child1_[NodeIndex] : -1;
+ }
+}
+
+template <typename ValueType, typename SpecProvider, int8_t NodeIndex>
+constexpr bool union_vector_primitive_array_spec() {
+ if constexpr (!is_vector_v<ValueType>) {
+ return false;
+ } else {
+ using Element = element_type_t<ValueType>;
+ if constexpr (!std::is_same_v<decay_t<Element>, int8_t> &&
+ !std::is_same_v<decay_t<Element>, uint8_t>) {
+ return false;
+ } else {
+ constexpr FieldNodeKind kind = union_node_kind<SpecProvider, NodeIndex>();
+ constexpr int8_t child = union_node_child<SpecProvider, NodeIndex, 0>();
+ if constexpr (kind != FieldNodeKind::List || child < 0) {
+ return false;
+ } else if constexpr (union_node_kind<SpecProvider, child>() !=
+ FieldNodeKind::Scalar ||
+ union_node_encoding<SpecProvider, child>() !=
+ Encoding::Default) {
+ return false;
+ } else if constexpr (std::is_same_v<decay_t<Element>, int8_t>) {
+ return union_node_scalar<SpecProvider, child>() ==
+ FieldScalarKind::Int8;
+ } else {
+ return union_node_scalar<SpecProvider, child>() ==
+ FieldScalarKind::UInt8;
+ }
+ }
+ }
+}
+
template <typename T>
constexpr uint32_t resolve_union_type_id(const ::fory::FieldMeta &meta) {
if (meta.type_id_override_ >= 0) {
@@ -218,6 +291,21 @@ constexpr uint32_t resolve_union_type_id(const ::fory::FieldMeta &meta) {
return static_cast<uint32_t>(Serializer<Inner>::type_id);
}
+template <typename T, typename SpecProvider>
+constexpr uint32_t
+resolve_union_type_id_for_spec(const ::fory::FieldMeta &meta) {
+ using Inner = union_unwrap_optional_inner_t<T>;
+ if constexpr (union_vector_primitive_array_spec<Inner, SpecProvider, 0>()) {
+ using Element = element_type_t<Inner>;
+ if constexpr (std::is_same_v<decay_t<Element>, int8_t>) {
+ return static_cast<uint32_t>(TypeId::INT8_ARRAY);
+ } else {
+ return static_cast<uint32_t>(TypeId::UINT8_ARRAY);
+ }
+ }
+ return resolve_union_type_id<T>(meta);
+}
+
template <typename T>
constexpr bool needs_manual_encoding(const ::fory::FieldMeta &meta) {
if (meta.type_id_override_ >= 0 ||
@@ -229,6 +317,362 @@ constexpr bool needs_manual_encoding(const ::fory::FieldMeta &meta) {
resolve_union_type_id<T>(meta);
}
+template <typename FieldType, typename SpecProvider, int8_t NodeIndex>
+FORY_ALWAYS_INLINE void write_union_configured_scalar(const FieldType &value,
+ WriteContext &ctx) {
+ constexpr Encoding enc = union_node_encoding<SpecProvider, NodeIndex>();
+ if constexpr (is_configurable_int_v<FieldType>) {
+ if constexpr (std::is_same_v<FieldType, uint32_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ ctx.write_var_uint32(value);
+ } else {
+ ctx.buffer().write_int32(static_cast<int32_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ ctx.write_var_uint64(value);
+ } else if constexpr (enc == Encoding::Tagged) {
+ ctx.write_tagged_uint64(value);
+ } else {
+ ctx.buffer().write_int64(static_cast<int64_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, int32_t> ||
+ std::is_same_v<FieldType, int>) {
+ if constexpr (enc == Encoding::Fixed) {
+ ctx.buffer().write_int32(static_cast<int32_t>(value));
+ } else {
+ ctx.write_var_int32(static_cast<int32_t>(value));
+ }
+ } else if constexpr (std::is_same_v<FieldType, int64_t> ||
+ std::is_same_v<FieldType, long long>) {
+ if constexpr (enc == Encoding::Fixed) {
+ ctx.buffer().write_int64(static_cast<int64_t>(value));
+ } else if constexpr (enc == Encoding::Tagged) {
+ ctx.write_tagged_int64(static_cast<int64_t>(value));
+ } else {
+ ctx.write_var_int64(static_cast<int64_t>(value));
+ }
+ } else {
+ Serializer<FieldType>::write_data(value, ctx);
+ }
+ } else {
+ Serializer<FieldType>::write_data(value, ctx);
+ }
+}
+
+template <typename FieldType, typename SpecProvider, int8_t NodeIndex>
+FORY_ALWAYS_INLINE FieldType read_union_configured_scalar(ReadContext &ctx) {
+ if constexpr (is_configurable_int_v<FieldType>) {
+ constexpr Encoding enc = union_node_encoding<SpecProvider, NodeIndex>();
+ if constexpr (std::is_same_v<FieldType, uint32_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
+ if constexpr (enc == Encoding::Varint) {
+ return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<FieldType>(ctx.read_tagged_uint64(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, int32_t> ||
+ std::is_same_v<FieldType, int>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_var_int32(ctx.error()));
+ } else if constexpr (std::is_same_v<FieldType, int64_t> ||
+ std::is_same_v<FieldType, long long>) {
+ if constexpr (enc == Encoding::Fixed) {
+ return static_cast<FieldType>(ctx.read_int64(ctx.error()));
+ } else if constexpr (enc == Encoding::Tagged) {
+ return static_cast<FieldType>(ctx.read_tagged_int64(ctx.error()));
+ }
+ return static_cast<FieldType>(ctx.read_var_int64(ctx.error()));
+ } else {
+ return Serializer<FieldType>::read_data(ctx);
+ }
+ } else {
+ return Serializer<FieldType>::read_data(ctx);
+ }
+}
+
+template <typename ValueType, typename SpecProvider, int8_t NodeIndex>
+void write_union_configured_value(const ValueType &value, WriteContext &ctx,
+ RefMode ref_mode, bool write_type,
+ bool has_generics);
+
+template <typename ValueType, typename SpecProvider, int8_t NodeIndex>
+ValueType read_union_configured_value(ReadContext &ctx, RefMode ref_mode,
+ bool read_type);
+
+template <typename Container, typename SpecProvider, int8_t ElemNode>
+void write_union_configured_list_data(const Container &coll,
+ WriteContext &ctx) {
+ using Elem = element_type_t<Container>;
+ ctx.write_var_uint32(static_cast<uint32_t>(coll.size()));
+ if (coll.empty()) {
+ return;
+ }
+ ctx.write_uint8(COLL_DECL_ELEMENT_TYPE | COLL_IS_SAME_TYPE);
+ for (const auto &elem : coll) {
+ if constexpr (ElemNode >= 0) {
+ write_union_configured_value<Elem, SpecProvider, ElemNode>(
+ elem, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Elem>::write_data(elem, ctx);
+ }
+ }
+}
+
+template <typename Container, typename SpecProvider, int8_t ElemNode>
+Container read_union_configured_list_data(ReadContext &ctx) {
+ using Elem = element_type_t<Container>;
+ uint32_t length = ctx.read_var_uint32(ctx.error());
+ Container result;
+ if constexpr (has_reserve_v<Container>) {
+ result.reserve(length);
+ }
+ if (FORY_PREDICT_FALSE(ctx.has_error()) || length == 0) {
+ return result;
+ }
+ uint8_t bitmap = ctx.read_uint8(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ const bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0;
+ const bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0;
+ if (is_same_type && !is_decl_type) {
+ (void)ctx.read_any_type_info(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ }
+ for (uint32_t i = 0; i < length; ++i) {
+ if constexpr (ElemNode >= 0) {
+ auto elem = read_union_configured_value<Elem, SpecProvider, ElemNode>(
+ ctx, RefMode::None, false);
+ collection_insert(result, std::move(elem));
+ } else {
+ auto elem = Serializer<Elem>::read_data(ctx);
+ collection_insert(result, std::move(elem));
+ }
+ }
+ return result;
+}
+
+template <typename MapType, typename SpecProvider, int8_t KeyNode,
+ int8_t ValueNode>
+void write_union_configured_map_data(const MapType &map, WriteContext &ctx) {
+ using Key = key_type_t<MapType>;
+ using Value = mapped_type_t<MapType>;
+ ctx.write_var_uint32(static_cast<uint32_t>(map.size()));
+ if (map.empty()) {
+ return;
+ }
+ size_t header_offset = 0;
+ uint8_t pair_counter = 0;
+ bool need_write_header = true;
+ for (const auto &[key, value] : map) {
+ if (need_write_header) {
+ ctx.enter_flush_barrier();
+ header_offset = ctx.buffer().writer_index();
+ ctx.write_uint16(0);
+ ctx.buffer().unsafe_put_byte(
+ header_offset, static_cast<uint8_t>(DECL_KEY_TYPE | DECL_VALUE_TYPE));
+ need_write_header = false;
+ }
+ if constexpr (KeyNode >= 0) {
+ write_union_configured_value<Key, SpecProvider, KeyNode>(
+ key, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Key>::write_data(key, ctx);
+ }
+ if constexpr (ValueNode >= 0) {
+ write_union_configured_value<Value, SpecProvider, ValueNode>(
+ value, ctx, RefMode::None, false, true);
+ } else {
+ Serializer<Value>::write_data(value, ctx);
+ }
+ ++pair_counter;
+ if (pair_counter == MAX_CHUNK_SIZE) {
+ write_chunk_size(ctx, header_offset, pair_counter);
+ ctx.exit_flush_barrier();
+ ctx.try_flush();
+ pair_counter = 0;
+ need_write_header = true;
+ }
+ }
+ if (pair_counter > 0) {
+ write_chunk_size(ctx, header_offset, pair_counter);
+ ctx.exit_flush_barrier();
+ ctx.try_flush();
+ }
+}
+
+template <typename MapType, typename SpecProvider, int8_t KeyNode,
+ int8_t ValueNode>
+MapType read_union_configured_map_data(ReadContext &ctx) {
+ using Key = key_type_t<MapType>;
+ using Value = mapped_type_t<MapType>;
+ uint32_t length = ctx.read_var_uint32(ctx.error());
+ MapType result;
+ MapReserver<MapType>::reserve(result, length);
+ uint32_t read_count = 0;
+ while (read_count < length && !ctx.has_error()) {
+ uint8_t header = ctx.read_uint8(ctx.error());
+ uint8_t chunk_size = ctx.read_uint8(ctx.error());
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return result;
+ }
+ const bool key_decl = (header & DECL_KEY_TYPE) != 0;
+ const bool value_decl = (header & DECL_VALUE_TYPE) != 0;
+ if (!key_decl) {
+ (void)ctx.read_any_type_info(ctx.error());
+ }
+ if (!value_decl) {
+ (void)ctx.read_any_type_info(ctx.error());
+ }
+ for (uint8_t i = 0; i < chunk_size && read_count < length; ++i) {
+ Key key = [&]() {
+ if constexpr (KeyNode >= 0) {
+ return read_union_configured_value<Key, SpecProvider, KeyNode>(
+ ctx, RefMode::None, false);
+ } else {
+ return Serializer<Key>::read_data(ctx);
+ }
+ }();
+ Value value = [&]() {
+ if constexpr (ValueNode >= 0) {
+ return read_union_configured_value<Value, SpecProvider, ValueNode>(
+ ctx, RefMode::None, false);
+ } else {
+ return Serializer<Value>::read_data(ctx);
+ }
+ }();
+ result.emplace(std::move(key), std::move(value));
+ ++read_count;
+ }
+ }
+ return result;
+}
+
+template <typename ValueType, typename SpecProvider, int8_t NodeIndex>
+void write_union_configured_value(const ValueType &value, WriteContext &ctx,
+ RefMode ref_mode, bool write_type,
+ bool has_generics) {
+ constexpr FieldNodeKind kind = union_node_kind<SpecProvider, NodeIndex>();
+ constexpr FieldScalarKind scalar_kind =
+ union_node_scalar<SpecProvider, NodeIndex>();
+ static_assert(configured_scalar_kind_matches<ValueType, scalar_kind>(),
+ "fory::T typed scalar spec does not match the C++ union case "
+ "type");
+ if constexpr (is_optional_v<ValueType>) {
+ using Inner = typename ValueType::value_type;
+ if (!value.has_value()) {
+ if (ref_mode != RefMode::None) {
+ ctx.write_int8(NULL_FLAG);
+ }
+ return;
+ }
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t child =
+ kind == FieldNodeKind::Inner
+ ? union_node_child<SpecProvider, NodeIndex, 0>()
+ : NodeIndex;
+ write_union_configured_value<Inner, SpecProvider, child>(
+ *value, ctx, RefMode::None, false, has_generics);
+ } else if constexpr ((is_vector_v<ValueType> || is_list_v<ValueType> ||
+ is_deque_v<ValueType> || is_set_like_v<ValueType>) &&
+ (kind == FieldNodeKind::List ||
+ kind == FieldNodeKind::Set)) {
+ if constexpr (union_vector_primitive_array_spec<ValueType, SpecProvider,
+ NodeIndex>()) {
+ Serializer<ValueType>::write(value, ctx, ref_mode, false, has_generics);
+ } else {
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t child = union_node_child<SpecProvider, NodeIndex, 0>();
+ write_union_configured_list_data<ValueType, SpecProvider, child>(value,
+ ctx);
+ }
+ } else if constexpr (is_map_like_v<ValueType> && kind == FieldNodeKind::Map) {
+ write_not_null_ref_flag(ctx, ref_mode);
+ constexpr int8_t key_child = union_node_child<SpecProvider, NodeIndex, 0>();
+ constexpr int8_t value_child =
+ union_node_child<SpecProvider, NodeIndex, 1>();
+ write_union_configured_map_data<ValueType, SpecProvider, key_child,
+ value_child>(value, ctx);
+ } else if constexpr (kind == FieldNodeKind::Scalar ||
+ union_node_encoding<SpecProvider, NodeIndex>() !=
+ Encoding::Default) {
+ write_not_null_ref_flag(ctx, ref_mode);
+ write_union_configured_scalar<ValueType, SpecProvider, NodeIndex>(value,
+ ctx);
+ } else {
+ Serializer<ValueType>::write(value, ctx, ref_mode, write_type,
+ has_generics);
+ }
+}
+
+template <typename ValueType, typename SpecProvider, int8_t NodeIndex>
+ValueType read_union_configured_value(ReadContext &ctx, RefMode ref_mode,
+ bool read_type) {
+ constexpr FieldNodeKind kind = union_node_kind<SpecProvider, NodeIndex>();
+ constexpr FieldScalarKind scalar_kind =
+ union_node_scalar<SpecProvider, NodeIndex>();
+ static_assert(configured_scalar_kind_matches<ValueType, scalar_kind>(),
+ "fory::T typed scalar spec does not match the C++ union case "
+ "type");
+ if constexpr (is_optional_v<ValueType>) {
+ using Inner = typename ValueType::value_type;
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return std::nullopt;
+ }
+ constexpr int8_t child =
+ kind == FieldNodeKind::Inner
+ ? union_node_child<SpecProvider, NodeIndex, 0>()
+ : NodeIndex;
+ Inner inner = read_union_configured_value<Inner, SpecProvider, child>(
+ ctx, RefMode::None, false);
+ return ValueType{std::move(inner)};
+ } else if constexpr ((is_vector_v<ValueType> || is_list_v<ValueType> ||
+ is_deque_v<ValueType> || is_set_like_v<ValueType>) &&
+ (kind == FieldNodeKind::List ||
+ kind == FieldNodeKind::Set)) {
+ if constexpr (union_vector_primitive_array_spec<ValueType, SpecProvider,
+ NodeIndex>()) {
+ return Serializer<ValueType>::read(ctx, ref_mode, false);
+ } else {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ constexpr int8_t child = union_node_child<SpecProvider, NodeIndex, 0>();
+ return read_union_configured_list_data<ValueType, SpecProvider, child>(
+ ctx);
+ }
+ } else if constexpr (is_map_like_v<ValueType> && kind == FieldNodeKind::Map) {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ constexpr int8_t key_child = union_node_child<SpecProvider, NodeIndex, 0>();
+ constexpr int8_t value_child =
+ union_node_child<SpecProvider, NodeIndex, 1>();
+ return read_union_configured_map_data<ValueType, SpecProvider, key_child,
+ value_child>(ctx);
+ } else if constexpr (kind == FieldNodeKind::Scalar ||
+ union_node_encoding<SpecProvider, NodeIndex>() !=
+ Encoding::Default) {
+ if (!read_null_only_flag(ctx, ref_mode)) {
+ return ValueType{};
+ }
+ return read_union_configured_scalar<ValueType, SpecProvider, NodeIndex>(
+ ctx);
+ } else {
+ return Serializer<ValueType>::read(ctx, ref_mode, read_type);
+ }
+}
+
template <typename T>
inline bool write_union_ref_flag(const T &value, WriteContext &ctx,
RefMode ref_mode, bool nullable) {
@@ -410,6 +854,12 @@ inline bool dispatch_union_case(uint32_t case_id, F &&fn) {
return false;
}
+template <typename Factory> struct UnionFactoryArg;
+
+template <typename R, typename Arg> struct UnionFactoryArg<R (*)(Arg)> {
+ using type = Arg;
+};
+
} // namespace detail
// ============================================================================//
@@ -476,9 +926,13 @@ struct Serializer<T, std::enable_if_t<detail::is_union_type_v<T>>> {
using CaseT = typename detail::UnionInfo<T>::template CaseT<index>;
constexpr ::fory::FieldMeta meta =
detail::UnionInfo<T>::template Meta<index>::value;
+ using SpecProvider = detail::UnionCaseSpecProvider<T, index>;
constexpr uint32_t field_type_id =
- detail::resolve_union_type_id<CaseT>(meta);
- const bool manual = detail::needs_manual_encoding<CaseT>(meta);
+ detail::resolve_union_type_id_for_spec<CaseT, SpecProvider>(meta);
+ constexpr bool configured =
+ detail::union_node_has_override<SpecProvider, 0>();
+ const bool manual =
+ configured || detail::needs_manual_encoding<CaseT>(meta);
constexpr bool nullable =
meta.nullable_ || is_nullable_v<detail::decay_t<CaseT>>;
const RefMode value_ref_mode =
@@ -493,7 +947,12 @@ struct Serializer<T, std::enable_if_t<detail::is_union_type_v<T>>> {
return;
}
ctx.write_uint8(static_cast<uint8_t>(field_type_id));
- detail::write_union_value_data(value, ctx, field_type_id);
+ if constexpr (detail::union_node_has_override<SpecProvider, 0>()) {
+ detail::write_union_configured_value<CaseT, SpecProvider, 0>(
+ value, ctx, RefMode::None, false, true);
+ } else {
+ detail::write_union_value_data(value, ctx, field_type_id);
+ }
return;
}
Serializer<CaseT>::write(value, ctx, value_ref_mode, true);
@@ -557,9 +1016,13 @@ struct Serializer<T, std::enable_if_t<detail::is_union_type_v<T>>> {
using CaseT = typename detail::UnionInfo<T>::template CaseT<index>;
constexpr ::fory::FieldMeta meta =
detail::UnionInfo<T>::template Meta<index>::value;
+ using SpecProvider = detail::UnionCaseSpecProvider<T, index>;
constexpr uint32_t field_type_id =
- detail::resolve_union_type_id<CaseT>(meta);
- const bool manual = detail::needs_manual_encoding<CaseT>(meta);
+ detail::resolve_union_type_id_for_spec<CaseT, SpecProvider>(meta);
+ constexpr bool configured =
+ detail::union_node_has_override<SpecProvider, 0>();
+ const bool manual =
+ configured || detail::needs_manual_encoding<CaseT>(meta);
constexpr bool nullable =
meta.nullable_ || is_nullable_v<detail::decay_t<CaseT>>;
const RefMode value_ref_mode =
@@ -587,7 +1050,14 @@ struct Serializer<T, std::enable_if_t<detail::is_union_type_v<T>>> {
result = default_value();
return;
}
- CaseT value = detail::read_union_value_data<CaseT>(ctx, field_type_id);
+ CaseT value = [&]() {
+ if constexpr (detail::union_node_has_override<SpecProvider, 0>()) {
+ return detail::read_union_configured_value<CaseT, SpecProvider, 0>(
+ ctx, RefMode::None, false);
+ } else {
+ return detail::read_union_value_data<CaseT>(ctx, field_type_id);
+ }
+ }();
if (FORY_PREDICT_FALSE(ctx.has_error())) {
result = default_value();
return;
@@ -633,7 +1103,7 @@ struct Serializer<T, std::enable_if_t<detail::is_union_type_v<T>>> {
return default_value();
}
FieldType field_type;
- field_type.type_id = type_info->type_id;
+ field_type.set_type_id(type_info->type_id);
field_type.nullable = false;
skip_field_value(ctx, field_type, RefMode::None);
ctx.set_error(Error::invalid_data("Unknown union case id"));
@@ -659,14 +1129,35 @@ private:
// Union registration macros for generated code
// ============================================================================//
-#define FORY_UNION_CASE_TYPE(tuple) FORY_UNION_CASE_TYPE_IMPL tuple
-#define FORY_UNION_CASE_TYPE_IMPL(type, name, meta) type
-
-#define FORY_UNION_CASE_NAME(tuple) FORY_UNION_CASE_NAME_IMPL tuple
-#define FORY_UNION_CASE_NAME_IMPL(type, name, meta) name
-
-#define FORY_UNION_CASE_META(tuple) FORY_UNION_CASE_META_IMPL tuple
-#define FORY_UNION_CASE_META_IMPL(type, name, meta) meta
+#define FORY_UNION_TUPLE_SIZE(tuple) FORY_UNION_TUPLE_SIZE_IMPL tuple
+#define FORY_UNION_TUPLE_SIZE_IMPL(...) \
+ FORY_UNION_TUPLE_SIZE_SELECT(__VA_ARGS__, 3, 2, 1, 0)
+#define FORY_UNION_TUPLE_SIZE_SELECT(_1, _2, _3, N, ...) N
+
+#define FORY_UNION_CASE_NAME(tuple) \
+ FORY_PP_CONCAT(FORY_UNION_CASE_NAME_, FORY_UNION_TUPLE_SIZE(tuple))(tuple)
+#define FORY_UNION_CASE_NAME_2(tuple) FORY_UNION_CASE_NAME_2_IMPL tuple
+#define FORY_UNION_CASE_NAME_2_IMPL(name, meta) name
+#define FORY_UNION_CASE_NAME_3(tuple) FORY_UNION_CASE_NAME_3_IMPL tuple
+#define FORY_UNION_CASE_NAME_3_IMPL(name, type, meta) name
+
+#define FORY_UNION_CASE_TYPE(Type, tuple) \
+ FORY_PP_CONCAT(FORY_UNION_CASE_TYPE_, FORY_UNION_TUPLE_SIZE(tuple)) \
+ (Type, tuple)
+#define FORY_UNION_CASE_TYPE_2(Type, tuple) \
+ FORY_UNION_CASE_TYPE_2_IMPL(Type, tuple)
+#define FORY_UNION_CASE_TYPE_2_IMPL(Type, tuple) \
+ typename ::fory::serialization::detail::UnionFactoryArg< \
+ decltype(&Type::FORY_UNION_CASE_NAME(tuple))>::type
+#define FORY_UNION_CASE_TYPE_3(Type, tuple) FORY_UNION_CASE_TYPE_3_IMPL tuple
+#define FORY_UNION_CASE_TYPE_3_IMPL(name, type, meta) type
+
+#define FORY_UNION_CASE_META(tuple) \
+ FORY_PP_CONCAT(FORY_UNION_CASE_META_, FORY_UNION_TUPLE_SIZE(tuple))(tuple)
+#define FORY_UNION_CASE_META_2(tuple) FORY_UNION_CASE_META_2_IMPL tuple
+#define FORY_UNION_CASE_META_2_IMPL(name, meta) meta
+#define FORY_UNION_CASE_META_3(tuple) FORY_UNION_CASE_META_3_IMPL tuple
+#define FORY_UNION_CASE_META_3_IMPL(name, type, meta) meta
#define FORY_UNION_PP_FOREACH_2(M, A, ...) \
FORY_PP_INVOKE(FORY_PP_CONCAT(FORY_UNION_PP_FOREACH_2_IMPL_, \
@@ -808,13 +1299,18 @@ private:
#define FORY_UNION_CASE_ID(Type, tuple) \
static_cast<uint32_t>(FORY_UNION_CASE_META(tuple).id_)
+#define FORY_UNION_CASE_REQUIRE_ID(Type, tuple) \
+ static_assert(FORY_UNION_CASE_META(tuple).has_id_, \
+ "FORY_UNION cases must use fory::F(id)");
+
#define FORY_UNION_CASE_ID_ENTRY(Type, tuple) FORY_UNION_CASE_ID(Type, tuple),
-#define FORY_UNION_CASE_TYPE_VALUE(Type, tuple) FORY_UNION_CASE_TYPE(tuple)
+#define FORY_UNION_CASE_TYPE_VALUE(Type, tuple) \
+ FORY_UNION_CASE_TYPE(Type, tuple)
#define FORY_UNION_CASE_META_VALUE(Type, tuple) FORY_UNION_CASE_META(tuple)
#define FORY_UNION_CASE_ID_VALUE(Type, tuple) FORY_UNION_CASE_ID(Type, tuple)
#define FORY_UNION_CASE_FACTORY_VALUE(Type, tuple) \
- static_cast<Type (*)(FORY_UNION_CASE_TYPE(tuple))>( \
+ static_cast<Type (*)(FORY_UNION_CASE_TYPE(Type, tuple))>( \
&Type::FORY_UNION_CASE_NAME(tuple))
#define FORY_UNION_IDS_DESCRIPTOR_NAME(line) \
@@ -838,6 +1334,8 @@ private:
static_assert(true)
#define FORY_UNION_CASE(Type, CaseId, CaseType, Factory, MetaExpr) \
+ static_assert((MetaExpr).has_id_, \
+ "FORY_UNION_CASE metadata must use fory::F(id)"); \
struct FORY_UNION_CASE_DESCRIPTOR_NAME(__LINE__) { \
using CaseT = CaseType; \
static constexpr ::fory::FieldMeta meta = MetaExpr; \
@@ -855,6 +1353,7 @@ private:
"FORY_UNION supports up to 16 cases; use " \
"FORY_UNION_IDS/FORY_UNION_CASE " \
"for larger unions"); \
+ FORY_UNION_PP_FOREACH_2(FORY_UNION_CASE_REQUIRE_ID, Type, __VA_ARGS__) \
struct FORY_UNION_DESCRIPTOR_NAME(__LINE__) { \
using UnionType = Type; \
static constexpr size_t case_count = FORY_PP_NARG(__VA_ARGS__); \
diff --git a/cpp/fory/serialization/xlang_test_main.cc b/cpp/fory/serialization/xlang_test_main.cc
index db6d02a82..29e84488b 100644
--- a/cpp/fory/serialization/xlang_test_main.cc
+++ b/cpp/fory/serialization/xlang_test_main.cc
@@ -616,6 +616,32 @@ struct CircularRefStruct {
FORY_STRUCT(CircularRefStruct, name, self_ref);
};
+struct NestedAnnotatedContainerSchemaConsistent {
+ std::map<uint32_t, std::vector<uint64_t>> values;
+
+ bool operator==(const NestedAnnotatedContainerSchemaConsistent &other) const {
+ return values == other.values;
+ }
+
+ FORY_STRUCT(NestedAnnotatedContainerSchemaConsistent,
+ (values,
+ fory::F().map(fory::T::uint32().fixed(),
+ fory::T::list(fory::T::uint64().tagged()))));
+};
+
+struct NestedAnnotatedContainerCompatible {
+ std::map<uint32_t, std::vector<uint64_t>> values;
+
+ bool operator==(const NestedAnnotatedContainerCompatible &other) const {
+ return values == other.values;
+ }
+
+ FORY_STRUCT(NestedAnnotatedContainerCompatible,
+ (values,
+ fory::F().map(fory::T::uint32().fixed(),
+ fory::T::list(fory::T::uint64().tagged()))));
+};
+
// ============================================================================
// Unsigned Number Test Types
// ============================================================================
@@ -630,11 +656,9 @@ struct UnsignedSchemaConsistentSimple {
return u64_tagged == other.u64_tagged &&
u64_tagged_nullable == other.u64_tagged_nullable;
}
- FORY_STRUCT(UnsignedSchemaConsistentSimple, u64_tagged, u64_tagged_nullable);
+ FORY_STRUCT(UnsignedSchemaConsistentSimple, (u64_tagged, fory::F().tagged()),
+ (u64_tagged_nullable, fory::F().nullable().tagged()));
};
-FORY_FIELD_CONFIG(UnsignedSchemaConsistentSimple,
- (u64_tagged, fory::F().tagged()),
- (u64_tagged_nullable, fory::F().nullable().tagged()));
// UnsignedSchemaConsistent (type id 501)
// Test struct for unsigned numbers in SCHEMA_CONSISTENT mode.
@@ -674,26 +698,20 @@ struct UnsignedSchemaConsistent {
u64_fixed_nullable_field == other.u64_fixed_nullable_field &&
u64_tagged_nullable_field == other.u64_tagged_nullable_field;
}
- FORY_STRUCT(UnsignedSchemaConsistent, u8_field, u16_field, u32_var_field,
- u32_fixed_field, u64_var_field, u64_fixed_field, u64_tagged_field,
- u8_nullable_field, u16_nullable_field, u32_var_nullable_field,
- u32_fixed_nullable_field, u64_var_nullable_field,
- u64_fixed_nullable_field, u64_tagged_nullable_field);
+ FORY_STRUCT(UnsignedSchemaConsistent, (u8_field, fory::F()),
+ (u16_field, fory::F()), (u32_var_field, fory::F().varint()),
+ (u32_fixed_field, fory::F().fixed()),
+ (u64_var_field, fory::F().varint()),
+ (u64_fixed_field, fory::F().fixed()),
+ (u64_tagged_field, fory::F().tagged()),
+ (u8_nullable_field, fory::F().nullable()),
+ (u16_nullable_field, fory::F().nullable()),
+ (u32_var_nullable_field, fory::F().nullable().varint()),
+ (u32_fixed_nullable_field, fory::F().nullable().fixed()),
+ (u64_var_nullable_field, fory::F().nullable().varint()),
+ (u64_fixed_nullable_field, fory::F().nullable().fixed()),
+ (u64_tagged_nullable_field, fory::F().nullable().tagged()));
};
-// Use new FORY_FIELD_CONFIG with builder pattern for encoding specification
-FORY_FIELD_CONFIG(UnsignedSchemaConsistent, (u8_field, fory::F()),
- (u16_field, fory::F()), (u32_var_field, fory::F().varint()),
- (u32_fixed_field, fory::F().fixed()),
- (u64_var_field, fory::F().varint()),
- (u64_fixed_field, fory::F().fixed()),
- (u64_tagged_field, fory::F().tagged()),
- (u8_nullable_field, fory::F().nullable()),
- (u16_nullable_field, fory::F().nullable()),
- (u32_var_nullable_field, fory::F().nullable().varint()),
- (u32_fixed_nullable_field, fory::F().nullable().fixed()),
- (u64_var_nullable_field, fory::F().nullable().varint()),
- (u64_fixed_nullable_field, fory::F().nullable().fixed()),
- (u64_tagged_nullable_field, fory::F().nullable().tagged()));
// UnsignedSchemaCompatible (type id 502)
// Test struct for unsigned numbers in COMPATIBLE mode.
@@ -733,28 +751,20 @@ struct UnsignedSchemaCompatible {
u64_fixed_field2 == other.u64_fixed_field2 &&
u64_tagged_field2 == other.u64_tagged_field2;
}
- FORY_STRUCT(UnsignedSchemaCompatible, u8_field1, u16_field1, u32_var_field1,
- u32_fixed_field1, u64_var_field1, u64_fixed_field1,
- u64_tagged_field1, u8_field2, u16_field2, u32_var_field2,
- u32_fixed_field2, u64_var_field2, u64_fixed_field2,
- u64_tagged_field2);
+ FORY_STRUCT(UnsignedSchemaCompatible, (u8_field1, fory::F().nullable()),
+ (u16_field1, fory::F().nullable()),
+ (u32_var_field1, fory::F().nullable().varint()),
+ (u32_fixed_field1, fory::F().nullable().fixed()),
+ (u64_var_field1, fory::F().nullable().varint()),
+ (u64_fixed_field1, fory::F().nullable().fixed()),
+ (u64_tagged_field1, fory::F().nullable().tagged()),
+ (u8_field2, fory::F()), (u16_field2, fory::F()),
+ (u32_var_field2, fory::F().varint()),
+ (u32_fixed_field2, fory::F().fixed()),
+ (u64_var_field2, fory::F().varint()),
+ (u64_fixed_field2, fory::F().fixed()),
+ (u64_tagged_field2, fory::F().tagged()));
};
-// Use new FORY_FIELD_CONFIG with builder pattern for encoding specification
-// Group 1: nullable in C++ (std::optional), non-nullable in Java
-// Group 2: non-nullable in C++, nullable in Java
-FORY_FIELD_CONFIG(UnsignedSchemaCompatible, (u8_field1, fory::F().nullable()),
- (u16_field1, fory::F().nullable()),
- (u32_var_field1, fory::F().nullable().varint()),
- (u32_fixed_field1, fory::F().nullable().fixed()),
- (u64_var_field1, fory::F().nullable().varint()),
- (u64_fixed_field1, fory::F().nullable().fixed()),
- (u64_tagged_field1, fory::F().nullable().tagged()),
- (u8_field2, fory::F()), (u16_field2, fory::F()),
- (u32_var_field2, fory::F().varint()),
- (u32_fixed_field2, fory::F().fixed()),
- (u64_var_field2, fory::F().varint()),
- (u64_fixed_field2, fory::F().fixed()),
- (u64_tagged_field2, fory::F().tagged()));
namespace fory {
namespace serialization {
@@ -955,6 +965,10 @@ void run_test_ref_compatible(const std::string &data_file);
void run_test_collection_element_ref_override(const std::string &data_file);
void run_test_circular_ref_schema_consistent(const std::string &data_file);
void run_test_circular_ref_compatible(const std::string &data_file);
+void run_test_nested_annotated_container_schema_consistent(
+ const std::string &data_file);
+void run_test_nested_annotated_container_compatible(
+ const std::string &data_file);
void run_test_decimal(const std::string &data_file);
void run_test_unsigned_schema_consistent_simple(const std::string &data_file);
void run_test_unsigned_schema_consistent(const std::string &data_file);
@@ -1069,6 +1083,11 @@ int main(int argc, char **argv) {
run_test_circular_ref_schema_consistent(data_file);
} else if (case_name == "test_circular_ref_compatible") {
run_test_circular_ref_compatible(data_file);
+ } else if (case_name ==
+ "test_nested_annotated_container_schema_consistent") {
+ run_test_nested_annotated_container_schema_consistent(data_file);
+ } else if (case_name == "test_nested_annotated_container_compatible") {
+ run_test_nested_annotated_container_compatible(data_file);
} else if (case_name == "test_unsigned_schema_consistent_simple") {
run_test_unsigned_schema_consistent_simple(data_file);
} else if (case_name == "test_unsigned_schema_consistent") {
@@ -2790,6 +2809,45 @@ void run_test_circular_ref_compatible(const std::string &data_file) {
write_file(data_file, out);
}
+std::map<uint32_t, std::vector<uint64_t>>
+build_nested_annotated_container_values() {
+ return {{4000000000u, {7u, 1000000000u}}, {3u, {42u}}};
+}
+
+template <typename T>
+void run_nested_annotated_container_test(Fory &fory,
+ const std::string &data_file) {
+ auto bytes = read_file(data_file);
+ Buffer buffer = make_buffer(bytes);
+ auto value = read_next<T>(fory, buffer);
+ const auto expected = build_nested_annotated_container_values();
+ if (value.values != expected) {
+ fail("Nested annotated container values mismatch");
+ }
+
+ std::vector<uint8_t> out;
+ append_serialized(fory, value, out);
+ write_file(data_file, out);
+}
+
+void run_test_nested_annotated_container_schema_consistent(
+ const std::string &data_file) {
+ auto fory = build_fory(false, true, true);
+ ensure_ok(fory.register_struct<NestedAnnotatedContainerSchemaConsistent>(801),
+ "register NestedAnnotatedContainerSchemaConsistent");
+ run_nested_annotated_container_test<NestedAnnotatedContainerSchemaConsistent>(
+ fory, data_file);
+}
+
+void run_test_nested_annotated_container_compatible(
+ const std::string &data_file) {
+ auto fory = build_fory(true, true);
+ ensure_ok(fory.register_struct<NestedAnnotatedContainerCompatible>(802),
+ "register NestedAnnotatedContainerCompatible");
+ run_nested_annotated_container_test<NestedAnnotatedContainerCompatible>(
+ fory, data_file);
+}
+
// ============================================================================
// Unsigned Number Tests
// ============================================================================
diff --git a/docs/benchmarks/cpp/README.md b/docs/benchmarks/cpp/README.md
index 9811c8a54..0d7c25a60 100644
--- a/docs/benchmarks/cpp/README.md
+++ b/docs/benchmarks/cpp/README.md
@@ -1,6 +1,6 @@
# C++ Benchmark Performance Report
-_Generated on 2026-02-19 10:20:08_
+_Generated on 2026-04-29 21:16:35_
## How to Generate This Report
@@ -21,7 +21,7 @@ python benchmark_report.py --json-file build/benchmark_results.json --output-dir
| CPU Cores (Physical) | 12 |
| CPU Cores (Logical) | 12 |
| Total RAM (GB) | 48.0 |
-| Benchmark Date | 2026-02-19T10:19:31+08:00 |
+| Benchmark Date | 2026-04-29T21:15:49+08:00 |
| CPU Cores (from benchmark) | 12 |
## Benchmark Plots
@@ -32,65 +32,65 @@ All class-level plots below show throughput (ops/sec).

-### Mediacontent
-
-
-
-### Mediacontentlist
+### Struct
-
+
### Sample

-### Samplelist
-
-
-
-### Struct
+### Mediacontent
-
+
### Structlist

+### Samplelist
+
+
+
+### Mediacontentlist
+
+
+
## Benchmark Results
### Timing Results (nanoseconds)
| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest |
| ---------------- | ----------- | --------- | ------------- | ------------ | ------- |
-| MediaContent | Serialize | 120.4 | 863.8 | 281.1 | fory |
-| MediaContent | Deserialize | 397.9 | 1197.9 | 2768.6 | fory |
-| MediaContentList | Serialize | 480.9 | 4744.3 | 1407.5 | fory |
-| MediaContentList | Deserialize | 2022.1 | 6426.0 | 13595.9 | fory |
-| Sample | Serialize | 72.8 | 92.0 | 296.1 | fory |
-| Sample | Deserialize | 328.2 | 641.2 | 2642.9 | fory |
-| SampleList | Serialize | 287.3 | 4761.2 | 1506.7 | fory |
-| SampleList | Deserialize | 1711.8 | 4875.2 | 13232.0 | fory |
-| Struct | Serialize | 27.3 | 32.4 | 55.2 | fory |
-| Struct | Deserialize | 21.4 | 25.0 | 747.3 | fory |
-| StructList | Serialize | 69.4 | 419.8 | 285.9 | fory |
-| StructList | Deserialize | 129.4 | 334.4 | 3385.8 | fory |
+| Struct | Serialize | 25.6 | 34.3 | 54.3 | fory |
+| Struct | Deserialize | 23.1 | 25.5 | 769.9 | fory |
+| Sample | Serialize | 68.4 | 96.6 | 317.8 | fory |
+| Sample | Deserialize | 326.2 | 662.5 | 2633.8 | fory |
+| MediaContent | Serialize | 119.5 | 854.1 | 292.7 | fory |
+| MediaContent | Deserialize | 391.2 | 1207.2 | 2809.7 | fory |
+| StructList | Serialize | 72.6 | 421.4 | 286.4 | fory |
+| StructList | Deserialize | 122.7 | 364.1 | 3400.5 | fory |
+| SampleList | Serialize | 284.0 | 4898.1 | 1549.8 | fory |
+| SampleList | Deserialize | 1824.8 | 5047.4 | 13206.2 | fory |
+| MediaContentList | Serialize | 477.8 | 4788.9 | 1461.9 | fory |
+| MediaContentList | Deserialize | 2053.4 | 6561.8 | 13702.4 | fory |
### Throughput Results (ops/sec)
| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest |
| ---------------- | ----------- | ---------- | ------------ | ----------- | ------- |
-| MediaContent | Serialize | 8,306,128 | 1,157,712 | 3,557,700 | fory |
-| MediaContent | Deserialize | 2,513,488 | 834,808 | 361,190 | fory |
-| MediaContentList | Serialize | 2,079,229 | 210,777 | 710,492 | fory |
-| MediaContentList | Deserialize | 494,523 | 155,617 | 73,551 | fory |
-| Sample | Serialize | 13,745,041 | 10,871,787 | 3,377,292 | fory |
-| Sample | Deserialize | 3,047,224 | 1,559,633 | 378,369 | fory |
-| SampleList | Serialize | 3,481,110 | 210,029 | 663,693 | fory |
-| SampleList | Deserialize | 584,168 | 205,121 | 75,574 | fory |
-| Struct | Serialize | 36,672,581 | 30,900,039 | 18,114,682 | fory |
-| Struct | Deserialize | 46,637,124 | 39,947,557 | 1,338,225 | fory |
-| StructList | Serialize | 14,419,548 | 2,381,886 | 3,497,903 | fory |
-| StructList | Deserialize | 7,729,173 | 2,990,652 | 295,353 | fory |
+| Struct | Serialize | 39,028,134 | 29,190,083 | 18,400,879 | fory |
+| Struct | Deserialize | 43,236,159 | 39,162,796 | 1,298,861 | fory |
+| Sample | Serialize | 14,612,190 | 10,356,947 | 3,146,384 | fory |
+| Sample | Deserialize | 3,065,887 | 1,509,472 | 379,673 | fory |
+| MediaContent | Serialize | 8,364,768 | 1,170,804 | 3,416,327 | fory |
+| MediaContent | Deserialize | 2,556,154 | 828,370 | 355,913 | fory |
+| StructList | Serialize | 13,768,856 | 2,373,115 | 3,491,609 | fory |
+| StructList | Deserialize | 8,149,855 | 2,746,704 | 294,075 | fory |
+| SampleList | Serialize | 3,521,702 | 204,162 | 645,233 | fory |
+| SampleList | Deserialize | 547,995 | 198,123 | 75,722 | fory |
+| MediaContentList | Serialize | 2,092,938 | 208,817 | 684,050 | fory |
+| MediaContentList | Deserialize | 486,993 | 152,398 | 72,980 | fory |
### Serialized Data Sizes (bytes)
diff --git a/docs/benchmarks/cpp/mediacontent.png b/docs/benchmarks/cpp/mediacontent.png
index 7fd07e461..6cb966085 100644
Binary files a/docs/benchmarks/cpp/mediacontent.png and b/docs/benchmarks/cpp/mediacontent.png differ
diff --git a/docs/benchmarks/cpp/mediacontentlist.png b/docs/benchmarks/cpp/mediacontentlist.png
index 1fccde649..267ff574f 100644
Binary files a/docs/benchmarks/cpp/mediacontentlist.png and b/docs/benchmarks/cpp/mediacontentlist.png differ
diff --git a/docs/benchmarks/cpp/sample.png b/docs/benchmarks/cpp/sample.png
index 127a6e1aa..bb25b5433 100644
Binary files a/docs/benchmarks/cpp/sample.png and b/docs/benchmarks/cpp/sample.png differ
diff --git a/docs/benchmarks/cpp/samplelist.png b/docs/benchmarks/cpp/samplelist.png
index 2ebde46ef..bdaa4f05d 100644
Binary files a/docs/benchmarks/cpp/samplelist.png and b/docs/benchmarks/cpp/samplelist.png differ
diff --git a/docs/benchmarks/cpp/struct.png b/docs/benchmarks/cpp/struct.png
index 88e586ff6..398ab52f8 100644
Binary files a/docs/benchmarks/cpp/struct.png and b/docs/benchmarks/cpp/struct.png differ
diff --git a/docs/benchmarks/cpp/structlist.png b/docs/benchmarks/cpp/structlist.png
index c676e84df..47c5f680f 100644
Binary files a/docs/benchmarks/cpp/structlist.png and b/docs/benchmarks/cpp/structlist.png differ
diff --git a/docs/benchmarks/cpp/throughput.png b/docs/benchmarks/cpp/throughput.png
index 9ffa742b6..525f58ff7 100644
Binary files a/docs/benchmarks/cpp/throughput.png and b/docs/benchmarks/cpp/throughput.png differ
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index 583cd79b6..5098fb2af 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -515,7 +515,9 @@ class Animal final {
};
```
-Generated headers also include `FORY_UNION`, `FORY_FIELD_CONFIG`, `FORY_ENUM`, and `FORY_STRUCT` macros for serialization metadata.
+Generated headers include `FORY_UNION`, `FORY_ENUM`, and `FORY_STRUCT` macros
+for serialization metadata. Field and payload configuration is embedded in the
+generated `FORY_STRUCT`/`FORY_UNION` entries.
### Registration
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index bffa4eb46..9f514fd0d 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -1237,8 +1237,11 @@ message Order {
Use the `list<...>` type for list fields. `repeated` is accepted as an alias. See [Field Modifiers](#field-modifiers) for
modifier combinations and language mapping.
-Nested collection types are not supported. Use a message wrapper if you need
-`list<list<...>>`, `list<map<...>>`, or `map<..., list<...>>`.
+Nested collection support is target-capability based. The C++ generator accepts
+nested collection specs such as `list<list<...>>`, `list<map<...>>`, and
+`map<..., list<...>>`; targets that have not implemented nested field specs
+continue to reject them. Use a message wrapper when you need portable schemas
+across all targets.
#### Map
diff --git a/docs/guide/cpp/field-configuration.md b/docs/guide/cpp/field-configuration.md
index 94f5170dd..02029453a 100644
--- a/docs/guide/cpp/field-configuration.md
+++ b/docs/guide/cpp/field-configuration.md
@@ -19,550 +19,188 @@ license: |
limitations under the License.
---
-This page explains how to configure field-level metadata for serialization.
-
-## Overview
-
-Apache Fory™ provides three ways to configure field-level metadata at compile time:
-
-1. **`fory::field<>` template** - Inline metadata in struct definition with wrapper types
-2. **`FORY_FIELD_TAGS` macro** - Non-invasive metadata for basic field configuration
-3. **`FORY_FIELD_CONFIG` macro** - Advanced configuration with builder pattern and encoding control
-
-These enable:
-
-- **Tag IDs**: Assign compact numeric IDs for schema evolution
-- **Nullability**: Mark pointer fields as nullable
-- **Reference Tracking**: Enable reference tracking for shared pointers
-- **Encoding Control**: Specify wire format for integers (varint, fixed, tagged)
-- **Dynamic Dispatch**: Control polymorphic type info for smart pointers
-
-**Comparison:**
-
-| Feature | `fory::field<>` | `FORY_FIELD_TAGS` | `FORY_FIELD_CONFIG` |
-| ----------------------- | --------------------- | ----------------- | ------------------------- |
-| **Struct modification** | Required (wrap types) | None | None |
-| **Encoding control** | No | No | Yes (varint/fixed/tagged) |
-| **Builder pattern** | No | No | Yes |
-| **Dynamic control** | Yes | No | Yes |
-| **Compile-time verify** | Yes | Limited | Yes (member pointers) |
-| **Cross-lang compat** | Limited | Limited | Full |
-| **Recommended for** | Simple structs | Third-party types | Complex/xlang structs |
-
-## The fory::field Template
-
-```cpp
-template <typename T, int16_t Id, typename... Options>
-class field;
-```
-
-### Template Parameters
-
-| Parameter | Description |
-| --------- | ------------------------------------------------ |
-| `T` | The underlying field type |
-| `Id` | Field tag ID (int16_t) for compact serialization |
-| `Options` | Optional tags: `fory::nullable`, `fory::ref` |
-
-### Basic Usage
+Field configuration is embedded directly in `FORY_STRUCT`. A field entry may be
+bare, or it may be a tuple containing the member name and a `fory::F(...)`
+builder:
```cpp
#include "fory/serialization/fory.h"
-using namespace fory::serialization;
-
-struct Person {
- fory::field<std::string, 0> name;
- fory::field<int32_t, 1> age;
- fory::field<std::optional<std::string>, 2> nickname;
+struct DataV2 {
+ uint32_t id;
+ uint64_t timestamp;
+ std::optional<uint32_t> version;
};
-FORY_STRUCT(Person, name, age, nickname);
-```
-The `fory::field<>` wrapper is transparent - you can use it like the underlying type:
-
-```cpp
-Person person;
-person.name = "Alice"; // Direct assignment
-person.age = 30;
-std::string n = person.name; // Implicit conversion
-int a = person.age.get(); // Explicit get()
+FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version);
```
-## Tag Types
+The configuration is compile-time metadata. It does not allocate codec objects
+or add virtual dispatch on the serialization path.
-### fory::nullable
+## Field Identity
-Marks a smart pointer field as nullable (can be `nullptr`):
+`fory::F()` uses name-mode field identity. Bare fields are also name-mode:
```cpp
-struct Node {
- fory::field<std::string, 0> name;
- fory::field<std::shared_ptr<Node>, 1, fory::nullable> next; // Can be nullptr
-};
-FORY_STRUCT(Node, name, next);
+FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version);
```
-**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, `std::unique_ptr<T>`
-
-**Note:** For nullable primitives or strings, use `std::optional<T>` instead:
+`fory::F(id)` uses id-mode field identity:
```cpp
-// Correct: use std::optional for nullable primitives
-fory::field<std::optional<int32_t>, 0> optional_value;
-
-// Wrong: nullable is not allowed for primitives
-// fory::field<int32_t, 0, fory::nullable> value; // Compile error!
+FORY_STRUCT(DataV2, (id, fory::F(0)), (timestamp, fory::F(1).tagged()),
+ (version, fory::F(2)));
```
-### fory::not_null
-
-Explicitly marks a pointer field as non-nullable. This is the default for smart pointers, but can be used for documentation:
-
-```cpp
-fory::field<std::shared_ptr<Data>, 0, fory::not_null> data; // Must not be nullptr
-```
+A struct must use exactly one identity mode. If any field uses `fory::F(id)`,
+every field in that `FORY_STRUCT` must use `fory::F(id)`. Mixed name/id mode is a
+compile-time error.
-**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, `std::unique_ptr<T>`
+## Scalar Encoding
-### fory::ref
-
-Enables reference tracking for shared pointer fields. When multiple fields reference the same object, it will be serialized once and shared:
-
-```cpp
-struct Graph {
- fory::field<std::string, 0> name;
- fory::field<std::shared_ptr<Graph>, 1, fory::ref> left; // Ref tracked
- fory::field<std::shared_ptr<Graph>, 2, fory::ref> right; // Ref tracked
-};
-FORY_STRUCT(Graph, name, left, right);
-```
-
-**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>` (requires shared ownership)
-
-### fory::dynamic\<V\>
-
-Controls whether type info is written for polymorphic smart pointer fields:
-
-- `fory::dynamic<true>`: Force type info to be written (enable runtime subtype support)
-- `fory::dynamic<false>`: skip type info (use declared type directly, no dynamic dispatch)
-
-By default, Fory auto-detects polymorphism via `std::is_polymorphic<T>`. Use this tag to override:
+Integer encoding is configured on the field or on a nested value-node spec:
```cpp
-// Base class with virtual methods (detected as polymorphic by default)
-struct Animal {
- virtual ~Animal() = default;
- virtual std::string speak() const = 0;
+struct Counters {
+ uint32_t fixed_id;
+ uint64_t tagged_time;
+ int64_t signed_score;
};
-struct Zoo {
- // Auto: type info written because Animal has virtual methods
- fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal;
-
- // Force non-dynamic: skip type info even though Animal has virtual methods
- // Use when you know the runtime type will always be exactly as declared
- fory::field<std::shared_ptr<Animal>, 1, fory::nullable, fory::dynamic<false>> fixed_animal;
-};
-FORY_STRUCT(Zoo, animal, fixed_animal);
+FORY_STRUCT(Counters, (fixed_id, fory::F().fixed()),
+ (tagged_time, fory::F().tagged()),
+ (signed_score, fory::F().varint()));
```
-**Valid for:** `std::shared_ptr<T>`, `std::unique_ptr<T>`
-
-### Combining Tags
+Supported scalar encoding methods are:
-Multiple tags can be combined for shared pointers and SharedWeak:
+| Method | Meaning |
+| ---------- | -------------------------------------------- |
+| `fixed()` | Fixed-width integer encoding where valid |
+| `varint()` | Variable-length integer encoding where valid |
+| `tagged()` | Tagged integer encoding where valid |
-```cpp
-// Nullable + ref tracking
-fory::field<std::shared_ptr<Node>, 0, fory::nullable, fory::ref> link;
-```
+Invalid scalar/type combinations fail at compile time.
-## Type Rules
+## Nested Specs
-| Type | Allowed Options | Nullability |
-| ------------------------------------ | ------------------------------- | ---------------------------------- |
-| Primitives, strings | None | Use `std::optional<T>` if nullable |
-| `std::optional<T>` | None | Inherently nullable |
-| `std::shared_ptr<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default |
-| `fory::serialization::SharedWeak<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default |
-| `std::unique_ptr<T>` | `nullable`, `dynamic<V>` | Non-null by default |
-
-## Complete Example
+Use the `fory::T` namespace for value-node specs inside containers and wrapper
+carriers. Untyped specs infer the actual C++ type at that node:
```cpp
-#include "fory/serialization/fory.h"
-
-using namespace fory::serialization;
-
-// Define a struct with various field configurations
-struct Document {
- // Required fields (non-nullable)
- fory::field<std::string, 0> title;
- fory::field<int32_t, 1> version;
-
- // Optional primitive using std::optional
- fory::field<std::optional<std::string>, 2> description;
+namespace T = fory::T;
- // Nullable pointer
- fory::field<std::unique_ptr<std::string>, 3, fory::nullable> metadata;
-
- // Reference-tracked shared pointer
- fory::field<std::shared_ptr<Document>, 4, fory::ref> parent;
-
- // Nullable + reference-tracked
- fory::field<std::shared_ptr<Document>, 5, fory::nullable, fory::ref> related;
+struct Foo {
+ std::vector<uint32_t> values;
+ std::map<uint32_t, std::vector<int64_t>> nested;
};
-FORY_STRUCT(Document, title, version, description, metadata, parent, related);
-
-int main() {
- auto fory = Fory::builder().xlang(true).build();
- fory.register_struct<Document>(100);
-
- Document doc;
- doc.title = "My Document";
- doc.version = 1;
- doc.description = "A sample document";
- doc.metadata = nullptr; // Allowed because nullable
- doc.parent = std::make_shared<Document>();
- doc.parent.get()->title = "Parent Doc";
- doc.related = nullptr; // Allowed because nullable
-
- auto bytes = fory.serialize(doc).value();
- auto decoded = fory.deserialize<Document>(bytes).value();
-}
-```
-
-## Compile-Time Validation
-
-Invalid configurations are caught at compile time:
-
-```cpp
-// Error: nullable and not_null are mutually exclusive
-fory::field<std::shared_ptr<int>, 0, fory::nullable, fory::not_null> bad1;
-// Error: nullable only valid for smart pointers
-fory::field<int32_t, 0, fory::nullable> bad2;
-
-// Error: ref only valid for shared_ptr
-fory::field<std::unique_ptr<int>, 0, fory::ref> bad3;
-
-// Error: options not allowed for std::optional (inherently nullable)
-fory::field<std::optional<int>, 0, fory::nullable> bad4;
-```
-
-## Backwards Compatibility
-
-Existing structs without `fory::field<>` wrappers continue to work:
-
-```cpp
-// Old style - still works
-struct LegacyPerson {
- std::string name;
- int32_t age;
-};
-FORY_STRUCT(LegacyPerson, name, age);
-
-// New style with field metadata
-struct ModernPerson {
- fory::field<std::string, 0> name;
- fory::field<int32_t, 1> age;
-};
-FORY_STRUCT(ModernPerson, name, age);
+FORY_STRUCT(Foo,
+ (values, fory::F().list(T::fixed())),
+ (nested, fory::F().map(T::varint(),
+ T::list(T::tagged()))));
```
-## FORY_FIELD_TAGS Macro
-
-The `FORY_FIELD_TAGS` macro provides a non-invasive way to add field metadata without modifying struct definitions. This is useful for:
-
-- **Third-party types**: Add metadata to types you don't own
-- **Clean structs**: Keep struct definitions as pure C++
-- **Isolated dependencies**: Confine Fory headers to serialization config files
-
-### Usage
+Typed specs are optional validators and make the intended node type explicit:
```cpp
-// user_types.h - NO fory headers needed!
-struct Document {
- std::string title;
- int32_t version;
- std::optional<std::string> description;
- std::shared_ptr<User> author;
- std::shared_ptr<User> reviewer;
- std::shared_ptr<Document> parent;
- std::unique_ptr<Data> data;
-};
-
-// serialization_config.cpp - fory config isolated here
-#include "fory/serialization/fory.h"
-#include "user_types.h"
-
-FORY_STRUCT(Document, title, version, description, author, reviewer, parent, data)
-
-FORY_FIELD_TAGS(Document,
- (title, 0), // string: non-nullable
- (version, 1), // int: non-nullable
- (description, 2), // optional: inherently nullable
- (author, 3), // shared_ptr: non-nullable (default)
- (reviewer, 4, nullable), // shared_ptr: nullable
- (parent, 5, ref), // shared_ptr: non-nullable, with ref tracking
- (data, 6, nullable) // unique_ptr: nullable
-)
+FORY_STRUCT(Foo, (nested, fory::F().map(T::uint32().varint(),
+ T::list(T::int64().tagged()))));
```
-### FORY_FIELD_TAGS Options
-
-| Field Type | Valid Combinations |
-| -------------------- | ---------------------------------------------------------------------------------------- |
-| Primitives, strings | `(field, id)` only |
-| `std::optional<T>` | `(field, id)` only |
-| `std::shared_ptr<T>` | `(field, id)`, `(field, id, nullable)`, `(field, id, ref)`, `(field, id, nullable, ref)` |
-| `std::unique_ptr<T>` | `(field, id)`, `(field, id, nullable)` |
+Supported recursive composition methods are:
-## FORY_FIELD_CONFIG Macro
+| Method | Applies to |
+| ------------------- | ------------------------------------- |
+| `list(elem)` | `std::vector<T>` and list-like fields |
+| `set(elem)` | `std::set<T>` and set-like fields |
+| `map(key, value)` | `std::map<K, V>` and map-like fields |
+| `map().key(spec)` | Override only the map key |
+| `map().value(spec)` | Override only the map value |
+| `inner(child)` | Transparent single-child carriers |
-The `FORY_FIELD_CONFIG` macro is the most powerful and flexible way to configure field-level serialization. It provides:
-
-- **Builder pattern API**: Fluent, chainable configuration with `F(id).option1().option2()`
-- **Encoding control**: Specify how unsigned integers are encoded (varint, fixed, tagged)
-- **Compile-time verification**: Field names are verified against member pointers
-- **Cross-language compatibility**: Configure encoding to match other languages (Java, Rust, etc.)
-
-### Basic Syntax
+Partial map overrides are useful when only one side needs a non-default
+encoding:
```cpp
-FORY_FIELD_CONFIG(StructType,
- (field1, fory::F(0)), // Simple: just ID
- (field2, fory::F(1).nullable()), // With nullable
- (field3, fory::F(2).varint()), // With encoding
- (field4, fory::F(3).nullable().ref()), // Multiple options
- (field5, 4) // Backward compatible: integer ID
-);
+FORY_STRUCT(Foo,
+ (nested, fory::F().map().key(T::varint())),
+ (other, fory::F().map().value(T::list(T::tagged()))));
```
-### The F() Builder
-
-The `fory::F(id)` factory creates a `FieldMeta` object that supports method chaining:
+## Carrier Inner Specs
-```cpp
-fory::F(0) // Create with field ID 0
- .nullable() // Mark as nullable
- .ref() // Enable reference tracking
- .varint() // Use variable-length encoding
- .fixed() // Use fixed-size encoding
- .tagged() // Use tagged encoding
- .dynamic(false) // skip type info (no dynamic dispatch)
- .dynamic(true) // Force type info (enable dynamic dispatch)
- .compress(false) // Disable compression
-```
-
-**Tip:** To use `F()` without the `fory::` prefix, add a using declaration:
+Use `.inner(...)` for wrapper-like carriers. The carrier kind still comes from
+the actual C++ type, and controls nullable/reference behavior:
```cpp
-using fory::F;
+struct WrapperFields {
+ std::optional<std::vector<uint32_t>> maybe_values;
+ std::shared_ptr<std::vector<int64_t>> shared_values;
+};
-FORY_FIELD_CONFIG(MyStruct,
- (field1, F(0).varint()), // No prefix needed
- (field2, F(1).nullable())
-);
+FORY_STRUCT(WrapperFields,
+ (maybe_values, fory::F().inner(T::list(T::varint()))),
+ (shared_values,
+ fory::F().nullable().ref().inner(T::list(T::tagged()))));
```
-### Encoding Options for Unsigned Integers
+`.inner(...)` is the only public combinator for `std::optional<T>`,
+`std::shared_ptr<T>`, `std::unique_ptr<T>`, and
+`fory::serialization::SharedWeak<T>`.
-For `uint32_t` and `uint64_t` fields, you can specify the wire encoding:
+## Nullability, Reference Tracking, And Dynamic Fields
-| Method | Type ID | Description | Use Case |
-| ----------- | ------------- | ---------------------------------------------- | ------------------------------------- |
-| `.varint()` | VAR_UINT32/64 | Variable-length encoding (1-5 or 1-10 bytes) | Values typically small |
-| `.fixed()` | UINT32/64 | Fixed-size encoding (always 4 or 8 bytes) | Values uniformly distributed |
-| `.tagged()` | TAGGED_UINT64 | Tagged hybrid encoding with size hint (uint64) | Mixed small and large values (uint64) |
-
-**Note:** `uint8_t` and `uint16_t` always use fixed encoding (UINT8, UINT16).
-
-### Complete Example
+`std::optional<T>` is nullable by default. Smart pointers may be marked nullable
+or reference-tracked in the field spec:
```cpp
-#include "fory/serialization/fory.h"
-
-using namespace fory::serialization;
-
-// Define struct with unsigned integer fields
-struct MetricsData {
- // Counters - often small values, use varint for space efficiency
- uint32_t request_count;
- uint64_t bytes_sent;
-
- // IDs - uniformly distributed, use fixed for consistent performance
- uint32_t user_id;
- uint64_t session_id;
-
- // Timestamps - use tagged encoding for mixed value ranges
- uint64_t created_at;
-
- // Nullable fields
- std::optional<uint32_t> error_count;
- std::optional<uint64_t> last_access_time;
+struct Node {
+ std::string name;
+ std::shared_ptr<Node> next;
};
-FORY_STRUCT(MetricsData, request_count, bytes_sent, user_id, session_id,
- created_at, error_count, last_access_time);
-
-// Configure field encoding
-FORY_FIELD_CONFIG(MetricsData,
- // Small counters - varint saves space
- (request_count, fory::F(0).varint()),
- (bytes_sent, fory::F(1).varint()),
-
- // IDs - fixed for consistent performance
- (user_id, fory::F(2).fixed()),
- (session_id, fory::F(3).fixed()),
-
- // Timestamp - tagged encoding
- (created_at, fory::F(4).tagged()),
-
- // Nullable fields
- (error_count, fory::F(5).nullable().varint()),
- (last_access_time, fory::F(6).nullable().tagged())
-);
-
-int main() {
- auto fory = Fory::builder().xlang(true).build();
- fory.register_struct<MetricsData>(100);
-
- MetricsData data;
- data.request_count = 42;
- data.bytes_sent = 1024;
- data.user_id = 12345678;
- data.session_id = 9876543210;
- data.created_at = 1704067200000000000ULL; // 2024-01-01 in nanoseconds
- data.error_count = 3;
- data.last_access_time = std::nullopt;
-
- auto bytes = fory.serialize(data).value();
- auto decoded = fory.deserialize<MetricsData>(bytes).value();
-}
+FORY_STRUCT(Node, name, (next, fory::F().nullable().ref()));
```
-### Cross-Language Compatibility
-
-When serializing data to be read by other languages, use `FORY_FIELD_CONFIG` to match their encoding expectations:
-
-**Java Compatibility:**
+For polymorphic pointer fields, use `.dynamic(true)` to always write runtime
+type information, `.dynamic(false)` to use the declared type directly, or omit
+it to let Fory infer the behavior from the C++ type:
```cpp
-// Java uses these type IDs for unsigned integers:
-// - Byte (u8): UINT8 (fixed)
-// - Short (u16): UINT16 (fixed)
-// - Integer (u32): VAR_UINT32 (varint) or UINT32 (fixed)
-// - Long (u64): VAR_UINT64 (varint), UINT64 (fixed), or TAGGED_UINT64
-
-struct JavaCompatible {
- uint8_t byte_field; // Maps to Java Byte
- uint16_t short_field; // Maps to Java Short
- uint32_t int_var_field; // Maps to Java Integer with varint
- uint32_t int_fixed_field; // Maps to Java Integer with fixed
- uint64_t long_var_field; // Maps to Java Long with varint
- uint64_t long_tagged; // Maps to Java Long with tagged
+struct Zoo {
+ std::shared_ptr<Animal> star;
+ std::shared_ptr<Animal> mascot;
};
-FORY_STRUCT(JavaCompatible, byte_field, short_field, int_var_field,
- int_fixed_field, long_var_field, long_tagged);
-
-FORY_FIELD_CONFIG(JavaCompatible,
- (byte_field, fory::F(0)), // UINT8 (auto)
- (short_field, fory::F(1)), // UINT16 (auto)
- (int_var_field, fory::F(2).varint()), // VAR_UINT32
- (int_fixed_field, fory::F(3).fixed()), // UINT32
- (long_var_field, fory::F(4).varint()), // VAR_UINT64
- (long_tagged, fory::F(5).tagged()) // TAGGED_UINT64
-);
+FORY_STRUCT(Zoo, (star, fory::F().nullable().dynamic(true)),
+ (mascot, fory::F().nullable().dynamic(false)));
```
-### Schema Evolution with FORY_FIELD_CONFIG
+## Unions
-In compatible mode, fields can have different nullability between sender and receiver:
+`FORY_UNION` cases must use explicit ids. Name-mode `fory::F()` is invalid for
+union metadata:
```cpp
-// Version 1: All fields non-nullable
-struct DataV1 {
- uint32_t id;
- uint64_t timestamp;
-};
-FORY_STRUCT(DataV1, id, timestamp);
-FORY_FIELD_CONFIG(DataV1,
- (id, fory::F(0).varint()),
- (timestamp, fory::F(1).tagged())
-);
+struct Choice {
+ std::variant<std::string, uint32_t> value;
-// Version 2: Added nullable fields
-struct DataV2 {
- uint32_t id;
- uint64_t timestamp;
- std::optional<uint32_t> version; // New nullable field
+ static Choice text(std::string value);
+ static Choice code(uint32_t value);
};
-FORY_STRUCT(DataV2, id, timestamp, version);
-FORY_FIELD_CONFIG(DataV2,
- (id, fory::F(0).varint()),
- (timestamp, fory::F(1).tagged()),
- (version, fory::F(2).nullable().varint()) // New field with nullable
-);
-```
-
-### FORY_FIELD_CONFIG Options Reference
-
-| Method | Description | Valid For |
-| ----------------- | ------------------------------------------------ | ---------------------------------------------------- |
-| `.nullable()` | Mark field as nullable | Smart pointers, primitives |
-| `.ref()` | Enable reference tracking | `std::shared_ptr`, `fory::serialization::SharedWeak` |
-| `.dynamic(true)` | Force type info to be written (dynamic dispatch) | Smart pointers |
-| `.dynamic(false)` | skip type info (use declared type directly) | Smart pointers |
-| `.varint()` | Use variable-length encoding | `uint32_t`, `uint64_t` |
-| `.fixed()` | Use fixed-size encoding | `uint32_t`, `uint64_t` |
-| `.tagged()` | Use tagged hybrid encoding | `uint64_t` only |
-| `.compress(v)` | Enable/disable field compression | All types |
-
-## Default Values
-
-- **Nullable**: Only `std::optional<T>` is nullable by default; all other types (including `std::shared_ptr`) are non-nullable
-- **Ref tracking**: Enabled by default for `std::shared_ptr<T>` and `fory::serialization::SharedWeak<T>`, disabled for other types unless configured
-You **need to configure fields** when:
+FORY_UNION(Choice, (text, std::string, fory::F(1)),
+ (code, uint32_t, fory::F(2).fixed()));
+```
-- A field can be null (use `std::optional<T>` or mark with `nullable()`)
-- A field needs reference tracking for shared/circular objects (use `ref()`)
-- Integer types need specific encoding for cross-language compatibility
-- You want to reduce metadata size (use field IDs)
+Generated C++ may omit the explicit case type when it can infer the payload type
+from a non-overloaded one-argument factory:
```cpp
-// Xlang mode: explicit configuration required
-struct User {
- std::string name; // Non-nullable by default
- std::optional<std::string> email; // Nullable (std::optional)
- std::shared_ptr<User> friend_ptr; // Ref tracking by default
-};
-
-FORY_STRUCT(User, name, email, friend_ptr);
-
-FORY_FIELD_CONFIG(User,
- (name, fory::F(0)),
- (email, fory::F(1)), // nullable implicit for optional
- (friend_ptr, fory::F(2).nullable().ref()) // explicit nullable + ref
-);
+FORY_UNION(GeneratedChoice, (text, fory::F(1)),
+ (code, fory::F(2).fixed()));
```
-### Default Values Summary
-
-| Type | Default Nullable | Default Ref Tracking |
-| ------------------------------------ | ---------------- | -------------------- |
-| Primitives, `string` | `false` | `false` |
-| `std::optional<T>` | `true` | `false` |
-| `std::shared_ptr<T>` | `false` | `true` |
-| `fory::serialization::SharedWeak<T>` | `false` | `true` |
-| `std::unique_ptr<T>` | `false` | `false` |
-
-## Related Topics
-
-- [Type Registration](type-registration.md) - Registering types with FORY_STRUCT
-- [Schema Evolution](schema-evolution.md) - Using tag IDs for schema evolution
-- [Configuration](configuration.md) - Enabling reference tracking globally
-- [Cross-Language](cross-language.md) - Interoperability with Java, Rust, Python
+The three-element form is the stable public form for handwritten code.
diff --git a/docs/guide/cpp/polymorphism.md b/docs/guide/cpp/polymorphism.md
index 97f5bad11..4465553ef 100644
--- a/docs/guide/cpp/polymorphism.md
+++ b/docs/guide/cpp/polymorphism.md
@@ -137,7 +137,8 @@ struct Container2 {
## Controlling Dynamic Dispatch
-Use `fory::dynamic<V>` to override automatic polymorphism detection:
+Use `fory::F().dynamic(V)` in `FORY_STRUCT` to override automatic
+polymorphism detection:
```cpp
struct Animal {
@@ -150,23 +151,24 @@ struct Pet {
std::shared_ptr<Animal> animal1;
// Force dynamic: type info written explicitly
- fory::field<std::shared_ptr<Animal>, 0, fory::dynamic<true>> animal2;
+ std::shared_ptr<Animal> animal2;
// Force non-dynamic: skip type info (faster but no runtime subtyping)
- fory::field<std::shared_ptr<Animal>, 1, fory::dynamic<false>> animal3;
+ std::shared_ptr<Animal> animal3;
};
-FORY_STRUCT(Pet, animal1, animal2, animal3);
+FORY_STRUCT(Pet, animal1, (animal2, fory::F().dynamic(true)),
+ (animal3, fory::F().dynamic(false)));
```
-**When to use `fory::dynamic<false>`:**
+**When to use `dynamic(false)`:**
- You know the runtime type will always match the declared type
- Performance is critical and you don't need subtype support
- Working with monomorphic data despite having a polymorphic base class
-### Field Configuration Without Wrapper Types
+### Field Configuration
-Use `FORY_FIELD_CONFIG` to configure fields without `fory::field<>` wrapper:
+Configure field metadata directly in `FORY_STRUCT`:
```cpp
struct Zoo {
@@ -174,17 +176,13 @@ struct Zoo {
std::shared_ptr<Animal> backup; // Nullable polymorphic field
std::shared_ptr<Animal> mascot; // Non-dynamic (no subtype dispatch)
};
-FORY_STRUCT(Zoo, star, backup, mascot);
-
-// Configure fields with tag IDs and options
-FORY_FIELD_CONFIG(Zoo,
- (star, fory::F(0)), // Tag ID 0, default options
- (backup, fory::F(1).nullable()), // Tag ID 1, allow nullptr
- (mascot, fory::F(2).dynamic(false)) // Tag ID 2, disable polymorphism
-);
+FORY_STRUCT(Zoo, (star, fory::F(0)),
+ (backup, fory::F(1).nullable()),
+ (mascot, fory::F(2).dynamic(false)));
```
-See [Field Configuration](field-configuration.md) for complete details on `fory::nullable`, `fory::ref`, and other field-level options
+See [Field Configuration](field-configuration.md) for complete details on
+`nullable()`, `ref()`, and other field-level options.
## std::unique_ptr Polymorphism
@@ -307,8 +305,8 @@ assert(!result.ok()); // Fails with depth exceeded error
## Nullability for Polymorphic Fields
By default, `std::shared_ptr<T>` and `std::unique_ptr<T>` fields are treated as
-non-nullable in the schema. To allow `nullptr`, wrap the field with
-`fory::field<>` (or `FORY_FIELD_TAGS`) and opt in with `fory::nullable`.
+non-nullable in the schema. To allow `nullptr`, mark the field nullable in
+`FORY_STRUCT`.
```cpp
struct Pet {
@@ -316,9 +314,9 @@ struct Pet {
std::shared_ptr<Animal> primary;
// Nullable via explicit field metadata
- fory::field<std::shared_ptr<Animal>, 0, fory::nullable> optional;
+ std::shared_ptr<Animal> optional;
};
-FORY_STRUCT(Pet, primary, optional);
+FORY_STRUCT(Pet, primary, (optional, fory::F().nullable()));
```
See [Field Configuration](field-configuration.md) for more details.
@@ -415,10 +413,10 @@ auto fory = Fory::builder()
auto fory = Fory::builder().max_dyn_depth(10).build();
```
-7. **Use `fory::nullable`** for optional polymorphic fields:
+7. **Use `nullable()`** for optional polymorphic fields:
```cpp
- fory::field<std::shared_ptr<Base>, 0, fory::nullable> optional_ptr;
+ FORY_STRUCT(Holder, (optional_ptr, fory::F().nullable()));
```
## Error Handling
@@ -455,10 +453,10 @@ if (!decoded_result.ok()) {
**Optimization tips:**
-1. **Use `fory::dynamic<false>`** when runtime type matches declared type:
+1. **Use `dynamic(false)`** when runtime type matches declared type:
```cpp
- fory::field<std::shared_ptr<Base>, 0, fory::dynamic<false>> fixed_type;
+ FORY_STRUCT(Holder, (fixed_type, fory::F().dynamic(false)));
```
2. **Minimize nesting depth** to reduce metadata overhead
diff --git a/docs/guide/xlang/field-nullability.md b/docs/guide/xlang/field-nullability.md
index ab888a563..de7f9dc2b 100644
--- a/docs/guide/xlang/field-nullability.md
+++ b/docs/guide/xlang/field-nullability.md
@@ -200,17 +200,32 @@ public class Config {
}
```
-### C++: fory::field Wrapper
+### C++: FORY_STRUCT Field Config
```cpp
struct Config {
- // Explicitly mark as nullable
- fory::field<std::string, 1, fory::nullable<true>> optional_setting;
+ std::optional<std::string> optional_setting;
+ std::string required_setting;
+};
+
+FORY_STRUCT(Config,
+ (optional_setting, fory::F(1)),
+ (required_setting, fory::F(2))
+);
+```
- // Explicitly mark as non-nullable (default)
- fory::field<std::string, 2, fory::nullable<false>> required_setting;
+For nullable pointer carriers, opt in with `.nullable()`:
+
+```cpp
+struct ConfigRef {
+ std::shared_ptr<std::string> optional_setting;
+ std::shared_ptr<std::string> required_setting;
};
-FORY_STRUCT(Config, optional_setting, required_setting);
+
+FORY_STRUCT(ConfigRef,
+ (optional_setting, fory::F(1).nullable()),
+ (required_setting, fory::F(2))
+);
```
## Null Value Handling
diff --git a/docs/guide/xlang/field-reference-tracking.md b/docs/guide/xlang/field-reference-tracking.md
index 1ed94bd3d..2aafbf2ee 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -141,7 +141,7 @@ public class Document {
}
```
-#### C++: fory::field Wrapper
+#### C++: FORY_STRUCT Field Config
```cpp
struct Document {
@@ -151,10 +151,14 @@ struct Document {
std::shared_ptr<Author> author;
fory::serialization::SharedWeak<Data> data;
- // Explicitly mark ref tracking when using field wrappers (optional)
- fory::field<std::shared_ptr<Tag>, 1, fory::ref> tag_owner;
+ std::shared_ptr<Tag> tag_owner;
};
-FORY_STRUCT(Document, title, author, data, tag_owner);
+FORY_STRUCT(Document,
+ title,
+ author,
+ data,
+ (tag_owner, fory::F().ref())
+);
```
To disable reference tracking for C++ entirely, set
diff --git a/docs/guide/xlang/field-type-meta.md b/docs/guide/xlang/field-type-meta.md
index 1c6880c85..58870fd36 100644
--- a/docs/guide/xlang/field-type-meta.md
+++ b/docs/guide/xlang/field-type-meta.md
@@ -85,9 +85,7 @@ public class Container {
### C++
-C++ uses the `fory::dynamic<V>` template tag or `.dynamic(bool)` builder method:
-
-**Using `fory::field<>` template**:
+C++ uses the `.dynamic(bool)` builder method inside `FORY_STRUCT`:
```cpp
#include "fory/serialization/fory.h"
@@ -100,29 +98,15 @@ struct Animal {
struct Zoo {
// Auto: type info written because Animal is polymorphic (std::is_polymorphic)
- fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal;
+ std::shared_ptr<Animal> animal;
// Force non-dynamic: skip type info even though Animal is polymorphic
- fory::field<std::shared_ptr<Animal>, 1, fory::nullable, fory::dynamic<false>> fixed_animal;
+ std::shared_ptr<Animal> fixed_animal;
// Force dynamic: write type info even for non-polymorphic types
- fory::field<std::shared_ptr<Data>, 2, fory::dynamic<true>> polymorphic_data;
-};
-FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data);
-```
-
-**Using `FORY_FIELD_CONFIG` macro**:
-
-```cpp
-struct Zoo {
- std::shared_ptr<Animal> animal;
- std::shared_ptr<Animal> fixed_animal;
std::shared_ptr<Data> polymorphic_data;
};
-
-FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data);
-
-FORY_FIELD_CONFIG(Zoo,
+FORY_STRUCT(Zoo,
(animal, fory::F(0).nullable()), // Auto-detect polymorphism
(fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info
(polymorphic_data, fory::F(2).dynamic(true)) // Force type info
@@ -286,9 +270,7 @@ struct Zoo {
std::shared_ptr<Dog> maybe_mixed_breed;
};
-FORY_STRUCT(Zoo, animal, maybe_mixed_breed);
-
-FORY_FIELD_CONFIG(Zoo,
+FORY_STRUCT(Zoo,
(animal, fory::F(0).nullable()), // Auto-detect (Animal is polymorphic)
(maybe_mixed_breed, fory::F(1).dynamic(true)) // Force dynamic for concrete type
);
diff --git a/java/fory-core/src/test/java/org/apache/fory/xlang/CPPXlangTest.java b/java/fory-core/src/test/java/org/apache/fory/xlang/CPPXlangTest.java
index 8a02157ae..c1f320d8a 100644
--- a/java/fory-core/src/test/java/org/apache/fory/xlang/CPPXlangTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/CPPXlangTest.java
@@ -210,6 +210,18 @@ public class CPPXlangTest extends XlangTestBase {
super.testStructWithMap(enableCodegen);
}
+ @Test(groups = "xlang", dataProvider = "enableCodegenParallel")
+ public void testNestedAnnotatedContainerSchemaConsistent(boolean enableCodegen)
+ throws java.io.IOException {
+ super.testNestedAnnotatedContainerSchemaConsistent(enableCodegen);
+ }
+
+ @Test(groups = "xlang", dataProvider = "enableCodegenParallel")
+ public void testNestedAnnotatedContainerCompatible(boolean enableCodegen)
+ throws java.io.IOException {
+ super.testNestedAnnotatedContainerCompatible(enableCodegen);
+ }
+
@Test(groups = "xlang", dataProvider = "enableCodegenParallel")
public void testCollectionElementRefOverride(boolean enableCodegen) throws java.io.IOException {
super.testCollectionElementRefOverride(enableCodegen);
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org