blob: 1371e02cb8704c08d47c3613072b6b79a0c6c421 [file] [log] [blame]
// 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::TokenStream;
use quote::quote;
use std::sync::atomic::{AtomicU32, Ordering};
use syn::Field;
use super::field_meta::{
classify_field_type, extract_option_inner_type, is_option_type, parse_field_meta,
};
use super::util::{
classify_trait_object_field, generic_tree_to_tokens, get_filtered_source_fields_iter,
get_sort_fields_ts, parse_generic_tree, StructField,
};
use crate::util::SourceField;
// Global type ID counter that auto-grows from 0 at macro processing time
static TYPE_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
/// Allocates a new unique type ID at macro processing time
pub fn allocate_type_id() -> u32 {
TYPE_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
#[allow(dead_code)]
fn hash(fields: &[&Field]) -> TokenStream {
let props = fields.iter().enumerate().map(|(idx, field)| {
let ty = &field.ty;
let name = super::util::get_field_name(field, idx);
quote! {
(#name, <#ty as fory_core::serializer::Serializer>::fory_get_type_id())
}
});
quote! {
fn fory_hash() -> u32 {
use std::sync::Once;
static mut name_hash: u32 = 0u32;
static name_hash_once: Once = Once::new();
unsafe {
name_hash_once.call_once(|| {
name_hash = fory_core::types::compute_struct_hash(vec![#(#props),*]);
});
name_hash
}
}
}
}
pub fn gen_actual_type_id() -> TokenStream {
quote! {
fory_core::serializer::struct_::actual_type_id(type_id, register_by_name, compatible)
}
}
pub fn gen_get_sorted_field_names(fields: &[&Field]) -> TokenStream {
let static_field_names = get_sort_fields_ts(fields);
quote! {
#static_field_names
}
}
pub fn gen_field_fields_info(source_fields: &[SourceField<'_>]) -> TokenStream {
let field_infos = get_filtered_source_fields_iter(source_fields).map(|sf| {
let field = sf.field;
let ty = &field.ty;
let name = &sf.field_name;
// Parse field metadata for nullable/ref tracking and field ID
let meta = parse_field_meta(field).unwrap_or_default();
let type_class = classify_field_type(ty);
// For nullable, check both the classified type AND whether outer type is Option
// This handles Option<Rc<T>> correctly - classify_field_type returns Rc for ref_tracking,
// but we also need to detect that the outer wrapper is Option for nullable.
let is_outer_option = is_option_type(ty);
let nullable = meta.effective_nullable(type_class) || is_outer_option;
let ref_tracking = meta.effective_ref(type_class);
// Only use explicit field ID when user sets #[fory(id = N)]
// Otherwise use -1 to indicate field name encoding should be used
let field_id = if meta.uses_tag_id() {
meta.effective_id() as i16
} else {
-1i16 // Use field name encoding when no explicit ID
};
match classify_trait_object_field(ty) {
StructField::None => {
// Check if this is an i32/u32/u64 field (or Option<i32>/Option<u32>/Option<u64>) with encoding attributes
// In this case, we need to generate the FieldType with the correct type ID directly
let inner_ty = extract_option_inner_type(ty).unwrap_or_else(|| ty.clone());
let inner_ty_str = quote::ToTokens::to_token_stream(&inner_ty)
.to_string()
.replace(' ', "");
let has_encoding =
(inner_ty_str == "i32" || inner_ty_str == "u32" || inner_ty_str == "u64")
&& meta.type_id.is_some();
let has_array_override = matches!(
meta.type_id,
Some(tid)
if tid == fory_core::types::TypeId::INT8_ARRAY as i16
|| tid == fory_core::types::TypeId::UINT8_ARRAY as i16
) && (inner_ty_str == "Vec<u8>"
|| inner_ty_str == "Vec<i8>"
|| inner_ty_str.starts_with("[u8;")
|| inner_ty_str.starts_with("[i8;"));
if has_encoding {
// Generate FieldType directly with the correct type ID based on meta.type_id
let type_id_ts = match (inner_ty_str.as_str(), meta.type_id) {
// i32: VARINT32 (default) or INT32 (fixed)
("i32", Some(tid)) if tid == fory_core::types::TypeId::INT32 as i16 => {
quote! { fory_core::types::TypeId::INT32 as u32 }
}
("i32", _) => {
quote! { fory_core::types::TypeId::VARINT32 as u32 }
}
// u32: VAR_UINT32 (default) or UINT32 (fixed)
("u32", Some(tid)) if tid == fory_core::types::TypeId::INT32 as i16 => {
quote! { fory_core::types::TypeId::UINT32 as u32 }
}
("u32", _) => {
quote! { fory_core::types::TypeId::VAR_UINT32 as u32 }
}
// u64: VAR_UINT64 (default), UINT64 (fixed), or TAGGED_UINT64 (tagged)
("u64", Some(tid)) if tid == fory_core::types::TypeId::INT32 as i16 => {
quote! { fory_core::types::TypeId::UINT64 as u32 }
}
("u64", Some(tid))
if tid == fory_core::types::TypeId::TAGGED_UINT64 as i16 =>
{
quote! { fory_core::types::TypeId::TAGGED_UINT64 as u32 }
}
("u64", _) => {
quote! { fory_core::types::TypeId::VAR_UINT64 as u32 }
}
_ => unreachable!(),
};
quote! {
fory_core::meta::FieldInfo::new_with_id(
#field_id,
#name,
fory_core::meta::FieldType {
type_id: #type_id_ts,
nullable: #nullable,
ref_tracking: #ref_tracking,
generics: Vec::new()
}
)
}
} else if has_array_override {
let type_id_ts = match meta.type_id {
Some(tid) if tid == fory_core::types::TypeId::INT8_ARRAY as i16 => {
quote! { fory_core::types::TypeId::INT8_ARRAY as u32 }
}
Some(tid) if tid == fory_core::types::TypeId::UINT8_ARRAY as i16 => {
quote! { fory_core::types::TypeId::UINT8_ARRAY as u32 }
}
_ => unreachable!(),
};
quote! {
fory_core::meta::FieldInfo::new_with_id(
#field_id,
#name,
fory_core::meta::FieldType {
type_id: #type_id_ts,
nullable: #nullable,
ref_tracking: #ref_tracking,
generics: Vec::new()
}
)
}
} else {
let generic_tree = parse_generic_tree(ty);
let generic_token = generic_tree_to_tokens(&generic_tree);
quote! {
fory_core::meta::FieldInfo::new_with_id(
#field_id,
#name,
{
let mut ft = #generic_token;
ft.nullable = #nullable;
ft.ref_tracking = #ref_tracking;
ft
}
)
}
}
}
StructField::VecBox(_) | StructField::VecRc(_) | StructField::VecArc(_) => {
quote! {
fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::LIST as u32,
nullable: #nullable,
ref_tracking: #ref_tracking,
generics: vec![fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::UNKNOWN as u32,
nullable: false,
ref_tracking: false,
generics: Vec::new()
}]
})
}
}
StructField::HashMapBox(key_ty, _)
| StructField::HashMapRc(key_ty, _)
| StructField::HashMapArc(key_ty, _) => {
let key_generic_tree = parse_generic_tree(key_ty.as_ref());
let key_generic_token = generic_tree_to_tokens(&key_generic_tree);
quote! {
fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::MAP as u32,
nullable: #nullable,
ref_tracking: #ref_tracking,
generics: vec![
#key_generic_token,
fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::UNKNOWN as u32,
nullable: false,
ref_tracking: false,
generics: Vec::new()
}
]
})
}
}
_ => {
quote! {
fory_core::meta::FieldInfo::new_with_id(#field_id, #name, fory_core::meta::FieldType {
type_id: fory_core::types::TypeId::UNKNOWN as u32,
nullable: #nullable,
ref_tracking: #ref_tracking,
generics: Vec::new()
})
}
}
}
});
let fields: Vec<&Field> = source_fields.iter().map(|sf| sf.field).collect();
let static_field_names = get_sort_fields_ts(&fields);
quote! {
let mut field_infos: Vec<fory_core::meta::FieldInfo> = vec![#(#field_infos),*];
let sorted_field_names = #static_field_names;
fory_core::meta::sort_fields(&mut field_infos, sorted_field_names)?;
Ok(field_infos)
}
}