| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use proc_macro2::{Ident, TokenStream}; |
| use quote::{format_ident, quote}; |
| use syn::Field; |
| |
| use super::field_meta::{extract_option_inner_type, parse_field_meta}; |
| use super::util::{ |
| classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc, |
| determine_field_ref_mode, extract_type_name, gen_struct_version_hash_ts, |
| get_option_inner_primitive_name, get_primitive_reader_method_with_encoding, get_struct_name, |
| is_debug_enabled, is_direct_primitive_type, is_option_encoding_primitive, is_primitive_type, |
| is_skip_field, should_skip_type_info_for_field, FieldRefMode, StructField, |
| }; |
| use crate::util::SourceField; |
| |
| /// Check if a type is a primitive type that needs special compatible mode handling |
| /// Returns the type name if it's u8, u16, u32, or u64 (or Option<u8/u16/u32/u64>) |
| fn is_compatible_primitive_type(ty: &syn::Type) -> Option<&'static str> { |
| let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone()); |
| let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty) |
| .to_string() |
| .replace(' ', ""); |
| match inner_ty_str.as_str() { |
| "u8" => Some("u8"), |
| "u16" => Some("u16"), |
| "u32" => Some("u32"), |
| "u64" => Some("u64"), |
| _ => None, |
| } |
| } |
| |
| /// Check if a type is u32 or u64 (for encoding-aware reading) |
| fn is_unsigned_encoding_type(ty: &syn::Type) -> Option<&'static str> { |
| let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone()); |
| let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty) |
| .to_string() |
| .replace(' ', ""); |
| match inner_ty_str.as_str() { |
| "u32" => Some("u32"), |
| "u64" => Some("u64"), |
| _ => None, |
| } |
| } |
| |
| /// Generate compatible mode read code for u32/u64 fields based on remote type_id |
| fn gen_compatible_unsigned_read( |
| unsigned_type: &str, |
| var_name: &Ident, |
| is_option: bool, |
| ) -> TokenStream { |
| let read_value = if unsigned_type == "u32" { |
| quote! { |
| // Read u32 based on remote type_id |
| match _field.field_type.type_id { |
| fory_core::types::UINT32 => context.reader.read_u32()?, |
| fory_core::types::VAR_UINT32 => context.reader.read_varuint32()?, |
| _ => context.reader.read_varuint32()?, // Default to varint |
| } |
| } |
| } else { |
| // u64 |
| quote! { |
| // Read u64 based on remote type_id |
| match _field.field_type.type_id { |
| fory_core::types::UINT64 => context.reader.read_u64()?, |
| fory_core::types::VAR_UINT64 => context.reader.read_varuint64()?, |
| fory_core::types::TAGGED_UINT64 => context.reader.read_tagged_u64()?, |
| _ => context.reader.read_varuint64()?, // Default to varint |
| } |
| } |
| }; |
| |
| if is_option { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| if read_ref_flag { |
| let ref_flag = context.reader.read_i8()?; |
| if ref_flag == fory_core::RefFlag::Null as i8 { |
| #var_name = Some(None); |
| } else { |
| #var_name = Some(Some(#read_value)); |
| } |
| } else { |
| #var_name = Some(Some(#read_value)); |
| } |
| } |
| } else { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| if read_ref_flag { |
| let ref_flag = context.reader.read_i8()?; |
| if ref_flag == fory_core::RefFlag::Null as i8 { |
| // Remote sent null but local field is non-nullable, use default |
| #var_name = 0; |
| } else { |
| #var_name = #read_value; |
| } |
| } else { |
| #var_name = #read_value; |
| } |
| } |
| } |
| } |
| |
| /// Generate compatible mode read code for u8/u16 Option fields |
| /// These need special handling because when remote field is non-nullable, |
| /// Java sends just the raw bytes without a ref flag |
| fn gen_compatible_primitive_option_read(prim_type: &str, var_name: &Ident) -> TokenStream { |
| let read_value = match prim_type { |
| "u8" => quote! { context.reader.read_u8()? }, |
| "u16" => quote! { context.reader.read_u16()? }, |
| _ => unreachable!("Only u8/u16 should use this function"), |
| }; |
| |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| if read_ref_flag { |
| let ref_flag = context.reader.read_i8()?; |
| if ref_flag == fory_core::RefFlag::Null as i8 { |
| #var_name = Some(None); |
| } else { |
| #var_name = Some(Some(#read_value)); |
| } |
| } else { |
| // Remote field is non-nullable, read raw value directly |
| #var_name = Some(Some(#read_value)); |
| } |
| } |
| } |
| |
| /// Create a private variable name for a field during deserialization. |
| /// For named fields: `_field_name` |
| /// For tuple struct fields: `_0`, `_1`, etc. |
| pub(crate) fn create_private_field_name(field: &Field, index: usize) -> Ident { |
| match &field.ident { |
| Some(ident) => format_ident!("_{}", ident), |
| None => format_ident!("_{}", index), |
| } |
| } |
| |
| fn need_declared_by_option(field: &Field) -> bool { |
| let type_name = extract_type_name(&field.ty); |
| type_name == "Option" || !is_primitive_type(type_name.as_str()) |
| } |
| |
| pub(crate) fn declare_var(source_fields: &[SourceField<'_>]) -> Vec<TokenStream> { |
| source_fields |
| .iter() |
| .map(|sf| { |
| let field = sf.field; |
| let ty = &field.ty; |
| let var_name = create_private_field_name(field, sf.original_index); |
| match classify_trait_object_field(ty) { |
| StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => { |
| quote! { |
| let mut #var_name: #ty = <#ty as fory_core::serializer::ForyDefault>::fory_default(); |
| } |
| } |
| _ => { |
| if need_declared_by_option(field) { |
| quote! { |
| let mut #var_name: Option<#ty> = None; |
| } |
| } else if extract_type_name(&field.ty) == "bool" { |
| quote! { |
| let mut #var_name: bool = false; |
| } |
| } else { |
| quote! { |
| let mut #var_name: #ty = 0 as #ty; |
| } |
| } |
| } |
| } |
| }) |
| .collect() |
| } |
| |
| pub(crate) fn assign_value(source_fields: &[SourceField<'_>]) -> Vec<TokenStream> { |
| let is_tuple = source_fields |
| .first() |
| .map(|sf| sf.is_tuple_struct) |
| .unwrap_or(false); |
| |
| // Generate field value expressions with original index for sorting |
| let mut indexed: Vec<_> = source_fields |
| .iter() |
| .map(|sf| { |
| let var_name = create_private_field_name(sf.field, sf.original_index); |
| let value_expr = match classify_trait_object_field(&sf.field.ty) { |
| StructField::BoxDyn | StructField::RcDyn(_) | StructField::ArcDyn(_) => { |
| quote! { #var_name } |
| } |
| StructField::ContainsTraitObject => { |
| quote! { #var_name.unwrap() } |
| } |
| _ => { |
| if need_declared_by_option(sf.field) { |
| let ty = &sf.field.ty; |
| quote! { #var_name.unwrap_or_else(|| <#ty as fory_core::ForyDefault>::fory_default()) } |
| } else { |
| quote! { #var_name } |
| } |
| } |
| }; |
| (sf.original_index, sf.field_init(value_expr)) |
| }) |
| .collect(); |
| |
| // For tuple structs, sort by original index to construct Self(field0, field1, ...) correctly |
| if is_tuple { |
| indexed.sort_by_key(|(idx, _)| *idx); |
| } |
| |
| indexed.into_iter().map(|(_, ts)| ts).collect() |
| } |
| |
| pub fn gen_read_field(field: &Field, private_ident: &Ident, field_name: &str) -> TokenStream { |
| let ty = &field.ty; |
| if is_skip_field(field) { |
| return quote! { |
| let #private_ident = <#ty as fory_core::ForyDefault>::fory_default(); |
| }; |
| } |
| |
| // Determine RefMode from field meta (respects #[fory(ref=false)] attribute) |
| let ref_mode = determine_field_ref_mode(field); |
| let base = match classify_trait_object_field(ty) { |
| StructField::BoxDyn => { |
| // Box<dyn Trait> - respect field meta for ref mode |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| } |
| } |
| StructField::RcDyn(trait_name) => { |
| // Rc<dyn Trait> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| let #private_ident = std::rc::Rc::<dyn #trait_ident>::from(wrapper); |
| } |
| } |
| StructField::ArcDyn(trait_name) => { |
| // Arc<dyn Trait> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| let #private_ident = std::sync::Arc::<dyn #trait_ident>::from(wrapper); |
| } |
| } |
| StructField::VecRc(trait_name) => { |
| // Vec<Rc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| let #private_ident = wrapper_vec.into_iter() |
| .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w)) |
| .collect(); |
| } |
| } |
| StructField::VecArc(trait_name) => { |
| // Vec<Arc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| let #private_ident = wrapper_vec.into_iter() |
| .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w)) |
| .collect(); |
| } |
| } |
| StructField::VecBox(_) => { |
| // Vec<Box<dyn Any>> - respect field meta for ref mode |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| } |
| } |
| StructField::HashMapBox(_, _) => { |
| // HashMap<K, Box<dyn Any>> - respect field meta for ref mode |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| } |
| } |
| StructField::HashMapRc(key_ty, trait_name) => { |
| // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| let #private_ident = wrapper_map.into_iter() |
| .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v))) |
| .collect(); |
| } |
| } |
| StructField::HashMapArc(key_ty, trait_name) => { |
| // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| let #private_ident = wrapper_map.into_iter() |
| .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v))) |
| .collect(); |
| } |
| } |
| StructField::Forward => { |
| // Forward types (trait objects, forward references) - polymorphic, always need type info |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| } |
| } |
| _ => { |
| let skip_type_info = should_skip_type_info_for_field(ty); |
| let meta = parse_field_meta(field).unwrap_or_default(); |
| |
| // Check if this is Option<u32> or Option<u64> with encoding attributes |
| // These need special inline handling because the generic Option<T> serializer |
| // doesn't know about field-level encoding attributes. |
| if is_option_encoding_primitive(ty, &meta) { |
| let inner_name = get_option_inner_primitive_name(ty).unwrap(); |
| let reader_method = get_primitive_reader_method_with_encoding(inner_name, &meta); |
| let reader_ident = syn::Ident::new(reader_method, proc_macro2::Span::call_site()); |
| // For Option<primitive>, read null flag first, then value if not null |
| quote! { |
| let ref_flag = context.reader.read_i8()?; |
| let #private_ident = if ref_flag == fory_core::RefFlag::Null as i8 { |
| None |
| } else { |
| Some(context.reader.#reader_ident()?) |
| }; |
| } |
| } |
| // Check if this is a direct primitive type that can use direct reader calls |
| // Only apply when ref_mode is None (no ref tracking needed) |
| else if ref_mode == FieldRefMode::None && is_direct_primitive_type(ty) { |
| let type_name = extract_type_name(ty); |
| if type_name == "String" { |
| // String: call fory_read_data directly |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?; |
| } |
| } else { |
| // Numeric primitives: use direct buffer methods |
| // For u32/u64, consider encoding attributes |
| let reader_method = |
| get_primitive_reader_method_with_encoding(&type_name, &meta); |
| let reader_ident = |
| syn::Ident::new(reader_method, proc_macro2::Span::call_site()); |
| quote! { |
| let #private_ident = context.reader.#reader_ident()?; |
| } |
| } |
| } else if skip_type_info { |
| // Known types (primitives, strings, collections) - skip type info at compile time |
| if ref_mode == FieldRefMode::None { |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read_data(context)?; |
| } |
| } else { |
| quote! { |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| } |
| } |
| } else { |
| // Custom types (struct/enum/ext) - always need mode-dependent type info logic |
| // Determine read_type_info based on mode: |
| // - compatible=true: use need_to_write_type_for_field (struct types need type info) |
| // - compatible=false: use fory_is_polymorphic |
| // This applies regardless of ref_mode because Java always writes type info |
| // for struct-type fields in compatible mode, even for non-nullable fields. |
| quote! { |
| let read_type_info = if context.is_compatible() { |
| fory_core::types::need_to_write_type_for_field( |
| <#ty as fory_core::Serializer>::fory_static_type_id() |
| ) |
| } else { |
| <#ty as fory_core::Serializer>::fory_is_polymorphic() |
| }; |
| let #private_ident = <#ty as fory_core::Serializer>::fory_read(context, #ref_mode, read_type_info)?; |
| } |
| } |
| } |
| }; |
| |
| if is_debug_enabled() { |
| let struct_name = get_struct_name().expect("struct context not set"); |
| let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site()); |
| let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site()); |
| quote! { |
| fory_core::serializer::struct_::struct_before_read_field( |
| #struct_name_lit, |
| #field_name_lit, |
| context, |
| ); |
| #base |
| fory_core::serializer::struct_::struct_after_read_field( |
| #struct_name_lit, |
| #field_name_lit, |
| (&#private_ident) as &dyn std::any::Any, |
| context, |
| ); |
| } |
| } else { |
| base |
| } |
| } |
| |
| pub fn gen_read_type_info() -> TokenStream { |
| quote! { |
| fory_core::serializer::struct_::read_type_info::<Self>(context) |
| } |
| } |
| |
| fn get_source_fields_loop_ts(source_fields: &[SourceField<'_>]) -> TokenStream { |
| let read_fields_ts: Vec<_> = source_fields |
| .iter() |
| .map(|sf| { |
| let private_ident = create_private_field_name(sf.field, sf.original_index); |
| gen_read_field(sf.field, &private_ident, &sf.field_name) |
| }) |
| .collect(); |
| quote! { |
| #(#read_fields_ts)* |
| } |
| } |
| |
| pub fn gen_read_data(source_fields: &[SourceField<'_>]) -> TokenStream { |
| let fields: Vec<&Field> = source_fields.iter().map(|sf| sf.field).collect(); |
| // Generate runtime version hash computation that detects enum fields |
| let version_hash_ts = gen_struct_version_hash_ts(&fields); |
| let read_fields = if source_fields.is_empty() { |
| quote! {} |
| } else { |
| let loop_ts = get_source_fields_loop_ts(source_fields); |
| quote! { |
| #loop_ts |
| } |
| }; |
| |
| let is_tuple = source_fields |
| .first() |
| .map(|sf| sf.is_tuple_struct) |
| .unwrap_or(false); |
| |
| // Generate field initializations, sorted by original index for tuple structs |
| let mut indexed: Vec<_> = source_fields |
| .iter() |
| .map(|sf| { |
| let private_ident = create_private_field_name(sf.field, sf.original_index); |
| let value = quote! { #private_ident }; |
| (sf.original_index, sf.field_init(value)) |
| }) |
| .collect(); |
| |
| if is_tuple { |
| indexed.sort_by_key(|(idx, _)| *idx); |
| } |
| |
| let field_inits: Vec<_> = indexed.into_iter().map(|(_, ts)| ts).collect(); |
| let self_construction = crate::util::ok_self_construction(is_tuple, &field_inits); |
| |
| quote! { |
| // Read and check version hash when class version checking is enabled |
| if context.is_check_struct_version() { |
| let read_version = context.reader.read_i32()?; |
| let type_name = std::any::type_name::<Self>(); |
| let local_version: i32 = #version_hash_ts; |
| fory_core::meta::TypeMeta::check_struct_version(read_version, local_version, type_name)?; |
| } |
| #read_fields |
| #self_construction |
| } |
| } |
| |
| pub(crate) fn gen_read_compatible_match_arm_body( |
| field: &Field, |
| var_name: &Ident, |
| field_name: &str, |
| ) -> TokenStream { |
| let ty = &field.ty; |
| let field_kind = classify_trait_object_field(ty); |
| let is_skip_flag = is_skip_field(field); |
| // Determine RefMode from field meta (respects #[fory(ref=false)] attribute) |
| let ref_mode = determine_field_ref_mode(field); |
| |
| let base = if is_skip_flag { |
| match field_kind { |
| StructField::None => { |
| let dec_by_option = need_declared_by_option(field); |
| if dec_by_option { |
| quote! { |
| #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default()); |
| } |
| } else { |
| quote! { |
| #var_name = <#ty as fory_core::ForyDefault>::fory_default(); |
| } |
| } |
| } |
| _ => { |
| quote! { |
| #var_name = Some(<#ty as fory_core::ForyDefault>::fory_default()); |
| } |
| } |
| } |
| } else { |
| match field_kind { |
| StructField::BoxDyn => { |
| // Box<dyn Trait> - respect field meta for ref mode |
| quote! { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?); |
| } |
| } |
| StructField::RcDyn(trait_name) => { |
| // Rc<dyn Trait> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| #var_name = Some(std::rc::Rc::<dyn #trait_ident>::from(wrapper)); |
| } |
| } |
| StructField::ArcDyn(trait_name) => { |
| // Arc<dyn Trait> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper = <#wrapper_ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?; |
| #var_name = Some(std::sync::Arc::<dyn #trait_ident>::from(wrapper)); |
| } |
| } |
| StructField::VecRc(trait_name) => { |
| // Vec<Rc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| #var_name = Some(wrapper_vec.into_iter() |
| .map(|w| std::rc::Rc::<dyn #trait_ident>::from(w)) |
| .collect()); |
| } |
| } |
| StructField::VecArc(trait_name) => { |
| // Vec<Arc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_vec = <Vec<#wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| #var_name = Some(wrapper_vec.into_iter() |
| .map(|w| std::sync::Arc::<dyn #trait_ident>::from(w)) |
| .collect()); |
| } |
| } |
| StructField::VecBox(_) => { |
| // Vec<Box<dyn Any>> uses standard Vec deserialization with polymorphic elements |
| // Check nullable and ref_tracking flags from remote field info |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| if read_ref_flag || _field.field_type.ref_tracking { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?); |
| } else { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?); |
| } |
| } |
| } |
| StructField::HashMapBox(_, _) => { |
| // HashMap<K, Box<dyn Any>> uses standard HashMap deserialization with polymorphic values |
| // Check nullable and ref_tracking flags from remote field info |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| if read_ref_flag || _field.field_type.ref_tracking { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?); |
| } else { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?); |
| } |
| } |
| } |
| StructField::HashMapRc(key_ty, trait_name) => { |
| // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_rc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| #var_name = Some(wrapper_map.into_iter() |
| .map(|(k, v)| (k, std::rc::Rc::<dyn #trait_ident>::from(v))) |
| .collect()); |
| } |
| } |
| StructField::HashMapArc(key_ty, trait_name) => { |
| // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode |
| let types = create_wrapper_types_arc(&trait_name); |
| let wrapper_ty = types.wrapper_ty; |
| let trait_ident = types.trait_ident; |
| quote! { |
| let wrapper_map = <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_read(context, #ref_mode, false)?; |
| #var_name = Some(wrapper_map.into_iter() |
| .map(|(k, v)| (k, std::sync::Arc::<dyn #trait_ident>::from(v))) |
| .collect()); |
| } |
| } |
| StructField::ContainsTraitObject => { |
| // Struct containing trait objects - respect field meta for ref mode |
| quote! { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, #ref_mode, true)?); |
| } |
| } |
| StructField::Forward => { |
| // Forward types (trait objects, forward references) - polymorphic, always need type info |
| // Use remote field's ref_tracking flag for ref_mode |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| // Forward types are polymorphic, always read type info |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, true)?); |
| } |
| } |
| StructField::None => { |
| let skip_type_info = should_skip_type_info_for_field(ty); |
| let dec_by_option = need_declared_by_option(field); |
| let is_option_type = extract_option_inner_type(ty).is_some(); |
| |
| // Check if this is a u32/u64 field that needs encoding-aware reading |
| if let Some(unsigned_type) = is_unsigned_encoding_type(ty) { |
| gen_compatible_unsigned_read( |
| unsigned_type, |
| var_name, |
| is_option_type || dec_by_option, |
| ) |
| } else if is_option_type { |
| // Check if it's Option<u8> or Option<u16> which need special handling |
| if let Some(prim_type) = is_compatible_primitive_type(ty) { |
| if prim_type == "u8" || prim_type == "u16" { |
| gen_compatible_primitive_option_read(prim_type, var_name) |
| } else { |
| // u32/u64 handled above |
| unreachable!() |
| } |
| } else if skip_type_info { |
| // Non-primitive Option type with skip_type_info |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| if read_ref_flag || _field.field_type.ref_tracking { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?); |
| } else { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?); |
| } |
| } |
| } else { |
| // Non-primitive Option type without skip_type_info |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| // For ref-tracked struct types, Java writes type info after RefValue flag |
| let read_type_info = fory_core::types::need_to_write_type_for_field( |
| <#ty as fory_core::Serializer>::fory_static_type_id() |
| ); |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?); |
| } |
| } |
| } else if skip_type_info { |
| if dec_by_option { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| if read_ref_flag || _field.field_type.ref_tracking { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?); |
| } else { |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read_data(context)?); |
| } |
| } |
| } else { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| if read_ref_flag || _field.field_type.ref_tracking { |
| #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, false)?; |
| } else { |
| #var_name = <#ty as fory_core::Serializer>::fory_read_data(context)?; |
| } |
| } |
| } |
| } else if dec_by_option { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| // For ref-tracked struct types, Java writes type info after RefValue flag |
| let read_type_info = fory_core::types::need_to_write_type_for_field( |
| <#ty as fory_core::Serializer>::fory_static_type_id() |
| ); |
| #var_name = Some(<#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?); |
| } |
| } else { |
| quote! { |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| _field.field_type.type_id, |
| _field.field_type.nullable, |
| ); |
| // Use RefMode::Tracking if remote field has ref_tracking enabled |
| let ref_mode = if _field.field_type.ref_tracking { |
| fory_core::RefMode::Tracking |
| } else if read_ref_flag { |
| fory_core::RefMode::NullOnly |
| } else { |
| fory_core::RefMode::None |
| }; |
| // For ref-tracked struct types, Java writes type info after RefValue flag |
| let read_type_info = fory_core::types::need_to_write_type_for_field( |
| <#ty as fory_core::Serializer>::fory_static_type_id() |
| ); |
| #var_name = <#ty as fory_core::Serializer>::fory_read(context, ref_mode, read_type_info)?; |
| } |
| } |
| } |
| } |
| }; |
| |
| if is_debug_enabled() { |
| let struct_name = get_struct_name().expect("struct context not set"); |
| let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site()); |
| let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site()); |
| quote! { |
| fory_core::serializer::struct_::struct_before_read_field( |
| #struct_name_lit, |
| #field_name_lit, |
| context, |
| ); |
| #base |
| fory_core::serializer::struct_::struct_after_read_field( |
| #struct_name_lit, |
| #field_name_lit, |
| (&#var_name) as &dyn std::any::Any, |
| context, |
| ); |
| } |
| } else { |
| quote! { |
| #base |
| } |
| } |
| } |
| |
| pub fn gen_read(_struct_ident: &Ident) -> TokenStream { |
| // Note: We use `Self` instead of `#struct_ident` to correctly handle generic types. |
| // When the struct has generics (e.g., LeaderId<C>), using `Self` ensures the full |
| // type with generics is used in the impl block. |
| quote! { |
| let ref_flag = if ref_mode != fory_core::RefMode::None { |
| context.reader.read_i8()? |
| } else { |
| fory_core::RefFlag::NotNullValue as i8 |
| }; |
| if ref_flag == (fory_core::RefFlag::NotNullValue as i8) || ref_flag == (fory_core::RefFlag::RefValue as i8) { |
| // For RefValueFlag with Tracking mode, reserve a ref_id to participate in ref tracking. |
| // This is needed for xlang compatibility where all objects (not just Rc/Arc) |
| // participate in reference tracking when ref tracking is enabled. |
| // Only reserve for Tracking mode, not NullOnly mode. |
| if ref_flag == (fory_core::RefFlag::RefValue as i8) && ref_mode == fory_core::RefMode::Tracking { |
| context.ref_reader.reserve_ref_id(); |
| } |
| if context.is_compatible() { |
| let type_info = if read_type_info { |
| context.read_any_typeinfo()? |
| } else { |
| let rs_type_id = std::any::TypeId::of::<Self>(); |
| context.get_type_info(&rs_type_id)? |
| }; |
| <Self as fory_core::StructSerializer>::fory_read_compatible(context, type_info) |
| } else { |
| if read_type_info { |
| <Self as fory_core::Serializer>::fory_read_type_info(context)?; |
| } |
| <Self as fory_core::Serializer>::fory_read_data(context) |
| } |
| } else if ref_flag == (fory_core::RefFlag::Null as i8) { |
| Ok(<Self as fory_core::ForyDefault>::fory_default()) |
| } else { |
| Err(fory_core::error::Error::invalid_ref(format!("Unknown ref flag, value:{ref_flag}"))) |
| } |
| } |
| } |
| |
| pub fn gen_read_with_type_info() -> TokenStream { |
| // fn fory_read_with_type_info( |
| // context: &mut ReadContext, |
| // ref_mode: RefMode, |
| // type_info: Rc<TypeInfo>, |
| // ) -> Result<Self, Error> |
| // Note: We use `Self` instead of `#struct_ident` to correctly handle generic types. |
| quote! { |
| let ref_flag = if ref_mode != fory_core::RefMode::None { |
| context.reader.read_i8()? |
| } else { |
| fory_core::RefFlag::NotNullValue as i8 |
| }; |
| if ref_flag == (fory_core::RefFlag::NotNullValue as i8) || ref_flag == (fory_core::RefFlag::RefValue as i8) { |
| if context.is_compatible() { |
| <Self as fory_core::StructSerializer>::fory_read_compatible(context, type_info) |
| } else { |
| <Self as fory_core::Serializer>::fory_read_data(context) |
| } |
| } else if ref_flag == (fory_core::RefFlag::Null as i8) { |
| Ok(<Self as fory_core::ForyDefault>::fory_default()) |
| } else { |
| Err(fory_core::error::Error::invalid_ref(format!("Unknown ref flag, value:{ref_flag}"))) |
| } |
| } |
| } |
| |
| pub fn gen_read_compatible(source_fields: &[SourceField<'_>]) -> TokenStream { |
| gen_read_compatible_with_construction(source_fields, None) |
| } |
| |
| pub(crate) fn gen_read_compatible_with_construction( |
| source_fields: &[SourceField<'_>], |
| variant_ident: Option<&Ident>, |
| ) -> TokenStream { |
| let declare_ts: Vec<TokenStream> = declare_var(source_fields); |
| let assign_ts: Vec<TokenStream> = assign_value(source_fields); |
| |
| let match_arms: Vec<TokenStream> = source_fields |
| .iter() |
| .enumerate() |
| .map(|(sorted_idx, sf)| { |
| let var_name = create_private_field_name(sf.field, sf.original_index); |
| // Use sorted index for matching. The assign_field_ids function assigns |
| // the sorted index to matched fields after lookup (by ID or name). |
| // Fields that don't match or have type mismatches get field_id = -1. |
| let field_id = sorted_idx as i16; |
| let body = gen_read_compatible_match_arm_body(sf.field, &var_name, &sf.field_name); |
| quote! { |
| #field_id => { |
| #body |
| } |
| } |
| }) |
| .collect(); |
| |
| let skip_arm = if is_debug_enabled() { |
| let struct_name = get_struct_name().expect("struct context not set"); |
| let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site()); |
| quote! { |
| _ => { |
| let field_type = &_field.field_type; |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| field_type.type_id, |
| field_type.nullable, |
| ); |
| let field_name = _field.field_name.as_str(); |
| fory_core::serializer::struct_::struct_before_read_field( |
| #struct_name_lit, |
| field_name, |
| context, |
| ); |
| fory_core::serializer::skip::skip_field_value(context, &field_type, read_ref_flag)?; |
| let placeholder: &dyn std::any::Any = &(); |
| fory_core::serializer::struct_::struct_after_read_field( |
| #struct_name_lit, |
| field_name, |
| placeholder, |
| context, |
| ); |
| } |
| } |
| } else { |
| quote! { |
| _ => { |
| let field_type = &_field.field_type; |
| let read_ref_flag = fory_core::serializer::util::field_need_write_ref_into( |
| field_type.type_id, |
| field_type.nullable, |
| ); |
| fory_core::serializer::skip::skip_field_value(context, field_type, read_ref_flag)?; |
| } |
| } |
| }; |
| |
| // Generate construction based on whether this is a struct or enum variant |
| let is_tuple = source_fields |
| .first() |
| .map(|sf| sf.is_tuple_struct) |
| .unwrap_or(false); |
| |
| let construction = if let Some(variant) = variant_ident { |
| // Enum variants use named syntax (struct variants) or tuple syntax (tuple variants) |
| quote! { |
| Ok(Self::#variant { |
| #(#assign_ts),* |
| }) |
| } |
| } else { |
| crate::util::ok_self_construction(is_tuple, &assign_ts) |
| }; |
| |
| quote! { |
| let fields = type_info.get_type_meta().get_field_infos().clone(); |
| #(#declare_ts)* |
| let meta = context.get_type_info(&std::any::TypeId::of::<Self>())?.get_type_meta(); |
| let local_type_hash = meta.get_hash(); |
| let remote_type_hash = type_info.get_type_meta().get_hash(); |
| if remote_type_hash == local_type_hash { |
| <Self as fory_core::Serializer>::fory_read_data(context) |
| } else { |
| for _field in fields.iter() { |
| match _field.field_id { |
| #(#match_arms)* |
| #skip_arm |
| } |
| } |
| #construction |
| } |
| } |
| } |