ignorable/lib.rs
1#, "?style=flat-square&logo=rust)](https://crates.io/crates/", env!("CARGO_PKG_NAME"), ")")]
2#, "?style=flat-square&logo=docs.rs)](https://docs.rs/", env!("CARGO_PKG_NAME"), ")")]
3#"]
4#, "-blue?style=flat-square&logo=rust)")]
5#, ")](https://github.com/nik-rev/", env!("CARGO_PKG_NAME"), ")")]
6//!
7//! This crate provides 5 derives that are just like the standard library's, but they allow
8//! to ignore fields when deriving. Inspired by [RFC 3869](https://github.com/rust-lang/rfcs/pull/3869)
9//!
10//! ```toml
11#![doc = concat!(env!("CARGO_PKG_NAME"), " = ", "\"", env!("CARGO_PKG_VERSION_MAJOR"), ".", env!("CARGO_PKG_VERSION_MINOR"), "\"")]
12//! ```
13//!
14//! # Usage
15//!
16//! This crate provides 5 derive macros:
17//!
18//! - `PartialEq`
19//! - `PartialOrd`
20//! - `Ord`
21//! - `Debug`
22//! - `Hash`
23//!
24//! The advantage of these derives over the standard library's is that they support
25//! the `#[ignored]` attribute to ignore individual fields when deriving the respective traits.
26//!
27//! ```rust
28//! use ignorable::{PartialEq, Hash};
29//!
30//! // `PartialEq` and `Hash` impls will only check
31//! // the `id` field of 2 `User`s
32//! #[derive(Clone, PartialEq, Eq, Hash)]
33//! struct User {
34//! #[ignored(PartialEq, Hash)]
35//! name: String,
36//! #[ignored(PartialEq, Hash)]
37//! age: u8,
38//! id: u64
39//! }
40//! ```
41//!
42//! Advantages:
43//!
44//! - **Significantly** less boilerplate
45//! - Less maintenance overhead, it's not your responsibility to remember to update manual implementations of traits,
46//! keep traits like `Hash` and `PartialEq` in sync. We've got that covered!
47//! - This might become a language feature in the future ([RFC 3869](https://github.com/rust-lang/rfcs/pull/3869)),
48//! so you'll be able to transition away from this crate once that time comes!
49//!
50//! Remember that it is a [logic error](https://doc.rust-lang.org/stable/std/hash/trait.Hash.html#hash-and-eq)
51//! for the implementations of `Hash` and `PartialEq` to differ, and if you need to manually implement the traits
52//! to skip certain fields, **you** must remember to keep them in sync because you can't use the `derive` anymore.
53//!
54//! # With `ignorable`
55//!
56//! Uses derives provided by this crate.
57//!
58//! ```rust
59//! # use std::marker::PhantomData;
60//! # use std::cell::RefCell;
61//! # use std::rc::Rc;
62//! #
63//! # #[derive(PartialEq, Debug, Hash, Clone)]
64//! # struct Symbol;
65//! # #[derive(PartialEq, Debug, Hash, Clone)]
66//! # struct Value;
67//! #
68//! # mod protocols {
69//! # #[derive(PartialEq, Debug, Hash, Clone)]
70//! # pub struct IPersistentMap;
71//! # };
72//! use ignorable::{Debug, PartialEq, Hash};
73//!
74//! #[derive(Clone, Debug, PartialEq, Hash)]
75//! pub struct Var<T> {
76//! pub ns: Symbol,
77//! pub sym: Symbol,
78//! #[ignored(PartialEq, Hash)]
79//! meta: RefCell<protocols::IPersistentMap>,
80//! #[ignored(PartialEq, Hash)]
81//! pub root: RefCell<Rc<Value>>,
82//! #[ignored(Debug)]
83//! _phantom: PhantomData<T>
84//! }
85//! ```
86//!
87//! # Without
88//!
89//! You must manually implement each trait.
90//!
91//! ```rust
92//! # use std::marker::PhantomData;
93//! # use std::hash::{Hasher, Hash};
94//! # use std::fmt;
95//! # use std::cell::RefCell;
96//! # use std::rc::Rc;
97//! #
98//! # #[derive(PartialEq, Debug, Hash, Clone)]
99//! # struct Symbol;
100//! # #[derive(PartialEq, Debug, Hash, Clone)]
101//! # struct Value;
102//! #
103//! # mod protocols {
104//! # #[derive(PartialEq, Debug, Hash, Clone)]
105//! # pub struct IPersistentMap;
106//! # };
107//! #[derive(Clone)]
108//! pub struct Var<T> {
109//! pub ns: Symbol,
110//! pub sym: Symbol,
111//! meta: RefCell<protocols::IPersistentMap>,
112//! pub root: RefCell<Rc<Value>>,
113//! _phantom: PhantomData<T>
114//! }
115//!
116//! impl<T> fmt::Debug for Var<T> {
117//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118//! f.debug_struct("Var")
119//! .field("ns", &self.ns)
120//! .field("sym", &self.sym)
121//! .field("meta", &self.meta)
122//! .field("root", &self.root)
123//! .finish()
124//! }
125//! }
126//!
127//! impl<T> PartialEq for Var<T> {
128//! fn eq(&self, other: &Self) -> bool {
129//! self.ns == other.ns && self.sym == other.sym
130//! }
131//! }
132//!
133//! impl<T> Hash for Var<T> {
134//! fn hash<H: Hasher>(&self, state: &mut H) {
135//! (&self.ns, &self.sym).hash(state);
136//! }
137//! }
138//! ```
139//!
140//! Notes:
141//!
142//! - It is logically incorrect for `Hash` and `PartialEq` implementations
143//! to differ, so you must remember to keep them in sync if `Var` changes
144//! - You must remember to update the string names of the `Debug` impl if you
145//! ever rename the fields or `Var` itself
146use proc_macro2::{Span, TokenStream};
147use quote::{format_ident, quote, ToTokens};
148use std::cmp::Ordering;
149use syn::{parse_macro_input, DeriveInput};
150use syn::{parse_quote, Index, Member};
151use syn::{punctuated::Punctuated, token::Comma, Error, Field, Ident, Variant};
152
153create_derive!(PartialEq);
154create_derive!(PartialOrd);
155create_derive!(Ord);
156create_derive!(Debug);
157create_derive!(Hash);
158
159fn generate(
160 input: &mut syn::DeriveInput,
161 deriving: Deriving,
162 errors: &mut Vec<Error>,
163) -> TokenStream {
164 let body = match &input.data {
165 syn::Data::Struct(data) => {
166 let handle_struct_fields = data
167 .fields
168 .iter()
169 .enumerate()
170 .filter(|(_, field)| !deriving.is_ignored_in(field, errors))
171 .map(|(i, field)| {
172 let member = field
173 .ident
174 .clone()
175 .map(Member::Named)
176 .unwrap_or_else(|| Member::Unnamed(Index::from(i)));
177
178 deriving.handle_struct_field(member)
179 });
180
181 deriving.handle_struct(
182 handle_struct_fields,
183 &input.ident,
184 matches!(data.fields, syn::Fields::Unnamed(_)),
185 )
186 }
187 syn::Data::Enum(data) => 'body: {
188 if data.variants.is_empty() {
189 break 'body quote! { match *self {} };
190 }
191
192 let handle_variants = data.variants.iter().map(|variant| {
193 let (members, (fields_patterns, handle_variant_fields)): (
194 Vec<_>,
195 (Vec<_>, Vec<_>),
196 ) = variant
197 .fields
198 .iter()
199 .enumerate()
200 .filter(|(_, field)| !deriving.is_ignored_in(field, errors))
201 .map(|(i, field)| {
202 let member = field
203 .ident
204 .clone()
205 .map(Member::Named)
206 .unwrap_or_else(|| Member::Unnamed(Index::from(i)));
207
208 (member.clone(), deriving.handle_variant_field(member))
209 })
210 .unzip();
211
212 let handle_variant = deriving.handle_struct(handle_variant_fields.into_iter(), &variant.ident, matches!(variant.fields, syn::Fields::Unnamed(_)));
213 let variant_name = &variant.ident;
214
215 if matches!(deriving, Deriving::Debug | Deriving::Hash) {
216 let patterns = fields_patterns.into_iter().map(|x| match x {
217 EnumPatternField::One(ident) => ident,
218 EnumPatternField::Two(..) => unreachable!(
219 "variant depends on variant of `deriving`, but it is constant in this arm"
220 ),
221 });
222
223 // match self {
224 quote! {
225 Self::#variant_name { #(#members: #patterns,)* .. } => {
226 #handle_variant
227 }
228 }
229 } else {
230 let (lefts, rights): (Vec<_>, Vec<_>) = fields_patterns
231 .into_iter()
232 .map(|x| match x {
233 EnumPatternField::One(..) => unreachable!("variant depends on variant of `deriving`, but it is constant in this arm"),
234 EnumPatternField::Two(left, right) => (left, right),
235 })
236 .unzip();
237
238 // match (self, other) {
239 quote! {
240 (
241 Self::#variant_name { #(#members: #lefts,)* .. },
242 Self::#variant_name { #(#members: #rights,)* .. }
243 ) => {
244 #handle_variant
245 }
246 }
247 }
248 });
249
250 deriving.handle_enum(handle_variants, &data.variants)
251 }
252 syn::Data::Union(_) => {
253 return Error::new(Span::call_site(), "this trait cannot be derived for unions")
254 .into_compile_error();
255 }
256 };
257
258 for type_param in input.generics.type_params_mut() {
259 type_param.bounds.push(parse_quote!( #deriving ));
260 }
261 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
262 let name = &input.ident;
263 let signature = deriving.signature();
264
265 quote! {
266 #[allow(non_snake_case, non_shorthand_field_patterns)]
267 #[automatically_derived]
268 impl #impl_generics #deriving for #name #ty_generics #where_clause {
269 #signature {
270 #body
271 }
272 }
273 }
274}
275
276/// The standard library trait that we are deriving
277///
278/// Each trait supports `#[ignore]` attribute on fields. The `#[ignore]` attribute
279/// receives a list of identifiers. If the `#[ignore]` attribute's list contains exactly
280/// the identifier of this trait, then the trait will skip this field.
281#[derive(Copy, Clone)]
282enum Deriving {
283 /// Deriving [`trait@PartialEq`]
284 PartialEq,
285 /// Deriving [`trait@PartialOrd`]
286 PartialOrd,
287 /// Deriving [`trait@Ord`]
288 Ord,
289 /// Deriving [`Debug`]
290 Debug,
291 /// Deriving [`Hash`]
292 Hash,
293}
294
295/// Path to the trait we are deriving
296impl ToTokens for Deriving {
297 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
298 let path = match self {
299 Deriving::PartialEq => quote! { ::core::cmp::PartialEq },
300 Deriving::PartialOrd => quote! { ::core::cmp::PartialOrd },
301 Deriving::Ord => quote! { ::core::cmp::Ord },
302 Deriving::Debug => quote! { ::core::fmt::Debug },
303 Deriving::Hash => quote! { ::core::hash::Hash },
304 };
305
306 tokens.extend(path);
307 }
308}
309
310impl Deriving {
311 /// Signature of the single function belonging to the trait we are deriving
312 pub fn signature(self) -> TokenStream {
313 match self {
314 Deriving::PartialEq => quote! {
315 fn eq(&self, other: &Self) -> bool
316 },
317 Deriving::PartialOrd => quote! {
318 fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering>
319 },
320 Deriving::Ord => quote! {
321 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering
322 },
323 Deriving::Debug => quote! {
324 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result
325 },
326 Deriving::Hash => quote! {
327 fn hash<__H>(&self, state: &mut __H) where __H: ::core::hash::Hasher
328 },
329 }
330 }
331
332 /// Generate logic for a single field
333 pub fn handle_struct_field(self, member: Member) -> proc_macro2::TokenStream {
334 match self {
335 Deriving::PartialEq => quote! {
336 if self.#member != other.#member {
337 return false;
338 }
339 },
340 Deriving::PartialOrd => quote! {
341 match #self::partial_cmp(&self.#member, &other.#member) {
342 ::core::option::Option::Some(::core::cmp::Ordering::Equal) => {},
343 cmp => return cmp,
344 }
345 },
346 Deriving::Ord => quote! {
347 match #self::cmp(&self.#member, &other.#member) {
348 ::core::cmp::Ordering::Equal => {},
349 cmp => return cmp,
350 }
351 },
352 Deriving::Debug => match &member {
353 Member::Named(ident) => {
354 let name = ident.to_string();
355 quote! { .field(#name, &self.#member) }
356 }
357 Member::Unnamed(_) => {
358 quote! { .field(&self.#member) }
359 }
360 },
361 Deriving::Hash => quote! {
362 #self::hash(&self.#member, state);
363 },
364 }
365 }
366
367 /// Generate logic for the enum
368 pub fn handle_enum(
369 self,
370 handle_variants: impl Iterator<Item = TokenStream>,
371 variants: &Punctuated<Variant, Comma>,
372 ) -> proc_macro2::TokenStream {
373 match self {
374 Deriving::PartialEq => {
375 let arms_eq_discriminants = map_enum_discriminant_permutations(
376 variants,
377 |discriminant_left, variant_left, discriminant_right, variant_right| {
378 let is_equal = discriminant_left == discriminant_right;
379
380 quote! {
381 (
382 Self::#variant_left { .. },
383 Self::#variant_right { .. }
384 ) => #is_equal
385 }
386 },
387 );
388
389 quote! {
390 let discriminant = match (self, other) {
391 #(#arms_eq_discriminants),*
392 };
393 discriminant && match (self, other) {
394 #(#handle_variants),*
395 _ => true
396 }
397 }
398 }
399 Deriving::PartialOrd | Deriving::Ord => {
400 let some = match self {
401 Deriving::PartialOrd => Some(quote! { ::core::option::Option::Some }),
402 Deriving::Ord => None,
403 _ => unreachable!(),
404 };
405
406 let arms_cmp_discriminants = map_enum_discriminant_permutations(
407 variants,
408 |discriminant_left, variant_left, discriminant_right, variant_right| {
409 let ordering = match discriminant_left.cmp(&discriminant_right) {
410 Ordering::Less => quote!(Less),
411 Ordering::Equal => quote!(Equal),
412 Ordering::Greater => quote!(Greater),
413 };
414
415 quote! {
416 (
417 Self::#variant_left { .. },
418 Self::#variant_right { .. }
419 ) => { ::core::cmp::Ordering::#ordering }
420 }
421 },
422 );
423
424 quote! {
425 match (self, other) {
426 #(#handle_variants),*
427 _ => {
428 // if variants are not equal, then we just compare their discriminants
429 #some(match (self, other) {
430 #(#arms_cmp_discriminants)*
431 })
432 }
433 }
434 }
435 }
436 Deriving::Debug => quote! {
437 match &self {
438 #(#handle_variants,)*
439 }
440 },
441 Deriving::Hash => {
442 let arms = variants.iter().enumerate().map(|(discriminant, variant)| {
443 let ident = &variant.ident;
444 quote! { Self::#ident { .. } => #self::hash(&#discriminant, state) }
445 });
446 quote! {
447 match self {
448 #(#arms,)*
449 }
450 match &self {
451 #(#handle_variants)*
452 }
453 }
454 }
455 }
456 }
457
458 /// Generates logic for handling a single field of a variant
459 pub fn handle_variant_field(
460 self,
461 member: Member,
462 ) -> (EnumPatternField, proc_macro2::TokenStream) {
463 let member_str = member.to_token_stream().to_string();
464 match self {
465 Deriving::PartialEq => {
466 let left = format_ident!("__l_{}", member_str);
467 let right = format_ident!("__r_{}", member_str);
468
469 (
470 EnumPatternField::Two(left.clone(), right.clone()),
471 quote! {
472 if #left != #right {
473 return false;
474 }
475 },
476 )
477 }
478 Deriving::PartialOrd | Deriving::Ord => {
479 let (method, equal) = match self {
480 Deriving::PartialOrd => (
481 quote! { partial_cmp },
482 quote! { ::core::option::Option::Some(::core::cmp::Ordering::Equal) },
483 ),
484 Deriving::Ord => (quote! { cmp }, quote! { ::core::cmp::Ordering::Equal }),
485 _ => unreachable!(),
486 };
487
488 let left = format_ident!("__l_{member_str}");
489 let right = format_ident!("__r_{member_str}");
490
491 (
492 EnumPatternField::Two(left.clone(), right.clone()),
493 quote! {
494 match #self::#method(&#left, &#right) {
495 #equal => {},
496 cmp => return cmp,
497 }
498 },
499 )
500 }
501 Deriving::Debug => {
502 let ident = format_ident!("__{member_str}");
503 match &member {
504 Member::Named(ident) => {
505 let name = ident.to_string();
506 (
507 EnumPatternField::One(ident.clone()),
508 quote! { .field(#name, #ident) },
509 )
510 }
511 Member::Unnamed(_) => (
512 EnumPatternField::One(ident.clone()),
513 quote! { .field(#ident) },
514 ),
515 }
516 }
517 Deriving::Hash => {
518 let ident = format_ident!("__{member_str}");
519 (
520 EnumPatternField::One(ident.clone()),
521 quote! {
522 #self::hash(&#ident, state);
523 },
524 )
525 }
526 }
527 }
528
529 pub fn handle_struct(
530 self,
531 fields: impl Iterator<Item = TokenStream>,
532 name: &Ident,
533 is_tuple: bool,
534 ) -> TokenStream {
535 match self {
536 Deriving::PartialEq => quote! {
537 #(#fields)*
538 true
539 },
540 Deriving::PartialOrd => quote! {
541 #(#fields)*
542 ::core::option::Option::Some(::core::cmp::Ordering::Equal)
543 },
544 Deriving::Ord => quote! {
545 #(#fields)*
546 ::core::cmp::Ordering::Equal
547 },
548 Deriving::Debug => {
549 let name = name.to_string();
550 match is_tuple {
551 true => quote! {
552 f.debug_tuple(#name)
553 #(#fields)*
554 .finish()
555 },
556 false => quote! {
557 f.debug_struct(#name)
558 #(#fields)*
559 .finish()
560 },
561 }
562 }
563 Deriving::Hash => quote! {
564 #(#fields)*
565 },
566 }
567 }
568
569 /// If this derive ignores `field`
570 pub fn is_ignored_in(self, field: &Field, errors: &mut Vec<Error>) -> bool {
571 let mut partial_eq = false;
572 let mut partial_ord = false;
573 let mut ord = false;
574 let mut debug = false;
575 let mut hash = false;
576
577 for attr in &field.attrs {
578 if attr.path().is_ident("ignored") {
579 let result = attr.parse_nested_meta(|meta| {
580 let ident = meta.path.require_ident()?;
581
582 let value = match ident.to_string().as_str() {
583 "PartialEq" => &mut partial_eq,
584 "PartialOrd" => &mut partial_ord,
585 "Ord" => &mut ord,
586 "Debug" => &mut debug,
587 "Hash" => &mut hash,
588 _ => {
589 return Err(Error::new(
590 ident.span(),
591 concat!(
592 "expected one of: `PartialEq`, `PartialOrd`, ",
593 "`Ord`, `Debug`, `Hash`"
594 ),
595 ));
596 }
597 };
598
599 if *value {
600 errors.push(Error::new(
601 ident.span(),
602 "this derive has already been ignored",
603 ));
604 }
605
606 *value = true;
607
608 Ok(())
609 });
610
611 if let Err(error) = result {
612 errors.push(error);
613 }
614 }
615 }
616
617 match self {
618 Deriving::PartialEq => partial_eq,
619 Deriving::PartialOrd => partial_ord,
620 Deriving::Ord => ord,
621 Deriving::Debug => debug,
622 Deriving::Hash => hash,
623 }
624 }
625}
626
627/// Maps permutations of the enum `variants` with `f` into a `TokenStream`
628///
629/// Because Rust provides us no way of getting the discriminant value
630/// of an arbitrary enum, iterate through `n^2` permutations instead
631///
632/// For example, when deciding if enum `A`'s discriminant is greater than
633/// enum `B`'s discriminant (and both enums must be the same type), we
634/// `match` against every variant in `A`, and in each arm we again `match`
635/// against each variant in `B`. All sub-arms evaluate to `PartialOrd`.
636///
637/// This should optimize away at compile-time, but it could be done
638/// more nicely if we had a stable `core::intrinsics::discriminant_value`, for example
639/// the RFC <https://github.com/rust-lang/rfcs/pull/3607> would suffice.
640///
641/// # Correctness
642///
643/// This function is not always correct. For example, in this enum:
644///
645/// ```rust
646/// enum Foo {
647/// Foo = 3,
648/// Bar = 0,
649/// Baz
650/// }
651/// ```
652///
653/// `Foo::Foo`'s discriminant is greater than `Foo::Bar` or `Foo::Baz`,
654/// but our macro assigns `Foo::Foo` the smallest discriminant.
655///
656/// **There's no way to fix this**. Because value of the discriminant is
657/// only available after our macro generates code.
658///
659/// But we need to get value of the discriminant **inside** our proc macro
660/// in order to know what code to emit.
661///
662/// For the purposes of our macros, we don't care about **what** the actual discriminant
663/// value is - we only care that it's equal to, greater than or less than some
664/// other discriminant value of the same enum - which means it's sufficient
665/// to start discriminant at 0 for the first variant, and increment it for
666/// each variant.
667fn map_enum_discriminant_permutations(
668 variants: &Punctuated<Variant, Comma>,
669 f: fn(usize, &Ident, usize, &Ident) -> TokenStream,
670) -> impl Iterator<Item = TokenStream> + '_ {
671 variants
672 .iter()
673 .enumerate()
674 .flat_map(move |(discriminant_left, variant_left)| {
675 variants
676 .iter()
677 .enumerate()
678 .map(move |(discriminant_right, variant_right)| {
679 f(
680 discriminant_left,
681 &variant_left.ident,
682 discriminant_right,
683 &variant_right.ident,
684 )
685 })
686 })
687}
688
689/// We need to construct a pattern from a bunch of pieces, but this pattern
690/// can `match` either 1 enum or 2 enums.
691#[derive(std::fmt::Debug)]
692enum EnumPatternField {
693 /// For implementation of [`Debug`] and [`Hash`]
694 ///
695 /// ```ignore
696 /// match self {
697 /// Self::One { 0: __0 } => { /* ... */ },
698 /// // ...
699 /// }
700 /// ```
701 ///
702 /// `One` would hold `Ident(__0)`
703 One(Ident),
704 /// For implementation of [`trait@PartialOrd`], [`trait@Ord`] and [`trait@PartialEq`]
705 ///
706 /// ```ignore
707 /// match (self, other) {
708 /// (Self::One { 0: __l_0 }, Self::One { 0: __r_0 }) => { /* ... */ },
709 /// // ...
710 /// }
711 /// ```
712 ///
713 /// `Two` would hold `(Ident(__l_0), Ident(__r_0))`
714 Two(Ident, Ident),
715}
716
717impl ToTokens for EnumPatternField {
718 fn to_tokens(&self, tokens: &mut TokenStream) {
719 let new = match self {
720 EnumPatternField::One(ident) => quote! { #ident },
721 EnumPatternField::Two(left, right) => quote! { (#left, #right) },
722 };
723
724 tokens.extend(new);
725 }
726}
727
728macro_rules! create_derive {
729 { $Derive:ident $($tt:tt)* } => {
730 #[doc = concat!("Derives an `", stringify!($Derive), "` implementation.")]
731 ///
732 /// The only difference from the standard library's derive is that
733 #[doc = concat!("fields marked with `#[ignored(", stringify!($Derive), ")]`")]
734 /// will be ignored when implementing this trait
735 ///
736 /// See the [crate-level](crate) documentation for more info
737 #[proc_macro_derive($Derive, attributes(ignored))]
738 #[allow(non_snake_case)]
739 pub fn $Derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
740 let mut input = parse_macro_input!(input as DeriveInput);
741
742 let mut errors = Vec::new();
743 let output = generate(&mut input, Deriving::$Derive, &mut errors);
744 let errors = errors.into_iter().map(|error| error.into_compile_error());
745
746 quote! {
747 #output
748 #(#errors)*
749 }
750 .into()
751 }
752 };
753}
754use create_derive;