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