use super::ParseMacroInput;
use crate::ast_types;
use crate::ast_types::AstRecordField;
use crate::ast_types::AstRecordFields;
use crate::ast_types::FCEAst;
use crate::syn_error;
use crate::parsed_type::ParsedType;
use syn::Result;
use syn::spanned::Spanned;
impl ParseMacroInput for syn::ItemStruct {
fn parse_macro_input(self) -> Result<FCEAst> {
check_record(&self)?;
let fields = match &self.fields {
syn::Fields::Named(named_fields) => &named_fields.named,
_ => return syn_error!(self.span(), "only named fields are allowed in structs"),
};
let fields = fields_into_ast(fields)?;
let fields = AstRecordFields::Named(fields);
let name = self.ident.to_string();
let ast_record_item = ast_types::AstRecord {
name,
fields,
original: self,
};
Ok(FCEAst::Record(ast_record_item))
}
}
fn check_record(record: &syn::ItemStruct) -> Result<()> {
if record.generics.lt_token.is_some()
|| record.generics.gt_token.is_some()
|| record.generics.where_clause.is_some()
{
return syn_error!(
record.span(),
"#[fce] couldn't be applied to a struct with generics or lifetimes"
);
}
Ok(())
}
fn fields_into_ast(
fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
) -> Result<Vec<AstRecordField>> {
fields
.iter()
.map(|field| {
check_field(field)?;
let name = field.ident.as_ref().map(|ident| {
ident
.to_string()
.split(' ')
.last()
.unwrap_or_default()
.to_string()
});
let ty = ParsedType::from_type(&field.ty)?;
let record_field = AstRecordField { name, ty };
Ok(record_field)
})
.collect::<Result<Vec<_>>>()
}
fn check_field(field: &syn::Field) -> Result<()> {
match field.vis {
syn::Visibility::Public(_) => {}
_ => {
return syn_error!(
field.span(),
"#[fce] could be applied only to struct with all public fields"
)
}
};
const DOC_ATTR_NAME: &str = "doc";
let is_all_attrs_public = field.attrs.iter().all(|attr| {
let meta = match attr.parse_meta() {
Ok(meta) => meta,
Err(_) => return false,
};
meta.path().is_ident(DOC_ATTR_NAME)
});
if !is_all_attrs_public {
return syn_error!(field.span(), "field attributes isn't allowed");
}
Ok(())
}