1#![forbid(unsafe_code)]
2#![deny(clippy::unwrap_used)]
3#![allow(clippy::needless_continue)]
4
5mod fluent_aliases;
6
7use proc_macro::TokenStream;
8
9use darling::{Error, FromDeriveInput, FromField, ast};
10use proc_macro2::Span;
11use quote::quote;
12use syn::{DeriveInput, Ident, ItemTrait, Path, Type, Visibility, parse_macro_input};
13
14#[derive(Debug, FromField)]
15#[darling(attributes(assertr_eq))]
16struct MyFieldReceiver {
17 ident: Option<Ident>,
18
19 ty: Type,
20
21 vis: Visibility,
22
23 #[darling(default)]
24 map_type: Option<Type>,
25
26 #[darling(default)]
27 compare_with: Option<Path>,
28}
29
30#[derive(Debug, FromDeriveInput)]
31#[darling(attributes(assertr_eq), supports(struct_any))]
32struct MyInputReceiver {
33 ident: Ident,
34
35 data: ast::Data<(), MyFieldReceiver>,
36}
37
38impl MyInputReceiver {
39 pub fn fields(&self) -> &ast::Fields<MyFieldReceiver> {
40 match &self.data {
41 ast::Data::Enum(_) => panic!("Only structs are supported"),
42 ast::Data::Struct(fields) => fields,
43 }
44 }
45}
46
47#[proc_macro_derive(AssertrEq, attributes(assertr_eq))]
53pub fn store(input: TokenStream) -> TokenStream {
54 let ast = parse_macro_input!(input as DeriveInput);
55
56 let input: MyInputReceiver = match FromDeriveInput::from_derive_input(&ast) {
57 Ok(args) => args,
58 Err(err) => return Error::write_errors(err).into(),
59 };
60
61 let original_struct_ident = input.ident.clone();
62
63 let filtered_fields = input.fields().iter().filter(|field| match field.vis {
64 Visibility::Public(_) => true,
65 Visibility::Restricted(_) | Visibility::Inherited => false,
66 });
67
68 let eq_struct_ident = Ident::new(
69 format!("{}AssertrEq", input.ident).as_str(),
70 Span::call_site(),
71 );
72
73 let eq_struct_fields = filtered_fields.clone().map(|field| {
74 let vis = &field.vis;
75 let ident = &field.ident;
76 let ty = match &field.map_type {
77 None => &field.ty,
78 Some(ty) => ty,
79 };
80 quote! { #vis #ident: ::assertr::Eq<#ty> }
81 });
82
83 let eq_impls = filtered_fields.map(|field| {
84 let ident = field
85 .ident
86 .as_ref()
87 .expect("only named fields are supported!");
88 let ident_string = ident.to_string();
89 let ty = match &field.map_type {
90 None => &field.ty,
91 Some(ty) => ty,
92 };
93 let eq_args = quote! { &self.#ident, v, ctx.as_deref_mut() };
94 let eq_check = match &field.compare_with {
95 None => quote! { ::assertr::AssertrPartialEq::<#ty>::eq(#eq_args) },
96 Some(eq_check) => {
97 quote! { #eq_check(#eq_args) }
98 }
99 };
100 quote! {
101 && match &other.#ident {
102 ::assertr::Eq::Any => true,
103 ::assertr::Eq::Eq(v) => {
104 let eq = #eq_check;
105 if !eq {
106 if let Some(ctx) = ctx.as_mut() {
107 ctx.add_field_difference(#ident_string, v, &self.#ident);
108 }
109 }
110 eq
111 },
112 }
113 }
114 });
115
116 Into::into(quote! {
117 #[derive(::core::fmt::Debug, ::core::default::Default)]
118 pub struct #eq_struct_ident {
119 #(#eq_struct_fields),*
120 }
121
122 impl ::assertr::AssertrPartialEq<#eq_struct_ident> for &#original_struct_ident {
123 fn eq(&self, other: &#eq_struct_ident, mut ctx: Option<&mut ::assertr::EqContext>) -> bool {
124 true #(#eq_impls)*
125 }
126 }
127
128 impl ::assertr::AssertrPartialEq<#eq_struct_ident> for #original_struct_ident {
129 fn eq(&self, other: &#eq_struct_ident, ctx: Option<&mut ::assertr::EqContext>) -> bool {
130 ::assertr::AssertrPartialEq::eq(&self, other, ctx)
131 }
132 }
133 })
134}
135
136#[proc_macro_attribute]
145pub fn fluent_aliases(_attr: TokenStream, item: TokenStream) -> TokenStream {
146 let trait_def = parse_macro_input!(item as ItemTrait);
147 fluent_aliases::fluent_aliases_impl(trait_def).into()
148}