This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 045a10918 refactor(java/c++): rename morphic to dynamic (#3142)
045a10918 is described below
commit 045a10918491e1034a9806fda1e812cf3369d6fa
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Jan 13 19:56:12 2026 +0800
refactor(java/c++): rename morphic to dynamic (#3142)
## Why?
## What does this PR do?
## Related issues
#3107
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
cpp/fory/meta/field.h | 113 +++++++++++++--------
.../serialization/smart_ptr_serializer_test.cc | 26 ++---
cpp/fory/serialization/struct_serializer.h | 63 +++++++-----
dart/packages/fory/lib/src/const/obj_type.dart | 22 ++--
docs/guide/cpp/field-configuration.md | 63 +++++++++---
docs/specification/xlang_serialization_spec.md | 12 +--
.../java/org/apache/fory/annotation/ForyField.java | 16 +--
.../org/apache/fory/resolver/ClassResolver.java | 6 +-
.../org/apache/fory/resolver/XtypeResolver.java | 8 +-
.../main/java/org/apache/fory/type/Descriptor.java | 6 +-
.../java/org/apache/fory/xlang/XlangTestBase.java | 4 +-
rust/fory-derive/src/object/util.rs | 2 +-
12 files changed, 211 insertions(+), 130 deletions(-)
diff --git a/cpp/fory/meta/field.h b/cpp/fory/meta/field.h
index 7bcdc4e04..177346cb3 100644
--- a/cpp/fory/meta/field.h
+++ b/cpp/fory/meta/field.h
@@ -50,11 +50,17 @@ struct not_null {};
/// tracking).
struct ref {};
-/// Tag to mark a polymorphic shared_ptr/unique_ptr field as monomorphic.
-/// Use this when the field type has virtual methods but you know the actual
-/// runtime type will always be exactly T (not a derived type).
-/// This avoids dynamic type dispatch overhead during serialization.
-struct monomorphic {};
+/// Template tag to control dynamic type dispatch for smart pointer fields.
+/// - `dynamic<true>`: Force type info to be written (enable runtime subtype
+/// support)
+/// - `dynamic<false>`: Skip type info (use declared type directly)
+///
+/// By default, Fory auto-detects polymorphism via `std::is_polymorphic<T>`.
+/// Use this tag to override the default behavior.
+///
+/// Example:
+/// fory::field<std::shared_ptr<Base>, 0, fory::dynamic<false>> ptr;
+template <bool V> struct dynamic : std::bool_constant<V> {};
namespace detail {
@@ -97,17 +103,46 @@ inline constexpr bool is_smart_ptr_v = is_shared_ptr_v<T>
|| is_unique_ptr_v<T>;
template <typename Tag, typename... Options>
inline constexpr bool has_option_v = (std::is_same_v<Tag, Options> || ...);
+/// Check if a type is a dynamic<V> tag
+template <typename T> struct is_dynamic_tag : std::false_type {};
+template <bool V> struct is_dynamic_tag<dynamic<V>> : std::true_type {};
+template <typename T>
+inline constexpr bool is_dynamic_tag_v = is_dynamic_tag<T>::value;
+
+/// Check if any dynamic<V> tag is present in Options pack
+template <typename... Options>
+inline constexpr bool has_dynamic_option_v = (is_dynamic_tag_v<Options> ||
...);
+
+/// Extract the dynamic value from Options pack (default = -1 for AUTO)
+/// Returns: 1 for dynamic<true>, 0 for dynamic<false>, -1 for AUTO (not
+/// specified)
+template <typename... Options> struct get_dynamic_value {
+ static constexpr int value = -1; // AUTO
+};
+template <bool V, typename... Rest>
+struct get_dynamic_value<dynamic<V>, Rest...> {
+ static constexpr int value = V ? 1 : 0;
+};
+template <typename First, typename... Rest>
+struct get_dynamic_value<First, Rest...> {
+ static constexpr int value = get_dynamic_value<Rest...>::value;
+};
+template <typename... Options>
+inline constexpr int get_dynamic_value_v =
get_dynamic_value<Options...>::value;
+
// ============================================================================
// Field Tag Entry for FORY_FIELD_TAGS Macro
// ============================================================================
/// Compile-time field tag metadata entry
-template <int16_t Id, bool Nullable, bool Ref, bool Monomorphic = false>
+/// Dynamic: -1 = AUTO (use std::is_polymorphic), 0 = FALSE (not dynamic), 1 =
+/// TRUE (dynamic)
+template <int16_t Id, bool Nullable, bool Ref, int Dynamic = -1>
struct FieldTagEntry {
static constexpr int16_t id = Id;
static constexpr bool is_nullable = Nullable;
static constexpr bool track_ref = Ref;
- static constexpr bool is_monomorphic = Monomorphic;
+ static constexpr int dynamic_value = Dynamic;
};
/// Default: no field tags defined for type T
@@ -139,12 +174,12 @@ enum class Encoding {
/// Compile-time field metadata with fluent builder API.
/// Supports both:
/// - Simple: F(0) - just field ID
-/// - Full: F(0).nullable().varint().compress(false)
+/// - Full: F(0).nullable().varint().compress(false).dynamic(false)
struct FieldMeta {
int16_t id_ = -1;
bool nullable_ = false;
bool ref_ = false;
- bool monomorphic_ = false;
+ int dynamic_ = -1; // -1 = AUTO, 0 = FALSE, 1 = TRUE
Encoding encoding_ = Encoding::Default;
bool compress_ = true;
@@ -164,9 +199,10 @@ struct FieldMeta {
c.ref_ = v;
return c;
}
- constexpr FieldMeta monomorphic(bool v = true) const {
+ /// Set dynamic type dispatch: true = write type info, false = skip type info
+ constexpr FieldMeta dynamic(bool v) const {
auto c = *this;
- c.monomorphic_ = v;
+ c.dynamic_ = v ? 1 : 0;
return c;
}
constexpr FieldMeta encoding(Encoding v) const {
@@ -213,14 +249,14 @@ template <typename T> constexpr auto normalize_config(T
&&v) {
}
}
-/// Apply old-style tag to FieldMeta (for backward compatibility)
+/// 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(); }
-constexpr FieldMeta apply_tag(FieldMeta m, monomorphic) {
- return m.monomorphic();
+template <bool V> constexpr FieldMeta apply_tag(FieldMeta m, dynamic<V>) {
+ return m.dynamic(V);
}
/// Fold multiple tags onto a base config
@@ -265,7 +301,7 @@ struct GetFieldConfigEntry {
static constexpr int16_t id = -1;
static constexpr bool nullable = false;
static constexpr bool ref = false;
- static constexpr bool monomorphic = false;
+ static constexpr int dynamic_value = -1; // AUTO
static constexpr bool compress = true;
};
@@ -284,7 +320,7 @@ public:
static constexpr int16_t id = get_entry().meta.id_;
static constexpr bool nullable = get_entry().meta.nullable_;
static constexpr bool ref = get_entry().meta.ref_;
- static constexpr bool monomorphic = get_entry().meta.monomorphic_;
+ static constexpr int dynamic_value = get_entry().meta.dynamic_;
static constexpr bool compress = get_entry().meta.compress_;
};
@@ -341,10 +377,10 @@ template <typename T, int16_t Id, typename... Options>
class field {
"fory::ref is only valid for shared_ptr "
"(reference tracking requires shared ownership).");
- // Validate: monomorphic only for smart pointers
- static_assert(!detail::has_option_v<monomorphic, Options...> ||
+ // Validate: dynamic<V> only for smart pointers
+ static_assert(!detail::has_dynamic_option_v<Options...> ||
detail::is_smart_ptr_v<T>,
- "fory::monomorphic is only valid for shared_ptr/unique_ptr.");
+ "fory::dynamic<V> is only valid for shared_ptr/unique_ptr.");
// Validate: no options for optional (inherently nullable)
static_assert(!detail::is_optional_v<T> || sizeof...(Options) == 0,
@@ -372,12 +408,11 @@ public:
static constexpr bool track_ref =
detail::is_shared_ptr_v<T> && detail::has_option_v<ref, Options...>;
- /// Monomorphic serialization is enabled if:
- /// - It's std::shared_ptr or std::unique_ptr with fory::monomorphic option
- /// - When true, the serializer will not use dynamic type dispatch
- static constexpr bool is_monomorphic =
- detail::is_smart_ptr_v<T> &&
- detail::has_option_v<monomorphic, Options...>;
+ /// Dynamic type dispatch control:
+ /// - -1 (AUTO): Use std::is_polymorphic<T> to decide
+ /// - 0 (FALSE): Skip type info, use declared type directly
+ /// - 1 (TRUE): Write type info, enable runtime subtype support
+ static constexpr int dynamic_value = detail::get_dynamic_value_v<Options...>;
T value{};
@@ -499,18 +534,18 @@ struct field_track_ref<field<T, Id, Options...>> {
template <typename T>
inline constexpr bool field_track_ref_v = field_track_ref<T>::value;
-/// Get is_monomorphic from field type
-template <typename T> struct field_is_monomorphic {
- static constexpr bool value = false;
+/// Get dynamic_value from field type (-1 = AUTO, 0 = FALSE, 1 = TRUE)
+template <typename T> struct field_dynamic_value {
+ static constexpr int value = -1; // AUTO
};
template <typename T, int16_t Id, typename... Options>
-struct field_is_monomorphic<field<T, Id, Options...>> {
- static constexpr bool value = field<T, Id, Options...>::is_monomorphic;
+struct field_dynamic_value<field<T, Id, Options...>> {
+ static constexpr int value = field<T, Id, Options...>::dynamic_value;
};
template <typename T>
-inline constexpr bool field_is_monomorphic_v = field_is_monomorphic<T>::value;
+inline constexpr int field_dynamic_value_v = field_dynamic_value<T>::value;
// ============================================================================
// FORY_FIELD_TAGS Macro Support
@@ -520,7 +555,7 @@ 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, monomorphic), etc.
+// (field, id, nullable, ref), (field, id, dynamic<false>), etc.
template <typename FieldType, int16_t Id, typename... Options>
struct ParseFieldTagEntry {
static constexpr bool is_nullable =
@@ -530,8 +565,7 @@ struct ParseFieldTagEntry {
static constexpr bool track_ref =
is_shared_ptr_v<FieldType> && has_option_v<ref, Options...>;
- static constexpr bool is_monomorphic =
- is_smart_ptr_v<FieldType> && has_option_v<monomorphic, Options...>;
+ static constexpr int dynamic_value = get_dynamic_value_v<Options...>;
// Compile-time validation
static_assert(!has_option_v<nullable, Options...> ||
@@ -541,11 +575,10 @@ struct ParseFieldTagEntry {
static_assert(!has_option_v<ref, Options...> || is_shared_ptr_v<FieldType>,
"fory::ref is only valid for shared_ptr");
- static_assert(!has_option_v<monomorphic, Options...> ||
- is_smart_ptr_v<FieldType>,
- "fory::monomorphic is only valid for shared_ptr/unique_ptr");
+ static_assert(!has_dynamic_option_v<Options...> || is_smart_ptr_v<FieldType>,
+ "fory::dynamic<V> is only valid for shared_ptr/unique_ptr");
- using type = FieldTagEntry<Id, is_nullable, track_ref, is_monomorphic>;
+ using type = FieldTagEntry<Id, is_nullable, track_ref, dynamic_value>;
};
/// Get field tag entry by index from ForyFieldTagsImpl
@@ -553,7 +586,7 @@ template <typename T, size_t Index, typename = void> struct
GetFieldTagEntry {
static constexpr int16_t id = -1;
static constexpr bool is_nullable = false;
static constexpr bool track_ref = false;
- static constexpr bool is_monomorphic = false;
+ static constexpr int dynamic_value = -1; // AUTO
};
template <typename T, size_t Index>
@@ -566,7 +599,7 @@ struct GetFieldTagEntry<
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 bool is_monomorphic = Entry::is_monomorphic;
+ static constexpr int dynamic_value = Entry::dynamic_value;
};
} // namespace detail
diff --git a/cpp/fory/serialization/smart_ptr_serializer_test.cc
b/cpp/fory/serialization/smart_ptr_serializer_test.cc
index 63f20ff33..b4151c6e9 100644
--- a/cpp/fory/serialization/smart_ptr_serializer_test.cc
+++ b/cpp/fory/serialization/smart_ptr_serializer_test.cc
@@ -456,30 +456,30 @@ struct PolymorphicBaseForMono {
};
FORY_STRUCT(PolymorphicBaseForMono, value, data);
-// Holder with monomorphic field using fory::field<>
-struct MonomorphicFieldHolder {
- // Field marked as monomorphic - no dynamic type dispatch, always
+// Holder with non-dynamic field using fory::field<>
+struct NonDynamicFieldHolder {
+ // Field marked as dynamic<false> - no dynamic type dispatch, always
// PolymorphicBaseForMono
fory::field<std::shared_ptr<PolymorphicBaseForMono>, 0, fory::nullable,
- fory::monomorphic>
+ fory::dynamic<false>>
ptr;
};
-FORY_STRUCT(MonomorphicFieldHolder, ptr);
+FORY_STRUCT(NonDynamicFieldHolder, ptr);
-TEST(SmartPtrSerializerTest, MonomorphicFieldWithForyField) {
- MonomorphicFieldHolder original;
+TEST(SmartPtrSerializerTest, NonDynamicFieldWithForyField) {
+ NonDynamicFieldHolder original;
original.ptr.value = std::make_shared<PolymorphicBaseForMono>();
original.ptr.value->value = 42;
original.ptr.value->data = "test data";
auto fory = Fory::builder().track_ref(false).build();
- fory.register_struct<MonomorphicFieldHolder>(400);
+ fory.register_struct<NonDynamicFieldHolder>(400);
fory.register_struct<PolymorphicBaseForMono>(401);
auto bytes_result = fory.serialize(original);
ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
- auto deserialize_result = fory.deserialize<MonomorphicFieldHolder>(
+ auto deserialize_result = fory.deserialize<NonDynamicFieldHolder>(
bytes_result->data(), bytes_result->size());
ASSERT_TRUE(deserialize_result.ok())
<< deserialize_result.error().to_string();
@@ -491,18 +491,18 @@ TEST(SmartPtrSerializerTest,
MonomorphicFieldWithForyField) {
EXPECT_EQ(deserialized.ptr.value->name(), "PolymorphicBaseForMono");
}
-TEST(SmartPtrSerializerTest, MonomorphicFieldNullValue) {
- MonomorphicFieldHolder original;
+TEST(SmartPtrSerializerTest, NonDynamicFieldNullValue) {
+ NonDynamicFieldHolder original;
original.ptr.value = nullptr;
auto fory = Fory::builder().track_ref(false).build();
- fory.register_struct<MonomorphicFieldHolder>(404);
+ fory.register_struct<NonDynamicFieldHolder>(404);
fory.register_struct<PolymorphicBaseForMono>(405);
auto bytes_result = fory.serialize(original);
ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
- auto deserialize_result = fory.deserialize<MonomorphicFieldHolder>(
+ auto deserialize_result = fory.deserialize<NonDynamicFieldHolder>(
bytes_result->data(), bytes_result->size());
ASSERT_TRUE(deserialize_result.ok())
<< deserialize_result.error().to_string();
diff --git a/cpp/fory/serialization/struct_serializer.h
b/cpp/fory/serialization/struct_serializer.h
index 29c1d31f6..3844e5048 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -408,27 +408,28 @@ template <typename T> struct CompileTimeFieldHelpers {
}
}
- /// Returns true if the field at Index is marked as monomorphic.
- /// Use this for shared_ptr/unique_ptr fields with polymorphic inner types
- /// when you know the actual runtime type will always be exactly T.
- template <size_t Index> static constexpr bool field_monomorphic() {
+ /// Returns the dynamic value for the field at Index.
+ /// -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)
+ template <size_t Index> static constexpr int field_dynamic_value() {
if constexpr (FieldCount == 0) {
- return false;
+ return -1; // AUTO
} else {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
- // If it's a fory::field<> wrapper, use its is_monomorphic metadata
+ // If it's a fory::field<> wrapper, use its dynamic_value metadata
if constexpr (is_fory_field_v<RawFieldType>) {
- return RawFieldType::is_monomorphic;
+ return RawFieldType::dynamic_value;
}
// Else if FORY_FIELD_TAGS is defined, use that metadata
else if constexpr (::fory::detail::has_field_tags_v<T>) {
- return ::fory::detail::GetFieldTagEntry<T, Index>::is_monomorphic;
+ return ::fory::detail::GetFieldTagEntry<T, Index>::dynamic_value;
}
- // Default: not monomorphic (polymorphic types use dynamic dispatch)
+ // Default: AUTO (use std::is_polymorphic to decide)
else {
- return false;
+ return -1;
}
}
}
@@ -1529,13 +1530,19 @@ void write_single_field(const T &obj, WriteContext &ctx,
constexpr bool is_ext = is_ext_type(field_type_id);
constexpr bool is_polymorphic = field_type_id == TypeId::UNKNOWN;
- // Check if field is marked as monomorphic (skip dynamic type dispatch)
- constexpr bool is_monomorphic = Helpers::template field_monomorphic<Index>();
+ // Get dynamic value: -1=AUTO, 0=FALSE (no type info), 1=TRUE (write type
+ // info)
+ constexpr int dynamic_val = Helpers::template field_dynamic_value<Index>();
// Per C++ read logic: struct fields need type info only in compatible mode
- // Polymorphic types always need type info, UNLESS marked as monomorphic
- bool write_type = (is_polymorphic && !is_monomorphic) ||
- ((is_struct || is_ext) && ctx.is_compatible());
+ // Polymorphic types need type info based on dynamic_val:
+ // - TRUE (1): always write type info
+ // - FALSE (0): never write type info for this field
+ // - AUTO (-1): write type info if is_polymorphic (auto-detected)
+ 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());
Serializer<FieldType>::write(field_value, ctx, field_ref_mode, write_type);
}
@@ -1757,11 +1764,16 @@ void read_single_field_by_index(T &obj, ReadContext
&ctx) {
constexpr bool is_ext_field = is_ext_type(field_type_id);
constexpr bool is_polymorphic_field = field_type_id == TypeId::UNKNOWN;
- // Check if field is marked as monomorphic (skip dynamic type dispatch)
- constexpr bool is_monomorphic = Helpers::template field_monomorphic<Index>();
+ // Get dynamic value: -1=AUTO, 0=FALSE (no type info), 1=TRUE (write type
+ // info)
+ constexpr int dynamic_val = Helpers::template field_dynamic_value<Index>();
- // Polymorphic types need type info, UNLESS marked as monomorphic
- bool read_type = is_polymorphic_field && !is_monomorphic;
+ // Polymorphic types need type info based on dynamic_val:
+ // - TRUE (1): always read type info
+ // - FALSE (0): never read type info for this field
+ // - AUTO (-1): read type info if is_polymorphic_field (auto-detected)
+ bool read_type =
+ (dynamic_val == 1) || (dynamic_val == -1 && is_polymorphic_field);
// Get field metadata from fory::field<> or FORY_FIELD_TAGS or defaults
constexpr bool is_nullable = Helpers::template field_nullable<Index>();
@@ -1949,11 +1961,16 @@ void read_single_field_by_index_compatible(T &obj,
ReadContext &ctx,
constexpr bool is_polymorphic_field = field_type_id == TypeId::UNKNOWN;
constexpr bool is_primitive_field = is_primitive_type_id(field_type_id);
- // Check if field is marked as monomorphic (skip dynamic type dispatch)
- constexpr bool is_monomorphic = Helpers::template field_monomorphic<Index>();
+ // Get dynamic value: -1=AUTO, 0=FALSE (no type info), 1=TRUE (write type
+ // info)
+ constexpr int dynamic_val = Helpers::template field_dynamic_value<Index>();
- // Polymorphic types need type info, UNLESS marked as monomorphic
- bool read_type = is_polymorphic_field && !is_monomorphic;
+ // Polymorphic types need type info based on dynamic_val:
+ // - TRUE (1): always read type info
+ // - FALSE (0): never read type info for this field
+ // - AUTO (-1): read type info if is_polymorphic_field (auto-detected)
+ bool read_type =
+ (dynamic_val == 1) || (dynamic_val == -1 && is_polymorphic_field);
// In compatible mode, nested struct fields always carry type metadata
// (xtypeId + meta index). We must read this metadata so that
diff --git a/dart/packages/fory/lib/src/const/obj_type.dart
b/dart/packages/fory/lib/src/const/obj_type.dart
index 2bedcd7ec..da0c3d3f3 100644
--- a/dart/packages/fory/lib/src/const/obj_type.dart
+++ b/dart/packages/fory/lib/src/const/obj_type.dart
@@ -80,12 +80,12 @@ enum ObjType {
/// named_enum: an enum whose value will be serialized as the registered
name.
NAMED_ENUM(14, true), // 14
- /// A morphic(final) type serialized by Fory Struct serializer. i.e. it
doesn't have subclasses.
+ /// A dynamic(final) type serialized by Fory Struct serializer. i.e. it
doesn't have subclasses.
/// Suppose we're deserializing {@code List<SomeClass>}, we can save dynamic
serializer dispatch
- /// since `SomeClass` is morphic(final).
+ /// since `SomeClass` is dynamic(final).
STRUCT(15, false), // 15
- /// A morphic(final) type serialized by Fory compatible Struct serializer.
+ /// A dynamic(final) type serialized by Fory compatible Struct serializer.
COMPATIBLE_STRUCT(16, false), // 16
// x
@@ -149,25 +149,25 @@ enum ObjType {
/// One dimensional int16 array.
INT16_ARRAY(32, true),
-
+
/// One dimensional int32 array.
INT32_ARRAY(33, true),
-
+
/// One dimensional int64 array.
INT64_ARRAY(34, true),
-
+
/// One dimensional half_float_16 array.
FLOAT16_ARRAY(35, true),
-
+
/// One dimensional float32 array.
FLOAT32_ARRAY(36, true),
-
+
/// One dimensional float64 array.
FLOAT64_ARRAY(37, true),
-
+
/// An (arrow record batch) object.
ARROW_RECORD_BATCH(38, false),
-
+
/// An (arrow table) object.
ARROW_TABLE(39, false);
@@ -189,7 +189,7 @@ enum ObjType {
|| this == NAMED_STRUCT
|| this == NAMED_COMPATIBLE_STRUCT;
}
-
+
bool isTimeType() {
return this == TIMESTAMP
|| this == LOCAL_DATE
diff --git a/docs/guide/cpp/field-configuration.md
b/docs/guide/cpp/field-configuration.md
index fac2ee394..a70b1ead8 100644
--- a/docs/guide/cpp/field-configuration.md
+++ b/docs/guide/cpp/field-configuration.md
@@ -125,6 +125,35 @@ FORY_STRUCT(Graph, name, left, right);
**Valid for:** `std::shared_ptr<T>` only (requires shared ownership)
+### fory::dynamic\<V\>
+
+Controls whether type info is written for polymorphic smart pointer fields:
+
+- `fory::dynamic<true>`: Force type info to be written (enable runtime subtype
support)
+- `fory::dynamic<false>`: Skip type info (use declared type directly, no
dynamic dispatch)
+
+By default, Fory auto-detects polymorphism via `std::is_polymorphic<T>`. Use
this tag to override:
+
+```cpp
+// Base class with virtual methods (detected as polymorphic by default)
+struct Animal {
+ virtual ~Animal() = default;
+ virtual std::string speak() const = 0;
+};
+
+struct Zoo {
+ // Auto: type info written because Animal has virtual methods
+ fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal;
+
+ // Force non-dynamic: skip type info even though Animal has virtual methods
+ // Use when you know the runtime type will always be exactly as declared
+ fory::field<std::shared_ptr<Animal>, 1, fory::nullable,
fory::dynamic<false>> fixed_animal;
+};
+FORY_STRUCT(Zoo, animal, fixed_animal);
+```
+
+**Valid for:** `std::shared_ptr<T>`, `std::unique_ptr<T>`
+
### Combining Tags
Multiple tags can be combined for shared pointers:
@@ -136,12 +165,12 @@ fory::field<std::shared_ptr<Node>, 0, fory::nullable,
fory::ref> link;
## Type Rules
-| Type | Allowed Options | Nullability
|
-| -------------------- | ----------------- |
---------------------------------- |
-| Primitives, strings | None | Use `std::optional<T>` if
nullable |
-| `std::optional<T>` | None | Inherently nullable
|
-| `std::shared_ptr<T>` | `nullable`, `ref` | Non-null by default
|
-| `std::unique_ptr<T>` | `nullable` | Non-null by default
|
+| Type | Allowed Options | Nullability
|
+| -------------------- | ------------------------------- |
---------------------------------- |
+| Primitives, strings | None | Use
`std::optional<T>` if nullable |
+| `std::optional<T>` | None | Inherently nullable
|
+| `std::shared_ptr<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default
|
+| `std::unique_ptr<T>` | `nullable`, `dynamic<V>` | Non-null by default
|
## Complete Example
@@ -316,7 +345,8 @@ fory::F(0) // Create with field ID 0
.varint() // Use variable-length encoding
.fixed() // Use fixed-size encoding
.tagged() // Use tagged encoding
- .monomorphic() // Mark as monomorphic type
+ .dynamic(false) // Skip type info (no dynamic dispatch)
+ .dynamic(true) // Force type info (enable dynamic dispatch)
.compress(false) // Disable compression
```
@@ -475,15 +505,16 @@ FORY_FIELD_CONFIG(DataV2,
### FORY_FIELD_CONFIG Options Reference
-| Method | Description | Valid For
|
-| ---------------- | ------------------------------------------- |
-------------------------- |
-| `.nullable()` | Mark field as nullable | Smart
pointers, primitives |
-| `.ref()` | Enable reference tracking |
`std::shared_ptr` only |
-| `.monomorphic()` | Mark pointer as always pointing to one type | 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
|
+| Method | Description | Valid
For |
+| ----------------- | ------------------------------------------------ |
-------------------------- |
+| `.nullable()` | Mark field as nullable | Smart
pointers, primitives |
+| `.ref()` | Enable reference tracking |
`std::shared_ptr` only |
+| `.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 |
### Comparing Field Configuration Macros
diff --git a/docs/specification/xlang_serialization_spec.md
b/docs/specification/xlang_serialization_spec.md
index 579ea7c1b..a48c43d67 100644
--- a/docs/specification/xlang_serialization_spec.md
+++ b/docs/specification/xlang_serialization_spec.md
@@ -58,9 +58,9 @@ This specification defines the Fory xlang binary format. The
format is dynamic r
- enum: a data type consisting of a set of named values. Rust enum with
non-predefined field values are not supported as
an enum.
- named_enum: an enum whose value will be serialized as the registered name.
-- struct: a morphic(final) type serialized by Fory Struct serializer. i.e. it
doesn't have subclasses. Suppose we're
- deserializing `List<SomeClass>`, we can save dynamic serializer dispatch
since `SomeClass` is morphic(final).
-- compatible_struct: a morphic(final) type serialized by Fory compatible
Struct serializer.
+- struct: a dynamic(final) type serialized by Fory Struct serializer. i.e. it
doesn't have subclasses. Suppose we're
+ deserializing `List<SomeClass>`, we can save dynamic serializer dispatch
since `SomeClass` is dynamic(final).
+- compatible_struct: a dynamic(final) type serialized by Fory compatible
Struct serializer.
- named_struct: a `struct` whose type mapping will be encoded as a name.
- named_compatible_struct: a `compatible_struct` whose type mapping will be
encoded as a name.
- ext: a type which will be serialized by a customized serializer.
@@ -592,11 +592,11 @@ Polymorphism spec:
- `struct/named_struct/ext/named_ext` are taken as polymorphic, the meta for
those types are written separately
instead of inlining here to reduce meta space cost if object of this type is
serialized in current object graph
multiple times, and the field value may be null too.
-- `enum` is taken as morphic, if deserialization doesn't have this field, or
the type is not enum, enum value
+- `enum` is taken as dynamic, if deserialization doesn't have this field, or
the type is not enum, enum value
will be skipped.
-- `list/map/set` are taken as morphic, when serializing values of those type,
the concrete types won't be written
+- `list/map/set` are taken as dynamic, when serializing values of those type,
the concrete types won't be written
again.
-- Other types that fory supported are taken as morphic too.
+- Other types that fory supported are taken as dynamic too.
List/Set/Map nested type spec:
diff --git
a/java/fory-core/src/main/java/org/apache/fory/annotation/ForyField.java
b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyField.java
index aae072196..a43ad2db9 100644
--- a/java/fory-core/src/main/java/org/apache/fory/annotation/ForyField.java
+++ b/java/fory-core/src/main/java/org/apache/fory/annotation/ForyField.java
@@ -29,7 +29,7 @@ import java.lang.annotation.Target;
public @interface ForyField {
/** Controls polymorphism behavior for struct fields in cross-language
serialization. */
- enum Morphic {
+ enum Dynamic {
/**
* Auto-detect based on declared type (default).
*
@@ -42,10 +42,10 @@ public @interface ForyField {
AUTO,
/** Treat as final/sealed - no type info written, uses declared type's
serializer directly. */
- FINAL,
+ FALSE,
/** Treat as polymorphic - type info written to support subtypes at
runtime. */
- POLYMORPHIC
+ TRUE
}
/**
@@ -80,13 +80,13 @@ public @interface ForyField {
* Controls polymorphism behavior for this field in cross-language
serialization.
*
* <ul>
- * <li>{@link Morphic#AUTO} (default): Interface/abstract types are
polymorphic, concrete types
- * are final
- * <li>{@link Morphic#FINAL}: No type info written, uses declared type's
serializer
- * <li>{@link Morphic#POLYMORPHIC}: Type info written to support runtime
subtypes
+ * <li>{@link Dynamic#AUTO} (default): Interface/abstract types are
dynamic(polymorphic),
+ * concrete types are not dynamic
+ * <li>{@link Dynamic#FALSE}: No type info written, uses declared type's
serializer
+ * <li>{@link Dynamic#TRUE}: Type info written to support runtime subtypes
* </ul>
*
* <p>Default: AUTO (concrete struct types are final, interface/abstract are
polymorphic)
*/
- Morphic morphic() default Morphic.AUTO;
+ Dynamic dynamic() default Dynamic.AUTO;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index f5307c0c4..fe6c0f121 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -685,10 +685,10 @@ public class ClassResolver extends TypeResolver {
public boolean isMonomorphic(Descriptor descriptor) {
ForyField foryField = descriptor.getForyField();
if (foryField != null) {
- switch (foryField.morphic()) {
- case POLYMORPHIC:
+ switch (foryField.dynamic()) {
+ case TRUE:
return false;
- case FINAL:
+ case FALSE:
return true;
default:
return isMonomorphic(descriptor.getRawType());
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index a4a4eab4b..79983798c 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -438,11 +438,11 @@ public class XtypeResolver extends TypeResolver {
@Override
public boolean isMonomorphic(Descriptor descriptor) {
ForyField foryField = descriptor.getForyField();
- ForyField.Morphic morphic = foryField != null ? foryField.morphic() :
ForyField.Morphic.AUTO;
- switch (morphic) {
- case POLYMORPHIC:
+ ForyField.Dynamic dynamic = foryField != null ? foryField.dynamic() :
ForyField.Dynamic.AUTO;
+ switch (dynamic) {
+ case TRUE:
return false;
- case FINAL:
+ case FALSE:
return true;
default:
Class<?> rawType = descriptor.getRawType();
diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
index f41852e7c..a12a03ef8 100644
--- a/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/type/Descriptor.java
@@ -279,11 +279,11 @@ public class Descriptor {
*
* @return the morphic setting from @ForyField annotation, or AUTO if not
specified
*/
- public ForyField.Morphic getMorphic() {
+ public ForyField.Dynamic getMorphic() {
if (foryField != null) {
- return foryField.morphic();
+ return foryField.dynamic();
}
- return ForyField.Morphic.AUTO;
+ return ForyField.Dynamic.AUTO;
}
public Annotation getTypeAnnotation() {
diff --git
a/java/fory-core/src/test/java/org/apache/fory/xlang/XlangTestBase.java
b/java/fory-core/src/test/java/org/apache/fory/xlang/XlangTestBase.java
index 06217ad3e..31ee15caa 100644
--- a/java/fory-core/src/test/java/org/apache/fory/xlang/XlangTestBase.java
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/XlangTestBase.java
@@ -2190,10 +2190,10 @@ public abstract class XlangTestBase extends
ForyTestBase {
*/
@Data
static class RefOuterSchemaConsistent {
- @ForyField(ref = true, nullable = true, morphic = ForyField.Morphic.FINAL)
+ @ForyField(ref = true, nullable = true, dynamic = ForyField.Dynamic.FALSE)
RefInnerSchemaConsistent inner1;
- @ForyField(ref = true, nullable = true, morphic = ForyField.Morphic.FINAL)
+ @ForyField(ref = true, nullable = true, dynamic = ForyField.Dynamic.FALSE)
RefInnerSchemaConsistent inner2;
}
diff --git a/rust/fory-derive/src/object/util.rs
b/rust/fory-derive/src/object/util.rs
index 33a3bc56f..da9c41ec5 100644
--- a/rust/fory-derive/src/object/util.rs
+++ b/rust/fory-derive/src/object/util.rs
@@ -1491,7 +1491,7 @@ pub(crate) fn determine_field_ref_mode(field:
&syn::Field) -> FieldRefMode {
/// - List/Set/Map fields: skip type info
/// - Struct fields: WRITE type info
/// - Ext fields: WRITE type info
-/// - Enum fields: skip type info (enum is morphic)
+/// - Enum fields: skip type info (enum is dynamic)
///
/// Returns true if type info should be skipped, false if it should be written.
pub(crate) fn should_skip_type_info_for_field(ty: &Type) -> bool {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]