use std::fmt;
use quote::quote;
use quote::ToTokens;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use crate::traits::DocInfo;
use crate::traits::StripAttributes;
use crate::traits::ToDocInfo;
use crate::traits::ToMacroPattern;
use super::{ParamAttr, PermutedItem};
#[derive(Clone, Debug)]
pub struct StructFields {
#[allow(unused)]
pub ident: syn::Ident,
pub fields: Vec<StructField>,
}
#[derive(Clone)]
pub struct StructField {
pub vis: syn::Visibility,
pub attrs: Vec<syn::Attribute>,
pub ident: syn::Ident,
pub is_tuple: bool,
pub ty: syn::Type,
pub default_value: ParamAttr,
dot_dot: bool,
}
impl fmt::Debug for StructField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StructField")
.field("ident", &self.ident)
.finish()
}
}
impl PartialEq for StructField {
fn eq(&self, other: &Self) -> bool {
self.ident.to_token_stream().to_string() == other.ident.to_token_stream().to_string()
}
}
impl ToMacroPattern for PermutedItem<StructField> {
fn to_macro_pattern(&self) -> Option<proc_macro2::TokenStream> {
if self.inner().dot_dot {
return Some(quote! {..});
}
match self {
Self::Positional(StructField { ident, .. }) => {
let pat = syn::Ident::new(&format!("{}_val", ident), ident.span());
Some(quote! {$#pat: expr})
}
Self::Named(StructField { ident, .. }) => {
let pat = syn::Ident::new(&format!("{}_val", ident), ident.span());
Some(quote! {#ident: $#pat: expr})
}
Self::Default(StructField { default_value, .. }) => match default_value {
ParamAttr::None => unimplemented!("default value must be present"),
ParamAttr::Default | ParamAttr::Value(_) => None,
},
}
}
fn to_func_call_pattern(&self) -> proc_macro2::TokenStream {
if self.inner().dot_dot {
return quote! {};
}
match self {
PermutedItem::Positional(StructField {
ident, is_tuple, ..
}) => {
let pat = syn::Ident::new(&format!("{}_val", ident), ident.span());
match is_tuple {
true => quote! {$#pat},
false => quote! {#ident: $#pat},
}
}
PermutedItem::Named(StructField { ident, .. }) => {
let pat = syn::Ident::new(&format!("{}_val", ident), ident.span());
quote! {#ident: $#pat}
}
PermutedItem::Default(StructField {
ident,
is_tuple,
default_value,
..
}) => match (default_value, is_tuple) {
(ParamAttr::None, _) => unimplemented!("default value must be present"),
(ParamAttr::Default, true) => quote! {core::default::Default::default()},
(ParamAttr::Default, false) => quote! {#ident: core::default::Default::default()},
(ParamAttr::Value(expr), true) => quote! {#expr},
(ParamAttr::Value(expr), false) => quote! {#ident: #expr},
},
}
}
}
impl StripAttributes for StructFields {
type Original = syn::Fields;
fn strip_attributes(&self) -> Self::Original {
let x = self.fields.first().expect("at least one field expected");
let fields = self
.fields
.iter()
.map(|f| syn::Field {
attrs: f
.attrs
.iter()
.filter(|a| !a.path().is_ident(crate::DEFAULT_HELPER_ATTR))
.cloned()
.collect::<Vec<_>>(),
vis: f.vis.clone(),
mutability: syn::FieldMutability::None,
ident: if f.is_tuple {
None
} else {
Some(f.ident.clone())
},
colon_token: Default::default(),
ty: f.ty.clone(),
})
.collect();
match x.is_tuple {
true => syn::Fields::Unnamed(syn::FieldsUnnamed {
paren_token: Default::default(),
unnamed: fields,
}),
false => syn::Fields::Named(syn::FieldsNamed {
brace_token: Default::default(),
named: fields,
}),
}
}
}
impl ToDocInfo for StructField {
fn to_doc_info(&self) -> DocInfo {
DocInfo {
ident: self.ident.to_string(),
ty: self.ty.to_token_stream().to_string(),
default_value: match &self.default_value {
ParamAttr::None => None,
ParamAttr::Default => Some("Default::default()".to_string()),
ParamAttr::Value(expr) => Some(expr.to_token_stream().to_string()),
},
}
}
}
impl StructFields {
pub fn from_named(
ident: syn::Ident,
fields: Punctuated<syn::Field, syn::Token![,]>,
) -> Result<Self, syn::Error> {
let fields = fields
.into_iter()
.map(|f| StructField::from_field_type(f, None))
.collect::<Result<_, _>>()?;
Ok(Self { ident, fields })
}
pub fn from_unnamed(
ident: syn::Ident,
fields: Punctuated<syn::Field, syn::Token![,]>,
) -> Result<Self, syn::Error> {
let fields = fields
.into_iter()
.enumerate()
.map(|(idx, field)| StructField::from_field_type(field, Some(idx)))
.collect::<Result<_, _>>()?;
Ok(Self { ident, fields })
}
pub fn first_invalid(&self) -> Option<&StructField> {
let mut iter = self
.fields
.iter()
.skip_while(|f| matches!(f.default_value, ParamAttr::None));
iter.find(|f| matches!(f.default_value, ParamAttr::None))
}
}
impl StructField {
pub fn from_field_type(
field: syn::Field,
tuple_elem: Option<usize>,
) -> Result<Self, syn::Error> {
let mut default_value = ParamAttr::None;
if !field.attrs.is_empty() {
for attr in &field.attrs {
if attr.path().is_ident(crate::DEFAULT_HELPER_ATTR) {
let meta = attr.meta.clone();
match meta {
syn::Meta::Path(_) => default_value = ParamAttr::Default,
syn::Meta::List(l) => {
let l_span = l.span();
let first_item = l.tokens.into_iter().next().ok_or(syn::Error::new(
l_span,
"expected at least 1 item in metalist",
))?;
let e: syn::Expr = syn::parse2(first_item.to_token_stream())?;
default_value = ParamAttr::Value(e);
}
syn::Meta::NameValue(nv) => {
let e = syn::Error::new(
nv.span(),
format!("name-values are not supported. Use #[{}] or #[{}(CONST_EXPRESSION)] instead.",
crate::DEFAULT_HELPER_ATTR,
crate::DEFAULT_HELPER_ATTR
),
);
return Err(e);
}
}
break;
}
}
};
let res = match tuple_elem {
Some(mut tup_id) => {
let mut id = vec![];
if tup_id == 0 {
id.push(0);
}
while tup_id != 0 {
id.push(tup_id % 26);
tup_id /= 26;
}
let tup_ident = id
.into_iter()
.map(|i| char::from_u32(i as u32 + 97).unwrap())
.collect::<String>();
assert!(!tup_ident.is_empty());
Self {
vis: field.vis,
attrs: field.attrs,
ident: syn::Ident::new(&tup_ident, field.ty.span()),
is_tuple: true,
ty: field.ty,
default_value,
dot_dot: false,
}
}
None => Self {
vis: field.vis,
attrs: field.attrs,
ident: field.ident.expect("named field must have an identifier"),
is_tuple: false,
ty: field.ty,
default_value,
dot_dot: false,
},
};
Ok(res)
}
pub fn dot_dot() -> Self {
Self {
vis: syn::Visibility::Inherited,
attrs: vec![],
ident: syn::Ident::new(
"___DOT_DOT_NO_COLLISIONS_DOT_DOT___",
proc_macro2::Span::call_site(),
),
is_tuple: false,
ty: syn::parse_quote! {u8},
default_value: ParamAttr::None,
dot_dot: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
use quote::quote;
use syn::Ident;
#[test]
fn test_init_named_field() {
let default_attr = syn::Ident::new(crate::DEFAULT_HELPER_ATTR, Span::call_site());
let item_struct: syn::ItemStruct = syn::parse2(quote! {
struct Item {
pub x: i32,
#[#default_attr]
pub y: i32,
#[#default_attr(1)]
pub z: i32,
}
})
.unwrap();
let fields = match item_struct.fields {
syn::Fields::Named(fields_named) => fields_named,
syn::Fields::Unnamed(_) | syn::Fields::Unit => panic!("item must be named struct"),
};
let fields = StructFields::from_named(item_struct.ident, fields.named).unwrap();
let inner = fields.fields;
assert!(inner.len() == 3);
assert!(matches!(inner[0].default_value, ParamAttr::None));
assert!(matches!(inner[1].default_value, ParamAttr::Default));
assert!(matches!(inner[2].default_value, ParamAttr::Value(_)));
}
#[test]
fn test_init_unnamed_field() {
let default_attr = syn::Ident::new(crate::DEFAULT_HELPER_ATTR, Span::call_site());
let item_struct: syn::ItemStruct = syn::parse2(quote! {
struct Item(i32, #[#default_attr] i32, #[#default_attr(1)] i32);
})
.unwrap();
let fields = match item_struct.fields {
syn::Fields::Named(_) => panic!("expected unnamed fields"),
syn::Fields::Unnamed(fields_unnamed) => fields_unnamed,
syn::Fields::Unit => panic!("expected unnamed fields"),
};
let fields = StructFields::from_unnamed(item_struct.ident, fields.unnamed).unwrap();
let inner = fields.fields;
assert!(inner.len() == 3);
assert!(matches!(inner[0].default_value, ParamAttr::None));
assert!(matches!(inner[1].default_value, ParamAttr::Default));
assert!(matches!(inner[2].default_value, ParamAttr::Value(_)));
}
#[test]
fn test_first_invalid_param() {
let default_attr = syn::Ident::new(crate::DEFAULT_HELPER_ATTR, Span::call_site());
let item_struct = quote! {
struct Item {
pub x: i32,
#[#default_attr]
pub y: i32,
pub z: i32,
}
};
let item_struct: syn::ItemStruct = syn::parse2(item_struct).unwrap();
let fields = match item_struct.fields {
syn::Fields::Named(fields_named) => fields_named,
syn::Fields::Unnamed(_) | syn::Fields::Unit => panic!("item must be named struct"),
};
let fields = StructFields::from_named(item_struct.ident, fields.named).unwrap();
let first_invalid = fields.first_invalid();
println!("first invalid: {:?}", first_invalid);
match first_invalid {
Some(iv) => {
assert_eq!(*iv, fields.fields[2]);
}
None => panic!("last field must be invalid"),
}
}
#[test]
fn test() {
let x: Option<Ident> = None;
println!("ident: {}", quote! {#x});
}
}