use quote::ToTokens;
#[path = "../../src/internals/mod.rs"]
mod internals;
fn derive_diff_struct(input: &str) -> syn::ItemStruct {
let parsed: syn::DeriveInput =
syn::parse_str(input).expect("input parsed as a DeriveInput");
let diff_name = format!("{}Diff", parsed.ident);
let generated = internals::derive_diffable(parsed).into_token_stream();
let file: syn::File =
syn::parse2(generated.clone()).unwrap_or_else(|err| {
panic!("generated code parsed as a file: {err}\n{generated}");
});
file.items
.into_iter()
.find_map(|item| match item {
syn::Item::Struct(s) if s.ident == diff_name => Some(s),
_ => None,
})
.unwrap_or_else(|| {
panic!("generated code defines {diff_name}:\n{generated}");
})
}
fn assert_no_field_defaults(s: &syn::ItemStruct) {
let kept: Vec<_> = s
.fields
.iter()
.enumerate()
.filter(|(_, f)| f.default.is_some())
.map(|(i, f)| match &f.ident {
Some(ident) => ident.to_string(),
None => i.to_string(),
})
.collect();
assert!(
kept.is_empty(),
"fields of {} kept a default value from the original struct: {:?}\n{}",
s.ident,
kept,
s.to_token_stream(),
);
}
fn field_names(s: &syn::ItemStruct) -> Vec<String> {
s.fields
.iter()
.map(|f| f.ident.as_ref().expect("named field").to_string())
.collect()
}
#[test]
fn named_field_default_is_dropped() {
let diff =
derive_diff_struct("struct HasDefault { a: u32 = 5, b: String }");
assert_no_field_defaults(&diff);
assert_eq!(field_names(&diff), ["a", "b"]);
}
#[test]
fn leaf_field_default_is_dropped() {
let diff = derive_diff_struct(
"struct HasLeafDefault { #[daft(leaf)] a: u32 = 5 }",
);
assert_no_field_defaults(&diff);
assert_eq!(field_names(&diff), ["a"]);
}
#[test]
fn ignored_field_default_is_dropped() {
let diff = derive_diff_struct(
"struct HasIgnoredDefault { #[daft(ignore)] a: u32 = 5, b: String }",
);
assert_no_field_defaults(&diff);
assert_eq!(field_names(&diff), ["b"]);
}