approx_derive/lib.rs
1#![deny(missing_docs)]
2//! This crate provides derive macros for the
3//! [AbsDiffEq](https://docs.rs/approxim/latest/approxim/trait.AbsDiffEq.html) and
4//! [RelativeEq](https://docs.rs/approxim/latest/approxim/trait.RelativeEq.html) traits of the
5//! [approxim](https://docs.rs/approxim/latest/approxim/) crate.
6//!
7//! These derive macros only implement both traits with `...<Rhs = Self>`.
8//! The macros infer the `EPSILON` type of the [AbsDiffEq] trait by looking
9//! at the type of the first struct or enum field or any type specified by the user.
10//!
11//! This table lists all attributes which can be used to customize the derived traits.
12//! They are ordered in descending priority, meaning setting the `#[approx(equal)]` will overwrite
13//! any specifications made in the `#[approx(map = ...)]` attribute.
14//!
15//! | Field Attribute | Functionality |
16//! |:--- | --- |
17//! | [`#[approx(skip)]`](#skipping-fields) | Skips the field entirely |
18//! | [`#[approx(equal)]`](#testing-for-equality) | Checks this field with `==` for Equality |
19//! | [`#[approx(cast_field)]`](#casting-fields) | Casts the field with `.. as ..` syntax. |
20//! | [`#[approx(cast_value)]`](#casting-fields) | Casts the epsilon value with `.. as ..` syntax. |
21//! | [`#[approx(map = ..)]`](#mapping-values) | Maps values before comparing them. |
22//! | [`#[approx(epsilon_map = ..)]`](#mapping-epsilon-values) | Maps epsilon values before using them. |
23//! | [`#[approx(static_epsilon = ..)]`](#static-values) | Defines a static epsilon value for this particular field. |
24//! | [`#[approx(into_iter)]`](#into-iterator) | Tries to use the `into_iterator` method to compare fields. |
25//! | | |
26//! | **Object Attribute** | |
27//! | [`#[approx(default_epsilon = ...)]`](#default-epsilon) | Sets the default epsilon value |
28//! | [`#[approx(default_max_relative = ...)]`](#default-max-relative) | Sets the default `max_relative` value. |
29//! | [`#[approx(epsilon_type = ...)]`](#epsilon-type) | Sets the type of the epsilon value |
30//!
31//! # Usage
32//!
33//! ```
34//! # #[cfg(not(feature = "infer_name"))] use approx::*;
35//! # #[cfg(feature = "infer_name")] use approxim::*;
36//! use approx_derive::AbsDiffEq;
37//!
38//! // Define a new type and derive the AbsDiffEq trait
39//! #[derive(AbsDiffEq, PartialEq, Debug)]
40//! struct Position {
41//! x: f64,
42//! y: f64
43//! }
44//!
45//! // Compare if two given positions match
46//! // with respect to geiven epsilon.
47//! let p1 = Position { x: 1.01, y: 2.36 };
48//! let p2 = Position { x: 0.99, y: 2.38 };
49//! assert_abs_diff_eq!(p1, p2, epsilon = 0.021);
50//! ```
51//! In this case, the generated code looks something like this:
52//! ```ignore
53//! const _ : () =
54//! {
55//! #[automatically_derived] impl approx :: AbsDiffEq for Position
56//! {
57//! type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
58//!
59//! fn default_epsilon() -> Self :: Epsilon {
60//! <f64 as approx::AbsDiffEq>::default_epsilon()
61//! }
62//!
63//! fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
64//! <f64 as approx::AbsDiffEq>::abs_diff_eq(
65//! &self.x,
66//! & other.x,
67//! epsilon.clone()
68//! ) &&
69//! <f64 as approx::AbsDiffEq>::abs_diff_eq(
70//! &self.y,
71//! &other.y,
72//! epsilon.clone()
73//! ) && true
74//! }
75//! }
76//! };
77//! ```
78//! The [AbsDiffEq] derive macro calls the `abs_diff_eq` method repeatedly on all fields
79//! to determine if all are matching.
80//!
81//! ## Enums
82//! Since `approx-derive` supports enums since `0.2`
83//!
84//! ```
85//! # #[cfg(not(feature = "infer_name"))] use approx::*;
86//! # #[cfg(feature = "infer_name")] use approxim::*;
87//! use approx_derive::AbsDiffEq;
88//!
89//! #[derive(AbsDiffEq, PartialEq, Debug)]
90//! enum Position {
91//! Smooth { x: f32, y: f32, },
92//! #[approx(cast_value)]
93//! Lattice { x: isize, y: isize },
94//! }
95//!
96//! let p1 = Position::Smooth { x: 1.0, y: 1.1 };
97//! let p2 = Position::Smooth { x: 1.1, y: 1.0};
98//! let p3 = Position::Lattice { x: 1, y: 1 };
99//!
100//! assert_abs_diff_eq!(p1, p2, epsilon=0.2);
101//! ```
102//!
103//! ```should_panic
104//! # #[cfg(not(feature = "infer_name"))] use approx::*;
105//! # #[cfg(feature = "infer_name")] use approxim::*;
106//! # use approx_derive::AbsDiffEq;
107//! # #[derive(AbsDiffEq, PartialEq, Debug)]
108//! # enum Position {
109//! # Smooth { x: f32, y: f32, },
110//! # #[approx(cast_value)]
111//! # Lattice { x: isize, y: isize },
112//! # }
113//! # let p1 = Position::Smooth { x: 1.0, y: 1.1 };
114//! # let p3 = Position::Lattice { x: 1, y: 1 };
115//! // Note! Different enum variants can never be equal!
116//! assert_abs_diff_eq!(p1, p3, epsilon = 1000.0);
117//! ```
118//!
119//!
120//! # Field Attributes
121//! ## Skipping Fields
122//!
123//! Sometimes, we only want to compare certain fields and omit others completely.
124//! ```
125//! # #[cfg(not(feature = "infer_name"))] use approx::*;
126//! # #[cfg(feature = "infer_name")] use approxim::*;
127//! # use approx_derive::*;
128//! #[derive(AbsDiffEq, PartialEq, Debug)]
129//! struct Player {
130//! hit_points: f32,
131//! pos_x: f32,
132//! pos_y: f32,
133//! #[approx(skip)]
134//! id: (usize, usize),
135//! }
136//!
137//! let player1 = Player {
138//! hit_points: 100.0,
139//! pos_x: 2.0,
140//! pos_y: -650.345,
141//! id: (0, 1),
142//! };
143//!
144//! let player2 = Player {
145//! hit_points: 99.9,
146//! pos_x: 2.001,
147//! pos_y: -649.898,
148//! id: (22, 0),
149//! };
150//!
151//! assert_abs_diff_eq!(player1, player2, epsilon = 0.5);
152//! ```
153//!
154//! ## Testing for [Equality](core::cmp::Eq)
155//!
156//! When identical equality is desired, we can specify this with the `#[approx(equal)]` attribute.
157//!
158//! ```
159//! # #[cfg(not(feature = "infer_name"))] use approx::*;
160//! # use approx_derive::*;
161//! #[derive(AbsDiffEq, PartialEq, Debug)]
162//! struct Prediction {
163//! confidence: f64,
164//! #[approx(equal)]
165//! category: String,
166//! }
167//! ```
168//!
169//! Note that in this case, the type of the epsilon value for the implementation of
170//! [AbsDiffEq](https://docs.rs/approx/latest/approx/trait.AbsDiffEq.html) is inferred from the
171//! first field of the `Prediction` struct.
172//! This means if we reorder the arguments of the struct, we need to manually set the epsilon type.
173//!
174//! ```
175//! # use approx_derive::*;
176//! #[derive(AbsDiffEq, PartialEq, Debug)]
177//! #[approx(epsilon_type = f64)]
178//! struct Prediction {
179//! #[approx(equal)]
180//! category: String,
181//! confidence: f64,
182//! }
183//! ```
184//!
185//! ## Casting Fields
186//!
187//! Structs which consist of multiple fields with different
188//! numeric types, can not be derived without additional hints.
189//! After all, we should specify how this type mismatch will be handled.
190//!
191//! ```compile_fail
192//! # #[cfg(not(feature = "infer_name"))] use approx::*;
193//! # #[cfg(feature = "infer_name")] use approxim::*;
194//! # use approx_derive::*;
195//! #[derive(AbsDiffEq, PartialEq, Debug)]
196//! struct MyStruct {
197//! v1: f32,
198//! v2: f64,
199//! }
200//! ```
201//!
202//! We can use the `#[approx(cast_field)]` and `#[approx(cast_value)]`
203//! attributes to achieve this goal.
204//!
205//! ### Example 1
206//! Here, the second field will be casted to the type of the inferred epsilon value (`f32`).
207//! We can check this by testing if a change in the size of `f64::MIN_POSITIVE` would get lost by
208//! this procedure.
209//! ```
210//! # #[cfg(not(feature = "infer_name"))] use approx::*;
211//! # #[cfg(feature = "infer_name")] use approxim::*;
212//! # use approx_derive::*;
213//! # #[derive(RelativeEq, PartialEq, Debug)]
214//! # struct MyStruct {
215//! # v1: f32,
216//! # #[approx(cast_field)]
217//! # v2: f64,
218//! # }
219//! let ms1 = MyStruct {
220//! v1: 1.0,
221//! v2: 3.0,
222//! };
223//! let ms2 = MyStruct {
224//! v1: 1.0,
225//! v2: 3.0 + f64::MIN_POSITIVE,
226//! };
227//! assert_relative_eq!(ms1, ms2);
228//! ```
229//!
230//! ### Example 2
231//! In this example, we cast the `f64` type to `isize`.
232//! ```
233//! # #[cfg(not(feature = "infer_name"))] use approx::*;
234//! # #[cfg(feature = "infer_name")] use approxim::*;
235//! # use approx_derive::*;
236//! #[derive(AbsDiffEq, PartialEq, Debug)]
237//! struct MyStruct {
238//! v1: isize,
239//! #[approx(cast_field)]
240//! v2: f64,
241//! }
242//! let ms1 = MyStruct { v1: 1, v2: 2.0 };
243//! let ms2 = MyStruct { v1: 1, v2: 2.1 };
244//! assert_abs_diff_eq!(ms1, ms2);
245//!
246//! // The underlying generated code performs
247//! assert!(isize::abs_diff_eq(
248//! &(ms1.v2 as isize),
249//! &(ms2.v2 as isize),
250//! 0,
251//! ));
252//! ```
253//! When we use the `#[approx(cast_value)]` syntax, we get a different result.
254//! ```
255//! # #[cfg(not(feature = "infer_name"))] use approx::*;
256//! # #[cfg(feature = "infer_name")] use approxim::*;
257//! # use approx_derive::*;
258//! #[derive(AbsDiffEq, PartialEq, Debug)]
259//! struct MyStruct2 {
260//! v1: isize,
261//! #[approx(cast_value)]
262//! v2: f64,
263//! }
264//! let ms1 = MyStruct2 { v1: 1, v2: 2.0 };
265//! let ms2 = MyStruct2 { v1: 1, v2: 2.1 };
266//! assert_abs_diff_ne!(ms1, ms2);
267//!
268//! // Here, the epsilon value for isize is casted to f64
269//! assert!(!f64::abs_diff_eq(
270//! &ms1.v2,
271//! &ms2.v2,
272//! 0isize as f64
273//! ));
274//! ```
275//!
276//! ## Mapping Values
277//!
278//! We can map values before comparing them.
279//! By default, we need to return an option of the value in question.
280//! This allows to do computations where error can occur.
281//! Although this error is not caught, the comparison will fail if any of the two compared objects
282//! return a `None` value.
283//! ```
284//! # use approx_derive::*;
285//! # #[cfg(not(feature = "infer_name"))] use approx::*;
286//! # #[cfg(feature = "infer_name")] use approxim::*;
287//! #[derive(AbsDiffEq, PartialEq, Debug)]
288//! struct Tower {
289//! height_in_meters: f32,
290//! #[approx(map = |x: &f32| Some(x.sqrt()))]
291//! area_in_meters_squared: f32,
292//! }
293//! # let t1 = Tower {
294//! # height_in_meters: 100.0,
295//! # area_in_meters_squared: 30.1,
296//! # };
297//! # let t2 = Tower {
298//! # height_in_meters: 100.0,
299//! # area_in_meters_squared: 30.5,
300//! # };
301//! # assert_abs_diff_ne!(t1, t2, epsilon = 0.03);
302//! ```
303//!
304//! This functionality can also be useful when having more complex datatypes.
305//! ```
306//! # #[cfg(not(feature = "infer_name"))] use approx::*;
307//! # #[cfg(feature = "infer_name")] use approxim::*;
308//! # use approx_derive::*;
309//! #[derive(PartialEq, Debug)]
310//! enum Time {
311//! Years(u16),
312//! Months(u16),
313//! Weeks(u16),
314//! Days(u16),
315//! }
316//!
317//! fn time_to_days(time: &Time) -> Option<u16> {
318//! match time {
319//! Time::Years(y) => Some(365 * y),
320//! Time::Months(m) => Some(30 * m),
321//! Time::Weeks(w) => Some(7 * w),
322//! Time::Days(d) => Some(*d),
323//! }
324//! }
325//!
326//! #[derive(AbsDiffEq, PartialEq, Debug)]
327//! #[approx(epsilon_type = u16)]
328//! struct Dog {
329//! #[approx(map = time_to_days)]
330//! age: Time,
331//! #[approx(map = time_to_days)]
332//! next_doctors_appointment: Time,
333//! }
334//! ```
335//!
336//! ## Mapping Epsilon Values
337//!
338//! We can also map `epsilon` values before using them. This is usefull i.e. for tuples or arrays.
339//! Note that the example below currently requires the
340//! [approxim](https://docs.rs/approxim/latest/approxim/) crate with the `infer_name` feature flag
341//! enabled since [approx](https://docs.rs/approx/latest/approx/) does not support tuples yet.
342//!
343//! ```
344//! # #[cfg(feature = "infer_name")] {
345//! # #[cfg(not(feature = "infer_name"))] use approx::*;
346//! # #[cfg(feature = "infer_name")] use approxim::*;
347//! # use approx_derive::*;
348//! #[derive(AbsDiffEq, PartialEq, Debug)]
349//! struct DifferentialEvolution {
350//! recombination: f32,
351//! #[approx(epsilon_map = |x| (x, x))]
352//! mutation: (f32, f32),
353//! }
354//! # let d1 = DifferentialEvolution {
355//! # recombination: 0.7,
356//! # mutation: (0.5, 1.5),
357//! # };
358//! # let d2 = DifferentialEvolution {
359//! # recombination: 0.7001,
360//! # mutation: (0.501, 1.499),
361//! # };
362//! # assert_abs_diff_eq!(d1, d2, epsilon = 0.02);
363//! # };
364//! ```
365//!
366//! ## Static Values
367//! We can force a static `EPSILON` or `max_relative` value for individual fields.
368//! ```
369//! # #[cfg(not(feature = "infer_name"))] use approx::*;
370//! # #[cfg(feature = "infer_name")] use approxim::*;
371//! # use approx_derive::*;
372//! #[derive(AbsDiffEq, PartialEq, Debug)]
373//! struct Rectangle {
374//! #[approx(static_epsilon = 5e-2)]
375//! a: f64,
376//! b: f64,
377//! #[approx(static_epsilon = 7e-2)]
378//! c: f64,
379//! }
380//!
381//! let r1 = Rectangle {
382//! a: 100.01,
383//! b: 40.0001,
384//! c: 30.055,
385//! };
386//! let r2 = Rectangle {
387//! a: 99.97,
388//! b: 40.0005,
389//! c: 30.049,
390//! };
391//!
392//! // This is always true although the epsilon is smaller than the
393//! // difference between fields a and b respectively.
394//! assert_abs_diff_eq!(r1, r2, epsilon = 1e-1);
395//! assert_abs_diff_eq!(r1, r2, epsilon = 1e-2);
396//! assert_abs_diff_eq!(r1, r2, epsilon = 1e-3);
397//!
398//! // Here, the epsilon value has become larger than the difference between the
399//! // b field values.
400//! assert_abs_diff_ne!(r1, r2, epsilon = 1e-4);
401//! ```
402//! # Object Attributes
403//! ## Default Epsilon
404//! The [AbsDiffEq] trait allows to specify a default value for its `EPSILON` associated type.
405//! We can control this value by specifying it on an object level.
406//!
407//! ```
408//! # #[cfg(not(feature = "infer_name"))] use approx::*;
409//! # #[cfg(feature = "infer_name")] use approxim::*;
410//! # use approx_derive::*;
411//! #[derive(AbsDiffEq, PartialEq, Debug)]
412//! #[approx(default_epsilon = 10)]
413//! struct Benchmark {
414//! cycles: u64,
415//! warm_up: u64,
416//! }
417//!
418//! let benchmark1 = Benchmark {
419//! cycles: 248,
420//! warm_up: 36,
421//! };
422//! let benchmark2 = Benchmark {
423//! cycles: 239,
424//! warm_up: 28,
425//! };
426//!
427//! // When testing with not additional arguments, the results match
428//! assert_abs_diff_eq!(benchmark1, benchmark2);
429//! // Once we specify a lower epsilon, the values do not agree anymore.
430//! assert_abs_diff_ne!(benchmark1, benchmark2, epsilon = 5);
431//! ```
432//!
433//! ## Default Max Relative
434//! Similarly to [Default Epsilon], we can also choose a default max_relative devaition.
435//! ```
436//! # use approx_derive::*;
437//! # #[cfg(not(feature = "infer_name"))] use approx::*;
438//! # #[cfg(feature = "infer_name")] use approxim::*;
439//! #[derive(RelativeEq, PartialEq, Debug)]
440//! #[approx(default_max_relative = 0.1)]
441//! struct Benchmark {
442//! time: f32,
443//! warm_up: f32,
444//! }
445//!
446//! let bench1 = Benchmark {
447//! time: 3.502785781,
448//! warm_up: 0.58039458,
449//! };
450//! let bench2 = Benchmark {
451//! time: 3.7023458,
452//! warm_up: 0.59015897,
453//! };
454//!
455//! assert_relative_eq!(bench1, bench2);
456//! assert_relative_ne!(bench1, bench2, max_relative = 0.05);
457//! ```
458//! ## Epsilon Type
459//! When specifying nothing, the macros will infer the `EPSILON` type from the type of the
460//! first struct/enum field (the order in which it is parsed).
461//! This can be problematic in certain scenarios which is why we can also manually specify this
462//! type.
463//!
464//! ```
465//! # #[cfg(not(feature = "infer_name"))] use approx::*;
466//! # #[cfg(feature = "infer_name")] use approxim::*;
467//! # use approx_derive::*;
468//! #[derive(RelativeEq, PartialEq, Debug)]
469//! #[approx(epsilon_type = f32)]
470//! struct Car {
471//! #[approx(cast_field)]
472//! produced_year: u32,
473//! horse_power: f32,
474//! }
475//!
476//! let car1 = Car {
477//! produced_year: 1992,
478//! horse_power: 122.87,
479//! };
480//! let car2 = Car {
481//! produced_year: 2000,
482//! horse_power: 117.45,
483//! };
484//!
485//! assert_relative_eq!(car1, car2, max_relative = 0.05);
486//! assert_relative_ne!(car1, car2, max_relative = 0.01);
487//! ```
488//!
489//! # Into Iterator
490//! To compare two fields which consist of a iterable list of values, we can use the
491//! `#[approx(into_iter)]` field attribute.
492//!
493//! ```
494//! # #[cfg(not(feature = "infer_name"))] use approx::*;
495//! # #[cfg(feature = "infer_name")] use approxim::*;
496//! # use approx_derive::*;
497//! #[derive(AbsDiffEq, PartialEq, Debug)]
498//! struct Parameter {
499//! value: f32,
500//! #[approx(into_iter)]
501//! bounds: [f32; 2],
502//! }
503//! let p1 = Parameter { value: 3.144, bounds: [0.0, 10.0] };
504//! let p2 = Parameter { value: 3.145, bounds: [0.1, 10.2] };
505//!
506//! assert_abs_diff_ne!(p1, p2);
507//! assert_abs_diff_eq!(p1, p2, epsilon = 0.21);
508//! ```
509//! It has to be noted that whenever both iterator are not of the same length, that the comparison
510//! will fail.
511//!
512//! ```should_panic
513//! # #[cfg(not(feature = "infer_name"))] use approx::*;
514//! # #[cfg(feature = "infer_name")] use approxim::*;
515//! # use approx_derive::*;
516//! #[derive(AbsDiffEq, PartialEq, Debug)]
517//! #[approx(epsilon_type = f64)]
518//! struct Polynomial {
519//! #[approx(into_iter)]
520//! coefficients: Vec<f64>,
521//! }
522//! let poly1 = Polynomial { coefficients: vec![1.0, 0.5] };
523//! let poly2 = Polynomial { coefficients: vec![1.0, 0.5, 1.0/6.0] };
524//! assert_abs_diff_eq!(poly1, poly2);
525//! ```
526
527mod abs_diff_eq;
528mod args_parsing;
529mod base_types;
530mod rel_diff_eq;
531
532use args_parsing::*;
533use base_types::*;
534
535struct AbsDiffEqParser {
536 pub base_type: BaseType,
537 pub struct_args: StructArgs,
538}
539
540/// See the [crate] level documentation for a guide.
541#[proc_macro_derive(AbsDiffEq, attributes(approx))]
542pub fn derive_abs_diff_eq(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
543 let parsed = syn::parse_macro_input!(input as AbsDiffEqParser);
544 parsed.implement_derive_abs_diff_eq().into()
545}
546
547/// See the [crate] level documentation for a guide.
548#[proc_macro_derive(RelativeEq, attributes(approx))]
549pub fn derive_rel_diff_eq(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
550 let parsed = syn::parse_macro_input!(input as AbsDiffEqParser);
551 let mut output = quote::quote!();
552 output.extend(parsed.implement_derive_abs_diff_eq());
553 output.extend(parsed.implement_derive_rel_diff_eq());
554 output.into()
555}