use syn::{Field, Type};
use crate::common::{MacroError, MacroResult, utils};
#[derive(Debug, Clone, PartialEq)]
pub struct FieldTypeInfo {
pub original_type: String,
pub simple_name: String,
pub is_optional: bool,
pub inner_type: Option<Box<FieldTypeInfo>>,
pub is_supported: bool,
}
#[derive(Debug, Clone)]
pub struct FieldAnalysis {
pub name: String,
pub type_info: FieldTypeInfo,
pub is_marked_as_attr: bool,
pub attributes: Vec<String>,
pub original_field: Field,
}
pub struct FieldAnalyzer;
impl FieldAnalyzer {
pub fn analyze_field_type(field_type: &Type) -> FieldTypeInfo {
let original_type = quote::quote! { #field_type }.to_string();
let simple_name = utils::extract_type_name(field_type);
if utils::is_option_type(field_type) {
if let Some(inner_type) =
utils::extract_option_inner_type(field_type)
{
let inner_info = Self::analyze_field_type(inner_type);
FieldTypeInfo {
original_type,
simple_name,
is_optional: true,
inner_type: Some(Box::new(inner_info.clone())),
is_supported: inner_info.is_supported, }
} else {
FieldTypeInfo {
original_type,
simple_name,
is_optional: true,
inner_type: None,
is_supported: false,
}
}
} else {
let is_supported = utils::is_supported_basic_type(field_type);
FieldTypeInfo {
original_type,
simple_name,
is_optional: false,
inner_type: None,
is_supported,
}
}
}
pub fn analyze_field(field: &Field) -> MacroResult<FieldAnalysis> {
let field_name = field
.ident
.as_ref()
.ok_or_else(|| {
MacroError::parse_error("字段缺少名称(不支持匿名字段)", field)
})?
.to_string();
let type_info = Self::analyze_field_type(&field.ty);
let (is_marked_as_attr, attributes) =
Self::analyze_field_attributes(field)?;
Ok(FieldAnalysis {
name: field_name,
type_info,
is_marked_as_attr,
attributes,
original_field: field.clone(),
})
}
pub fn analyze_fields(fields: &[Field]) -> MacroResult<Vec<FieldAnalysis>> {
let mut analyses = Vec::with_capacity(fields.len());
for field in fields {
let analysis = Self::analyze_field(field)?;
analyses.push(analysis);
}
Ok(analyses)
}
pub fn filter_attr_fields(
analyses: &[FieldAnalysis]
) -> Vec<&FieldAnalysis> {
analyses.iter().filter(|analysis| analysis.is_marked_as_attr).collect()
}
pub fn validate_field_type_support(
analysis: &FieldAnalysis
) -> MacroResult<()> {
if !analysis.type_info.is_supported {
return Err(MacroError::unsupported_field_type(
&analysis.name,
&analysis.type_info.simple_name,
&analysis.original_field,
));
}
if analysis.type_info.is_optional {
if let Some(inner_type) = &analysis.type_info.inner_type {
if !inner_type.is_supported {
return Err(MacroError::unsupported_field_type(
&analysis.name,
&inner_type.simple_name,
&analysis.original_field,
));
}
}
}
Ok(())
}
pub fn validate_all_field_types(
analyses: &[FieldAnalysis]
) -> MacroResult<()> {
for analysis in analyses {
Self::validate_field_type_support(analysis)?;
}
Ok(())
}
fn analyze_field_attributes(
field: &Field
) -> MacroResult<(bool, Vec<String>)> {
let mut is_marked_as_attr = false;
let mut attributes = Vec::new();
for attr in &field.attrs {
if let Some(ident) = attr.path().get_ident() {
let attr_name = ident.to_string();
attributes.push(attr_name.clone());
if attr_name == "attr" {
is_marked_as_attr = true;
Self::validate_attr_attribute(attr)?;
}
}
}
Ok((is_marked_as_attr, attributes))
}
fn validate_attr_attribute(attr: &syn::Attribute) -> MacroResult<()> {
match &attr.meta {
syn::Meta::Path(_) => {
Ok(())
},
syn::Meta::List(_) => {
Err(MacroError::parse_error(
"#[attr] 不支持参数,请使用简单的 #[attr] 标记",
attr,
))
},
syn::Meta::NameValue(_) => {
Err(MacroError::parse_error(
"#[attr] 不支持值赋值,请使用简单的 #[attr] 标记",
attr,
))
},
}
}
}
impl FieldTypeInfo {
pub fn codegen_type_name(&self) -> &str {
&self.simple_name
}
pub fn base_type_name(&self) -> &str {
if self.is_optional {
if let Some(inner_type) = &self.inner_type {
inner_type.base_type_name()
} else {
&self.simple_name
}
} else {
&self.simple_name
}
}
pub fn is_string_type(&self) -> bool {
let base_name = self.base_type_name();
matches!(base_name, "String" | "str" | "&str")
}
pub fn is_numeric_type(&self) -> bool {
let base_name = self.base_type_name();
matches!(
base_name,
"i8" | "i16"
| "i32"
| "i64"
| "i128"
| "isize"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
| "f32"
| "f64"
)
}
}
impl FieldAnalysis {
pub fn validate_as_attribute(&self) -> MacroResult<()> {
if !self.is_marked_as_attr {
return Err(MacroError::validation_error(
&format!("字段 '{}' 没有 #[attr] 标记", self.name),
&self.original_field,
));
}
if !utils::is_valid_identifier(&self.name) {
return Err(MacroError::validation_error(
&format!("字段名称 '{}' 不是有效的标识符", self.name),
&self.original_field,
));
}
FieldAnalyzer::validate_field_type_support(self)?;
Ok(())
}
pub fn display_info(&self) -> String {
format!(
"字段 '{}': {} ({}{}{})",
self.name,
self.type_info.simple_name,
if self.type_info.is_optional { "可选, " } else { "必需, " },
if self.is_marked_as_attr {
"带属性标记, "
} else {
"无属性标记, "
},
if self.type_info.is_supported { "支持" } else { "不支持" }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_analyze_basic_field_type() {
let field_type: Type = parse_quote! { String };
let type_info = FieldAnalyzer::analyze_field_type(&field_type);
assert_eq!(type_info.simple_name, "String");
assert!(!type_info.is_optional);
assert!(type_info.is_supported);
assert!(type_info.inner_type.is_none());
}
#[test]
fn test_analyze_option_field_type() {
let field_type: Type = parse_quote! { Option<i32> };
let type_info = FieldAnalyzer::analyze_field_type(&field_type);
assert_eq!(type_info.simple_name, "Option<i32>");
assert!(type_info.is_optional);
assert!(type_info.is_supported);
assert!(type_info.inner_type.is_some());
let inner_type = type_info.inner_type.unwrap();
assert_eq!(inner_type.simple_name, "i32");
assert!(!inner_type.is_optional);
assert!(inner_type.is_supported);
}
#[test]
fn test_analyze_unsupported_field_type() {
let field_type: Type = parse_quote! { Vec<String> };
let type_info = FieldAnalyzer::analyze_field_type(&field_type);
assert!(!type_info.is_supported);
assert!(!type_info.is_optional);
}
#[test]
fn test_analyze_complete_field() {
let field: Field = parse_quote! {
#[attr]
name: String
};
let result = FieldAnalyzer::analyze_field(&field);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.name, "name");
assert!(analysis.is_marked_as_attr);
assert!(analysis.type_info.is_supported);
assert!(!analysis.type_info.is_optional);
assert!(analysis.attributes.contains(&"attr".to_string()));
}
#[test]
fn test_analyze_field_without_attr() {
let field: Field = parse_quote! {
name: String
};
let result = FieldAnalyzer::analyze_field(&field);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.name, "name");
assert!(!analysis.is_marked_as_attr);
assert!(analysis.attributes.is_empty());
}
#[test]
fn test_analyze_multiple_fields() {
let fields: Vec<Field> = vec![
parse_quote! {
#[attr]
name: String
},
parse_quote! {
#[attr]
age: Option<i32>
},
parse_quote! {
description: String
},
];
let result = FieldAnalyzer::analyze_fields(&fields);
assert!(result.is_ok());
let analyses = result.unwrap();
assert_eq!(analyses.len(), 3);
assert_eq!(analyses[0].name, "name");
assert!(analyses[0].is_marked_as_attr);
assert_eq!(analyses[1].name, "age");
assert!(analyses[1].is_marked_as_attr);
assert!(analyses[1].type_info.is_optional);
assert_eq!(analyses[2].name, "description");
assert!(!analyses[2].is_marked_as_attr);
}
#[test]
fn test_filter_attr_fields() {
let fields: Vec<Field> = vec![
parse_quote! {
#[attr]
name: String
},
parse_quote! {
#[attr]
age: Option<i32>
},
parse_quote! {
description: String
},
];
let analyses = FieldAnalyzer::analyze_fields(&fields).unwrap();
let attr_fields = FieldAnalyzer::filter_attr_fields(&analyses);
assert_eq!(attr_fields.len(), 2);
assert_eq!(attr_fields[0].name, "name");
assert_eq!(attr_fields[1].name, "age");
}
#[test]
fn test_validate_field_type_support() {
let field: Field = parse_quote! {
#[attr]
name: String
};
let analysis = FieldAnalyzer::analyze_field(&field).unwrap();
assert!(FieldAnalyzer::validate_field_type_support(&analysis).is_ok());
let field: Field = parse_quote! {
#[attr]
data: Vec<String>
};
let analysis = FieldAnalyzer::analyze_field(&field).unwrap();
assert!(FieldAnalyzer::validate_field_type_support(&analysis).is_err());
}
#[test]
fn test_field_type_info_helpers() {
let string_type: Type = parse_quote! { String };
let type_info = FieldAnalyzer::analyze_field_type(&string_type);
assert!(type_info.is_string_type());
assert!(!type_info.is_numeric_type());
let numeric_type: Type = parse_quote! { i32 };
let type_info = FieldAnalyzer::analyze_field_type(&numeric_type);
assert!(!type_info.is_string_type());
assert!(type_info.is_numeric_type());
let option_string: Type = parse_quote! { Option<String> };
let type_info = FieldAnalyzer::analyze_field_type(&option_string);
assert!(type_info.is_string_type());
assert!(!type_info.is_numeric_type());
assert_eq!(type_info.base_type_name(), "String");
}
#[test]
fn test_field_attribute_validation() {
let field: Field = parse_quote! {
#[attr]
name: String
};
let analysis = FieldAnalyzer::analyze_field(&field).unwrap();
assert!(analysis.validate_as_attribute().is_ok());
let field: Field = parse_quote! {
name: String
};
let analysis = FieldAnalyzer::analyze_field(&field).unwrap();
assert!(analysis.validate_as_attribute().is_err());
}
#[test]
fn test_field_display_info() {
let field: Field = parse_quote! {
#[attr]
name: Option<String>
};
let analysis = FieldAnalyzer::analyze_field(&field).unwrap();
let display = analysis.display_info();
assert!(display.contains("name"));
assert!(display.contains("Option<String>"));
assert!(display.contains("可选"));
assert!(display.contains("带属性标记"));
assert!(display.contains("支持"));
}
#[test]
fn test_invalid_attr_attribute_formats() {
let field: Field = parse_quote! {
#[attr]
name: String
};
let result = FieldAnalyzer::analyze_field(&field);
assert!(result.is_ok());
let analysis = result.unwrap();
assert!(analysis.is_marked_as_attr);
}
}