use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use crate::input::{Field, Fields, VariantData};
pub struct BoundField<'a> {
pub field: &'a Field,
pub binding: Ident
}
pub fn bind_struct_fields<'a>(
ident: &Ident,
fields: &'a Fields
) -> (TokenStream, Vec<BoundField<'a>>) {
match fields {
Fields::Unit => (quote!(let _ = value;), Vec::new()),
Fields::Named(list) => {
let mut pattern = Vec::new();
let mut bound = Vec::new();
for field in list {
let binding = binding_ident(field);
let pattern_binding = binding.clone();
pattern.push(quote!(#pattern_binding));
bound.push(BoundField {
field,
binding
});
}
let pattern_tokens = quote!(let #ident { #(#pattern),* } = value;);
(pattern_tokens, bound)
}
Fields::Unnamed(list) => {
let mut pattern = Vec::new();
let mut bound = Vec::new();
for field in list {
let binding = binding_ident(field);
let pattern_binding = binding.clone();
pattern.push(quote!(#pattern_binding));
bound.push(BoundField {
field,
binding
});
}
let pattern_tokens = quote!(let #ident(#(#pattern),*) = value;);
(pattern_tokens, bound)
}
}
}
pub fn bind_variant_fields<'a>(
enum_ident: &Ident,
variant: &'a VariantData
) -> (TokenStream, Vec<BoundField<'a>>) {
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => (quote!(#enum_ident::#variant_ident), Vec::new()),
Fields::Named(list) => {
let mut pattern = Vec::new();
let mut bound = Vec::new();
for field in list {
let binding = binding_ident(field);
let pattern_binding = binding.clone();
pattern.push(quote!(#pattern_binding));
bound.push(BoundField {
field,
binding
});
}
(quote!(#enum_ident::#variant_ident { #(#pattern),* }), bound)
}
Fields::Unnamed(list) => {
let mut pattern = Vec::new();
let mut bound = Vec::new();
for field in list {
let binding = binding_ident(field);
let pattern_binding = binding.clone();
pattern.push(quote!(#pattern_binding));
bound.push(BoundField {
field,
binding
});
}
(quote!(#enum_ident::#variant_ident(#(#pattern),*)), bound)
}
}
}
pub fn field_usage_tokens(bound_fields: &[BoundField<'_>]) -> TokenStream {
if bound_fields.is_empty() {
return TokenStream::new();
}
let names = bound_fields.iter().map(|field| &field.binding);
quote! {
let _ = (#(&#names),*);
}
}
pub fn binding_ident(field: &Field) -> Ident {
field
.ident
.clone()
.unwrap_or_else(|| format_ident!("__field{}", field.index, span = field.span))
}
#[cfg(test)]
mod tests {
use proc_macro2::Span;
use syn::parse_quote;
use super::*;
fn create_test_field(ident: Option<Ident>, index: usize) -> Field {
let ty = parse_quote!(String);
let member = match &ident {
Some(name) => syn::Member::Named(name.clone()),
None => syn::Member::Unnamed(syn::Index::from(index))
};
Field {
ident,
member,
ty,
index,
span: Span::call_site(),
attrs: Default::default()
}
}
#[test]
fn test_binding_ident_named_field() {
let field = create_test_field(Some(format_ident!("message")), 0);
let binding = binding_ident(&field);
assert_eq!(binding.to_string(), "message");
}
#[test]
fn test_binding_ident_unnamed_field() {
let field = create_test_field(None, 2);
let binding = binding_ident(&field);
assert_eq!(binding.to_string(), "__field2");
}
#[test]
fn test_field_usage_tokens_empty() {
let bound_fields = vec![];
let result = field_usage_tokens(&bound_fields);
assert!(result.is_empty());
}
#[test]
fn test_field_usage_tokens_single() {
let field = create_test_field(Some(format_ident!("field1")), 0);
let binding = binding_ident(&field);
let bound = vec![BoundField {
field: &field,
binding
}];
let result = field_usage_tokens(&bound);
let result_str = result.to_string();
assert!(result_str.contains("field1"));
}
#[test]
fn test_bind_struct_fields_unit() {
let ident = format_ident!("MyError");
let fields = Fields::Unit;
let (pattern, bound) = bind_struct_fields(&ident, &fields);
assert_eq!(pattern.to_string(), "let _ = value ;");
assert!(bound.is_empty());
}
#[test]
fn test_bind_struct_fields_named() {
let ident = format_ident!("MyError");
let field = create_test_field(Some(format_ident!("message")), 0);
let fields = Fields::Named(vec![field]);
let (pattern, bound) = bind_struct_fields(&ident, &fields);
let pattern_str = pattern.to_string();
assert!(pattern_str.contains("MyError"));
assert!(pattern_str.contains("message"));
assert_eq!(bound.len(), 1);
assert_eq!(bound[0].binding.to_string(), "message");
}
#[test]
fn test_bind_struct_fields_unnamed() {
let ident = format_ident!("MyError");
let field = create_test_field(None, 0);
let fields = Fields::Unnamed(vec![field]);
let (pattern, bound) = bind_struct_fields(&ident, &fields);
let pattern_str = pattern.to_string();
assert!(pattern_str.contains("MyError"));
assert!(pattern_str.contains("__field0"));
assert_eq!(bound.len(), 1);
assert_eq!(bound[0].binding.to_string(), "__field0");
}
#[test]
fn test_bind_variant_fields_unit() {
use crate::input::DisplaySpec;
let enum_ident = format_ident!("MyError");
let variant = VariantData {
ident: format_ident!("NotFound"),
fields: Fields::Unit,
display: DisplaySpec::Template(crate::template_support::DisplayTemplate {
segments: vec![]
}),
format_args: Default::default(),
app_error: None,
masterror: None,
span: Span::call_site()
};
let (pattern, bound) = bind_variant_fields(&enum_ident, &variant);
assert_eq!(pattern.to_string(), "MyError :: NotFound");
assert!(bound.is_empty());
}
#[test]
fn test_bind_variant_fields_named() {
use crate::input::DisplaySpec;
let enum_ident = format_ident!("MyError");
let field = create_test_field(Some(format_ident!("code")), 0);
let variant = VariantData {
ident: format_ident!("Auth"),
fields: Fields::Named(vec![field]),
display: DisplaySpec::Template(crate::template_support::DisplayTemplate {
segments: vec![]
}),
format_args: Default::default(),
app_error: None,
masterror: None,
span: Span::call_site()
};
let (pattern, bound) = bind_variant_fields(&enum_ident, &variant);
let pattern_str = pattern.to_string();
assert!(pattern_str.contains("MyError :: Auth"));
assert!(pattern_str.contains("code"));
assert_eq!(bound.len(), 1);
assert_eq!(bound[0].binding.to_string(), "code");
}
#[test]
fn test_bind_variant_fields_unnamed() {
use crate::input::DisplaySpec;
let enum_ident = format_ident!("MyError");
let field = create_test_field(None, 0);
let variant = VariantData {
ident: format_ident!("Io"),
fields: Fields::Unnamed(vec![field]),
display: DisplaySpec::Template(crate::template_support::DisplayTemplate {
segments: vec![]
}),
format_args: Default::default(),
app_error: None,
masterror: None,
span: Span::call_site()
};
let (pattern, bound) = bind_variant_fields(&enum_ident, &variant);
let pattern_str = pattern.to_string();
assert!(pattern_str.contains("MyError :: Io"));
assert!(pattern_str.contains("__field0"));
assert_eq!(bound.len(), 1);
assert_eq!(bound[0].binding.to_string(), "__field0");
}
#[test]
fn test_field_usage_tokens_multiple() {
let field1 = create_test_field(Some(format_ident!("field1")), 0);
let field2 = create_test_field(Some(format_ident!("field2")), 1);
let binding1 = binding_ident(&field1);
let binding2 = binding_ident(&field2);
let bound = vec![
BoundField {
field: &field1,
binding: binding1
},
BoundField {
field: &field2,
binding: binding2
},
];
let result = field_usage_tokens(&bound);
let result_str = result.to_string();
assert!(result_str.contains("field1"));
assert!(result_str.contains("field2"));
}
}