diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adfd5b080f..3a5b8db11a 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 f1d65bff40..b9ed84e5c6 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 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_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 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 1480818cff..fd3944d8e7 100644 --- a/benchmarks/cpp/benchmark_report.py +++ b/benchmarks/cpp/benchmark_report.py @@ -45,6 +45,17 @@ "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 @@ def plot_combined_tps_subplot(ax, grouped_datatypes, operation, title): 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 541e100a12..c0620b8547 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 0bbebe8a0e..f3f4537211 100644 --- a/compiler/fory_compiler/frontend/proto/translator.py +++ b/compiler/fory_compiler/frontend/proto/translator.py @@ -297,6 +297,7 @@ def _translate_oneof_field_reference( 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 f1b6f4de6b..66605d9731 100644 --- a/compiler/fory_compiler/generators/cpp.py +++ b/compiler/fory_compiler/generators/cpp.py @@ -125,14 +125,6 @@ def get_namespaced_type_name( 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 @@ def generate_header(self) -> GeneratedFile: 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 @@ def generate_header(self) -> GeneratedFile: [], enum_macros, union_macros, - field_config_macros, evolving_macros, "", ) @@ -353,10 +343,6 @@ def generate_header(self) -> GeneratedFile: 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 @@ def get_field_storage_type( 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 @@ def generate_message_definition( 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 @@ def generate_message_definition( lineage, enum_macros, union_macros, - field_config_macros, evolving_macros, body_indent, ) @@ -959,13 +945,7 @@ def generate_message_definition( 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 @@ def generate_union_macros( 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 @@ def generate_namespaced_type( 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 @@ def get_union_field_meta(self, field: Field) -> str: 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 acd7741778..0553c7524c 100644 --- a/compiler/fory_compiler/ir/validator.py +++ b/compiler/fory_compiler/ir/validator.py @@ -54,8 +54,9 @@ def __str__(self) -> str: 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 @@ def validate(self) -> bool: 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 881451aeb5..efb1ee6223 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> 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 f35d24742e..7b1510e886 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> 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 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 d5f02247f6..0b2b64a61d 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 a7e23d9b21..faad1c2e77 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 #include #include #include #include #include #include -#include namespace fory { @@ -37,372 +34,53 @@ namespace serialization { template 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 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`: Force type info to be written (enable runtime subtype -/// support) -/// - `dynamic`: skip type info (use declared type directly) -/// -/// By default, Fory auto-detects polymorphism via `std::is_polymorphic`. -/// Use this tag to override the default behavior. -/// -/// Example: -/// fory::field, 0, fory::dynamic> ptr; -template struct dynamic : std::bool_constant {}; - namespace detail { -// ============================================================================ -// Type Traits for Smart Pointers and Optional -// ============================================================================ - template using FieldInfo = decltype(::fory::meta::fory_field_info(std::declval())); -inline constexpr size_t k_invalid_field_index = static_cast(-1); - -template constexpr size_t field_index(std::string_view name) { - constexpr auto names = FieldInfo::Names; - for (size_t i = 0; i < names.size(); ++i) { - if (names[i] == name) { - return i; - } - } - return k_invalid_field_index; -} - -template struct FieldTypeAt; - -template -struct FieldTypeAt> { - using PtrsType = typename FieldInfo::PtrsType; - using PtrT = std::tuple_element_t; - using type = ::fory::meta::RemoveMemberPointerCVRefT; -}; - -template -struct FieldTypeAt> { - static_assert(Index != k_invalid_field_index, - "Unknown field name in FORY_FIELD_TAGS"); -}; - template struct is_shared_ptr : std::false_type {}; - template struct is_shared_ptr> : std::true_type {}; - template inline constexpr bool is_shared_ptr_v = is_shared_ptr::value; template struct is_shared_weak : std::false_type {}; - template struct is_shared_weak> : std::true_type {}; - template inline constexpr bool is_shared_weak_v = is_shared_weak::value; template struct is_unique_ptr : std::false_type {}; - template struct is_unique_ptr> : std::true_type {}; - template inline constexpr bool is_unique_ptr_v = is_unique_ptr::value; template struct is_optional : std::false_type {}; - template struct is_optional> : std::true_type {}; - template inline constexpr bool is_optional_v = is_optional::value; -/// Helper to check if type is shared_ptr/SharedWeak or unique_ptr template inline constexpr bool is_smart_ptr_v = is_shared_ptr_v || is_shared_weak_v || is_unique_ptr_v; -// ============================================================================ -// Option Tag Detection -// ============================================================================ - -/// Check if a specific tag type is present in the Options pack -template -inline constexpr bool has_option_v = (std::is_same_v || ...); - -/// Check if a type is a dynamic tag -template struct is_dynamic_tag : std::false_type {}; -template struct is_dynamic_tag> : std::true_type {}; template -inline constexpr bool is_dynamic_tag_v = is_dynamic_tag::value; - -/// Check if any dynamic tag is present in Options pack -template -inline constexpr bool has_dynamic_option_v = (is_dynamic_tag_v || ...); - -/// Extract the dynamic value from Options pack (default = -1 for AUTO) -/// Returns: 1 for dynamic, 0 for dynamic, -1 for AUTO (not -/// specified) -template struct get_dynamic_value { - static constexpr int value = -1; // AUTO -}; -template -struct get_dynamic_value, Rest...> { - static constexpr int value = V ? 1 : 0; -}; -template -struct get_dynamic_value { - static constexpr int value = get_dynamic_value::value; -}; -template -inline constexpr int get_dynamic_value_v = get_dynamic_value::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 -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 -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 struct ForyFieldTagsImpl { - static constexpr bool has_tags = false; -}; +using MemberFieldConfigDescriptor = + decltype(T::fory_field_config(std::declval>())); template -using AdlFieldTagsDescriptor = - decltype(fory_field_tags(std::declval>())); +using AdlFieldConfigDescriptor = + decltype(fory_field_config(std::declval>())); template -struct HasAdlFieldTags : std::false_type {}; +struct HasMemberFieldConfig : std::false_type {}; template -struct HasAdlFieldTags>> +struct HasMemberFieldConfig>> : std::true_type {}; -template 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; - static constexpr bool use_index = true; -}; - -template -struct FieldTagsInfo::value>> { - using Descriptor = AdlFieldTagsDescriptor; - static constexpr bool has_tags = Descriptor::has_tags; - static inline constexpr auto entries = Descriptor::entries; - using Entries = std::decay_t; - static constexpr size_t field_count = std::tuple_size_v; - static constexpr bool use_index = false; -}; - -template -struct FieldTagsInfo::value && - ForyFieldTagsImpl::has_tags>> { - static constexpr bool has_tags = true; - static constexpr size_t field_count = ForyFieldTagsImpl::field_count; - using Entries = typename ForyFieldTagsImpl::Entries; - static inline constexpr auto entries = Entries{}; - static constexpr bool use_index = true; -}; - -template -inline constexpr bool has_field_tags_v = FieldTagsInfo::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(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 constexpr auto normalize_config(T &&v) { - if constexpr (std::is_integral_v>) { - // Old syntax: just an integer ID - return FieldMeta{}.id(static_cast(v)); - } else if constexpr (std::is_same_v, FieldMeta>) { - // New syntax: already a FieldMeta - return v; - } else { - static_assert( - std::is_integral_v> || - std::is_same_v, 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 constexpr FieldMeta apply_tag(FieldMeta m, dynamic) { - return m.dynamic(V); -} - -/// Fold multiple tags onto a base config -template -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 struct ForyFieldConfigImpl { - static constexpr bool has_config = false; -}; - -template -using AdlFieldConfigDescriptor = - decltype(fory_field_config(std::declval>())); - template struct HasAdlFieldConfig : std::false_type {}; @@ -417,35 +95,36 @@ template struct FieldConfigInfo { }; template -struct FieldConfigInfo::value>> { - using Descriptor = AdlFieldConfigDescriptor; +struct FieldConfigInfo::value>> { + using Descriptor = MemberFieldConfigDescriptor; 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 -struct FieldConfigInfo::value && - ForyFieldConfigImpl::has_config>> { - static constexpr bool has_config = true; - static constexpr size_t field_count = ForyFieldConfigImpl::field_count; - static inline constexpr auto entries = ForyFieldConfigImpl::entries; +struct FieldConfigInfo::value && + HasAdlFieldConfig::value>> { + using Descriptor = AdlFieldConfigDescriptor; + 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 inline constexpr bool has_field_config_v = FieldConfigInfo::has_config; -/// Helper to get field encoding from FieldConfigInfo template 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 @@ struct GetFieldConfigEntry Template -// ============================================================================ - -/// Field wrapper template that provides compile-time field metadata. -/// -/// Usage: -/// struct Person { -/// fory::field name; // non-nullable -/// fory::field age; // non-nullable -/// fory::field, 2> nickname; // inherently -/// nullable fory::field, 3> parent; // -/// non-nullable fory::field, 4, fory::nullable> -/// guardian; fory::field, 5, fory::ref> node; -/// fory::field, 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: Inherently nullable, no options needed -/// - std::shared_ptr: Can use nullable and/or ref -/// - SharedWeak: Can use nullable and/or ref -/// - std::unique_ptr: Can use nullable only (no ref - exclusive -/// ownership) -template class field { - // Validate: nullable and not_null are mutually exclusive - static_assert(!(detail::has_option_v && - detail::has_option_v), - "fory::nullable and fory::not_null are mutually exclusive."); - - // Validate: nullable only for smart pointers - static_assert(!detail::has_option_v || - detail::is_smart_ptr_v, - "fory::nullable is only valid for shared_ptr/SharedWeak/" - "unique_ptr. " - "Use std::optional for nullable primitives/strings."); - - // Validate: not_null only for smart pointers (for now) - static_assert(!detail::has_option_v || - detail::is_smart_ptr_v, - "fory::not_null is only valid for pointer types."); - - // Validate: ref only for shared_ptr/SharedWeak - static_assert(!detail::has_option_v || - detail::is_shared_ptr_v || detail::is_shared_weak_v, - "fory::ref is only valid for shared_ptr/SharedWeak " - "(reference tracking requires shared ownership)."); - - // Validate: dynamic only for smart pointers - static_assert(!detail::has_dynamic_option_v || - detail::is_smart_ptr_v, - "fory::dynamic is only valid for shared_ptr/SharedWeak/" - "unique_ptr."); - - // Validate: no options for optional (inherently nullable) - static_assert(!detail::is_optional_v || sizeof...(Options) == 0, - "std::optional is inherently nullable. No options allowed."); - - // Validate: no options for non-smart-pointer types - static_assert(detail::is_smart_ptr_v || detail::is_optional_v || - sizeof...(Options) == 0, - "Options are only valid for shared_ptr/SharedWeak/unique_ptr " - "fields. " - "Use std::optional 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 || - (detail::is_smart_ptr_v && detail::has_option_v); - - /// 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 || - detail::is_shared_weak_v || - detail::has_option_v; - - /// Dynamic type dispatch control: - /// - -1 (AUTO): Use std::is_polymorphic 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; - - 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 struct is_fory_field : std::false_type {}; - -template -struct is_fory_field> : std::true_type {}; - template inline constexpr bool is_fory_field_v = is_fory_field::value; -/// unwrap fory::field to get the underlying type template struct unwrap_field { using type = T; }; - -template -struct unwrap_field> { - using type = T; -}; - template using unwrap_field_t = typename unwrap_field::type; -/// get tag ID from field type (returns -1 if not a fory::field) template struct field_tag_id { static constexpr int16_t value = -1; }; - -template -struct field_tag_id> { - static constexpr int16_t value = Id; -}; - template inline constexpr int16_t field_tag_id_v = field_tag_id::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 struct field_is_nullable { static constexpr bool value = detail::is_optional_v; }; - -template -struct field_is_nullable> { - static constexpr bool value = field::is_nullable; -}; - template inline constexpr bool field_is_nullable_v = field_is_nullable::value; -/// get track_ref from field type template struct field_track_ref { static constexpr bool value = detail::is_shared_ptr_v || detail::is_shared_weak_v; }; - -template -struct field_track_ref> { - static constexpr bool value = field::track_ref; -}; - template inline constexpr bool field_track_ref_v = field_track_ref::value; -/// get dynamic_value from field type (-1 = AUTO, 0 = FALSE, 1 = TRUE) template struct field_dynamic_value { - static constexpr int value = -1; // AUTO + static constexpr int value = -1; }; - -template -struct field_dynamic_value> { - static constexpr int value = field::dynamic_value; -}; - template inline constexpr int field_dynamic_value_v = field_dynamic_value::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), etc. -template -struct ParseFieldTagEntry { - static constexpr bool is_nullable = - is_optional_v || - (is_smart_ptr_v && has_option_v); - - static constexpr bool track_ref = is_shared_ptr_v || - is_shared_weak_v || - has_option_v; - - static constexpr int dynamic_value = get_dynamic_value_v; - - // Compile-time validation - static_assert(!has_option_v || - is_smart_ptr_v, - "fory::nullable is only valid for shared_ptr/SharedWeak/" - "unique_ptr"); - - static_assert(!has_option_v || is_shared_ptr_v || - is_shared_weak_v, - "fory::ref is only valid for shared_ptr/SharedWeak"); - - static_assert(!has_dynamic_option_v || is_smart_ptr_v, - "fory::dynamic is only valid for shared_ptr/SharedWeak/" - "unique_ptr"); - - using type = FieldTagEntry; -}; - -/// get field tag entry by index from FieldTagsInfo -template 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 -struct GetFieldTagEntry< - T, Index, - std::enable_if_t::has_tags && - (Index < FieldTagsInfo::field_count) && - FieldTagsInfo::use_index>> { - using Entry = std::tuple_element_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 -struct GetFieldTagEntry::has_tags && - !FieldTagsInfo::use_index>> { -private: - static constexpr std::string_view field_name = FieldInfo::Names[Index]; - - template static constexpr FieldTagEntryWithName find_entry() { - if constexpr (I >= std::tuple_size_v::Entries>) { - return FieldTagEntryWithName{"", -1, false, false, -1}; - } else { - constexpr auto entry = std::get(FieldTagsInfo::entries); - if (std::string_view{entry.name} == field_name) { - return entry; - } - return find_entry(); - } - } - - 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( \ - std::string_view{FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple))}) - -#define FORY_FT_FIELD_TYPE(Type, tuple) \ - typename ::fory::detail::FieldTypeAt::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; \ - static constexpr size_t field_count = std::tuple_size_v; \ - }; \ - constexpr auto fory_field_tags(::fory::meta::Identity) { \ - 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>; \ - }; \ - constexpr auto fory_field_config(::fory::meta::Identity) { \ - 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 f49cfddfee..b5476b5593 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 +#include #include #include #include @@ -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(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 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 \ 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; \ + [[maybe_unused]] static constexpr size_t field_count = \ + std::tuple_size_v; \ 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 &) noexcept { \ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ } \ + [[maybe_unused]] inline static constexpr auto fory_field_config( \ + const ::fory::meta::Identity &) noexcept { \ + return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ + } \ [[maybe_unused]] inline static constexpr std::true_type fory_struct_marker( \ const ::fory::meta::Identity &) 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 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 &) noexcept { \ return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ } \ + [[maybe_unused]] inline static constexpr auto fory_field_config( \ + const ::fory::meta::Identity &) noexcept { \ + return FORY_PP_CONCAT(ForyFieldInfoDescriptor_, unique_id){}; \ + } \ [[maybe_unused]] inline static constexpr std::true_type fory_struct_marker( \ const ::fory::meta::Identity &) noexcept { \ return {}; \ diff --git a/cpp/fory/meta/field_test.cc b/cpp/fory/meta/field_test.cc index f8c92af562..24285a133b 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 #include #include #include +#include -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); - static_assert(!detail::is_shared_ptr_v); - static_assert(!detail::is_shared_ptr_v>); static_assert(detail::is_shared_ptr_v>); - static_assert(detail::is_shared_ptr_v>); -} - -TEST(FieldTraits, IsUniquePtr) { static_assert(!detail::is_unique_ptr_v); - static_assert(!detail::is_unique_ptr_v); - static_assert(!detail::is_unique_ptr_v>); static_assert(detail::is_unique_ptr_v>); - static_assert(detail::is_unique_ptr_v>); -} - -TEST(FieldTraits, IsOptional) { static_assert(!detail::is_optional_v); - static_assert(!detail::is_optional_v); - static_assert(!detail::is_optional_v>); static_assert(detail::is_optional_v>); - static_assert(detail::is_optional_v>); -} - -TEST(FieldTraits, IsSmartPtr) { - static_assert(!detail::is_smart_ptr_v); - static_assert(!detail::is_smart_ptr_v); static_assert(!detail::is_smart_ptr_v>); static_assert(detail::is_smart_ptr_v>); static_assert(detail::is_smart_ptr_v>); } -// ============================================================================ -// fory::field<> Basic Tests -// ============================================================================ - -TEST(Field, BasicPrimitive) { - using FieldType = field; - 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; - 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, 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(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, 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(99); - EXPECT_NE(f.value, nullptr); - EXPECT_EQ(*f.value, 99); -} - -TEST(Field, SharedPtrNullable) { - // shared_ptr with nullable option - using FieldType = field, 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, 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, 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> values; -TEST(Field, UniquePtrNonNullable) { - // unique_ptr is non-nullable by default - using FieldType = field, 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, 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, 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, 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); - static_assert(!is_fory_field_v); - static_assert(!is_fory_field_v>); - static_assert(is_fory_field_v>); - static_assert(is_fory_field_v>); - static_assert(is_fory_field_v, 2, nullable>>); -} - -TEST(FieldTraits, UnwrapField) { - static_assert(std::is_same_v, int>); - static_assert(std::is_same_v, std::string>); - static_assert(std::is_same_v>, int>); - static_assert( - std::is_same_v>, std::string>); - static_assert( - std::is_same_v, 2, nullable>>, - std::shared_ptr>); -} - -TEST(FieldTraits, FieldTagId) { - static_assert(field_tag_id_v == -1); - static_assert(field_tag_id_v> == 0); - static_assert(field_tag_id_v> == 42); -} - -TEST(FieldTraits, FieldIsNullable) { - static_assert(field_is_nullable_v == false); - static_assert(field_is_nullable_v> == true); - static_assert(field_is_nullable_v> == false); - static_assert(field_is_nullable_v, 1>> == true); - static_assert(field_is_nullable_v, 2>> == false); - static_assert(field_is_nullable_v, 3, nullable>> == - true); -} - -TEST(FieldTraits, FieldTrackRef) { - static_assert(field_track_ref_v == false); - static_assert(field_track_ref_v> == true); - static_assert(field_track_ref_v> == false); - static_assert(field_track_ref_v, 1>> == true); - static_assert(field_track_ref_v, 2, ref>> == true); - static_assert( - field_track_ref_v, 3, nullable, ref>> == true); -} - -// ============================================================================ -// Struct with fory::field<> members -// ============================================================================ - -struct Person { - field name; - field age; - field, 2> nickname; - field, 3, ref> parent; - field, 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("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> values; -// Test struct with pure C++ types (no fory::field wrappers) -struct Document { - std::string title; - int32_t version; - std::optional description; - std::shared_ptr author; - std::shared_ptr reviewer; - std::shared_ptr parent; - std::unique_ptr 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 left; - std::shared_ptr 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 == true); - static_assert(detail::has_field_tags_v == false); // Uses fory::field - static_assert(detail::has_field_tags_v == false); -} - -TEST(FieldTags, FieldCount) { - static_assert(detail::FieldTagsInfo::field_count == 7); -} - -TEST(FieldTags, TagIds) { - // Check tag IDs - static_assert(detail::GetFieldTagEntry::id == 0); - static_assert(detail::GetFieldTagEntry::id == 1); - static_assert(detail::GetFieldTagEntry::id == 2); - static_assert(detail::GetFieldTagEntry::id == 3); - static_assert(detail::GetFieldTagEntry::id == 4); - static_assert(detail::GetFieldTagEntry::id == 5); - static_assert(detail::GetFieldTagEntry::id == 6); -} - -TEST(FieldTags, Nullability) { - // title (string): non-nullable - static_assert(detail::GetFieldTagEntry::is_nullable == false); - // version (int): non-nullable - static_assert(detail::GetFieldTagEntry::is_nullable == false); - // description (optional): inherently nullable - static_assert(detail::GetFieldTagEntry::is_nullable == true); - // author (shared_ptr): non-nullable (default) - static_assert(detail::GetFieldTagEntry::is_nullable == false); - // reviewer (shared_ptr, nullable): nullable - static_assert(detail::GetFieldTagEntry::is_nullable == true); - // parent (shared_ptr, ref): non-nullable - static_assert(detail::GetFieldTagEntry::is_nullable == false); - // metadata (unique_ptr, nullable): nullable - static_assert(detail::GetFieldTagEntry::is_nullable == true); +TEST(FieldConfig, NameModeEntries) { + static_assert(detail::has_field_config_v); + static_assert(!detail::GetFieldConfigEntry::has_id); + static_assert(!detail::GetFieldConfigEntry::has_id); + static_assert(detail::GetFieldConfigEntry::spec.kind_[0] == + FieldNodeKind::Inner); } -TEST(FieldTags, RefTracking) { - // shared_ptr fields track refs by default - static_assert(detail::GetFieldTagEntry::track_ref == false); - static_assert(detail::GetFieldTagEntry::track_ref == false); - static_assert(detail::GetFieldTagEntry::track_ref == false); - static_assert(detail::GetFieldTagEntry::track_ref == true); - static_assert(detail::GetFieldTagEntry::track_ref == true); - static_assert(detail::GetFieldTagEntry::track_ref == true); - static_assert(detail::GetFieldTagEntry::track_ref == false); +TEST(FieldConfig, IdModeEntries) { + static_assert(detail::has_field_config_v); + static_assert(detail::GetFieldConfigEntry::has_id); + static_assert(detail::GetFieldConfigEntry::id == 0); + static_assert(detail::GetFieldConfigEntry::encoding == + Encoding::Varint); + static_assert(detail::GetFieldConfigEntry::has_id); + static_assert(detail::GetFieldConfigEntry::id == 1); + static_assert(detail::GetFieldConfigEntry::spec.kind_[0] == + FieldNodeKind::Map); } -TEST(FieldTags, NullableWithRef) { - // name: non-nullable, no ref - static_assert(detail::GetFieldTagEntry::id == 0); - static_assert(detail::GetFieldTagEntry::is_nullable == false); - static_assert(detail::GetFieldTagEntry::track_ref == false); - - // left: nullable + ref - static_assert(detail::GetFieldTagEntry::id == 1); - static_assert(detail::GetFieldTagEntry::is_nullable == true); - static_assert(detail::GetFieldTagEntry::track_ref == true); - - // right: nullable + ref - static_assert(detail::GetFieldTagEntry::id == 2); - static_assert(detail::GetFieldTagEntry::is_nullable == true); - static_assert(detail::GetFieldTagEntry::track_ref == true); -} - -TEST(FieldTags, SingleField) { - static_assert(detail::has_field_tags_v == true); - static_assert(detail::FieldTagsInfo::field_count == 1); - static_assert(detail::GetFieldTagEntry::id == 0); - static_assert(detail::GetFieldTagEntry::is_nullable == false); - static_assert(detail::GetFieldTagEntry::track_ref == false); +TEST(FieldConfig, NamespaceScopeEntries) { + static_assert(detail::has_field_config_v); + static_assert( + detail::GetFieldConfigEntry::has_entry); + static_assert(detail::GetFieldConfigEntry::has_id); + static_assert(detail::GetFieldConfigEntry::id == 3); + static_assert( + detail::GetFieldConfigEntry::encoding == + Encoding::Varint); + static_assert(detail::GetFieldConfigEntry::has_id); + static_assert(detail::GetFieldConfigEntry::id == 4); + static_assert(detail::GetFieldConfigEntry::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 2308a59bf9..87b3ede02c 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 0dadca10f3..dfbd74478a 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(); } std::vector 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 72ca3ff685..0000000000 --- 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 -#include -#include -#include -#include - -// ============================================================================ -// Struct definitions with fory::field<> members -// ============================================================================ - -// Basic struct with primitive fields -struct FieldPerson { - fory::field name; - fory::field age; - fory::field score; - fory::field 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 required_name; - fory::field, 1> optional_age; - fory::field, 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, 0> value; - fory::field, 1> text; - - bool operator==(const FieldSharedPtrHolder &other) const { - if (static_cast(value.value) != static_cast(other.value.value)) - return false; - if (static_cast(text.value) != static_cast(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, 0, fory::nullable> nullable_value; - fory::field, 1, fory::nullable> nullable_text; - - bool operator==(const FieldNullableSharedPtr &other) const { - if (static_cast(nullable_value.value) != - static_cast(other.nullable_value.value)) - return false; - if (static_cast(nullable_text.value) != - static_cast(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, 0> value; - fory::field, 1, fory::nullable> nullable_value; - FORY_STRUCT(FieldUniquePtrHolder, value, nullable_value); -}; - -// Nested struct for reference tracking tests -struct FieldNode { - fory::field id; - fory::field 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, 0, fory::ref> first; - fory::field, 1, fory::ref> second; - FORY_STRUCT(FieldRefTrackingHolder, first, second); -}; - -// Struct with nullable + ref -struct FieldNullableRefHolder { - fory::field, 0, fory::nullable, fory::ref> node; - FORY_STRUCT(FieldNullableRefHolder, node); -}; - -// Struct with not_null + ref -struct FieldNotNullRefHolder { - fory::field, 0, fory::not_null, fory::ref> node; - FORY_STRUCT(FieldNotNullRefHolder, node); -}; - -// Struct with vector of field-wrapped structs -struct FieldVectorHolder { - fory::field, 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 field_name; - int32_t plain_age; // Not wrapped - fory::field 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(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(type_id++); - fory.register_struct(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(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(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(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(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(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(42); - original.text = std::make_shared("hello"); - - auto bytes_result = fory.serialize(original); - ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string(); - - auto deser_result = fory.deserialize( - 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(99); - original.nullable_text = std::make_shared("world"); - - auto bytes_result = fory.serialize(original); - ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string(); - - auto deser_result = fory.deserialize( - 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( - 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(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( - 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(2025); - original.nullable_value = std::make_unique(1234); - - auto bytes_result = fory.serialize(original); - ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string(); - - auto deser_result = fory.deserialize( - 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(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( - 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(); - 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( - 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(); - original.first.value->id = 1; - original.first.value->name = "first"; - original.second = std::make_shared(); - 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( - 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(); - 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( - 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( - 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(); - 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( - 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(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(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(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); - static_assert(is_fory_field_v); - static_assert(is_fory_field_v); - static_assert(is_fory_field_v); - - // 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 description; - std::shared_ptr data; - std::shared_ptr optional_data; - - bool operator==(const TagsTestDocument &other) const { - bool data_eq = static_cast(data) == static_cast(other.data); - if (data_eq && data && other.data) { - data_eq = (*data == *other.data); - } - bool opt_data_eq = static_cast(optional_data) == - static_cast(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 first; - std::shared_ptr 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 required_node; - std::shared_ptr 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 left; - std::shared_ptr 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(200); - fory.register_struct(201); - - TagsTestDocument original; - original.title = "My Document"; - original.version = 1; - original.description = "A test document"; - original.data = std::make_shared(); - 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(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(200); - fory.register_struct(201); - - TagsTestDocument original; - original.title = "Doc with optional"; - original.version = 2; - original.description = std::nullopt; - original.data = std::make_shared(); - original.data->content = "main data"; - original.data->value = 100; - original.optional_data = std::make_shared(); - 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(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; - using DataHelpers = detail::CompileTimeFieldHelpers; - - // Check tag IDs via GetFieldTagEntry for TagsTestData - static_assert(::fory::detail::GetFieldTagEntry::id == 0); - static_assert(::fory::detail::GetFieldTagEntry::id == 1); - - // Check tag IDs via GetFieldTagEntry for TagsTestDocument - static_assert(::fory::detail::GetFieldTagEntry::id == 0); - static_assert(::fory::detail::GetFieldTagEntry::id == 1); - static_assert(::fory::detail::GetFieldTagEntry::id == 2); - static_assert(::fory::detail::GetFieldTagEntry::id == 3); - static_assert(::fory::detail::GetFieldTagEntry::id == 4); - - // Check nullability via GetFieldTagEntry - // title (string): non-nullable - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - false); - // version (int): non-nullable - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - false); - // description (optional): inherently nullable - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - true); - // data (shared_ptr): non-nullable (default) - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - false); - // optional_data (shared_ptr, nullable): nullable - static_assert( - ::fory::detail::GetFieldTagEntry::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(300); - fory.register_struct(301); - - // Create a shared node that will be referenced by both fields - auto shared_node = std::make_shared(); - 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(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(300); - fory.register_struct(301); - - TagsRefHolder original; - original.first = std::make_shared(); - original.first->name = "first"; - original.first->id = 1; - original.second = std::make_shared(); - 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(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(300); - fory.register_struct(302); - - TagsNullableRefHolder original; - original.required_node = std::make_shared(); - original.required_node->name = "required"; - original.required_node->id = 100; - original.optional_node = std::make_shared(); - 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( - 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(300); - fory.register_struct(302); - - TagsNullableRefHolder original; - original.required_node = std::make_shared(); - 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( - 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(300); - fory.register_struct(302); - - // Both fields point to the same object - auto shared_node = std::make_shared(); - 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( - 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(303); - - // Build a simple tree: - // root - // / \ - // left right - // / \ - // ll lr - TagsTreeNode original; - original.value = "root"; - original.left = std::make_shared(); - original.left->value = "left"; - original.left->left = std::make_shared(); - original.left->left->value = "ll"; - original.left->left->left = nullptr; - original.left->left->right = nullptr; - original.left->right = std::make_shared(); - original.left->right->value = "lr"; - original.left->right->left = nullptr; - original.left->right->right = nullptr; - original.right = std::make_shared(); - 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(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(303); - - // Build a DAG (tree with shared subtree): - // root - // / \ - // left right - // \ / - // shared - auto shared = std::make_shared(); - shared->value = "shared_subtree"; - shared->left = nullptr; - shared->right = nullptr; - - TagsTreeNode original; - original.value = "root"; - original.left = std::make_shared(); - original.left->value = "left"; - original.left->left = nullptr; - original.left->right = shared; // left's right points to shared - original.right = std::make_shared(); - 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(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; - using NullableRefHelpers = - detail::CompileTimeFieldHelpers; - using TreeHelpers = detail::CompileTimeFieldHelpers; - - // TagsRefHolder: (first, 0, ref), (second, 1, ref) - static_assert(::fory::detail::GetFieldTagEntry::id == 0); - static_assert(::fory::detail::GetFieldTagEntry::id == 1); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == false); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == false); - static_assert(::fory::detail::GetFieldTagEntry::track_ref == - true); - static_assert(::fory::detail::GetFieldTagEntry::track_ref == - true); - - // TagsNullableRefHolder: (required_node, 0, ref), (optional_node, 1, - // nullable, ref) - static_assert( - ::fory::detail::GetFieldTagEntry::id == 0); - static_assert( - ::fory::detail::GetFieldTagEntry::id == 1); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - false); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == - true); - static_assert( - ::fory::detail::GetFieldTagEntry::track_ref == - true); - static_assert( - ::fory::detail::GetFieldTagEntry::track_ref == - true); - - // TagsTreeNode: (value, 0), (left, 1, nullable, ref), (right, 2, nullable, - // ref) - static_assert(::fory::detail::GetFieldTagEntry::id == 0); - static_assert(::fory::detail::GetFieldTagEntry::id == 1); - static_assert(::fory::detail::GetFieldTagEntry::id == 2); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == false); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == true); - static_assert( - ::fory::detail::GetFieldTagEntry::is_nullable == true); - static_assert(::fory::detail::GetFieldTagEntry::track_ref == - false); - static_assert(::fory::detail::GetFieldTagEntry::track_ref == - true); - static_assert(::fory::detail::GetFieldTagEntry::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 a508f57c61..155e951a61 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 +#include #include #include #include #include +#include namespace macro_test { @@ -47,7 +49,7 @@ class Configured final { int32_t id_ = 0; public: - FORY_STRUCT(Configured, id_); + FORY_STRUCT(Configured, (id_, fory::F(1).varint())); }; class OptionalHolder final { @@ -64,7 +66,7 @@ class OptionalHolder final { std::optional name_; public: - FORY_STRUCT(OptionalHolder, name_); + FORY_STRUCT(OptionalHolder, (name_, fory::F(1))); }; class Choice final { @@ -103,6 +105,32 @@ class Choice final { : value_(tag, std::forward(args)...) {} }; +class NestedChoice final { +public: + using Values = std::map>; + + NestedChoice() = default; + + static NestedChoice values(Values value) { + return NestedChoice(std::move(value)); + } + + uint32_t fory_case_id() const noexcept { return 1; } + + template decltype(auto) visit(Visitor &&vis) const { + return std::forward(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 @@ class Partial final { 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 @@ class EnumContainer final { 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); static_assert( ::fory::detail::GetFieldConfigEntry::id == 1); @@ -147,20 +175,20 @@ TEST(NamespaceMacros, FieldConfigAndTagsInNamespace) { 0>::encoding == ::fory::Encoding::Varint); - static_assert(::fory::detail::has_field_tags_v); - static_assert(::fory::detail::GetFieldTagEntry::is_nullable); + static_assert( + ::fory::detail::GetFieldConfigEntry::id == + 1); + static_assert(::fory::serialization::detail::CompileTimeFieldHelpers< + macro_test::OptionalHolder>::field_nullable<0>()); - static_assert(::fory::detail::GetFieldTagEntry::id == - 5); static_assert( - !::fory::detail::GetFieldTagEntry::has_entry); + ::fory::detail::GetFieldConfigEntry::id == 5); + static_assert( + ::fory::detail::GetFieldConfigEntry::id == 6); static_assert(::fory::serialization::detail::CompileTimeFieldHelpers< macro_test::Partial>::field_nullable<1>()); static_assert( ::fory::detail::GetFieldConfigEntry::id == 7); - static_assert( - ::fory::detail::GetFieldConfigEntry::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(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(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::defined); static_assert(::fory::meta::EnumInfo::size == 2); diff --git a/cpp/fory/serialization/serializer_traits.h b/cpp/fory/serialization/serializer_traits.h index 98c476f7ad..ad07c4aa6d 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::value; // Element Type Extraction // ============================================================================ -/// get element type for containers (reuse meta::GetValueType) -template using element_type_t = typename meta::GetValueType; +/// get element type for containers. +template struct element_type_impl { + using type = typename meta::GetValueType; +}; + +template +struct element_type_impl> { + using type = typename T::value_type; +}; + +template +using element_type_t = typename element_type_impl< + std::remove_cv_t>>::type; /// get key type for map-like containers template struct key_type_impl {}; diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc index 9835e0b453..d146c93953 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 1c8b105f62..971ea2966a 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 - no dynamic type dispatch, always - // PolymorphicBaseForMono - fory::field, 0, fory::nullable, - fory::dynamic> - ptr; - FORY_STRUCT(NonDynamicFieldHolder, ptr); + std::shared_ptr ptr; + FORY_STRUCT(NonDynamicFieldHolder, + (ptr, fory::F().nullable().dynamic(false))); }; -TEST(SmartPtrSerializerTest, NonDynamicFieldWithForyField) { +TEST(SmartPtrSerializerTest, NonDynamicFieldConfig) { NonDynamicFieldHolder original; - original.ptr.value = std::make_shared(); - original.ptr.value->value = 42; - original.ptr.value->data = "test data"; + original.ptr = std::make_shared(); + original.ptr->value = 42; + original.ptr->data = "test data"; auto fory = Fory::builder().track_ref(false).build(); fory.register_struct(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(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 cdd4a5f40d..88a6de6d23 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 #include #include +#include #include #include #include @@ -476,6 +479,587 @@ void for_each_index(std::index_sequence, Func &&func) { (func(std::integral_constant{}), ...); } +template +constexpr FieldNodeKind configured_node_kind() { + constexpr auto spec = + ::fory::detail::GetFieldConfigEntry::spec; + return NodeIndex >= 0 ? spec.kind_[NodeIndex] : FieldNodeKind::Default; +} + +template +constexpr Encoding configured_node_encoding() { + constexpr auto spec = + ::fory::detail::GetFieldConfigEntry::spec; + return NodeIndex >= 0 ? spec.encoding_[NodeIndex] : Encoding::Default; +} + +template +constexpr bool configured_node_has_override() { + constexpr auto spec = + ::fory::detail::GetFieldConfigEntry::spec; + return NodeIndex >= 0 && + (spec.kind_[NodeIndex] != FieldNodeKind::Default || + spec.encoding_[NodeIndex] != Encoding::Default || + spec.scalar_[NodeIndex] != FieldScalarKind::Inferred); +} + +template +constexpr FieldScalarKind configured_node_scalar() { + constexpr auto spec = + ::fory::detail::GetFieldConfigEntry::spec; + return NodeIndex >= 0 ? spec.scalar_[NodeIndex] : FieldScalarKind::Inferred; +} + +template +constexpr bool configured_scalar_kind_matches() { + using Decayed = decay_t; + if constexpr (ScalarKind == FieldScalarKind::Inferred) { + return true; + } else if constexpr (ScalarKind == FieldScalarKind::Bool) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Int8) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Int16) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Int32) { + return std::is_same_v || std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Int64) { + return std::is_same_v || + std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::UInt8) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::UInt16) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::UInt32) { + return std::is_same_v || + std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::UInt64) { + return std::is_same_v || + std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Float16) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::BFloat16) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Float32) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::Float64) { + return std::is_same_v; + } else if constexpr (ScalarKind == FieldScalarKind::String) { + return std::is_same_v; + } else { + return false; + } +} + +template +constexpr int8_t configured_node_child() { + constexpr auto spec = + ::fory::detail::GetFieldConfigEntry::spec; + if constexpr (ChildSlot == 0) { + return NodeIndex >= 0 ? spec.child0_[NodeIndex] : -1; + } else { + return NodeIndex >= 0 ? spec.child1_[NodeIndex] : -1; + } +} + +template +constexpr bool configured_vector_primitive_array_spec() { + if constexpr (!is_vector_v) { + return false; + } else { + using Element = element_type_t; + if constexpr (!std::is_same_v, int8_t> && + !std::is_same_v, uint8_t>) { + return false; + } else { + constexpr FieldNodeKind kind = + configured_node_kind(); + constexpr int8_t child = + configured_node_child(); + if constexpr (kind != FieldNodeKind::List || child < 0) { + return false; + } else if constexpr (configured_node_kind() != + FieldNodeKind::Scalar || + configured_node_encoding() != + Encoding::Default) { + return false; + } else if constexpr (std::is_same_v, int8_t>) { + return configured_node_scalar() == + FieldScalarKind::Int8; + } else { + return configured_node_scalar() == + FieldScalarKind::UInt8; + } + } + } +} + +template +constexpr uint32_t configured_vector_primitive_array_type_id() { + if constexpr (!configured_vector_primitive_array_spec()) { + return 0; + } else { + using Element = element_type_t; + if constexpr (std::is_same_v, int8_t>) { + return static_cast(TypeId::INT8_ARRAY); + } else { + return static_cast(TypeId::UINT8_ARRAY); + } + } +} + +template +constexpr uint32_t configured_scalar_type_id() { + constexpr Encoding enc = + configured_node_encoding(); + using Decayed = decay_t; + if constexpr (std::is_same_v) { + return static_cast(TypeId::BOOL); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::INT8); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::UINT8); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::INT16); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::UINT16); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(TypeId::INT32); + } + return static_cast(TypeId::VARINT32); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(TypeId::VAR_UINT32); + } + return static_cast(TypeId::UINT32); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(TypeId::INT64); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(TypeId::TAGGED_INT64); + } + return static_cast(TypeId::VARINT64); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(TypeId::VAR_UINT64); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(TypeId::TAGGED_UINT64); + } + return static_cast(TypeId::UINT64); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::FLOAT16); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::BFLOAT16); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::FLOAT32); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::FLOAT64); + } else if constexpr (std::is_same_v) { + return static_cast(TypeId::STRING); + } + return 0; +} + +template +constexpr uint32_t configured_effective_type_id() { + constexpr FieldNodeKind kind = + configured_node_kind(); + if constexpr (is_optional_v) { + using Inner = typename decay_t::value_type; + constexpr int8_t child = + kind == FieldNodeKind::Inner + ? configured_node_child() + : NodeIndex; + return configured_effective_type_id(); + } else { + constexpr uint32_t vector_array_tid = + configured_vector_primitive_array_type_id(); + if constexpr (vector_array_tid != 0) { + return vector_array_tid; + } else if constexpr (kind == FieldNodeKind::List) { + return static_cast(TypeId::LIST); + } else if constexpr (kind == FieldNodeKind::Set) { + return static_cast(TypeId::SET); + } else if constexpr (kind == FieldNodeKind::Map) { + return static_cast(TypeId::MAP); + } else if constexpr (kind == FieldNodeKind::Scalar || + configured_node_encoding() != + Encoding::Default) { + return configured_scalar_type_id(); + } + return 0; + } +} + +template +FORY_ALWAYS_INLINE void write_configured_scalar(const FieldType &value, + WriteContext &ctx) { + constexpr Encoding enc = + configured_node_encoding(); + if constexpr (is_configurable_int_v) { + if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + ctx.write_var_uint32(value); + } else { + ctx.buffer().write_int32(static_cast(value)); + } + } else if constexpr (std::is_same_v) { + 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(value)); + } + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + ctx.buffer().write_int32(static_cast(value)); + } else { + ctx.write_var_int32(static_cast(value)); + } + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + ctx.buffer().write_int64(static_cast(value)); + } else if constexpr (enc == Encoding::Tagged) { + ctx.write_tagged_int64(static_cast(value)); + } else { + ctx.write_var_int64(static_cast(value)); + } + } else { + Serializer::write_data(value, ctx); + } + } else { + Serializer::write_data(value, ctx); + } +} + +template +FORY_ALWAYS_INLINE FieldType read_configured_scalar(ReadContext &ctx) { + if constexpr (is_configurable_int_v) { + constexpr Encoding enc = + configured_node_encoding(); + if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(ctx.read_var_uint32(ctx.error())); + } + return static_cast(ctx.read_int32(ctx.error())); + } else if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(ctx.read_var_uint64(ctx.error())); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(ctx.read_tagged_uint64(ctx.error())); + } + return static_cast(ctx.read_uint64(ctx.error())); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(ctx.read_int32(ctx.error())); + } + return static_cast(ctx.read_var_int32(ctx.error())); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(ctx.read_int64(ctx.error())); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(ctx.read_tagged_int64(ctx.error())); + } + return static_cast(ctx.read_var_int64(ctx.error())); + } else { + return Serializer::read_data(ctx); + } + } else { + return Serializer::read_data(ctx); + } +} + +template +void write_configured_value(const ValueType &value, WriteContext &ctx, + RefMode ref_mode, bool write_type, + bool has_generics); + +template +ValueType read_configured_value(ReadContext &ctx, RefMode ref_mode, + bool read_type); + +template +void write_configured_list_data(const Container &coll, WriteContext &ctx) { + using Elem = element_type_t; + ctx.write_var_uint32(static_cast(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, ctx, RefMode::None, false, true); + } else { + Serializer::write_data(elem, ctx); + } + } +} + +template +Container read_configured_list_data(ReadContext &ctx) { + using Elem = element_type_t; + uint32_t length = ctx.read_var_uint32(ctx.error()); + Container result; + if constexpr (has_reserve_v) { + 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( + ctx, RefMode::None, false); + collection_insert(result, std::move(elem)); + } else { + auto elem = Serializer::read_data(ctx); + collection_insert(result, std::move(elem)); + } + } + return result; +} + +template +void write_configured_map_data(const MapType &map, WriteContext &ctx) { + using Key = key_type_t; + using Value = mapped_type_t; + ctx.write_var_uint32(static_cast(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(DECL_KEY_TYPE | DECL_VALUE_TYPE)); + need_write_header = false; + } + if constexpr (KeyNode >= 0) { + write_configured_value( + key, ctx, RefMode::None, false, true); + } else { + Serializer::write_data(key, ctx); + } + if constexpr (ValueNode >= 0) { + write_configured_value( + value, ctx, RefMode::None, false, true); + } else { + Serializer::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 +MapType read_configured_map_data(ReadContext &ctx) { + using Key = key_type_t; + using Value = mapped_type_t; + uint32_t length = ctx.read_var_uint32(ctx.error()); + MapType result; + MapReserver::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( + ctx, RefMode::None, false); + } else { + return Serializer::read_data(ctx); + } + }(); + Value value = [&]() { + if constexpr (ValueNode >= 0) { + return read_configured_value( + ctx, RefMode::None, false); + } else { + return Serializer::read_data(ctx); + } + }(); + result.emplace(std::move(key), std::move(value)); + ++read_count; + } + } + return result; +} + +template +void write_configured_value(const ValueType &value, WriteContext &ctx, + RefMode ref_mode, bool write_type, + bool has_generics) { + constexpr FieldNodeKind kind = + configured_node_kind(); + constexpr FieldScalarKind scalar_kind = + configured_node_scalar(); + static_assert(configured_scalar_kind_matches(), + "fory::T typed scalar spec does not match the C++ field type"); + if constexpr (is_optional_v) { + 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() + : NodeIndex; + write_configured_value( + *value, ctx, RefMode::None, false, has_generics); + } else if constexpr ((is_vector_v || is_list_v || + is_deque_v || is_set_like_v) && + (kind == FieldNodeKind::List || + kind == FieldNodeKind::Set)) { + if constexpr (configured_vector_primitive_array_spec()) { + Serializer::write(value, ctx, ref_mode, false, has_generics); + } else { + write_not_null_ref_flag(ctx, ref_mode); + constexpr int8_t child = + configured_node_child(); + write_configured_list_data( + value, ctx); + } + } else if constexpr (is_map_like_v && kind == FieldNodeKind::Map) { + write_not_null_ref_flag(ctx, ref_mode); + constexpr int8_t key_child = + configured_node_child(); + constexpr int8_t value_child = + configured_node_child(); + write_configured_map_data(value, ctx); + } else if constexpr (kind == FieldNodeKind::Scalar || + configured_node_encoding() != + Encoding::Default) { + write_not_null_ref_flag(ctx, ref_mode); + write_configured_scalar(value, ctx); + } else { + Serializer::write(value, ctx, ref_mode, write_type, + has_generics); + } +} + +template +ValueType read_configured_value(ReadContext &ctx, RefMode ref_mode, + bool read_type) { + constexpr FieldNodeKind kind = + configured_node_kind(); + constexpr FieldScalarKind scalar_kind = + configured_node_scalar(); + static_assert(configured_scalar_kind_matches(), + "fory::T typed scalar spec does not match the C++ field type"); + if constexpr (is_optional_v) { + 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() + : NodeIndex; + Inner inner = read_configured_value( + ctx, RefMode::None, false); + return ValueType{std::move(inner)}; + } else if constexpr ((is_vector_v || is_list_v || + is_deque_v || is_set_like_v) && + (kind == FieldNodeKind::List || + kind == FieldNodeKind::Set)) { + if constexpr (configured_vector_primitive_array_spec()) { + return Serializer::read(ctx, ref_mode, false); + } else { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + constexpr int8_t child = + configured_node_child(); + return read_configured_list_data(ctx); + } + } else if constexpr (is_map_like_v && kind == FieldNodeKind::Map) { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + constexpr int8_t key_child = + configured_node_child(); + constexpr int8_t value_child = + configured_node_child(); + return read_configured_map_data(ctx); + } else if constexpr (kind == FieldNodeKind::Scalar || + configured_node_encoding() != + Encoding::Default) { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + return read_configured_scalar(ctx); + } else { + return Serializer::read(ctx, ref_mode, read_type); + } +} + template void dispatch_field_index_impl(size_t target_index, Func &&func, std::index_sequence, bool &handled) { @@ -509,17 +1093,47 @@ template struct CompileTimeFieldHelpers { static inline constexpr auto ptrs = FieldDescriptor::ptrs(); using FieldPtrs = decltype(ptrs); + template + static constexpr bool any_field_has_id(std::index_sequence) { + if constexpr (sizeof...(Indices) == 0) { + return false; + } else { + return ((::fory::detail::GetFieldConfigEntry::has_id) || ...); + } + } + + template + static constexpr bool all_fields_have_id(std::index_sequence) { + if constexpr (sizeof...(Indices) == 0) { + return true; + } else { + return ((::fory::detail::GetFieldConfigEntry::has_id) && ...); + } + } + + static constexpr bool any_id_mode_field = + any_field_has_id(std::make_index_sequence{}); + static constexpr bool all_id_mode_fields = + all_fields_have_id(std::make_index_sequence{}); + + 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 static constexpr uint32_t field_type_id() { if constexpr (FieldCount == 0) { return 0; } else { using PtrT = std::tuple_element_t; using RawFieldType = meta::RemoveMemberPointerCVRefT; - // unwrap fory::field<> to get the actual type for serialization using FieldType = unwrap_field_t; - // Check for encoding override from FORY_FIELD_CONFIG if constexpr (::fory::detail::has_field_config_v) { + constexpr uint32_t effective_tid = + configured_effective_type_id(); + if constexpr (effective_tid != 0) { + return effective_tid; + } constexpr uint32_t unsigned_tid = compute_unsigned_type_id(); if constexpr (unsigned_tid != 0) { @@ -541,11 +1155,8 @@ template 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 static constexpr bool field_nullable() { if constexpr (FieldCount == 0) { return false; @@ -553,19 +1164,9 @@ template struct CompileTimeFieldHelpers { using PtrT = std::tuple_element_t; using RawFieldType = meta::RemoveMemberPointerCVRefT; - // If it's a fory::field<> wrapper, use its metadata if constexpr (is_fory_field_v) { return RawFieldType::is_nullable; - } - // Else if FORY_FIELD_TAGS is defined, use that metadata - else if constexpr (::fory::detail::has_field_tags_v) { - if constexpr (::fory::detail::GetFieldTagEntry::has_entry) { - return ::fory::detail::GetFieldTagEntry::is_nullable; - } - return field_is_nullable_v; - } - // Else if FORY_FIELD_CONFIG is defined, use nullable from config - else if constexpr (::fory::detail::has_field_config_v) { + } else if constexpr (::fory::detail::has_field_config_v) { if constexpr (::fory::detail::GetFieldConfigEntry::has_entry && ::fory::detail::GetFieldConfigEntry::nullable) { @@ -599,14 +1200,9 @@ template struct CompileTimeFieldHelpers { return config_id; } } - // If it's a fory::field<> wrapper, use its tag_id if constexpr (is_fory_field_v) { return RawFieldType::tag_id; } - // Else if FORY_FIELD_TAGS is defined, use that metadata - else if constexpr (::fory::detail::has_field_tags_v) { - return ::fory::detail::GetFieldTagEntry::id; - } // No tag ID defined else { return -1; @@ -633,16 +1229,9 @@ template struct CompileTimeFieldHelpers { using PtrT = std::tuple_element_t; using RawFieldType = meta::RemoveMemberPointerCVRefT; - // If it's a fory::field<> wrapper, use its track_ref metadata if constexpr (is_fory_field_v) { return RawFieldType::track_ref; - } - // Else if FORY_FIELD_TAGS is defined, use that metadata - else if constexpr (::fory::detail::has_field_tags_v) { - return ::fory::detail::GetFieldTagEntry::track_ref; - } - // Else if FORY_FIELD_CONFIG is defined, use ref from config - else if constexpr (::fory::detail::has_field_config_v) { + } else if constexpr (::fory::detail::has_field_config_v) { if constexpr (::fory::detail::GetFieldConfigEntry::has_entry && ::fory::detail::GetFieldConfigEntry::ref) { @@ -668,16 +1257,9 @@ template struct CompileTimeFieldHelpers { using PtrT = std::tuple_element_t; using RawFieldType = meta::RemoveMemberPointerCVRefT; - // If it's a fory::field<> wrapper, use its dynamic_value metadata if constexpr (is_fory_field_v) { return RawFieldType::dynamic_value; - } - // Else if FORY_FIELD_TAGS is defined, use that metadata - else if constexpr (::fory::detail::has_field_tags_v) { - return ::fory::detail::GetFieldTagEntry::dynamic_value; - } - // Else if FORY_FIELD_CONFIG is defined, use dynamic_value from config - else if constexpr (::fory::detail::has_field_config_v) { + } else if constexpr (::fory::detail::has_field_config_v) { constexpr int dynamic_value = ::fory::detail::GetFieldConfigEntry::dynamic_value; if constexpr (dynamic_value != -1) { @@ -733,7 +1315,7 @@ template struct CompileTimeFieldHelpers { !any_field_needs_type_info_in_compatible( std::make_index_sequence{}); - /// get the underlying field type (unwraps fory::field<> if present) + /// get the underlying field type. template struct UnwrappedFieldTypeHelper { using PtrT = std::tuple_element_t; using RawFieldType = meta::RemoveMemberPointerCVRefT; @@ -1635,7 +2217,7 @@ FORY_ALWAYS_INLINE void write_single_fixed_field(const T &obj, Buffer &buffer, using RawFieldType = typename meta::RemoveMemberPointerCVRefT; using FieldType = unwrap_field_t; - // 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) { 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; using FieldType = unwrap_field_t; - // 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) { 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; using FieldType = unwrap_field_t; - // 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) { return (obj.*field_ptr).value; @@ -1803,10 +2385,9 @@ void write_single_field(const T &obj, WriteContext &ctx, const auto field_ptr = std::get(field_ptrs); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; - // unwrap fory::field<> to get the actual type for serialization using FieldType = unwrap_field_t; - // 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) { @@ -1819,12 +2400,28 @@ void write_single_field(const T &obj, WriteContext &ctx, constexpr TypeId field_type_id = Serializer::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(); constexpr bool track_ref = Helpers::template field_track_ref(); // Some wrapper types always require ref/null flags in the wire format. constexpr bool field_type_is_nullable = is_nullable_v; + if constexpr (configured_node_has_override()) { + 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(); + 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( + field_value, ctx, field_ref_mode, write_type, has_generics); + return; + } + // Special handling for std::optional 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(field_ptrs); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; - // unwrap fory::field<> to get the actual type for deserialization using FieldType = unwrap_field_t; // 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(); constexpr bool track_ref = Helpers::template field_track_ref(); @@ -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()) { + FieldType result = read_configured_value( + ctx, field_ref_mode, read_type); + if constexpr (is_fory_field_v) { + (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::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(ctx, ctx.error()); }; - // Assign to field (handle fory::field<> wrapper if needed) + // Assign to field. if constexpr (is_fory_field_v) { (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(value); } } else { - // Assign to field (handle fory::field<> wrapper if needed) + // Assign to field. FieldType result = Serializer::read(ctx, field_ref_mode, read_type); if constexpr (is_fory_field_v) { @@ -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 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; const auto field_info = fory_field_info(obj); const auto &field_ptrs = decltype(field_info)::ptrs_ref(); const auto field_ptr = std::get(field_ptrs); using RawFieldType = typename meta::RemoveMemberPointerCVRefT; - // unwrap fory::field<> to get the actual type for deserialization using FieldType = unwrap_field_t; + 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::type_id; // Check if field is a struct type - use type_id to handle shared_ptr @@ -2527,6 +3135,17 @@ void read_single_field_by_index_compatible(T &obj, ReadContext &ctx, } } + if constexpr (configured_node_has_override()) { + FieldType result = read_configured_value( + ctx, remote_ref_mode, read_type); + if constexpr (is_fory_field_v) { + (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::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 -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) { +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) { using Helpers = CompileTimeFieldHelpers; // Short-circuit fold: stops at first match @@ -2552,8 +3173,8 @@ FORY_ALWAYS_INLINE void dispatch_compatible_field_read_impl( (void)((static_cast(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; FieldType result = read_fixed_primitive_at(buffer, base_offset + field_offset); - // Assign to field (handle fory::field<> wrapper if needed) + // Assign to field. if constexpr (is_fory_field_v) { (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(buffer, offset); } - // Assign to field (handle fory::field<> wrapper if needed) + // Assign to field. if constexpr (is_fory_field_v) { (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( - obj, ctx, field_id, remote_ref_mode, remote_field.field_type.type_id, - handled, std::index_sequence{}); + dispatch_compatible_field_read_impl(obj, ctx, field_id, + remote_field.field_type, handled, + std::index_sequence{}); 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 7330ee6bc2..83ab3e0c06 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 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> 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> key_only; + std::map> 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> 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(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); + fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); + fory.register_struct(type_id++); + fory.register_struct(type_id++); + fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); fory.register_struct(type_id++); @@ -455,6 +489,19 @@ inline void register_all_test_types(Fory &fory) { fory.register_enum(type_id++); } +inline FieldType make_test_field_type(TypeId type_id, + std::vector generics = {}) { + return FieldType(static_cast(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 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(101).ok()); - ASSERT_TRUE(fory_tags.register_struct(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 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(601).ok()); + ASSERT_TRUE(fory.serialize(obj).ok()); + TypeMeta meta = + fory.type_resolver().clone_struct_meta(); + 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(TypeId::MAP)); + ASSERT_EQ(field_type.generics.size(), 2); + EXPECT_EQ(field_type.generics[0].type_id, + static_cast(TypeId::VAR_UINT32)); + ASSERT_EQ(field_type.generics[1].type_id, + static_cast(TypeId::LIST)); + ASSERT_EQ(field_type.generics[1].generics.size(), 1); + EXPECT_EQ(field_type.generics[1].generics[0].type_id, + static_cast(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(602).ok()); + ASSERT_TRUE(fory.serialize(obj).ok()); + TypeMeta meta = + fory.type_resolver().clone_struct_meta(); + 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(TypeId::VAR_UINT32)); + EXPECT_EQ(value_only->field_type.generics[1].generics[0].type_id, + static_cast(TypeId::TAGGED_INT64)); +} + +TEST(StructComprehensiveTest, OptionalNestedAnnotation) { + test_roundtrip( + OptionalNestedAnnotatedStruct{{std::vector{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(603).ok()); + ASSERT_TRUE( + fory.serialize(OptionalNestedAnnotatedStruct{{std::vector{1}}}) + .ok()); + TypeMeta meta = + fory.type_resolver().clone_struct_meta(); + 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(TypeId::LIST)); + ASSERT_EQ(fields[0].field_type.generics.size(), 1); + EXPECT_EQ(fields[0].field_type.generics[0].type_id, + static_cast(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 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 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 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 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 b17f959fb2..563afb6b64 100644 --- a/cpp/fory/serialization/type_resolver.cc +++ b/cpp/fory/serialization/type_resolver.cc @@ -112,12 +112,12 @@ Result FieldType::read_from(Buffer &buffer, bool read_flag, if (tid == static_cast(TypeId::LIST) || tid == static_cast(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(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(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(-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(-1) + ? -1 + : static_cast(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 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(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(TypeId::STRUCT); - case TypeId::ENUM: - case TypeId::NAMED_ENUM: - return static_cast(TypeId::ENUM); - case TypeId::EXT: - case TypeId::NAMED_EXT: - return static_cast(TypeId::EXT); - case TypeId::BINARY: - case TypeId::INT8_ARRAY: - case TypeId::UINT8_ARRAY: - return static_cast(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 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(-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(-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 e5c7467813..c03970f98c 100644 --- a/cpp/fory/serialization/type_resolver.h +++ b/cpp/fory/serialization/type_resolver.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -110,17 +109,40 @@ class FieldType { bool track_ref; RefMode ref_mode; // Precomputed from nullable and track_ref std::vector generics; + uint64_t compatible_fingerprint; + + static uint64_t + compute_compatible_fingerprint(uint32_t tid, + const std::vector &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 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 @@ class FieldType { bool operator!=(const FieldType &other) const { return !(*this == other); } }; +inline uint32_t compatible_fingerprint_type_id(uint32_t type_id) { + switch (static_cast(type_id)) { + case TypeId::STRUCT: + case TypeId::COMPATIBLE_STRUCT: + case TypeId::NAMED_STRUCT: + case TypeId::NAMED_COMPATIBLE_STRUCT: + case TypeId::UNKNOWN: + return static_cast(TypeId::STRUCT); + case TypeId::ENUM: + case TypeId::NAMED_ENUM: + return static_cast(TypeId::ENUM); + case TypeId::EXT: + case TypeId::NAMED_EXT: + return static_cast(TypeId::EXT); + case TypeId::BINARY: + case TypeId::INT8_ARRAY: + case TypeId::UINT8_ARRAY: + return static_cast(TypeId::BINARY); + case TypeId::INT32: + case TypeId::VARINT32: + return static_cast(TypeId::VARINT32); + case TypeId::INT64: + case TypeId::VARINT64: + case TypeId::TAGGED_INT64: + return static_cast(TypeId::VARINT64); + case TypeId::UINT32: + case TypeId::VAR_UINT32: + return static_cast(TypeId::VAR_UINT32); + case TypeId::UINT64: + case TypeId::VAR_UINT64: + case TypeId::TAGGED_UINT64: + return static_cast(TypeId::VAR_UINT64); + default: + return type_id; + } +} + +inline uint64_t FieldType::compute_compatible_fingerprint( + uint32_t tid, const std::vector &generic_types) { + uint64_t hash = 14695981039346656037ULL; + hash = fnv1a_64_combine(hash, compatible_fingerprint_type_id(tid)); + hash = fnv1a_64_combine(hash, static_cast(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(TypeId::LIST) || + type_id == static_cast(TypeId::SET) || + type_id == static_cast(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>>> { if constexpr (vec_type_id == TypeId::LIST) { FieldType elem = FieldTypeBuilder::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>>> { static FieldType build(bool nullable) { FieldType elem = FieldTypeBuilder::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>>> { static FieldType build(bool nullable) { FieldType elem = FieldTypeBuilder::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>>> { static FieldType build(bool nullable) { FieldType elem = FieldTypeBuilder::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>>> { static FieldType build(bool nullable) { FieldType elem = FieldTypeBuilder::build(false); FieldType ft(to_type_id(Serializer::type_id), nullable); - ft.generics.push_back(std::move(elem)); + ft.add_generic(std::move(elem)); return ft; } }; @@ -442,8 +528,8 @@ struct FieldTypeBuilder>>> { FieldType key_ft = FieldTypeBuilder::build(false); FieldType value_ft = FieldTypeBuilder::build(false); FieldType ft(to_type_id(Serializer::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>>> { template static void add_element_types(FieldType &ft, std::index_sequence) { - (ft.generics.push_back( + (ft.add_generic( FieldTypeBuilder>::build(false)), ...); } @@ -475,8 +561,7 @@ struct FieldTypeBuilder>>> { add_element_types(ft, std::make_index_sequence{}); } else { // Empty tuple: use UNKNOWN as stub element type for schema encoding - ft.generics.push_back( - FieldType(static_cast(TypeId::UNKNOWN), false)); + ft.add_generic(FieldType(static_cast(TypeId::UNKNOWN), false)); } return ft; } @@ -510,19 +595,200 @@ struct FieldTypeBuilder< } }; -inline bool field_type_needs_user_type_id(uint32_t) { return false; } - template Result 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 +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, int8_t>) { + if (spec.scalar_[child] == FieldScalarKind::Int8) { + return TypeId::INT8_ARRAY; + } + } else if constexpr (std::is_same_v, uint8_t>) { + if (spec.scalar_[child] == FieldScalarKind::UInt8) { + return TypeId::UINT8_ARRAY; + } + } + return TypeId::LIST; +} + +template +void apply_integer_encoding(FieldType &ft, const FieldNodeSpec &spec, + int8_t node_index) { + using Decayed = decay_t; + const Encoding enc = + node_index >= 0 ? spec.encoding_[node_index] : Encoding::Default; + if (enc == Encoding::Default) { + return; + } + if constexpr (std::is_same_v) { + ft.set_type_id(static_cast(TypeId::UINT8)); + } else if constexpr (std::is_same_v) { + ft.set_type_id(static_cast(TypeId::UINT16)); + } else if constexpr (std::is_same_v) { + ft.set_type_id(static_cast( + enc == Encoding::Varint ? TypeId::VAR_UINT32 : TypeId::UINT32)); + } else if constexpr (std::is_same_v) { + ft.set_type_id(static_cast(enc == Encoding::Varint + ? TypeId::VAR_UINT64 + : (enc == Encoding::Tagged + ? TypeId::TAGGED_UINT64 + : TypeId::UINT64))); + } else if constexpr (std::is_same_v || + std::is_same_v) { + ft.set_type_id(static_cast( + enc == Encoding::Fixed ? TypeId::INT32 : TypeId::VARINT32)); + } else if constexpr (std::is_same_v || + std::is_same_v) { + ft.set_type_id(static_cast( + enc == Encoding::Fixed ? TypeId::INT64 + : (enc == Encoding::Tagged ? TypeId::TAGGED_INT64 + : TypeId::VARINT64))); + } +} + +template +FieldType build_field_type_with_spec(bool nullable, const FieldNodeSpec &spec, + int8_t node_index = 0) { + using Decayed = decay_t; + const FieldNodeKind node_kind = + node_index >= 0 ? spec.kind_[node_index] : FieldNodeKind::Default; + + if constexpr (is_optional_v) { + 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(true, spec, child); + inner.nullable = true; + return inner; + } else if constexpr (is_shared_ptr_v) { + using Inner = typename Decayed::element_type; + if constexpr (std::is_polymorphic_v) { + 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(true, spec, child); + inner.nullable = true; + return inner; + } + } else if constexpr (::fory::detail::is_shared_weak_v) { + using Inner = nullable_element_t; + if constexpr (std::is_polymorphic_v) { + 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(true, spec, child); + inner.nullable = true; + return inner; + } + } else if constexpr (is_unique_ptr_v) { + using Inner = typename Decayed::element_type; + if constexpr (std::is_polymorphic_v) { + 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(true, spec, child); + inner.nullable = true; + return inner; + } + } else if constexpr (is_vector_v) { + using Element = element_type_t; + constexpr TypeId default_type_id = Serializer::type_id; + const bool force_list = node_kind == FieldNodeKind::List; + const TypeId type_id = + vector_type_id_for_spec(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(false, spec, child) + : FieldTypeBuilder::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 || is_deque_v || + is_forward_list_v) { + 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(false, spec, child) + : FieldTypeBuilder::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) { + using Element = element_type_t; + 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(false, spec, child) + : FieldTypeBuilder::build(false); + FieldType ft(to_type_id(Serializer::type_id), nullable); + ft.add_generic(std::move(elem)); + return ft; + } else if constexpr (is_map_like_v) { + using Key = key_type_t; + using Value = mapped_type_t; + 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(false, spec, key_child) + : FieldTypeBuilder::build(false); + FieldType value_ft = + field_node_has_override(spec, value_child) + ? build_field_type_with_spec(false, spec, value_child) + : FieldTypeBuilder::build(false); + FieldType ft(to_type_id(Serializer::type_id), nullable); + ft.add_generic(std::move(key_ft)); + ft.add_generic(std::move(value_ft)); + return ft; + } else { + FieldType ft = FieldTypeBuilder::build(nullable); + apply_integer_encoding(ft, spec, node_index); + return ft; + } +} + template Result add_tuple_element_types(TypeResolver &resolver, FieldType &ft) { if constexpr (Index < std::tuple_size_v) { using Elem = std::tuple_element_t; FORY_TRY(elem_ft, build_field_type_with_resolver(resolver, false)); - ft.generics.push_back(std::move(elem_ft)); + ft.add_generic(std::move(elem_ft)); return add_tuple_element_types(resolver, ft); } else { return Result(); @@ -572,7 +838,7 @@ Result build_field_type_with_resolver(TypeResolver &resolver, if constexpr (vec_type_id == TypeId::LIST) { FORY_TRY(elem, build_field_type_with_resolver(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 build_field_type_with_resolver(TypeResolver &resolver, using Element = typename Decayed::value_type; FORY_TRY(elem, build_field_type_with_resolver(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) { using Element = typename Decayed::value_type; FORY_TRY(elem, build_field_type_with_resolver(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) { using Element = typename Decayed::value_type; FORY_TRY(elem, build_field_type_with_resolver(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) { using Set = Decayed; using Element = element_type_t; FORY_TRY(elem, build_field_type_with_resolver(resolver, false)); FieldType ft(to_type_id(Serializer::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) { using Map = Decayed; @@ -609,8 +875,8 @@ Result build_field_type_with_resolver(TypeResolver &resolver, FORY_TRY(key_ft, build_field_type_with_resolver(resolver, false)); FORY_TRY(val_ft, build_field_type_with_resolver(resolver, false)); FieldType ft(to_type_id(Serializer::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) { using StringT = std::basic_string build_field_type_with_resolver(TypeResolver &resolver, if constexpr (tuple_size > 0) { FORY_RETURN_IF_ERROR((add_tuple_element_types(resolver, ft))); } else { - ft.generics.push_back( - FieldType(static_cast(TypeId::UNKNOWN), false)); + ft.add_generic(FieldType(static_cast(TypeId::UNKNOWN), false)); } return ft; } else { @@ -636,7 +901,7 @@ Result build_field_type_with_resolver(TypeResolver &resolver, if (resolved_type_id == static_cast(TypeId::NAMED_ENUM)) { resolved_type_id = static_cast(TypeId::ENUM); } - ft.type_id = resolved_type_id; + ft.set_type_id(resolved_type_id); } } else { FORY_TRY(info, get_type_info_with_resolver(resolver)); @@ -652,27 +917,149 @@ Result 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 +Result +build_field_type_with_resolver_and_spec(TypeResolver &resolver, bool nullable, + const FieldNodeSpec &spec, + int8_t node_index = 0) { + using Decayed = decay_t; + const FieldNodeKind node_kind = + node_index >= 0 ? spec.kind_[node_index] : FieldNodeKind::Default; + + if constexpr (is_optional_v) { + 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( + resolver, true, spec, child))); + inner.nullable = true; + return inner; + } else if constexpr (is_shared_ptr_v) { + using Inner = typename Decayed::element_type; + if constexpr (std::is_polymorphic_v) { + 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( + resolver, true, spec, child))); + inner.nullable = true; + return inner; + } + } else if constexpr (::fory::detail::is_shared_weak_v) { + using Inner = nullable_element_t; + if constexpr (std::is_polymorphic_v) { + 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( + resolver, true, spec, child))); + inner.nullable = true; + return inner; + } + } else if constexpr (is_unique_ptr_v) { + using Inner = typename Decayed::element_type; + if constexpr (std::is_polymorphic_v) { + 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( + resolver, true, spec, child))); + inner.nullable = true; + return inner; + } + } else if constexpr (is_vector_v) { + using Element = element_type_t; + constexpr TypeId default_type_id = Serializer::type_id; + const bool force_list = node_kind == FieldNodeKind::List; + const TypeId type_id = + vector_type_id_for_spec(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( + resolver, false, spec, child) + : build_field_type_with_resolver(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 || is_deque_v || + is_forward_list_v) { + 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( + resolver, false, spec, child) + : build_field_type_with_resolver(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) { + using Element = element_type_t; + 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( + resolver, false, spec, child) + : build_field_type_with_resolver(resolver, false))); + FieldType ft(to_type_id(Serializer::type_id), nullable); + ft.add_generic(std::move(elem)); + return ft; + } else if constexpr (is_map_like_v) { + using Key = key_type_t; + using Value = mapped_type_t; + 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( + resolver, false, spec, key_child) + : build_field_type_with_resolver(resolver, false))); + FORY_TRY(value_ft, + (field_node_has_override(spec, value_child) + ? build_field_type_with_resolver_and_spec( + resolver, false, spec, value_child) + : build_field_type_with_resolver(resolver, false))); + FieldType ft(to_type_id(Serializer::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(resolver, nullable))); + apply_integer_encoding(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 constexpr bool compute_is_nullable() { - if constexpr (is_fory_field_v) { - return ActualFieldType::is_nullable; - } else if constexpr (::fory::detail::has_field_tags_v) { - return ::fory::detail::GetFieldTagEntry::is_nullable; - } else if constexpr (::fory::detail::has_field_config_v && - ::fory::detail::GetFieldConfigEntry::has_entry && - ::fory::detail::GetFieldConfigEntry::nullable) { + if constexpr (::fory::detail::has_field_config_v && + ::fory::detail::GetFieldConfigEntry::has_entry && + ::fory::detail::GetFieldConfigEntry::nullable) { return true; } else { // Default: nullable if std::optional or smart pointers. @@ -684,14 +1071,9 @@ constexpr bool compute_is_nullable() { template constexpr bool compute_track_ref() { - if constexpr (is_fory_field_v) { - return ActualFieldType::track_ref; - } else if constexpr (::fory::detail::has_field_tags_v) { - return ::fory::detail::GetFieldTagEntry::track_ref; - } else if constexpr (::fory::detail::has_field_config_v && - ::fory::detail::GetFieldConfigEntry::has_entry && - ::fory::detail::GetFieldConfigEntry::ref) { + if constexpr (::fory::detail::has_field_config_v && + ::fory::detail::GetFieldConfigEntry::has_entry && + ::fory::detail::GetFieldConfigEntry::ref) { return true; } else { using UnwrappedFieldType = fory::unwrap_field_t; @@ -709,15 +1091,6 @@ constexpr int16_t compute_field_id() { return config_id; } } - if constexpr (is_fory_field_v) { - return field_tag_id_v; - } - if constexpr (::fory::detail::has_field_tags_v) { - constexpr int16_t tag_id = ::fory::detail::GetFieldTagEntry::id; - if constexpr (tag_id >= 0) { - return tag_id; - } - } return -1; } @@ -741,10 +1114,10 @@ struct unwrap_optional_inner>>> { template using unwrap_optional_inner_t = typename unwrap_optional_inner::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 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) { constexpr auto enc = ::fory::detail::GetFieldConfigEntry::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 struct FieldInfoBuilder { typename meta::RemoveMemberPointerCVRefT; using ActualFieldType = std::remove_cv_t>; - // unwrap fory::field<> to get the underlying type for FieldTypeBuilder using UnwrappedFieldType = fory::unwrap_field_t; - // get nullable and track_ref from field tags (FORY_FIELD_TAGS or - // fory::field<>) constexpr bool is_nullable = compute_is_nullable(); constexpr bool track_ref = compute_track_ref(); constexpr int16_t field_id = compute_field_id(); - FieldType field_type = FieldTypeBuilder::build(false); - - // Override type_id for unsigned types based on encoding from - // FORY_FIELD_CONFIG - using InnerType = unwrap_optional_inner_t; - constexpr uint32_t unsigned_tid = - compute_unsigned_type_id(); - if constexpr (unsigned_tid != 0 && is_unsigned_integer_v) { - 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(); - if constexpr (signed_tid != 0) { - field_type.type_id = signed_tid; - } - - if constexpr (::fory::detail::has_field_config_v) { - constexpr int16_t override_id = - ::fory::detail::GetFieldConfigEntry::type_id_override; - if constexpr (override_id >= 0) { - field_type.type_id = static_cast(override_id); - } - } + constexpr FieldNodeSpec spec = + ::fory::detail::GetFieldConfigEntry::spec; + FieldType field_type = + build_field_type_with_spec(false, spec); // Override nullable and track_ref from field-level metadata field_type.nullable = is_nullable; @@ -862,9 +1210,9 @@ template 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 << " 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 struct FieldInfoBuilder { typename meta::RemoveMemberPointerCVRefT; using ActualFieldType = std::remove_cv_t>; - // unwrap fory::field<> to get the underlying type for FieldTypeBuilder using UnwrappedFieldType = fory::unwrap_field_t; - // get nullable and track_ref from field tags (FORY_FIELD_TAGS or - // fory::field<>) constexpr bool is_nullable = compute_is_nullable(); constexpr bool track_ref = compute_track_ref(); constexpr int16_t field_id = compute_field_id(); - FORY_TRY(field_type, build_field_type_with_resolver( - resolver, false)); - - // Override type_id for unsigned types based on encoding from - // FORY_FIELD_CONFIG - using InnerType = unwrap_optional_inner_t; - constexpr uint32_t unsigned_tid = - compute_unsigned_type_id(); - if constexpr (unsigned_tid != 0 && is_unsigned_integer_v) { - 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(); - if constexpr (signed_tid != 0) { - field_type.type_id = signed_tid; - } - - if constexpr (::fory::detail::has_field_config_v) { - constexpr int16_t override_id = - ::fory::detail::GetFieldConfigEntry::type_id_override; - if constexpr (override_id >= 0) { - field_type.type_id = static_cast(override_id); - } - } + constexpr FieldNodeSpec spec = + ::fory::detail::GetFieldConfigEntry::spec; + FORY_TRY(field_type, + (build_field_type_with_resolver_and_spec( + resolver, false, spec))); // Override nullable and track_ref from field-level metadata field_type.nullable = is_nullable; @@ -935,9 +1258,9 @@ template 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 << " 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 c90d3d0d27..7d5820f4f4 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 using union_unwrap_optional_inner_t = typename union_unwrap_optional_inner::type; +template struct UnionCaseSpecProvider { + static inline constexpr FieldNodeSpec spec = + UnionInfo::template Meta::value.spec_; +}; + +template +constexpr FieldNodeKind union_node_kind() { + constexpr auto spec = SpecProvider::spec; + return NodeIndex >= 0 ? spec.kind_[NodeIndex] : FieldNodeKind::Default; +} + +template +constexpr Encoding union_node_encoding() { + constexpr auto spec = SpecProvider::spec; + return NodeIndex >= 0 ? spec.encoding_[NodeIndex] : Encoding::Default; +} + +template +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 +constexpr FieldScalarKind union_node_scalar() { + constexpr auto spec = SpecProvider::spec; + return NodeIndex >= 0 ? spec.scalar_[NodeIndex] : FieldScalarKind::Inferred; +} + +template +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 +constexpr bool union_vector_primitive_array_spec() { + if constexpr (!is_vector_v) { + return false; + } else { + using Element = element_type_t; + if constexpr (!std::is_same_v, int8_t> && + !std::is_same_v, uint8_t>) { + return false; + } else { + constexpr FieldNodeKind kind = union_node_kind(); + constexpr int8_t child = union_node_child(); + if constexpr (kind != FieldNodeKind::List || child < 0) { + return false; + } else if constexpr (union_node_kind() != + FieldNodeKind::Scalar || + union_node_encoding() != + Encoding::Default) { + return false; + } else if constexpr (std::is_same_v, int8_t>) { + return union_node_scalar() == + FieldScalarKind::Int8; + } else { + return union_node_scalar() == + FieldScalarKind::UInt8; + } + } + } +} + template 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(Serializer::type_id); } +template +constexpr uint32_t +resolve_union_type_id_for_spec(const ::fory::FieldMeta &meta) { + using Inner = union_unwrap_optional_inner_t; + if constexpr (union_vector_primitive_array_spec()) { + using Element = element_type_t; + if constexpr (std::is_same_v, int8_t>) { + return static_cast(TypeId::INT8_ARRAY); + } else { + return static_cast(TypeId::UINT8_ARRAY); + } + } + return resolve_union_type_id(meta); +} + template 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(meta); } +template +FORY_ALWAYS_INLINE void write_union_configured_scalar(const FieldType &value, + WriteContext &ctx) { + constexpr Encoding enc = union_node_encoding(); + if constexpr (is_configurable_int_v) { + if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + ctx.write_var_uint32(value); + } else { + ctx.buffer().write_int32(static_cast(value)); + } + } else if constexpr (std::is_same_v) { + 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(value)); + } + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + ctx.buffer().write_int32(static_cast(value)); + } else { + ctx.write_var_int32(static_cast(value)); + } + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + ctx.buffer().write_int64(static_cast(value)); + } else if constexpr (enc == Encoding::Tagged) { + ctx.write_tagged_int64(static_cast(value)); + } else { + ctx.write_var_int64(static_cast(value)); + } + } else { + Serializer::write_data(value, ctx); + } + } else { + Serializer::write_data(value, ctx); + } +} + +template +FORY_ALWAYS_INLINE FieldType read_union_configured_scalar(ReadContext &ctx) { + if constexpr (is_configurable_int_v) { + constexpr Encoding enc = union_node_encoding(); + if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(ctx.read_var_uint32(ctx.error())); + } + return static_cast(ctx.read_int32(ctx.error())); + } else if constexpr (std::is_same_v) { + if constexpr (enc == Encoding::Varint) { + return static_cast(ctx.read_var_uint64(ctx.error())); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(ctx.read_tagged_uint64(ctx.error())); + } + return static_cast(ctx.read_uint64(ctx.error())); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(ctx.read_int32(ctx.error())); + } + return static_cast(ctx.read_var_int32(ctx.error())); + } else if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (enc == Encoding::Fixed) { + return static_cast(ctx.read_int64(ctx.error())); + } else if constexpr (enc == Encoding::Tagged) { + return static_cast(ctx.read_tagged_int64(ctx.error())); + } + return static_cast(ctx.read_var_int64(ctx.error())); + } else { + return Serializer::read_data(ctx); + } + } else { + return Serializer::read_data(ctx); + } +} + +template +void write_union_configured_value(const ValueType &value, WriteContext &ctx, + RefMode ref_mode, bool write_type, + bool has_generics); + +template +ValueType read_union_configured_value(ReadContext &ctx, RefMode ref_mode, + bool read_type); + +template +void write_union_configured_list_data(const Container &coll, + WriteContext &ctx) { + using Elem = element_type_t; + ctx.write_var_uint32(static_cast(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, ctx, RefMode::None, false, true); + } else { + Serializer::write_data(elem, ctx); + } + } +} + +template +Container read_union_configured_list_data(ReadContext &ctx) { + using Elem = element_type_t; + uint32_t length = ctx.read_var_uint32(ctx.error()); + Container result; + if constexpr (has_reserve_v) { + 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( + ctx, RefMode::None, false); + collection_insert(result, std::move(elem)); + } else { + auto elem = Serializer::read_data(ctx); + collection_insert(result, std::move(elem)); + } + } + return result; +} + +template +void write_union_configured_map_data(const MapType &map, WriteContext &ctx) { + using Key = key_type_t; + using Value = mapped_type_t; + ctx.write_var_uint32(static_cast(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(DECL_KEY_TYPE | DECL_VALUE_TYPE)); + need_write_header = false; + } + if constexpr (KeyNode >= 0) { + write_union_configured_value( + key, ctx, RefMode::None, false, true); + } else { + Serializer::write_data(key, ctx); + } + if constexpr (ValueNode >= 0) { + write_union_configured_value( + value, ctx, RefMode::None, false, true); + } else { + Serializer::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 +MapType read_union_configured_map_data(ReadContext &ctx) { + using Key = key_type_t; + using Value = mapped_type_t; + uint32_t length = ctx.read_var_uint32(ctx.error()); + MapType result; + MapReserver::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( + ctx, RefMode::None, false); + } else { + return Serializer::read_data(ctx); + } + }(); + Value value = [&]() { + if constexpr (ValueNode >= 0) { + return read_union_configured_value( + ctx, RefMode::None, false); + } else { + return Serializer::read_data(ctx); + } + }(); + result.emplace(std::move(key), std::move(value)); + ++read_count; + } + } + return result; +} + +template +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(); + constexpr FieldScalarKind scalar_kind = + union_node_scalar(); + static_assert(configured_scalar_kind_matches(), + "fory::T typed scalar spec does not match the C++ union case " + "type"); + if constexpr (is_optional_v) { + 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() + : NodeIndex; + write_union_configured_value( + *value, ctx, RefMode::None, false, has_generics); + } else if constexpr ((is_vector_v || is_list_v || + is_deque_v || is_set_like_v) && + (kind == FieldNodeKind::List || + kind == FieldNodeKind::Set)) { + if constexpr (union_vector_primitive_array_spec()) { + Serializer::write(value, ctx, ref_mode, false, has_generics); + } else { + write_not_null_ref_flag(ctx, ref_mode); + constexpr int8_t child = union_node_child(); + write_union_configured_list_data(value, + ctx); + } + } else if constexpr (is_map_like_v && kind == FieldNodeKind::Map) { + write_not_null_ref_flag(ctx, ref_mode); + constexpr int8_t key_child = union_node_child(); + constexpr int8_t value_child = + union_node_child(); + write_union_configured_map_data(value, ctx); + } else if constexpr (kind == FieldNodeKind::Scalar || + union_node_encoding() != + Encoding::Default) { + write_not_null_ref_flag(ctx, ref_mode); + write_union_configured_scalar(value, + ctx); + } else { + Serializer::write(value, ctx, ref_mode, write_type, + has_generics); + } +} + +template +ValueType read_union_configured_value(ReadContext &ctx, RefMode ref_mode, + bool read_type) { + constexpr FieldNodeKind kind = union_node_kind(); + constexpr FieldScalarKind scalar_kind = + union_node_scalar(); + static_assert(configured_scalar_kind_matches(), + "fory::T typed scalar spec does not match the C++ union case " + "type"); + if constexpr (is_optional_v) { + 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() + : NodeIndex; + Inner inner = read_union_configured_value( + ctx, RefMode::None, false); + return ValueType{std::move(inner)}; + } else if constexpr ((is_vector_v || is_list_v || + is_deque_v || is_set_like_v) && + (kind == FieldNodeKind::List || + kind == FieldNodeKind::Set)) { + if constexpr (union_vector_primitive_array_spec()) { + return Serializer::read(ctx, ref_mode, false); + } else { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + constexpr int8_t child = union_node_child(); + return read_union_configured_list_data( + ctx); + } + } else if constexpr (is_map_like_v && kind == FieldNodeKind::Map) { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + constexpr int8_t key_child = union_node_child(); + constexpr int8_t value_child = + union_node_child(); + return read_union_configured_map_data(ctx); + } else if constexpr (kind == FieldNodeKind::Scalar || + union_node_encoding() != + Encoding::Default) { + if (!read_null_only_flag(ctx, ref_mode)) { + return ValueType{}; + } + return read_union_configured_scalar( + ctx); + } else { + return Serializer::read(ctx, ref_mode, read_type); + } +} + template 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 struct UnionFactoryArg; + +template struct UnionFactoryArg { + using type = Arg; +}; + } // namespace detail // ============================================================================// @@ -476,9 +926,13 @@ struct Serializer>> { using CaseT = typename detail::UnionInfo::template CaseT; constexpr ::fory::FieldMeta meta = detail::UnionInfo::template Meta::value; + using SpecProvider = detail::UnionCaseSpecProvider; constexpr uint32_t field_type_id = - detail::resolve_union_type_id(meta); - const bool manual = detail::needs_manual_encoding(meta); + detail::resolve_union_type_id_for_spec(meta); + constexpr bool configured = + detail::union_node_has_override(); + const bool manual = + configured || detail::needs_manual_encoding(meta); constexpr bool nullable = meta.nullable_ || is_nullable_v>; const RefMode value_ref_mode = @@ -493,7 +947,12 @@ struct Serializer>> { return; } ctx.write_uint8(static_cast(field_type_id)); - detail::write_union_value_data(value, ctx, field_type_id); + if constexpr (detail::union_node_has_override()) { + detail::write_union_configured_value( + value, ctx, RefMode::None, false, true); + } else { + detail::write_union_value_data(value, ctx, field_type_id); + } return; } Serializer::write(value, ctx, value_ref_mode, true); @@ -557,9 +1016,13 @@ struct Serializer>> { using CaseT = typename detail::UnionInfo::template CaseT; constexpr ::fory::FieldMeta meta = detail::UnionInfo::template Meta::value; + using SpecProvider = detail::UnionCaseSpecProvider; constexpr uint32_t field_type_id = - detail::resolve_union_type_id(meta); - const bool manual = detail::needs_manual_encoding(meta); + detail::resolve_union_type_id_for_spec(meta); + constexpr bool configured = + detail::union_node_has_override(); + const bool manual = + configured || detail::needs_manual_encoding(meta); constexpr bool nullable = meta.nullable_ || is_nullable_v>; const RefMode value_ref_mode = @@ -587,7 +1050,14 @@ struct Serializer>> { result = default_value(); return; } - CaseT value = detail::read_union_value_data(ctx, field_type_id); + CaseT value = [&]() { + if constexpr (detail::union_node_has_override()) { + return detail::read_union_configured_value( + ctx, RefMode::None, false); + } else { + return detail::read_union_value_data(ctx, field_type_id); + } + }(); if (FORY_PREDICT_FALSE(ctx.has_error())) { result = default_value(); return; @@ -633,7 +1103,7 @@ struct Serializer>> { 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 @@ struct Serializer>> { // 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 @@ struct Serializer>> { #define FORY_UNION_CASE_ID(Type, tuple) \ static_cast(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( \ + static_cast( \ &Type::FORY_UNION_CASE_NAME(tuple)) #define FORY_UNION_IDS_DESCRIPTOR_NAME(line) \ @@ -838,6 +1334,8 @@ struct Serializer>> { 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 @@ struct Serializer>> { "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 db6d02a827..29e84488b3 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> 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> 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> +build_nested_annotated_container_values() { + return {{4000000000u, {7u, 1000000000u}}, {3u, {42u}}}; +} + +template +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(fory, buffer); + const auto expected = build_nested_annotated_container_values(); + if (value.values != expected) { + fail("Nested annotated container values mismatch"); + } + + std::vector 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(801), + "register NestedAnnotatedContainerSchemaConsistent"); + run_nested_annotated_container_test( + 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(802), + "register NestedAnnotatedContainerCompatible"); + run_nested_annotated_container_test( + fory, data_file); +} + // ============================================================================ // Unsigned Number Tests // ============================================================================ diff --git a/docs/benchmarks/cpp/README.md b/docs/benchmarks/cpp/README.md index 9811c8a54a..0d7c25a602 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). ![Throughput](throughput.png) -### Mediacontent - -![Mediacontent](mediacontent.png) - -### Mediacontentlist +### Struct -![Mediacontentlist](mediacontentlist.png) +![Struct](struct.png) ### Sample ![Sample](sample.png) -### Samplelist - -![Samplelist](samplelist.png) - -### Struct +### Mediacontent -![Struct](struct.png) +![Mediacontent](mediacontent.png) ### Structlist ![Structlist](structlist.png) +### Samplelist + +![Samplelist](samplelist.png) + +### Mediacontentlist + +![Mediacontentlist](mediacontentlist.png) + ## 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 7fd07e4619..6cb9660855 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 1fccde6493..267ff574f5 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 127a6e1aab..bb25b54337 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 2ebde46ef2..bdaa4f05d1 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 88e586ff64..398ab52f80 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 c676e84df0..47c5f680f0 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 9ffa742b65..525f58ff72 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 583cd79b6e..5098fb2af6 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 bffa4eb465..9f514fd0de 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>`, or `map<..., list<...>>`. +Nested collection support is target-capability based. The C++ generator accepts +nested collection specs such as `list>`, `list>`, 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 94f5170dd5..02029453ac 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 -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 name; - fory::field age; - fory::field, 2> nickname; +struct DataV2 { + uint32_t id; + uint64_t timestamp; + std::optional 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 name; - fory::field, 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`, `fory::serialization::SharedWeak`, `std::unique_ptr` - -**Note:** For nullable primitives or strings, use `std::optional` instead: +`fory::F(id)` uses id-mode field identity: ```cpp -// Correct: use std::optional for nullable primitives -fory::field, 0> optional_value; - -// Wrong: nullable is not allowed for primitives -// fory::field 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, 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`, `fory::serialization::SharedWeak`, `std::unique_ptr` +## 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 name; - fory::field, 1, fory::ref> left; // Ref tracked - fory::field, 2, fory::ref> right; // Ref tracked -}; -FORY_STRUCT(Graph, name, left, right); -``` - -**Valid for:** `std::shared_ptr`, `fory::serialization::SharedWeak` (requires shared ownership) - -### fory::dynamic\ - -Controls whether type info is written for polymorphic smart pointer fields: - -- `fory::dynamic`: Force type info to be written (enable runtime subtype support) -- `fory::dynamic`: skip type info (use declared type directly, no dynamic dispatch) - -By default, Fory auto-detects polymorphism via `std::is_polymorphic`. 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, 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, 1, fory::nullable, fory::dynamic> 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`, `std::unique_ptr` - -### 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, 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` if nullable | -| `std::optional` | None | Inherently nullable | -| `std::shared_ptr` | `nullable`, `ref`, `dynamic` | Non-null by default | -| `fory::serialization::SharedWeak` | `nullable`, `ref`, `dynamic` | Non-null by default | -| `std::unique_ptr` | `nullable`, `dynamic` | 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 title; - fory::field version; - - // Optional primitive using std::optional - fory::field, 2> description; +namespace T = fory::T; - // Nullable pointer - fory::field, 3, fory::nullable> metadata; - - // Reference-tracked shared pointer - fory::field, 4, fory::ref> parent; - - // Nullable + reference-tracked - fory::field, 5, fory::nullable, fory::ref> related; +struct Foo { + std::vector values; + std::map> nested; }; -FORY_STRUCT(Document, title, version, description, metadata, parent, related); - -int main() { - auto fory = Fory::builder().xlang(true).build(); - fory.register_struct(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(); - doc.parent.get()->title = "Parent Doc"; - doc.related = nullptr; // Allowed because nullable - - auto bytes = fory.serialize(doc).value(); - auto decoded = fory.deserialize(bytes).value(); -} -``` - -## Compile-Time Validation - -Invalid configurations are caught at compile time: - -```cpp -// Error: nullable and not_null are mutually exclusive -fory::field, 0, fory::nullable, fory::not_null> bad1; -// Error: nullable only valid for smart pointers -fory::field bad2; - -// Error: ref only valid for shared_ptr -fory::field, 0, fory::ref> bad3; - -// Error: options not allowed for std::optional (inherently nullable) -fory::field, 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 name; - fory::field 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 description; - std::shared_ptr author; - std::shared_ptr reviewer; - std::shared_ptr parent; - std::unique_ptr 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` | `(field, id)` only | -| `std::shared_ptr` | `(field, id)`, `(field, id, nullable)`, `(field, id, ref)`, `(field, id, nullable, ref)` | -| `std::unique_ptr` | `(field, id)`, `(field, id, nullable)` | +Supported recursive composition methods are: -## FORY_FIELD_CONFIG Macro +| Method | Applies to | +| ------------------- | ------------------------------------- | +| `list(elem)` | `std::vector` and list-like fields | +| `set(elem)` | `std::set` and set-like fields | +| `map(key, value)` | `std::map` 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> maybe_values; + std::shared_ptr> 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`, +`std::shared_ptr`, `std::unique_ptr`, and +`fory::serialization::SharedWeak`. -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` 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 error_count; - std::optional last_access_time; +struct Node { + std::string name; + std::shared_ptr 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(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(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 star; + std::shared_ptr 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 value; -// Version 2: Added nullable fields -struct DataV2 { - uint32_t id; - uint64_t timestamp; - std::optional 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` is nullable by default; all other types (including `std::shared_ptr`) are non-nullable -- **Ref tracking**: Enabled by default for `std::shared_ptr` and `fory::serialization::SharedWeak`, 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` 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 email; // Nullable (std::optional) - std::shared_ptr 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` | `true` | `false` | -| `std::shared_ptr` | `false` | `true` | -| `fory::serialization::SharedWeak` | `false` | `true` | -| `std::unique_ptr` | `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 97f5bad11d..4465553eff 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` 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 animal1; // Force dynamic: type info written explicitly - fory::field, 0, fory::dynamic> animal2; + std::shared_ptr animal2; // Force non-dynamic: skip type info (faster but no runtime subtyping) - fory::field, 1, fory::dynamic> animal3; + std::shared_ptr 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`:** +**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 backup; // Nullable polymorphic field std::shared_ptr 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` and `std::unique_ptr` 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 primary; // Nullable via explicit field metadata - fory::field, 0, fory::nullable> optional; + std::shared_ptr 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, 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`** when runtime type matches declared type: +1. **Use `dynamic(false)`** when runtime type matches declared type: ```cpp - fory::field, 0, fory::dynamic> 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 ab888a5630..de7f9dc2b4 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> optional_setting; + std::optional 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> required_setting; +For nullable pointer carriers, opt in with `.nullable()`: + +```cpp +struct ConfigRef { + std::shared_ptr optional_setting; + std::shared_ptr 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 1ed94bd3d5..2aafbf2ee0 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; fory::serialization::SharedWeak data; - // Explicitly mark ref tracking when using field wrappers (optional) - fory::field, 1, fory::ref> tag_owner; + std::shared_ptr 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 1c6880c857..58870fd36d 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` 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, 0, fory::nullable> animal; + std::shared_ptr animal; // Force non-dynamic: skip type info even though Animal is polymorphic - fory::field, 1, fory::nullable, fory::dynamic> fixed_animal; + std::shared_ptr fixed_animal; // Force dynamic: write type info even for non-polymorphic types - fory::field, 2, fory::dynamic> polymorphic_data; -}; -FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data); -``` - -**Using `FORY_FIELD_CONFIG` macro**: - -```cpp -struct Zoo { - std::shared_ptr animal; - std::shared_ptr fixed_animal; std::shared_ptr 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 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 8a02157aec..c1f320d8a9 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 void testStructWithMap(boolean enableCodegen) throws java.io.IOException 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);