├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── src ├── trait_handlers │ ├── copy │ │ ├── models │ │ │ ├── mod.rs │ │ │ ├── field_attribute.rs │ │ │ └── type_attribute.rs │ │ └── mod.rs │ ├── eq │ │ ├── models │ │ │ ├── mod.rs │ │ │ ├── field_attribute.rs │ │ │ └── type_attribute.rs │ │ └── mod.rs │ ├── hash │ │ ├── models │ │ │ ├── mod.rs │ │ │ └── type_attribute.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ ├── hash_union.rs │ │ └── hash_struct.rs │ ├── into │ │ ├── models │ │ │ ├── mod.rs │ │ │ └── field_attribute.rs │ │ ├── common.rs │ │ ├── panic.rs │ │ └── mod.rs │ ├── ord │ │ ├── models │ │ │ ├── mod.rs │ │ │ └── type_attribute.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ └── ord_struct.rs │ ├── clone │ │ ├── models │ │ │ ├── mod.rs │ │ │ ├── field_attribute.rs │ │ │ └── type_attribute.rs │ │ ├── mod.rs │ │ └── clone_union.rs │ ├── debug │ │ ├── models │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── panic.rs │ │ ├── common.rs │ │ └── debug_union.rs │ ├── default │ │ ├── models │ │ │ └── mod.rs │ │ ├── panic.rs │ │ └── mod.rs │ ├── deref │ │ ├── models │ │ │ ├── mod.rs │ │ │ ├── type_attribute.rs │ │ │ └── field_attribute.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ └── deref_struct.rs │ ├── deref_mut │ │ ├── models │ │ │ ├── mod.rs │ │ │ ├── type_attribute.rs │ │ │ └── field_attribute.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ ├── deref_mut_struct.rs │ │ └── deref_mut_enum.rs │ ├── partial_eq │ │ ├── models │ │ │ └── mod.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ ├── partial_eq_union.rs │ │ └── partial_eq_struct.rs │ ├── partial_ord │ │ ├── models │ │ │ ├── mod.rs │ │ │ └── type_attribute.rs │ │ ├── panic.rs │ │ ├── mod.rs │ │ └── partial_ord_struct.rs │ └── mod.rs ├── common │ ├── tools │ │ ├── mod.rs │ │ └── hash_type.rs │ ├── unsafe_punctuated_meta.rs │ ├── mod.rs │ ├── type.rs │ ├── ident_index.rs │ ├── path.rs │ ├── bound.rs │ ├── int.rs │ ├── where_predicates_bool.rs │ ├── expr.rs │ └── ident_bool.rs ├── supported_traits.rs └── panic.rs ├── tests ├── copy_clone_union.rs ├── deref_enum.rs ├── deref_struct.rs ├── deref_mut_enum.rs ├── partial_eq_union.rs ├── eq_union.rs ├── hash_union.rs ├── deref_mut_struct.rs ├── name_clashes.rs ├── debug_union.rs ├── into_struct.rs ├── into_enum.rs ├── copy_clone_struct.rs ├── clone_enum.rs ├── copy_clone_enum.rs └── clone_struct.rs ├── LICENSE ├── Cargo.toml ├── rustfmt.toml └── .gitignore /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /src/trait_handlers/copy/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/eq/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/into/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/ord/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/clone/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/debug/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/default/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_eq/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_ord/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod field_attribute; 2 | mod type_attribute; 3 | 4 | pub(crate) use field_attribute::*; 5 | pub(crate) use type_attribute::*; 6 | -------------------------------------------------------------------------------- /src/trait_handlers/ord/panic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | 3 | #[inline] 4 | pub(crate) fn reuse_a_rank(span: Span, rank: isize) -> syn::Error { 5 | syn::Error::new(span, format!("the rank `{rank}` is repeatedly used")) 6 | } 7 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_ord/panic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | 3 | #[inline] 4 | pub(crate) fn reuse_a_rank(span: Span, rank: isize) -> syn::Error { 5 | syn::Error::new(span, format!("the rank `{rank}` is repeatedly used")) 6 | } 7 | -------------------------------------------------------------------------------- /src/common/tools/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "PartialOrd", feature = "Ord"))] 2 | mod discriminant_type; 3 | 4 | #[cfg(any(feature = "PartialOrd", feature = "Ord"))] 5 | pub(crate) use discriminant_type::*; 6 | 7 | #[cfg(feature = "Into")] 8 | mod hash_type; 9 | 10 | #[cfg(feature = "Into")] 11 | pub(crate) use hash_type::*; 12 | -------------------------------------------------------------------------------- /src/trait_handlers/into/common.rs: -------------------------------------------------------------------------------- 1 | use quote::quote_spanned; 2 | use syn::{spanned::Spanned, Type}; 3 | 4 | use crate::common::{r#type::dereference_changed, tools::HashType}; 5 | 6 | #[inline] 7 | pub(crate) fn to_hash_type(ty: &Type) -> HashType { 8 | let (ty, is_ref) = dereference_changed(ty); 9 | 10 | let ty = if is_ref { 11 | syn::parse2(quote_spanned!( ty.span() => &'static #ty )).unwrap() 12 | } else { 13 | ty.clone() 14 | }; 15 | 16 | HashType::from(ty) 17 | } 18 | -------------------------------------------------------------------------------- /src/trait_handlers/default/panic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | 3 | #[inline] 4 | pub(crate) fn multiple_default_fields(span: Span) -> syn::Error { 5 | syn::Error::new(span, "multiple default fields are set") 6 | } 7 | 8 | #[inline] 9 | pub(crate) fn no_default_field(span: Span) -> syn::Error { 10 | syn::Error::new(span, "there is no field set as default") 11 | } 12 | 13 | #[inline] 14 | pub(crate) fn multiple_default_variants(span: Span) -> syn::Error { 15 | syn::Error::new(span, "multiple default variants are set") 16 | } 17 | 18 | #[inline] 19 | pub(crate) fn no_default_variant(span: Span) -> syn::Error { 20 | syn::Error::new(span, "there is no variant set as default") 21 | } 22 | -------------------------------------------------------------------------------- /tests/copy_clone_union.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Copy", feature = "Clone"))] 2 | #![no_std] 3 | #![allow(clippy::clone_on_copy)] 4 | 5 | use educe::Educe; 6 | 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(Copy, Clone)] 11 | union Union { 12 | f1: u8, 13 | } 14 | 15 | let u = Union { 16 | f1: 1 17 | } 18 | .clone(); 19 | 20 | assert_eq!(1, unsafe { u.f1 }); 21 | } 22 | 23 | #[test] 24 | fn bound() { 25 | #[derive(Educe)] 26 | #[educe(Copy, Clone)] 27 | union Union { 28 | f1: T, 29 | } 30 | 31 | let u = Union { 32 | f1: 1 33 | } 34 | .clone(); 35 | 36 | assert_eq!(1, unsafe { u.f1 }); 37 | } 38 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/panic.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{spanned::Spanned, Meta}; 3 | 4 | #[inline] 5 | pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { 6 | let mut s = meta.into_token_stream().to_string(); 7 | 8 | match s.len() { 9 | 4 => s.push_str("(unsafe)"), 10 | 6 => s.insert_str(10, "unsafe"), 11 | _ => unreachable!(), 12 | } 13 | 14 | syn::Error::new( 15 | meta.span(), 16 | format!( 17 | "a union's `Hash` implementation is not precise, because it ignores the type of \ 18 | fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \ 19 | the `Hash` trait for it." 20 | ), 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_eq/panic.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{spanned::Spanned, Meta}; 3 | 4 | #[inline] 5 | pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { 6 | let mut s = meta.into_token_stream().to_string(); 7 | 8 | match s.len() { 9 | 9 => s.push_str("(unsafe)"), 10 | 11 => s.insert_str(10, "unsafe"), 11 | _ => unreachable!(), 12 | } 13 | 14 | syn::Error::new( 15 | meta.span(), 16 | format!( 17 | "a union's `PartialEq` implementation is not precise, because it ignores the type of \ 18 | fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \ 19 | the `PartialEq` trait for it." 20 | ), 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /tests/deref_enum.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Deref")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(Deref)] 11 | enum Enum { 12 | Struct { 13 | f1: u8, 14 | }, 15 | Struct2 { 16 | f1: u8, 17 | #[educe(Deref)] 18 | f2: u8, 19 | }, 20 | Tuple(u8), 21 | Tuple2(u8, #[educe(Deref)] u8), 22 | } 23 | 24 | let s1 = Enum::Struct { 25 | f1: 1 26 | }; 27 | 28 | let s2 = Enum::Struct2 { 29 | f1: 1, f2: 2 30 | }; 31 | 32 | let t1 = Enum::Tuple(1); 33 | 34 | let t2 = Enum::Tuple2(1, 2); 35 | 36 | assert_eq!(1, *s1); 37 | assert_eq!(2, *s2); 38 | 39 | assert_eq!(1, *t1); 40 | assert_eq!(2, *t2); 41 | } 42 | -------------------------------------------------------------------------------- /src/trait_handlers/into/panic.rs: -------------------------------------------------------------------------------- 1 | use crate::common::tools::HashType; 2 | 3 | #[inline] 4 | pub(crate) fn reset_a_type(ty: &HashType) -> syn::Error { 5 | syn::Error::new(ty.span(), format!("the type `{ty}` is repeatedly set")) 6 | } 7 | 8 | #[inline] 9 | pub(crate) fn no_into_field(ty: &HashType) -> syn::Error { 10 | syn::Error::new(ty.span(), format!("there is no field which is assigned for `Into<{ty}>`")) 11 | } 12 | 13 | #[inline] 14 | pub(crate) fn no_into_impl(ty: &HashType) -> syn::Error { 15 | syn::Error::new( 16 | ty.span(), 17 | format!( 18 | "if you want to impl `Into<{ty}>` for this type, you should write \ 19 | `#[educe(Into({ty}))]` outside" 20 | ), 21 | ) 22 | } 23 | 24 | #[inline] 25 | pub(crate) fn multiple_into_fields(ty: &HashType) -> syn::Error { 26 | syn::Error::new(ty.span(), format!("multiple fields are set for `Into<{ty}>`")) 27 | } 28 | -------------------------------------------------------------------------------- /tests/deref_struct.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Deref")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(Deref)] 11 | struct Struct { 12 | f1: u8, 13 | } 14 | 15 | #[derive(Educe)] 16 | #[educe(Deref)] 17 | struct Struct2 { 18 | f1: u8, 19 | #[educe(Deref)] 20 | f2: u8, 21 | } 22 | 23 | #[derive(Educe)] 24 | #[educe(Deref)] 25 | struct Tuple(u8); 26 | 27 | #[derive(Educe)] 28 | #[educe(Deref)] 29 | struct Tuple2(u8, #[educe(Deref)] u8); 30 | 31 | let s1 = Struct { 32 | f1: 1 33 | }; 34 | 35 | let s2 = Struct2 { 36 | f1: 1, f2: 2 37 | }; 38 | 39 | let t1 = Tuple(1); 40 | let t2 = Tuple2(1, 2); 41 | 42 | assert_eq!(1, *s1); 43 | assert_eq!(2, *s2); 44 | 45 | assert_eq!(1, *t1); 46 | assert_eq!(2, *t2); 47 | } 48 | -------------------------------------------------------------------------------- /src/common/unsafe_punctuated_meta.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse::{Parse, ParseStream}, 3 | punctuated::Punctuated, 4 | Meta, Token, 5 | }; 6 | 7 | pub(crate) struct UnsafePunctuatedMeta { 8 | pub(crate) list: Punctuated, 9 | pub(crate) has_unsafe: bool, 10 | } 11 | 12 | impl Parse for UnsafePunctuatedMeta { 13 | #[inline] 14 | fn parse(input: ParseStream) -> syn::Result { 15 | let has_unsafe = input.parse::().is_ok(); 16 | 17 | if input.is_empty() { 18 | return Ok(Self { 19 | list: Punctuated::new(), 20 | has_unsafe, 21 | }); 22 | } 23 | 24 | if has_unsafe { 25 | input.parse::()?; 26 | } 27 | 28 | let list = input.parse_terminated(Meta::parse, Token![,])?; 29 | 30 | Ok(Self { 31 | list, 32 | has_unsafe, 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/panic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::Variant; 3 | 4 | #[inline] 5 | pub(crate) fn multiple_deref_fields(span: Span) -> syn::Error { 6 | syn::Error::new(span, "multiple fields are set for `Deref`") 7 | } 8 | 9 | #[inline] 10 | pub(crate) fn multiple_deref_fields_of_variant(span: Span, variant: &Variant) -> syn::Error { 11 | syn::Error::new( 12 | span, 13 | format!("multiple fields of the `{}` variant are set for `Deref`", variant.ident), 14 | ) 15 | } 16 | 17 | #[inline] 18 | pub(crate) fn no_deref_field(span: Span) -> syn::Error { 19 | syn::Error::new(span, "there is no field which is assigned for `Deref`") 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn no_deref_field_of_variant(span: Span, variant: &Variant) -> syn::Error { 24 | syn::Error::new( 25 | span, 26 | format!( 27 | "there is no field for the `{}` variant which is assigned for `Deref`", 28 | variant.ident 29 | ), 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /tests/deref_mut_enum.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Deref", feature = "DerefMut"))] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[test] 7 | #[allow(dead_code)] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(Deref, DerefMut)] 11 | enum Enum { 12 | Struct { 13 | f1: u8, 14 | }, 15 | Struct2 { 16 | f1: u8, 17 | #[educe(Deref, DerefMut)] 18 | f2: u8, 19 | }, 20 | Tuple(u8), 21 | Tuple2(u8, #[educe(Deref, DerefMut)] u8), 22 | } 23 | 24 | let mut s1 = Enum::Struct { 25 | f1: 1 26 | }; 27 | 28 | let mut s2 = Enum::Struct2 { 29 | f1: 1, f2: 2 30 | }; 31 | 32 | let mut t1 = Enum::Tuple(1); 33 | 34 | let mut t2 = Enum::Tuple2(1, 2); 35 | 36 | *s1 += 100; 37 | *s2 += 100; 38 | 39 | *t1 += 100; 40 | *t2 += 100; 41 | 42 | assert_eq!(101, *s1); 43 | assert_eq!(102, *s2); 44 | 45 | assert_eq!(101, *t1); 46 | assert_eq!(102, *t2); 47 | } 48 | -------------------------------------------------------------------------------- /tests/partial_eq_union.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "PartialEq")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(PartialEq(unsafe))] 11 | union Union { 12 | f1: u8, 13 | } 14 | 15 | assert!( 16 | Union { 17 | f1: 1 18 | } == Union { 19 | f1: 1 20 | } 21 | ); 22 | 23 | assert!( 24 | Union { 25 | f1: 1 26 | } != Union { 27 | f1: 2 28 | } 29 | ); 30 | } 31 | 32 | #[allow(dead_code)] 33 | #[test] 34 | fn bound() { 35 | #[derive(Educe)] 36 | #[educe(PartialEq(unsafe))] 37 | union Union { 38 | f1: T, 39 | } 40 | 41 | assert!( 42 | Union { 43 | f1: 1 44 | } == Union { 45 | f1: 1 46 | } 47 | ); 48 | 49 | assert!( 50 | Union { 51 | f1: 1 52 | } != Union { 53 | f1: 2 54 | } 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/panic.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::Variant; 3 | 4 | #[inline] 5 | pub(crate) fn multiple_deref_mut_fields(span: Span) -> syn::Error { 6 | syn::Error::new(span, "multiple fields are set for `DerefMut`") 7 | } 8 | 9 | #[inline] 10 | pub(crate) fn multiple_deref_mut_fields_of_variant(span: Span, variant: &Variant) -> syn::Error { 11 | syn::Error::new( 12 | span, 13 | format!("multiple fields of the `{}` variant are set for `DerefMut`", variant.ident), 14 | ) 15 | } 16 | 17 | #[inline] 18 | pub(crate) fn no_deref_mut_field(span: Span) -> syn::Error { 19 | syn::Error::new(span, "there is no field which is assigned for `DerefMut`") 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn no_deref_mut_field_of_variant(span: Span, variant: &Variant) -> syn::Error { 24 | syn::Error::new( 25 | span, 26 | format!( 27 | "there is no field for the `{}` variant which is assigned for `DerefMut`", 28 | variant.ident 29 | ), 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /tests/eq_union.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Eq", feature = "PartialEq"))] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(PartialEq(unsafe), Eq)] 11 | union Union { 12 | f1: u8, 13 | } 14 | 15 | assert!( 16 | Union { 17 | f1: 1 18 | } == Union { 19 | f1: 1 20 | } 21 | ); 22 | 23 | assert!( 24 | Union { 25 | f1: 1 26 | } != Union { 27 | f1: 2 28 | } 29 | ); 30 | } 31 | 32 | #[allow(dead_code)] 33 | #[test] 34 | fn bound() { 35 | #[derive(Educe)] 36 | #[educe(PartialEq(unsafe))] 37 | union Union { 38 | f1: T, 39 | } 40 | 41 | assert!( 42 | Union { 43 | f1: 1 44 | } == Union { 45 | f1: 1 46 | } 47 | ); 48 | 49 | assert!( 50 | Union { 51 | f1: 1 52 | } != Union { 53 | f1: 2 54 | } 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/mod.rs: -------------------------------------------------------------------------------- 1 | mod hash_enum; 2 | mod hash_struct; 3 | mod hash_union; 4 | mod models; 5 | mod panic; 6 | 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandler; 10 | use crate::Trait; 11 | 12 | pub(crate) struct HashHandler; 13 | 14 | impl TraitHandler for HashHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | match ast.data { 23 | Data::Struct(_) => { 24 | hash_struct::HashStructHandler::trait_meta_handler(ast, token_stream, traits, meta) 25 | }, 26 | Data::Enum(_) => { 27 | hash_enum::HashEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 28 | }, 29 | Data::Union(_) => { 30 | hash_union::HashUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) 31 | }, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/trait_handlers/into/mod.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod into_enum; 3 | mod into_struct; 4 | mod models; 5 | mod panic; 6 | 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandlerMultiple; 10 | use crate::Trait; 11 | 12 | pub(crate) struct IntoHandler; 13 | 14 | impl TraitHandlerMultiple for IntoHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &[Meta], 21 | ) -> syn::Result<()> { 22 | match ast.data { 23 | Data::Struct(_) => { 24 | into_struct::IntoStructHandler::trait_meta_handler(ast, token_stream, traits, meta) 25 | }, 26 | Data::Enum(_) => { 27 | into_enum::IntoEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 28 | }, 29 | Data::Union(_) => { 30 | Err(crate::panic::trait_not_support_union(meta[0].path().get_ident().unwrap())) 31 | }, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/hash_union.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "PartialEq", feature = "Eq", feature = "Hash"))] 2 | 3 | use std::collections::HashSet; 4 | 5 | use educe::Educe; 6 | 7 | #[allow(dead_code)] 8 | #[test] 9 | fn basic() { 10 | #[derive(Educe)] 11 | #[educe(PartialEq(unsafe), Eq, Hash(unsafe))] 12 | union Union { 13 | f1: u8, 14 | } 15 | 16 | let mut set = HashSet::new(); 17 | 18 | set.insert(Union { 19 | f1: 0 20 | }); 21 | 22 | assert!(set.contains(&Union { 23 | f1: 0 24 | })); 25 | 26 | assert!(!set.contains(&Union { 27 | f1: 1 28 | })); 29 | } 30 | 31 | #[allow(dead_code)] 32 | #[test] 33 | fn bound() { 34 | #[derive(Educe)] 35 | #[educe(PartialEq(unsafe), Eq, Hash(unsafe))] 36 | union Union { 37 | f1: T, 38 | } 39 | 40 | let mut set = HashSet::new(); 41 | 42 | set.insert(Union { 43 | f1: 0 44 | }); 45 | 46 | assert!(set.contains(&Union { 47 | f1: 0 48 | })); 49 | 50 | assert!(!set.contains(&Union { 51 | f1: 1 52 | })); 53 | } 54 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/mod.rs: -------------------------------------------------------------------------------- 1 | mod deref_enum; 2 | mod deref_struct; 3 | mod models; 4 | mod panic; 5 | 6 | use syn::{Data, DeriveInput, Meta}; 7 | 8 | use super::TraitHandler; 9 | use crate::Trait; 10 | 11 | pub(crate) struct DerefHandler; 12 | 13 | impl TraitHandler for DerefHandler { 14 | #[inline] 15 | fn trait_meta_handler( 16 | ast: &DeriveInput, 17 | token_stream: &mut proc_macro2::TokenStream, 18 | traits: &[Trait], 19 | meta: &Meta, 20 | ) -> syn::Result<()> { 21 | match ast.data { 22 | Data::Struct(_) => deref_struct::DerefStructHandler::trait_meta_handler( 23 | ast, 24 | token_stream, 25 | traits, 26 | meta, 27 | ), 28 | Data::Enum(_) => { 29 | deref_enum::DerefEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 30 | }, 31 | Data::Union(_) => { 32 | Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) 33 | }, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/trait_handlers/clone/mod.rs: -------------------------------------------------------------------------------- 1 | mod clone_enum; 2 | mod clone_struct; 3 | mod clone_union; 4 | mod models; 5 | 6 | use syn::{Data, DeriveInput, Meta}; 7 | 8 | use super::TraitHandler; 9 | use crate::Trait; 10 | 11 | pub(crate) struct CloneHandler; 12 | 13 | impl TraitHandler for CloneHandler { 14 | #[inline] 15 | fn trait_meta_handler( 16 | ast: &DeriveInput, 17 | token_stream: &mut proc_macro2::TokenStream, 18 | traits: &[Trait], 19 | meta: &Meta, 20 | ) -> syn::Result<()> { 21 | match ast.data { 22 | Data::Struct(_) => clone_struct::CloneStructHandler::trait_meta_handler( 23 | ast, 24 | token_stream, 25 | traits, 26 | meta, 27 | ), 28 | Data::Enum(_) => { 29 | clone_enum::CloneEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 30 | }, 31 | Data::Union(_) => { 32 | clone_union::CloneUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) 33 | }, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/deref_mut_struct.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Deref", feature = "DerefMut"))] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic() { 9 | #[derive(Educe)] 10 | #[educe(Deref, DerefMut)] 11 | struct Struct { 12 | f1: u8, 13 | } 14 | 15 | #[derive(Educe)] 16 | #[educe(Deref, DerefMut)] 17 | struct Struct2 { 18 | f1: u8, 19 | #[educe(Deref, DerefMut)] 20 | f2: u8, 21 | } 22 | 23 | #[derive(Educe)] 24 | #[educe(Deref, DerefMut)] 25 | struct Tuple(u8); 26 | 27 | #[derive(Educe)] 28 | #[educe(Deref, DerefMut)] 29 | struct Tuple2(u8, #[educe(Deref, DerefMut)] u8); 30 | 31 | let mut s1 = Struct { 32 | f1: 1 33 | }; 34 | 35 | let mut s2 = Struct2 { 36 | f1: 1, f2: 2 37 | }; 38 | 39 | let mut t1 = Tuple(1); 40 | let mut t2 = Tuple2(1, 2); 41 | 42 | *s1 += 100; 43 | *s2 += 100; 44 | 45 | *t1 += 100; 46 | *t2 += 100; 47 | 48 | assert_eq!(101, *s1); 49 | assert_eq!(102, *s2); 50 | 51 | assert_eq!(101, *t1); 52 | assert_eq!(102, *t2); 53 | } 54 | -------------------------------------------------------------------------------- /src/trait_handlers/debug/mod.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod debug_enum; 3 | mod debug_struct; 4 | mod debug_union; 5 | mod models; 6 | mod panic; 7 | 8 | use syn::{Data, DeriveInput, Meta}; 9 | 10 | use super::TraitHandler; 11 | use crate::Trait; 12 | 13 | pub(crate) struct DebugHandler; 14 | 15 | impl TraitHandler for DebugHandler { 16 | #[inline] 17 | fn trait_meta_handler( 18 | ast: &DeriveInput, 19 | token_stream: &mut proc_macro2::TokenStream, 20 | traits: &[Trait], 21 | meta: &Meta, 22 | ) -> syn::Result<()> { 23 | match ast.data { 24 | Data::Struct(_) => debug_struct::DebugStructHandler::trait_meta_handler( 25 | ast, 26 | token_stream, 27 | traits, 28 | meta, 29 | ), 30 | Data::Enum(_) => { 31 | debug_enum::DebugEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 32 | }, 33 | Data::Union(_) => { 34 | debug_union::DebugUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) 35 | }, 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/mod.rs: -------------------------------------------------------------------------------- 1 | mod deref_mut_enum; 2 | mod deref_mut_struct; 3 | mod models; 4 | mod panic; 5 | 6 | use syn::{Data, DeriveInput, Meta}; 7 | 8 | use super::TraitHandler; 9 | use crate::Trait; 10 | 11 | pub(crate) struct DerefMutHandler; 12 | 13 | impl TraitHandler for DerefMutHandler { 14 | #[inline] 15 | fn trait_meta_handler( 16 | ast: &DeriveInput, 17 | token_stream: &mut proc_macro2::TokenStream, 18 | traits: &[Trait], 19 | meta: &Meta, 20 | ) -> syn::Result<()> { 21 | match ast.data { 22 | Data::Struct(_) => deref_mut_struct::DerefMutStructHandler::trait_meta_handler( 23 | ast, 24 | token_stream, 25 | traits, 26 | meta, 27 | ), 28 | Data::Enum(_) => deref_mut_enum::DerefMutEnumHandler::trait_meta_handler( 29 | ast, 30 | token_stream, 31 | traits, 32 | meta, 33 | ), 34 | Data::Union(_) => { 35 | Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) 36 | }, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub(crate) mod bound; 3 | #[allow(dead_code)] 4 | pub(crate) mod path; 5 | #[allow(dead_code)] 6 | pub(crate) mod r#type; 7 | #[allow(dead_code)] 8 | pub(crate) mod where_predicates_bool; 9 | 10 | #[cfg(feature = "Default")] 11 | #[allow(dead_code)] 12 | pub(crate) mod expr; 13 | #[cfg(any( 14 | feature = "Debug", 15 | feature = "PartialEq", 16 | feature = "PartialOrd", 17 | feature = "Ord", 18 | feature = "Hash", 19 | feature = "Default" 20 | ))] 21 | #[allow(dead_code)] 22 | pub(crate) mod ident_bool; 23 | #[cfg(any( 24 | feature = "Debug", 25 | feature = "PartialEq", 26 | feature = "PartialOrd", 27 | feature = "Ord", 28 | feature = "Hash", 29 | feature = "Deref", 30 | feature = "DerefMut", 31 | feature = "Into" 32 | ))] 33 | #[allow(dead_code)] 34 | pub(crate) mod ident_index; 35 | #[cfg(any(feature = "PartialOrd", feature = "Ord"))] 36 | #[allow(dead_code)] 37 | pub(crate) mod int; 38 | #[cfg(any(feature = "Debug", feature = "PartialEq", feature = "Hash"))] 39 | #[allow(dead_code)] 40 | pub(crate) mod unsafe_punctuated_meta; 41 | 42 | #[cfg(any(feature = "PartialOrd", feature = "Ord", feature = "Into"))] 43 | pub(crate) mod tools; 44 | -------------------------------------------------------------------------------- /src/trait_handlers/default/mod.rs: -------------------------------------------------------------------------------- 1 | mod default_enum; 2 | mod default_struct; 3 | mod default_union; 4 | mod models; 5 | mod panic; 6 | 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandler; 10 | use crate::Trait; 11 | 12 | pub(crate) struct DefaultHandler; 13 | 14 | impl TraitHandler for DefaultHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | match ast.data { 23 | Data::Struct(_) => default_struct::DefaultStructHandler::trait_meta_handler( 24 | ast, 25 | token_stream, 26 | traits, 27 | meta, 28 | ), 29 | Data::Enum(_) => default_enum::DefaultEnumHandler::trait_meta_handler( 30 | ast, 31 | token_stream, 32 | traits, 33 | meta, 34 | ), 35 | Data::Union(_) => default_union::DefaultUnionHandler::trait_meta_handler( 36 | ast, 37 | token_stream, 38 | traits, 39 | meta, 40 | ), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/common/type.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse::{Parse, ParseStream}, 3 | punctuated::Punctuated, 4 | Meta, Token, Type, 5 | }; 6 | 7 | pub(crate) struct TypeWithPunctuatedMeta { 8 | pub(crate) ty: Type, 9 | pub(crate) list: Punctuated, 10 | } 11 | 12 | impl Parse for TypeWithPunctuatedMeta { 13 | #[inline] 14 | fn parse(input: ParseStream) -> syn::Result { 15 | let ty = input.parse::()?; 16 | 17 | if input.is_empty() { 18 | return Ok(Self { 19 | ty, 20 | list: Punctuated::new(), 21 | }); 22 | } 23 | 24 | input.parse::()?; 25 | 26 | let list = input.parse_terminated(Meta::parse, Token![,])?; 27 | 28 | Ok(Self { 29 | ty, 30 | list, 31 | }) 32 | } 33 | } 34 | 35 | #[inline] 36 | pub(crate) fn dereference(ty: &Type) -> &Type { 37 | if let Type::Reference(ty) = ty { 38 | dereference(ty.elem.as_ref()) 39 | } else { 40 | ty 41 | } 42 | } 43 | 44 | #[inline] 45 | pub(crate) fn dereference_changed(ty: &Type) -> (&Type, bool) { 46 | if let Type::Reference(ty) = ty { 47 | (dereference(ty.elem.as_ref()), true) 48 | } else { 49 | (ty, false) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_eq/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod panic; 3 | mod partial_eq_enum; 4 | mod partial_eq_struct; 5 | mod partial_eq_union; 6 | 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandler; 10 | use crate::Trait; 11 | 12 | pub(crate) struct PartialEqHandler; 13 | 14 | impl TraitHandler for PartialEqHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | match ast.data { 23 | Data::Struct(_) => partial_eq_struct::PartialEqStructHandler::trait_meta_handler( 24 | ast, 25 | token_stream, 26 | traits, 27 | meta, 28 | ), 29 | Data::Enum(_) => partial_eq_enum::PartialEqEnumHandler::trait_meta_handler( 30 | ast, 31 | token_stream, 32 | traits, 33 | meta, 34 | ), 35 | Data::Union(_) => partial_eq_union::PartialEqUnionHandler::trait_meta_handler( 36 | ast, 37 | token_stream, 38 | traits, 39 | meta, 40 | ), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/name_clashes.rs: -------------------------------------------------------------------------------- 1 | // These types test that we don't have name clashes between field names and our local variables and parameter names. 2 | 3 | #![cfg(feature = "default")] 4 | #![allow(dead_code)] 5 | 6 | use educe::Educe; 7 | 8 | #[derive(Educe)] 9 | #[educe(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | pub enum NameClashesEnum { 11 | Variant { f: i8, builder: i16, source: i32, other: i64, state: i128 }, 12 | } 13 | 14 | #[derive(Educe)] 15 | #[educe(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] 16 | pub enum NameClashesEnumNoOrd { 17 | Variant { f: i8, builder: i16, source: i32, other: i64, state: i128 }, 18 | } 19 | 20 | #[derive(Educe)] 21 | #[educe(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub struct NameClashesStruct { 23 | f: i8, 24 | builder: i16, 25 | source: i32, 26 | other: i64, 27 | state: i128, 28 | } 29 | 30 | #[derive(Educe)] 31 | #[educe(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] 32 | pub struct NameClashesStructNoOrd { 33 | f: i8, 34 | builder: i16, 35 | source: i32, 36 | other: i64, 37 | state: i128, 38 | } 39 | 40 | #[derive(Educe)] 41 | #[educe(Debug(unsafe), PartialEq(unsafe), Eq)] 42 | pub union NameClashesUnion { 43 | f: i8, 44 | builder: i16, 45 | other: i64, 46 | } 47 | -------------------------------------------------------------------------------- /src/trait_handlers/mod.rs: -------------------------------------------------------------------------------- 1 | use syn::{DeriveInput, Meta}; 2 | 3 | use crate::Trait; 4 | 5 | #[cfg(feature = "Clone")] 6 | pub(crate) mod clone; 7 | #[cfg(feature = "Copy")] 8 | pub(crate) mod copy; 9 | #[cfg(feature = "Debug")] 10 | pub(crate) mod debug; 11 | #[cfg(feature = "Default")] 12 | pub(crate) mod default; 13 | #[cfg(feature = "Deref")] 14 | pub(crate) mod deref; 15 | #[cfg(feature = "DerefMut")] 16 | pub(crate) mod deref_mut; 17 | #[cfg(feature = "Eq")] 18 | pub(crate) mod eq; 19 | #[cfg(feature = "Hash")] 20 | pub(crate) mod hash; 21 | #[cfg(feature = "Into")] 22 | pub(crate) mod into; 23 | #[cfg(feature = "Ord")] 24 | pub(crate) mod ord; 25 | #[cfg(feature = "PartialEq")] 26 | pub(crate) mod partial_eq; 27 | #[cfg(feature = "PartialOrd")] 28 | pub(crate) mod partial_ord; 29 | 30 | pub(crate) trait TraitHandler { 31 | #[allow(dead_code)] 32 | fn trait_meta_handler( 33 | ast: &DeriveInput, 34 | token_stream: &mut proc_macro2::TokenStream, 35 | traits: &[Trait], 36 | meta: &Meta, 37 | ) -> syn::Result<()>; 38 | } 39 | 40 | pub(crate) trait TraitHandlerMultiple { 41 | #[allow(dead_code)] 42 | fn trait_meta_handler( 43 | ast: &DeriveInput, 44 | token_stream: &mut proc_macro2::TokenStream, 45 | traits: &[Trait], 46 | meta: &[Meta], 47 | ) -> syn::Result<()>; 48 | } 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "educe" 3 | version = "0.6.0" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.60" 7 | repository = "https://github.com/magiclen/educe" 8 | homepage = "https://magiclen.org/educe" 9 | keywords = ["derive", "macro", "trait", "field", "procedural"] 10 | categories = ["no-std", "rust-patterns"] 11 | description = "This crate offers procedural macros designed to facilitate the swift implementation of Rust's built-in traits." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | syn = "2" 20 | quote = "1" 21 | proc-macro2 = "1" 22 | 23 | enum-ordinalize = { version = "4.2", default-features = false, features = ["derive"] } 24 | 25 | [dev-dependencies] 26 | syn = { version = "2", features = ["full"] } 27 | assert-eq-float = "0.1" 28 | rustversion = "1" 29 | 30 | [features] 31 | default = ["Debug", "Clone", "Copy", "PartialEq", "Eq", "PartialOrd", "Ord", "Hash", "Default", "Deref", "DerefMut", "Into"] 32 | 33 | full = ["syn/full"] 34 | 35 | Debug = [] 36 | Clone = [] 37 | Copy = [] 38 | PartialEq = [] 39 | Eq = [] 40 | PartialOrd = [] 41 | Ord = [] 42 | Hash = [] 43 | Default = [] 44 | Deref = [] 45 | DerefMut = [] 46 | Into = [] 47 | 48 | [package.metadata.docs.rs] 49 | all-features = true 50 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /src/common/ident_index.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{Ident, Index}; 3 | 4 | pub(crate) enum IdentOrIndex { 5 | Ident(Ident), 6 | Index(Index), 7 | } 8 | 9 | impl From for IdentOrIndex { 10 | #[inline] 11 | fn from(value: Ident) -> Self { 12 | Self::Ident(value) 13 | } 14 | } 15 | 16 | impl From for IdentOrIndex { 17 | #[inline] 18 | fn from(value: Index) -> Self { 19 | Self::Index(value) 20 | } 21 | } 22 | 23 | impl From<&Ident> for IdentOrIndex { 24 | #[inline] 25 | fn from(value: &Ident) -> Self { 26 | Self::Ident(value.clone()) 27 | } 28 | } 29 | 30 | impl From for IdentOrIndex { 31 | #[inline] 32 | fn from(value: usize) -> Self { 33 | Self::Index(Index::from(value)) 34 | } 35 | } 36 | 37 | impl ToTokens for IdentOrIndex { 38 | #[inline] 39 | fn to_tokens(&self, token_stream: &mut proc_macro2::TokenStream) { 40 | match self { 41 | Self::Ident(ident) => ToTokens::to_tokens(ident, token_stream), 42 | Self::Index(index) => ToTokens::to_tokens(index, token_stream), 43 | } 44 | } 45 | } 46 | 47 | impl IdentOrIndex { 48 | #[inline] 49 | pub(crate) fn from_ident_with_index(ident: Option<&Ident>, index: usize) -> IdentOrIndex { 50 | if let Some(ident) = ident { 51 | Self::from(ident) 52 | } else { 53 | Self::from(index) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/common/path.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{spanned::Spanned, Expr, Lit, LitStr, Meta, MetaNameValue, Path}; 3 | 4 | #[inline] 5 | pub(crate) fn meta_name_value_2_path(name_value: &MetaNameValue) -> syn::Result { 6 | match &name_value.value { 7 | Expr::Lit(lit) => { 8 | if let Lit::Str(lit) = &lit.lit { 9 | return lit.parse(); 10 | } 11 | }, 12 | Expr::Path(path) => return Ok(path.path.clone()), 13 | _ => (), 14 | } 15 | 16 | Err(syn::Error::new( 17 | name_value.value.span(), 18 | format!("expected `{path} = Path`", path = path_to_string(&name_value.path)), 19 | )) 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn meta_2_path(meta: &Meta) -> syn::Result { 24 | match &meta { 25 | Meta::NameValue(name_value) => meta_name_value_2_path(name_value), 26 | Meta::List(list) => { 27 | if let Ok(lit) = list.parse_args::() { 28 | lit.parse() 29 | } else { 30 | list.parse_args() 31 | } 32 | }, 33 | Meta::Path(path) => Err(syn::Error::new( 34 | path.span(), 35 | format!("expected `{path} = Path` or `{path}(Path)`", path = path_to_string(path)), 36 | )), 37 | } 38 | } 39 | 40 | #[inline] 41 | pub(crate) fn path_to_string(path: &Path) -> String { 42 | path.into_token_stream().to_string().replace(' ', "") 43 | } 44 | -------------------------------------------------------------------------------- /src/trait_handlers/debug/panic.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{spanned::Spanned, Ident, Meta, Variant}; 3 | 4 | #[inline] 5 | pub(crate) fn unit_struct_need_name(name: &Ident) -> syn::Error { 6 | syn::Error::new(name.span(), "a unit struct needs to have a name") 7 | } 8 | 9 | #[inline] 10 | pub(crate) fn unit_variant_need_name(variant: &Variant) -> syn::Error { 11 | syn::Error::new( 12 | variant.span(), 13 | "a unit variant which doesn't use an enum name needs to have a name", 14 | ) 15 | } 16 | 17 | #[inline] 18 | pub(crate) fn unit_enum_need_name(name: &Ident) -> syn::Error { 19 | syn::Error::new(name.span(), "a unit enum needs to have a name") 20 | } 21 | 22 | #[inline] 23 | pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { 24 | let mut s = meta.into_token_stream().to_string().replace(" , ", ", "); 25 | 26 | match s.len() { 27 | 5 => s.push_str("(unsafe)"), 28 | 7 => s.insert_str(6, "unsafe"), 29 | _ => s.insert_str(6, "unsafe, "), 30 | } 31 | 32 | syn::Error::new( 33 | meta.span(), 34 | format!( 35 | "a union's `Debug` implementation may expose uninitialized memory\n* It is \ 36 | recommended that, for a union where `Debug` is implemented, types that allow \ 37 | uninitialized memory should not be used in it.\n* If you can ensure that the union \ 38 | uses no such types, use `#[educe({s})]` to implement the `Debug` trait for it.\n* \ 39 | The `unsafe` keyword should be placed as the first parameter of the `Debug` \ 40 | attribute." 41 | ), 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/trait_handlers/ord/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod ord_enum; 3 | mod ord_struct; 4 | mod panic; 5 | 6 | use quote::quote; 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandler; 10 | use crate::Trait; 11 | 12 | pub(crate) struct OrdHandler; 13 | 14 | impl TraitHandler for OrdHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | match ast.data { 23 | Data::Struct(_) => { 24 | ord_struct::OrdStructHandler::trait_meta_handler(ast, token_stream, traits, meta) 25 | }, 26 | Data::Enum(_) => { 27 | ord_enum::OrdEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) 28 | }, 29 | Data::Union(_) => { 30 | Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) 31 | }, 32 | } 33 | } 34 | } 35 | 36 | fn supertraits(#[allow(unused_variables)] traits: &[Trait]) -> Vec { 37 | let mut supertraits = vec![]; 38 | supertraits.push(quote! {::core::cmp::Eq}); 39 | 40 | // We mustn't add the PartialOrd bound to the educed PartialOrd impl. 41 | // When we're educing PartialOrd we can leave it off the Ord impl too, 42 | // since we *know* Self is going to be PartialOrd. 43 | #[cfg(feature = "PartialOrd")] 44 | if !traits.contains(&Trait::PartialOrd) { 45 | supertraits.push(quote! {::core::cmp::PartialOrd}); 46 | }; 47 | 48 | supertraits 49 | } 50 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_ord/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | mod panic; 3 | mod partial_ord_enum; 4 | mod partial_ord_struct; 5 | 6 | use models::TypeAttributeBuilder; 7 | use syn::{Data, DeriveInput, Meta}; 8 | 9 | use super::TraitHandler; 10 | use crate::Trait; 11 | 12 | pub(crate) struct PartialOrdHandler; 13 | 14 | impl TraitHandler for PartialOrdHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | #[cfg(feature = "Ord")] 23 | let contains_ord = traits.contains(&Trait::Ord); 24 | 25 | #[cfg(not(feature = "Ord"))] 26 | let contains_ord = false; 27 | 28 | // if `contains_ord` is true, the implementation is handled by the `Ord` attribute 29 | if contains_ord { 30 | let _ = TypeAttributeBuilder { 31 | enable_flag: true, enable_bound: false 32 | } 33 | .build_from_partial_ord_meta(meta)?; 34 | 35 | // field attributes is also handled by the `Ord` attribute 36 | 37 | Ok(()) 38 | } else { 39 | match ast.data { 40 | Data::Struct(_) => partial_ord_struct::PartialOrdStructHandler::trait_meta_handler( 41 | ast, 42 | token_stream, 43 | traits, 44 | meta, 45 | ), 46 | Data::Enum(_) => partial_ord_enum::PartialOrdEnumHandler::trait_meta_handler( 47 | ast, 48 | token_stream, 49 | traits, 50 | meta, 51 | ), 52 | Data::Union(_) => { 53 | Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) 54 | }, 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/hash_union.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta}; 3 | 4 | use super::models::{FieldAttributeBuilder, TypeAttributeBuilder}; 5 | use crate::{supported_traits::Trait, trait_handlers::TraitHandler}; 6 | 7 | pub(crate) struct HashUnionHandler; 8 | 9 | impl TraitHandler for HashUnionHandler { 10 | #[inline] 11 | fn trait_meta_handler( 12 | ast: &DeriveInput, 13 | token_stream: &mut proc_macro2::TokenStream, 14 | traits: &[Trait], 15 | meta: &Meta, 16 | ) -> syn::Result<()> { 17 | let type_attribute = 18 | TypeAttributeBuilder { 19 | enable_flag: true, enable_unsafe: true, enable_bound: false 20 | } 21 | .build_from_hash_meta(meta)?; 22 | 23 | if !type_attribute.has_unsafe { 24 | return Err(super::panic::union_without_unsafe(meta)); 25 | } 26 | 27 | if let Data::Union(data) = &ast.data { 28 | for field in data.fields.named.iter() { 29 | let _ = FieldAttributeBuilder { 30 | enable_ignore: false, enable_method: false 31 | } 32 | .build_from_attributes(&field.attrs, traits)?; 33 | } 34 | } 35 | 36 | let ident = &ast.ident; 37 | 38 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 39 | 40 | token_stream.extend(quote! { 41 | impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { 42 | #[inline] 43 | fn hash(&self, state: &mut H) { 44 | let size = ::core::mem::size_of::(); 45 | let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; 46 | 47 | ::core::hash::Hash::hash(data, state) 48 | } 49 | } 50 | }); 51 | 52 | Ok(()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/common/bound.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, token::Comma, GenericParam, Meta, Path, Type, WherePredicate}; 2 | 3 | use crate::common::where_predicates_bool::{ 4 | create_where_predicates_from_all_generic_parameters, 5 | create_where_predicates_from_generic_parameters_check_types, meta_2_where_predicates, 6 | WherePredicates, WherePredicatesOrBool, 7 | }; 8 | 9 | pub(crate) enum Bound { 10 | Disabled, 11 | Auto, 12 | Custom(WherePredicates), 13 | All, 14 | } 15 | 16 | impl Bound { 17 | #[inline] 18 | pub(crate) fn from_meta(meta: &Meta) -> syn::Result { 19 | debug_assert!(meta.path().is_ident("bound")); 20 | 21 | Ok(match meta_2_where_predicates(meta)? { 22 | WherePredicatesOrBool::WherePredicates(where_predicates) => { 23 | Self::Custom(where_predicates) 24 | }, 25 | WherePredicatesOrBool::Bool(b) => { 26 | if b { 27 | Self::Auto 28 | } else { 29 | Self::Disabled 30 | } 31 | }, 32 | WherePredicatesOrBool::All => Self::All, 33 | }) 34 | } 35 | } 36 | 37 | impl Bound { 38 | #[inline] 39 | pub(crate) fn into_where_predicates_by_generic_parameters_check_types( 40 | self, 41 | params: &Punctuated, 42 | bound_trait: &Path, 43 | types: &[&Type], 44 | supertraits: &[proc_macro2::TokenStream], 45 | ) -> Punctuated { 46 | match self { 47 | Self::Disabled => Punctuated::new(), 48 | Self::Auto => create_where_predicates_from_generic_parameters_check_types( 49 | bound_trait, 50 | types, 51 | supertraits, 52 | ), 53 | Self::Custom(where_predicates) => where_predicates, 54 | Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /src/trait_handlers/debug/common.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{DeriveInput, Path, Type}; 3 | 4 | #[inline] 5 | pub(crate) fn create_debug_map_builder() -> proc_macro2::TokenStream { 6 | quote!( 7 | #[allow(non_camel_case_types)] // We're using __ to help avoid clashes. 8 | struct Educe__RawString(&'static str); 9 | 10 | impl ::core::fmt::Debug for Educe__RawString { 11 | #[inline] 12 | fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 13 | f.write_str(self.0) 14 | } 15 | } 16 | 17 | let mut builder = f.debug_map(); 18 | ) 19 | } 20 | 21 | #[inline] 22 | pub(crate) fn create_format_arg( 23 | ast: &DeriveInput, 24 | field_ty: &Type, 25 | format_method: &Path, 26 | field_expr: proc_macro2::TokenStream, 27 | ) -> proc_macro2::TokenStream { 28 | let ty_ident = &ast.ident; 29 | 30 | // We use the complete original generics, not filtered by field, 31 | // and include a PhantomData in our wrapper struct to use the generics. 32 | // 33 | // This avoids having to try to calculate the right *subset* of the generics 34 | // relevant for this field, which is nontrivial and maybe impossible. 35 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 36 | 37 | quote!( 38 | let arg = { 39 | #[allow(non_camel_case_types)] // We're using __ to help avoid clashes. 40 | struct Educe__DebugField(V, ::core::marker::PhantomData); 41 | 42 | impl #impl_generics ::core::fmt::Debug 43 | for Educe__DebugField<&#field_ty, #ty_ident #ty_generics> 44 | #where_clause 45 | { 46 | #[inline] 47 | fn fmt(&self, educe__f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 48 | #format_method(self.0, educe__f) 49 | } 50 | } 51 | 52 | Educe__DebugField(#field_expr, ::core::marker::PhantomData::) 53 | }; 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/trait_handlers/eq/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{panic, supported_traits::Trait}; 4 | 5 | pub(crate) struct FieldAttribute; 6 | 7 | pub(crate) struct FieldAttributeBuilder; 8 | 9 | impl FieldAttributeBuilder { 10 | pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result { 11 | debug_assert!(meta.path().is_ident("Eq")); 12 | 13 | return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())); 14 | } 15 | 16 | pub(crate) fn build_from_attributes( 17 | &self, 18 | attributes: &[Attribute], 19 | traits: &[Trait], 20 | ) -> syn::Result { 21 | let mut output = None; 22 | 23 | for attribute in attributes.iter() { 24 | let path = attribute.path(); 25 | 26 | if path.is_ident("educe") { 27 | if let Meta::List(list) = &attribute.meta { 28 | let result = 29 | list.parse_args_with(Punctuated::::parse_terminated)?; 30 | 31 | for meta in result { 32 | let path = meta.path(); 33 | 34 | let t = match Trait::from_path(path) { 35 | Some(t) => t, 36 | None => return Err(panic::unsupported_trait(meta.path())), 37 | }; 38 | 39 | if !traits.contains(&t) { 40 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 41 | } 42 | 43 | if t == Trait::Eq { 44 | if output.is_some() { 45 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 46 | } 47 | 48 | output = Some(self.build_from_eq_meta(&meta)?); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | Ok(output.unwrap_or(FieldAttribute)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/trait_handlers/copy/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{panic, supported_traits::Trait}; 4 | 5 | pub(crate) struct FieldAttribute; 6 | 7 | pub(crate) struct FieldAttributeBuilder; 8 | 9 | impl FieldAttributeBuilder { 10 | pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result { 11 | debug_assert!(meta.path().is_ident("Copy")); 12 | 13 | return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())); 14 | } 15 | 16 | pub(crate) fn build_from_attributes( 17 | &self, 18 | attributes: &[Attribute], 19 | traits: &[Trait], 20 | ) -> syn::Result { 21 | let mut output = None; 22 | 23 | for attribute in attributes.iter() { 24 | let path = attribute.path(); 25 | 26 | if path.is_ident("educe") { 27 | if let Meta::List(list) = &attribute.meta { 28 | let result = 29 | list.parse_args_with(Punctuated::::parse_terminated)?; 30 | 31 | for meta in result { 32 | let path = meta.path(); 33 | 34 | let t = match Trait::from_path(path) { 35 | Some(t) => t, 36 | None => return Err(panic::unsupported_trait(meta.path())), 37 | }; 38 | 39 | if !traits.contains(&t) { 40 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 41 | } 42 | 43 | if t == Trait::Copy { 44 | if output.is_some() { 45 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 46 | } 47 | 48 | output = Some(self.build_from_copy_meta(&meta)?); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | Ok(output.unwrap_or(FieldAttribute)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/int.rs: -------------------------------------------------------------------------------- 1 | use syn::{spanned::Spanned, Expr, Lit, Meta, MetaNameValue, UnOp}; 2 | 3 | use super::path::path_to_string; 4 | 5 | #[inline] 6 | pub(crate) fn meta_name_value_2_isize(name_value: &MetaNameValue) -> syn::Result { 7 | match &name_value.value { 8 | Expr::Lit(lit) => match &lit.lit { 9 | Lit::Str(lit) => { 10 | return lit 11 | .value() 12 | .parse::() 13 | .map_err(|error| syn::Error::new(lit.span(), error)) 14 | }, 15 | Lit::Int(lit) => return lit.base10_parse(), 16 | _ => (), 17 | }, 18 | Expr::Unary(unary) => { 19 | if let UnOp::Neg(_) = unary.op { 20 | if let Expr::Lit(lit) = unary.expr.as_ref() { 21 | if let Lit::Int(lit) = &lit.lit { 22 | let s = format!("-{}", lit.base10_digits()); 23 | 24 | return s 25 | .parse::() 26 | .map_err(|error| syn::Error::new(lit.span(), error)); 27 | } 28 | } 29 | } 30 | }, 31 | _ => (), 32 | } 33 | 34 | Err(syn::Error::new( 35 | name_value.value.span(), 36 | format!("expected `{path} = integer`", path = path_to_string(&name_value.path)), 37 | )) 38 | } 39 | 40 | #[inline] 41 | pub(crate) fn meta_2_isize(meta: &Meta) -> syn::Result { 42 | match &meta { 43 | Meta::NameValue(name_value) => meta_name_value_2_isize(name_value), 44 | Meta::List(list) => { 45 | let lit = list.parse_args::()?; 46 | 47 | match &lit { 48 | Lit::Str(lit) => { 49 | lit.value().parse::().map_err(|error| syn::Error::new(lit.span(), error)) 50 | }, 51 | Lit::Int(lit) => lit.base10_parse(), 52 | _ => Err(syn::Error::new(lit.span(), "not an integer")), 53 | } 54 | }, 55 | Meta::Path(path) => Err(syn::Error::new( 56 | path.span(), 57 | format!( 58 | "expected `{path} = integer` or `{path}(integer)`", 59 | path = path_to_string(path) 60 | ), 61 | )), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_eq/partial_eq_union.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta}; 3 | 4 | use super::models::{FieldAttributeBuilder, TypeAttributeBuilder}; 5 | use crate::{supported_traits::Trait, trait_handlers::TraitHandler}; 6 | 7 | pub(crate) struct PartialEqUnionHandler; 8 | 9 | impl TraitHandler for PartialEqUnionHandler { 10 | #[inline] 11 | fn trait_meta_handler( 12 | ast: &DeriveInput, 13 | token_stream: &mut proc_macro2::TokenStream, 14 | traits: &[Trait], 15 | meta: &Meta, 16 | ) -> syn::Result<()> { 17 | let type_attribute = 18 | TypeAttributeBuilder { 19 | enable_flag: true, enable_unsafe: true, enable_bound: false 20 | } 21 | .build_from_partial_eq_meta(meta)?; 22 | 23 | if !type_attribute.has_unsafe { 24 | return Err(super::panic::union_without_unsafe(meta)); 25 | } 26 | 27 | if let Data::Union(data) = &ast.data { 28 | for field in data.fields.named.iter() { 29 | let _ = FieldAttributeBuilder { 30 | enable_ignore: false, enable_method: false 31 | } 32 | .build_from_attributes(&field.attrs, traits)?; 33 | } 34 | } 35 | 36 | let ident = &ast.ident; 37 | 38 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 39 | 40 | token_stream.extend(quote! { 41 | impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { 42 | #[inline] 43 | fn eq(&self, other: &Self) -> bool { 44 | let size = ::core::mem::size_of::(); 45 | let self_data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; 46 | let other_data = unsafe { ::core::slice::from_raw_parts(other as *const Self as *const u8, size) }; 47 | 48 | ::core::cmp::PartialEq::eq(self_data, other_data) 49 | } 50 | } 51 | }); 52 | 53 | #[cfg(feature = "Eq")] 54 | if traits.contains(&Trait::Eq) { 55 | token_stream.extend(quote! { 56 | impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { 57 | } 58 | }); 59 | } 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/trait_handlers/clone/clone_union.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::supported_traits::Trait; 9 | 10 | pub(crate) struct CloneUnionHandler; 11 | 12 | impl TraitHandler for CloneUnionHandler { 13 | fn trait_meta_handler( 14 | ast: &DeriveInput, 15 | token_stream: &mut proc_macro2::TokenStream, 16 | traits: &[Trait], 17 | meta: &Meta, 18 | ) -> syn::Result<()> { 19 | let type_attribute = TypeAttributeBuilder { 20 | enable_flag: true, enable_bound: true 21 | } 22 | .build_from_clone_meta(meta)?; 23 | 24 | let mut field_types = vec![]; 25 | 26 | if let Data::Union(data) = &ast.data { 27 | for field in data.fields.named.iter() { 28 | field_types.push(&field.ty); 29 | let _ = FieldAttributeBuilder { 30 | enable_method: false 31 | } 32 | .build_from_attributes(&field.attrs, traits)?; 33 | } 34 | } 35 | 36 | let ident = &ast.ident; 37 | 38 | let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 39 | &ast.generics.params, 40 | &syn::parse2(quote!(::core::marker::Copy)).unwrap(), 41 | &field_types, 42 | &[], 43 | ); 44 | 45 | let mut generics = ast.generics.clone(); 46 | let where_clause = generics.make_where_clause(); 47 | 48 | for where_predicate in bound { 49 | where_clause.predicates.push(where_predicate); 50 | } 51 | 52 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 53 | 54 | token_stream.extend(quote! { 55 | impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause { 56 | #[inline] 57 | fn clone(&self) -> Self { 58 | *self 59 | } 60 | } 61 | }); 62 | 63 | #[cfg(feature = "Copy")] 64 | if traits.contains(&Trait::Copy) { 65 | token_stream.extend(quote! { 66 | impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { 67 | } 68 | }); 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/common/tools/hash_type.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | fmt::{self, Display, Formatter}, 4 | hash::{Hash, Hasher}, 5 | str::FromStr, 6 | }; 7 | 8 | use proc_macro2::Span; 9 | use quote::ToTokens; 10 | use syn::{spanned::Spanned, Path, Type}; 11 | 12 | #[derive(Debug, Clone)] 13 | pub(crate) struct HashType(String, Span); 14 | 15 | impl PartialEq for HashType { 16 | #[inline] 17 | fn eq(&self, other: &Self) -> bool { 18 | self.0.eq(&other.0) 19 | } 20 | } 21 | 22 | impl Eq for HashType {} 23 | 24 | impl PartialOrd for HashType { 25 | #[inline] 26 | fn partial_cmp(&self, other: &Self) -> Option { 27 | Some(self.cmp(other)) 28 | } 29 | } 30 | 31 | impl Ord for HashType { 32 | #[inline] 33 | fn cmp(&self, other: &Self) -> Ordering { 34 | self.0.cmp(&other.0) 35 | } 36 | } 37 | 38 | impl Hash for HashType { 39 | #[inline] 40 | fn hash(&self, state: &mut H) { 41 | Hash::hash(&self.0, state); 42 | } 43 | } 44 | 45 | impl Display for HashType { 46 | #[inline] 47 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 48 | Display::fmt(&self.0.replace("& '", "&'"), f) 49 | } 50 | } 51 | 52 | impl From for HashType { 53 | #[inline] 54 | fn from(value: Type) -> Self { 55 | Self::from(&value) 56 | } 57 | } 58 | 59 | impl From<&Type> for HashType { 60 | #[inline] 61 | fn from(value: &Type) -> Self { 62 | Self(value.into_token_stream().to_string(), value.span()) 63 | } 64 | } 65 | 66 | impl From for HashType { 67 | #[inline] 68 | fn from(value: Path) -> Self { 69 | Self::from(&value) 70 | } 71 | } 72 | 73 | impl From<&Path> for HashType { 74 | #[inline] 75 | fn from(value: &Path) -> Self { 76 | Self(value.into_token_stream().to_string(), value.span()) 77 | } 78 | } 79 | 80 | #[allow(dead_code)] 81 | impl HashType { 82 | #[inline] 83 | pub(crate) fn to_type(&self) -> Type { 84 | syn::parse_str(self.0.as_str()).unwrap() 85 | } 86 | 87 | #[inline] 88 | pub(crate) fn span(&self) -> Span { 89 | self.1 90 | } 91 | } 92 | 93 | impl ToTokens for HashType { 94 | #[inline] 95 | fn to_tokens(&self, token_stream: &mut proc_macro2::TokenStream) { 96 | let ty = proc_macro2::TokenStream::from_str(self.0.as_str()).unwrap(); 97 | 98 | token_stream.extend(ty); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/supported_traits.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any( 2 | feature = "Debug", 3 | feature = "Clone", 4 | feature = "Copy", 5 | feature = "PartialEq", 6 | feature = "Eq", 7 | feature = "PartialOrd", 8 | feature = "Ord", 9 | feature = "Hash", 10 | feature = "Default", 11 | feature = "Deref", 12 | feature = "DerefMut", 13 | feature = "Into", 14 | )))] 15 | compile_error!("at least one of the trait features must be enabled"); 16 | 17 | use enum_ordinalize::Ordinalize; 18 | use syn::Path; 19 | 20 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ordinalize)] 21 | #[ordinalize(impl_trait = false)] 22 | #[ordinalize(variants(pub(crate) const VARIANTS))] 23 | pub(crate) enum Trait { 24 | #[cfg(feature = "Debug")] 25 | Debug, 26 | #[cfg(feature = "Clone")] 27 | Clone, 28 | #[cfg(feature = "Copy")] 29 | Copy, 30 | #[cfg(feature = "PartialEq")] 31 | PartialEq, 32 | #[cfg(feature = "Eq")] 33 | Eq, 34 | #[cfg(feature = "PartialOrd")] 35 | PartialOrd, 36 | #[cfg(feature = "Ord")] 37 | Ord, 38 | #[cfg(feature = "Hash")] 39 | Hash, 40 | #[cfg(feature = "Default")] 41 | Default, 42 | #[cfg(feature = "Deref")] 43 | Deref, 44 | #[cfg(feature = "DerefMut")] 45 | DerefMut, 46 | #[cfg(feature = "Into")] 47 | Into, 48 | 49 | _Nothing, 50 | } 51 | 52 | impl Trait { 53 | #[inline] 54 | pub(crate) fn from_path(path: &Path) -> Option { 55 | let ident_string = match path.get_ident() { 56 | Some(ident) => ident.to_string(), 57 | None => return None, 58 | }; 59 | 60 | match ident_string.as_str() { 61 | #[cfg(feature = "Debug")] 62 | "Debug" => Some(Self::Debug), 63 | #[cfg(feature = "Clone")] 64 | "Clone" => Some(Self::Clone), 65 | #[cfg(feature = "Copy")] 66 | "Copy" => Some(Self::Copy), 67 | #[cfg(feature = "PartialEq")] 68 | "PartialEq" => Some(Self::PartialEq), 69 | #[cfg(feature = "Eq")] 70 | "Eq" => Some(Self::Eq), 71 | #[cfg(feature = "PartialOrd")] 72 | "PartialOrd" => Some(Self::PartialOrd), 73 | #[cfg(feature = "Ord")] 74 | "Ord" => Some(Self::Ord), 75 | #[cfg(feature = "Hash")] 76 | "Hash" => Some(Self::Hash), 77 | #[cfg(feature = "Default")] 78 | "Default" => Some(Self::Default), 79 | #[cfg(feature = "Deref")] 80 | "Deref" => Some(Self::Deref), 81 | #[cfg(feature = "DerefMut")] 82 | "DerefMut" => Some(Self::DerefMut), 83 | #[cfg(feature = "Into")] 84 | "Into" => Some(Self::Into), 85 | _ => None, 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/debug_union.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Debug")] 2 | #![no_std] 3 | 4 | #[macro_use] 5 | extern crate alloc; 6 | 7 | use educe::Educe; 8 | 9 | #[allow(dead_code)] 10 | #[test] 11 | fn name_1() { 12 | #[derive(Educe)] 13 | #[educe(Debug(unsafe))] 14 | union Union { 15 | f1: u8, 16 | } 17 | 18 | assert_eq!( 19 | "Union([1])", 20 | format!("{:?}", Union { 21 | f1: 1 22 | }) 23 | ); 24 | } 25 | 26 | #[allow(dead_code)] 27 | #[test] 28 | fn name_2() { 29 | #[derive(Educe)] 30 | #[educe(Debug(unsafe, name = A))] 31 | union Union { 32 | f1: u8, 33 | } 34 | 35 | assert_eq!( 36 | "A([1])", 37 | format!("{:?}", Union { 38 | f1: 1 39 | }) 40 | ); 41 | } 42 | 43 | #[allow(dead_code)] 44 | #[test] 45 | fn name_3() { 46 | #[derive(Educe)] 47 | #[educe(Debug(unsafe, name(A)))] 48 | union Union { 49 | f1: u8, 50 | } 51 | 52 | assert_eq!( 53 | "A([1])", 54 | format!("{:?}", Union { 55 | f1: 1 56 | }) 57 | ); 58 | } 59 | 60 | #[allow(dead_code)] 61 | #[test] 62 | fn unnamed_1() { 63 | #[derive(Educe)] 64 | #[educe(Debug(unsafe, name = false))] 65 | union Union { 66 | f1: u8, 67 | } 68 | 69 | assert_eq!( 70 | "[1]", 71 | format!("{:?}", Union { 72 | f1: 1 73 | }) 74 | ); 75 | } 76 | 77 | #[allow(dead_code)] 78 | #[test] 79 | fn unnamed_2() { 80 | #[derive(Educe)] 81 | #[educe(Debug(unsafe, name(false)))] 82 | union Union { 83 | f1: u8, 84 | } 85 | 86 | assert_eq!( 87 | "[1]", 88 | format!("{:?}", Union { 89 | f1: 1 90 | }) 91 | ); 92 | } 93 | 94 | #[allow(dead_code)] 95 | #[test] 96 | fn unnamed_3() { 97 | #[derive(Educe)] 98 | #[educe(Debug(unsafe, name = ""))] 99 | union Union { 100 | f1: u8, 101 | } 102 | 103 | assert_eq!( 104 | "[1]", 105 | format!("{:?}", Union { 106 | f1: 1 107 | }) 108 | ); 109 | } 110 | 111 | #[allow(dead_code)] 112 | #[test] 113 | fn unnamed_4() { 114 | #[derive(Educe)] 115 | #[educe(Debug(unsafe, name("")))] 116 | union Union { 117 | f1: u8, 118 | } 119 | 120 | assert_eq!( 121 | "[1]", 122 | format!("{:?}", Union { 123 | f1: 1 124 | }) 125 | ); 126 | } 127 | 128 | #[allow(dead_code)] 129 | #[test] 130 | fn bound() { 131 | #[derive(Educe)] 132 | #[educe(Debug(unsafe))] 133 | union Union { 134 | f1: T, 135 | } 136 | 137 | assert_eq!( 138 | "Union([1])", 139 | format!("{:?}", Union { 140 | f1: 1u8 141 | }) 142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute; 6 | 7 | #[derive(Debug)] 8 | pub(crate) struct TypeAttributeBuilder { 9 | pub(crate) enable_flag: bool, 10 | } 11 | 12 | impl TypeAttributeBuilder { 13 | pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result { 14 | debug_assert!(meta.path().is_ident("Deref")); 15 | 16 | let correct_usage_for_deref_attribute = { 17 | let mut usage = vec![]; 18 | 19 | if self.enable_flag { 20 | usage.push(stringify!(#[educe(Deref)])); 21 | } 22 | 23 | usage 24 | }; 25 | 26 | match meta { 27 | Meta::Path(_) => { 28 | if !self.enable_flag { 29 | return Err(panic::attribute_incorrect_format( 30 | meta.path().get_ident().unwrap(), 31 | &correct_usage_for_deref_attribute, 32 | )); 33 | } 34 | }, 35 | Meta::NameValue(_) | Meta::List(_) => { 36 | return Err(panic::attribute_incorrect_format( 37 | meta.path().get_ident().unwrap(), 38 | &correct_usage_for_deref_attribute, 39 | )); 40 | }, 41 | } 42 | 43 | Ok(TypeAttribute) 44 | } 45 | 46 | pub(crate) fn build_from_attributes( 47 | &self, 48 | attributes: &[Attribute], 49 | traits: &[Trait], 50 | ) -> syn::Result { 51 | let mut output = None; 52 | 53 | for attribute in attributes.iter() { 54 | let path = attribute.path(); 55 | 56 | if path.is_ident("educe") { 57 | if let Meta::List(list) = &attribute.meta { 58 | let result = 59 | list.parse_args_with(Punctuated::::parse_terminated)?; 60 | 61 | for meta in result { 62 | let path = meta.path(); 63 | 64 | let t = match Trait::from_path(path) { 65 | Some(t) => t, 66 | None => return Err(panic::unsupported_trait(meta.path())), 67 | }; 68 | 69 | if !traits.contains(&t) { 70 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 71 | } 72 | 73 | if t == Trait::Deref { 74 | if output.is_some() { 75 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 76 | } 77 | 78 | output = Some(self.build_from_deref_meta(&meta)?); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | Ok(output.unwrap_or(TypeAttribute)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute; 6 | 7 | #[derive(Debug)] 8 | pub(crate) struct TypeAttributeBuilder { 9 | pub(crate) enable_flag: bool, 10 | } 11 | 12 | impl TypeAttributeBuilder { 13 | pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result { 14 | debug_assert!(meta.path().is_ident("DerefMut")); 15 | 16 | let correct_usage_for_deref_mut_attribute = { 17 | let mut usage = vec![]; 18 | 19 | if self.enable_flag { 20 | usage.push(stringify!(#[educe(DerefMut)])); 21 | } 22 | 23 | usage 24 | }; 25 | 26 | match meta { 27 | Meta::Path(_) => { 28 | if !self.enable_flag { 29 | return Err(panic::attribute_incorrect_format( 30 | meta.path().get_ident().unwrap(), 31 | &correct_usage_for_deref_mut_attribute, 32 | )); 33 | } 34 | }, 35 | Meta::NameValue(_) | Meta::List(_) => { 36 | return Err(panic::attribute_incorrect_format( 37 | meta.path().get_ident().unwrap(), 38 | &correct_usage_for_deref_mut_attribute, 39 | )); 40 | }, 41 | } 42 | 43 | Ok(TypeAttribute) 44 | } 45 | 46 | pub(crate) fn build_from_attributes( 47 | &self, 48 | attributes: &[Attribute], 49 | traits: &[Trait], 50 | ) -> syn::Result { 51 | let mut output = None; 52 | 53 | for attribute in attributes.iter() { 54 | let path = attribute.path(); 55 | 56 | if path.is_ident("educe") { 57 | if let Meta::List(list) = &attribute.meta { 58 | let result = 59 | list.parse_args_with(Punctuated::::parse_terminated)?; 60 | 61 | for meta in result { 62 | let path = meta.path(); 63 | 64 | let t = match Trait::from_path(path) { 65 | Some(t) => t, 66 | None => return Err(panic::unsupported_trait(meta.path())), 67 | }; 68 | 69 | if !traits.contains(&t) { 70 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 71 | } 72 | 73 | if t == Trait::DerefMut { 74 | if output.is_some() { 75 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 76 | } 77 | 78 | output = Some(self.build_from_deref_mut_meta(&meta)?); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | Ok(output.unwrap_or(TypeAttribute)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/hash_struct.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta, Path, Type}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::{common::ident_index::IdentOrIndex, Trait}; 9 | 10 | pub(crate) struct HashStructHandler; 11 | 12 | impl TraitHandler for HashStructHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | let type_attribute = 21 | TypeAttributeBuilder { 22 | enable_flag: true, enable_unsafe: false, enable_bound: true 23 | } 24 | .build_from_hash_meta(meta)?; 25 | 26 | let mut hash_types: Vec<&Type> = Vec::new(); 27 | 28 | let mut hash_token_stream = proc_macro2::TokenStream::new(); 29 | 30 | if let Data::Struct(data) = &ast.data { 31 | let built_in_hash: Path = syn::parse2(quote!(::core::hash::Hash::hash)).unwrap(); 32 | 33 | for (index, field) in data.fields.iter().enumerate() { 34 | let field_attribute = FieldAttributeBuilder { 35 | enable_ignore: true, 36 | enable_method: true, 37 | } 38 | .build_from_attributes(&field.attrs, traits)?; 39 | 40 | if field_attribute.ignore { 41 | continue; 42 | } 43 | 44 | let field_name = if let Some(ident) = field.ident.as_ref() { 45 | IdentOrIndex::from(ident) 46 | } else { 47 | IdentOrIndex::from(index) 48 | }; 49 | 50 | let hash = field_attribute.method.as_ref().unwrap_or_else(|| { 51 | hash_types.push(&field.ty); 52 | &built_in_hash 53 | }); 54 | 55 | hash_token_stream.extend(quote!( #hash(&self.#field_name, state); )); 56 | } 57 | } 58 | 59 | let ident = &ast.ident; 60 | 61 | let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 62 | &ast.generics.params, 63 | &syn::parse2(quote!(::core::hash::Hash)).unwrap(), 64 | &hash_types, 65 | &[], 66 | ); 67 | 68 | let mut generics = ast.generics.clone(); 69 | let where_clause = generics.make_where_clause(); 70 | 71 | for where_predicate in bound { 72 | where_clause.predicates.push(where_predicate); 73 | } 74 | 75 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 76 | 77 | token_stream.extend(quote! { 78 | impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { 79 | #[inline] 80 | fn hash(&self, state: &mut H) { 81 | #hash_token_stream 82 | } 83 | } 84 | }); 85 | 86 | Ok(()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/trait_handlers/debug/debug_union.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}, 6 | TraitHandler, 7 | }; 8 | use crate::supported_traits::Trait; 9 | 10 | pub(crate) struct DebugUnionHandler; 11 | 12 | impl TraitHandler for DebugUnionHandler { 13 | fn trait_meta_handler( 14 | ast: &DeriveInput, 15 | token_stream: &mut proc_macro2::TokenStream, 16 | traits: &[Trait], 17 | meta: &Meta, 18 | ) -> syn::Result<()> { 19 | let type_attribute = TypeAttributeBuilder { 20 | enable_flag: true, 21 | enable_unsafe: true, 22 | enable_name: true, 23 | enable_named_field: false, 24 | enable_bound: false, 25 | name: TypeName::Default, 26 | named_field: false, 27 | } 28 | .build_from_debug_meta(meta)?; 29 | 30 | if !type_attribute.has_unsafe { 31 | return Err(super::panic::union_without_unsafe(meta)); 32 | } 33 | 34 | let name = type_attribute.name.to_ident_by_ident(&ast.ident); 35 | 36 | let mut builder_token_stream = proc_macro2::TokenStream::new(); 37 | 38 | if let Data::Union(data) = &ast.data { 39 | for field in data.fields.named.iter() { 40 | let _ = FieldAttributeBuilder { 41 | enable_name: false, 42 | enable_ignore: false, 43 | enable_method: false, 44 | name: FieldName::Default, 45 | } 46 | .build_from_attributes(&field.attrs, traits)?; 47 | } 48 | 49 | if let Some(name) = name { 50 | builder_token_stream.extend(quote!( 51 | let mut builder = f.debug_tuple(stringify!(#name)); 52 | 53 | let size = ::core::mem::size_of::(); 54 | 55 | let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; 56 | 57 | builder.field(&data); 58 | 59 | builder.finish() 60 | )); 61 | } else { 62 | builder_token_stream.extend(quote!( 63 | let size = ::core::mem::size_of::(); 64 | let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; 65 | 66 | ::core::fmt::Debug::fmt(data, f) 67 | )); 68 | } 69 | } 70 | 71 | let ident = &ast.ident; 72 | 73 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 74 | 75 | token_stream.extend(quote! { 76 | impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause { 77 | #[inline] 78 | fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { 79 | #builder_token_stream 80 | } 81 | } 82 | }); 83 | 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token}; 3 | 4 | use crate::{panic, supported_traits::Trait}; 5 | 6 | pub(crate) struct FieldAttribute { 7 | pub(crate) flag: bool, 8 | pub(crate) span: Span, 9 | } 10 | 11 | pub(crate) struct FieldAttributeBuilder { 12 | pub(crate) enable_flag: bool, 13 | } 14 | 15 | impl FieldAttributeBuilder { 16 | pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("Deref")); 18 | 19 | let correct_usage_for_deref_attribute = { 20 | let mut usage = vec![]; 21 | 22 | if self.enable_flag { 23 | usage.push(stringify!(#[educe(Deref)])); 24 | } 25 | 26 | usage 27 | }; 28 | 29 | match meta { 30 | Meta::Path(_) => { 31 | if !self.enable_flag { 32 | return Err(panic::attribute_incorrect_format( 33 | meta.path().get_ident().unwrap(), 34 | &correct_usage_for_deref_attribute, 35 | )); 36 | } 37 | }, 38 | Meta::NameValue(_) | Meta::List(_) => { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_deref_attribute, 42 | )); 43 | }, 44 | } 45 | 46 | Ok(FieldAttribute { 47 | flag: true, span: meta.span() 48 | }) 49 | } 50 | 51 | pub(crate) fn build_from_attributes( 52 | &self, 53 | attributes: &[Attribute], 54 | traits: &[Trait], 55 | ) -> syn::Result { 56 | let mut output = None; 57 | 58 | for attribute in attributes.iter() { 59 | let path = attribute.path(); 60 | 61 | if path.is_ident("educe") { 62 | if let Meta::List(list) = &attribute.meta { 63 | let result = 64 | list.parse_args_with(Punctuated::::parse_terminated)?; 65 | 66 | for meta in result { 67 | let path = meta.path(); 68 | 69 | let t = match Trait::from_path(path) { 70 | Some(t) => t, 71 | None => return Err(panic::unsupported_trait(meta.path())), 72 | }; 73 | 74 | if !traits.contains(&t) { 75 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 76 | } 77 | 78 | if t == Trait::Deref { 79 | if output.is_some() { 80 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 81 | } 82 | 83 | output = Some(self.build_from_deref_meta(&meta)?); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | Ok(output.unwrap_or(FieldAttribute { 91 | flag: false, span: Span::call_site() 92 | })) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token}; 3 | 4 | use crate::{panic, supported_traits::Trait}; 5 | 6 | pub(crate) struct FieldAttribute { 7 | pub(crate) flag: bool, 8 | pub(crate) span: Span, 9 | } 10 | 11 | pub(crate) struct FieldAttributeBuilder { 12 | pub(crate) enable_flag: bool, 13 | } 14 | 15 | impl FieldAttributeBuilder { 16 | pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("DerefMut")); 18 | 19 | let correct_usage_for_deref_mut_attribute = { 20 | let mut usage = vec![]; 21 | 22 | if self.enable_flag { 23 | usage.push(stringify!(#[educe(DerefMut)])); 24 | } 25 | 26 | usage 27 | }; 28 | 29 | match meta { 30 | Meta::Path(_) => { 31 | if !self.enable_flag { 32 | return Err(panic::attribute_incorrect_format( 33 | meta.path().get_ident().unwrap(), 34 | &correct_usage_for_deref_mut_attribute, 35 | )); 36 | } 37 | }, 38 | Meta::NameValue(_) | Meta::List(_) => { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_deref_mut_attribute, 42 | )); 43 | }, 44 | } 45 | 46 | Ok(FieldAttribute { 47 | flag: true, span: meta.span() 48 | }) 49 | } 50 | 51 | pub(crate) fn build_from_attributes( 52 | &self, 53 | attributes: &[Attribute], 54 | traits: &[Trait], 55 | ) -> syn::Result { 56 | let mut output = None; 57 | 58 | for attribute in attributes.iter() { 59 | let path = attribute.path(); 60 | 61 | if path.is_ident("educe") { 62 | if let Meta::List(list) = &attribute.meta { 63 | let result = 64 | list.parse_args_with(Punctuated::::parse_terminated)?; 65 | 66 | for meta in result { 67 | let path = meta.path(); 68 | 69 | let t = match Trait::from_path(path) { 70 | Some(t) => t, 71 | None => return Err(panic::unsupported_trait(meta.path())), 72 | }; 73 | 74 | if !traits.contains(&t) { 75 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 76 | } 77 | 78 | if t == Trait::DerefMut { 79 | if output.is_some() { 80 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 81 | } 82 | 83 | output = Some(self.build_from_deref_mut_meta(&meta)?); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | Ok(output.unwrap_or(FieldAttribute { 91 | flag: false, span: Span::call_site() 92 | })) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/deref_mut_struct.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Type}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::{common::ident_index::IdentOrIndex, Trait}; 9 | 10 | pub(crate) struct DerefMutStructHandler; 11 | 12 | impl TraitHandler for DerefMutStructHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | let _ = TypeAttributeBuilder { 21 | enable_flag: true 22 | } 23 | .build_from_deref_mut_meta(meta)?; 24 | 25 | let mut deref_mut_token_stream = proc_macro2::TokenStream::new(); 26 | 27 | if let Data::Struct(data) = &ast.data { 28 | let (index, field) = { 29 | let fields = &data.fields; 30 | 31 | if fields.len() == 1 { 32 | let field = fields.into_iter().next().unwrap(); 33 | 34 | let _ = FieldAttributeBuilder { 35 | enable_flag: true 36 | } 37 | .build_from_attributes(&field.attrs, traits)?; 38 | 39 | (0usize, field) 40 | } else { 41 | let mut deref_field: Option<(usize, &Field)> = None; 42 | 43 | for (index, field) in fields.iter().enumerate() { 44 | let field_attribute = FieldAttributeBuilder { 45 | enable_flag: true 46 | } 47 | .build_from_attributes(&field.attrs, traits)?; 48 | 49 | if field_attribute.flag { 50 | if deref_field.is_some() { 51 | return Err(super::panic::multiple_deref_mut_fields( 52 | field_attribute.span, 53 | )); 54 | } 55 | 56 | deref_field = Some((index, field)); 57 | } 58 | } 59 | 60 | if let Some(deref_field) = deref_field { 61 | deref_field 62 | } else { 63 | return Err(super::panic::no_deref_mut_field(meta.span())); 64 | } 65 | } 66 | }; 67 | 68 | let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index); 69 | 70 | deref_mut_token_stream.extend(if let Type::Reference(_) = &field.ty { 71 | quote! (self.#field_name) 72 | } else { 73 | quote! (&mut self.#field_name) 74 | }); 75 | } 76 | 77 | let ident = &ast.ident; 78 | 79 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 80 | 81 | token_stream.extend(quote! { 82 | impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause { 83 | #[inline] 84 | fn deref_mut(&mut self) -> &mut Self::Target { 85 | #deref_mut_token_stream 86 | } 87 | } 88 | }); 89 | 90 | Ok(()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | toolchain: 21 | - stable 22 | - nightly 23 | features: 24 | - 25 | - --no-default-features --features Debug 26 | - --no-default-features --features Clone 27 | - --no-default-features --features Copy 28 | - --no-default-features --features Clone --features Copy 29 | - --no-default-features --features PartialEq 30 | - --no-default-features --features Eq 31 | - --no-default-features --features PartialEq --features Eq 32 | - --no-default-features --features PartialOrd 33 | - --no-default-features --features Ord 34 | - --no-default-features --features PartialOrd --features Ord 35 | - --no-default-features --features Hash 36 | - --no-default-features --features Default 37 | - --no-default-features --features Deref 38 | - --no-default-features --features DerefMut 39 | - --no-default-features --features Deref --features DerefMut 40 | - --no-default-features --features Into 41 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 42 | runs-on: ${{ matrix.os }} 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: actions-rust-lang/setup-rust-toolchain@v1 46 | with: 47 | toolchain: ${{ matrix.toolchain }} 48 | - run: cargo test --release ${{ matrix.features }} 49 | - run: cargo doc --release ${{ matrix.features }} 50 | 51 | MSRV: 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | os: 56 | - ubuntu-latest 57 | - macos-latest 58 | - windows-latest 59 | toolchain: 60 | - "1.60" 61 | features: 62 | - 63 | - --no-default-features --features Debug 64 | - --no-default-features --features Clone 65 | - --no-default-features --features Copy 66 | - --no-default-features --features Clone --features Copy 67 | - --no-default-features --features PartialEq 68 | - --no-default-features --features Eq 69 | - --no-default-features --features PartialEq --features Eq 70 | - --no-default-features --features PartialOrd 71 | - --no-default-features --features Ord 72 | - --no-default-features --features PartialOrd --features Ord 73 | - --no-default-features --features Hash 74 | - --no-default-features --features Default 75 | - --no-default-features --features Deref 76 | - --no-default-features --features DerefMut 77 | - --no-default-features --features Deref --features DerefMut 78 | - --no-default-features --features Into 79 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 80 | runs-on: ${{ matrix.os }} 81 | steps: 82 | - uses: actions/checkout@v4 83 | - uses: actions-rust-lang/setup-rust-toolchain@v1 84 | with: 85 | toolchain: ${{ matrix.toolchain }} 86 | - run: cargo test --release --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | # debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /src/trait_handlers/copy/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | 3 | use models::{FieldAttributeBuilder, TypeAttributeBuilder}; 4 | use quote::quote; 5 | use syn::{Data, DeriveInput, Meta}; 6 | 7 | use super::TraitHandler; 8 | use crate::Trait; 9 | 10 | pub(crate) struct CopyHandler; 11 | 12 | impl TraitHandler for CopyHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | #[cfg(feature = "Clone")] 21 | let contains_clone = traits.contains(&Trait::Clone); 22 | 23 | #[cfg(not(feature = "Clone"))] 24 | let contains_clone = false; 25 | 26 | let type_attribute = TypeAttributeBuilder { 27 | enable_flag: true, 28 | enable_bound: !contains_clone, 29 | } 30 | .build_from_copy_meta(meta)?; 31 | 32 | let mut field_types = vec![]; 33 | 34 | // if `contains_clone` is true, the implementation is handled by the `Clone` attribute, and field attributes is also handled by the `Clone` attribute 35 | if !contains_clone { 36 | match &ast.data { 37 | Data::Struct(data) => { 38 | for field in data.fields.iter() { 39 | field_types.push(&field.ty); 40 | let _ = 41 | FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; 42 | } 43 | }, 44 | Data::Enum(data) => { 45 | for variant in data.variants.iter() { 46 | let _ = TypeAttributeBuilder { 47 | enable_flag: false, enable_bound: false 48 | } 49 | .build_from_attributes(&variant.attrs, traits)?; 50 | 51 | for field in variant.fields.iter() { 52 | field_types.push(&field.ty); 53 | let _ = FieldAttributeBuilder 54 | .build_from_attributes(&field.attrs, traits)?; 55 | } 56 | } 57 | }, 58 | Data::Union(data) => { 59 | for field in data.fields.named.iter() { 60 | field_types.push(&field.ty); 61 | let _ = 62 | FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; 63 | } 64 | }, 65 | } 66 | 67 | let ident = &ast.ident; 68 | 69 | let bound = 70 | type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 71 | &ast.generics.params, 72 | &syn::parse2(quote!(::core::marker::Copy)).unwrap(), 73 | &field_types, 74 | &[quote! {::core::clone::Clone}], 75 | ); 76 | 77 | let mut generics = ast.generics.clone(); 78 | let where_clause = generics.make_where_clause(); 79 | 80 | for where_predicate in bound { 81 | where_clause.predicates.push(where_predicate); 82 | } 83 | 84 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 85 | 86 | token_stream.extend(quote! { 87 | impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { 88 | } 89 | }); 90 | } 91 | 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_eq/partial_eq_struct.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{Data, DeriveInput, Meta, Type}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::{common::ident_index::IdentOrIndex, Trait}; 9 | 10 | pub(crate) struct PartialEqStructHandler; 11 | 12 | impl TraitHandler for PartialEqStructHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | let type_attribute = 21 | TypeAttributeBuilder { 22 | enable_flag: true, enable_unsafe: false, enable_bound: true 23 | } 24 | .build_from_partial_eq_meta(meta)?; 25 | 26 | let mut partial_eq_types: Vec<&Type> = Vec::new(); 27 | 28 | let mut eq_token_stream = proc_macro2::TokenStream::new(); 29 | 30 | if let Data::Struct(data) = &ast.data { 31 | for (index, field) in data.fields.iter().enumerate() { 32 | let field_attribute = FieldAttributeBuilder { 33 | enable_ignore: true, 34 | enable_method: true, 35 | } 36 | .build_from_attributes(&field.attrs, traits)?; 37 | 38 | if field_attribute.ignore { 39 | continue; 40 | } 41 | 42 | let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index); 43 | 44 | if let Some(method) = field_attribute.method { 45 | eq_token_stream.extend(quote! { 46 | if !#method(&self.#field_name, &other.#field_name) { 47 | return false; 48 | } 49 | }); 50 | } else { 51 | let ty = &field.ty; 52 | 53 | partial_eq_types.push(ty); 54 | 55 | eq_token_stream.extend(quote! { 56 | if ::core::cmp::PartialEq::ne(&self.#field_name, &other.#field_name) { 57 | return false; 58 | } 59 | }); 60 | } 61 | } 62 | } 63 | 64 | let ident = &ast.ident; 65 | 66 | let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 67 | &ast.generics.params, 68 | &syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(), 69 | &partial_eq_types, 70 | &[], 71 | ); 72 | 73 | let mut generics = ast.generics.clone(); 74 | let where_clause = generics.make_where_clause(); 75 | 76 | for where_predicate in bound { 77 | where_clause.predicates.push(where_predicate); 78 | } 79 | 80 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 81 | 82 | token_stream.extend(quote! { 83 | impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { 84 | #[inline] 85 | fn eq(&self, other: &Self) -> bool { 86 | #eq_token_stream 87 | 88 | true 89 | } 90 | } 91 | }); 92 | 93 | #[cfg(feature = "Eq")] 94 | if traits.contains(&Trait::Eq) { 95 | token_stream.extend(quote! { 96 | impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { 97 | } 98 | }); 99 | } 100 | 101 | Ok(()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/trait_handlers/deref/deref_struct.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::{ 9 | common::{ident_index::IdentOrIndex, r#type::dereference_changed}, 10 | Trait, 11 | }; 12 | 13 | pub(crate) struct DerefStructHandler; 14 | 15 | impl TraitHandler for DerefStructHandler { 16 | #[inline] 17 | fn trait_meta_handler( 18 | ast: &DeriveInput, 19 | token_stream: &mut proc_macro2::TokenStream, 20 | traits: &[Trait], 21 | meta: &Meta, 22 | ) -> syn::Result<()> { 23 | let _ = TypeAttributeBuilder { 24 | enable_flag: true 25 | } 26 | .build_from_deref_meta(meta)?; 27 | 28 | let mut target_token_stream = proc_macro2::TokenStream::new(); 29 | let mut deref_token_stream = proc_macro2::TokenStream::new(); 30 | 31 | if let Data::Struct(data) = &ast.data { 32 | let (index, field) = { 33 | let fields = &data.fields; 34 | 35 | if fields.len() == 1 { 36 | let field = fields.into_iter().next().unwrap(); 37 | 38 | let _ = FieldAttributeBuilder { 39 | enable_flag: true 40 | } 41 | .build_from_attributes(&field.attrs, traits)?; 42 | 43 | (0usize, field) 44 | } else { 45 | let mut deref_field: Option<(usize, &Field)> = None; 46 | 47 | for (index, field) in fields.iter().enumerate() { 48 | let field_attribute = FieldAttributeBuilder { 49 | enable_flag: true 50 | } 51 | .build_from_attributes(&field.attrs, traits)?; 52 | 53 | if field_attribute.flag { 54 | if deref_field.is_some() { 55 | return Err(super::panic::multiple_deref_fields( 56 | field_attribute.span, 57 | )); 58 | } 59 | 60 | deref_field = Some((index, field)); 61 | } 62 | } 63 | 64 | if let Some(deref_field) = deref_field { 65 | deref_field 66 | } else { 67 | return Err(super::panic::no_deref_field(meta.span())); 68 | } 69 | } 70 | }; 71 | 72 | let ty = &field.ty; 73 | let (dereference_ty, is_ref) = dereference_changed(ty); 74 | 75 | target_token_stream.extend(quote!(#dereference_ty)); 76 | 77 | let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index); 78 | 79 | deref_token_stream.extend(if is_ref { 80 | quote! (self.#field_name) 81 | } else { 82 | quote! (&self.#field_name) 83 | }); 84 | } 85 | 86 | let ident = &ast.ident; 87 | 88 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 89 | 90 | token_stream.extend(quote! { 91 | impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause { 92 | type Target = #target_token_stream; 93 | 94 | #[inline] 95 | fn deref(&self) -> &Self::Target { 96 | #deref_token_stream 97 | } 98 | } 99 | }); 100 | 101 | Ok(()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | toolchain: 37 | - stable 38 | - nightly 39 | features: 40 | - 41 | - --no-default-features --features Debug 42 | - --no-default-features --features Clone 43 | - --no-default-features --features Copy 44 | - --no-default-features --features Clone --features Copy 45 | - --no-default-features --features PartialEq 46 | - --no-default-features --features Eq 47 | - --no-default-features --features PartialEq --features Eq 48 | - --no-default-features --features PartialOrd 49 | - --no-default-features --features Ord 50 | - --no-default-features --features PartialOrd --features Ord 51 | - --no-default-features --features Hash 52 | - --no-default-features --features Default 53 | - --no-default-features --features Deref 54 | - --no-default-features --features DerefMut 55 | - --no-default-features --features Deref --features DerefMut 56 | - --no-default-features --features Into 57 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: actions-rust-lang/setup-rust-toolchain@v1 62 | with: 63 | toolchain: ${{ matrix.toolchain }} 64 | - run: cargo test ${{ matrix.features }} 65 | - run: cargo doc ${{ matrix.features }} 66 | 67 | MSRV: 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | os: 72 | - ubuntu-latest 73 | - macos-latest 74 | - windows-latest 75 | toolchain: 76 | - "1.60" 77 | features: 78 | - 79 | - --no-default-features --features Debug 80 | - --no-default-features --features Clone 81 | - --no-default-features --features Copy 82 | - --no-default-features --features Clone --features Copy 83 | - --no-default-features --features PartialEq 84 | - --no-default-features --features Eq 85 | - --no-default-features --features PartialEq --features Eq 86 | - --no-default-features --features PartialOrd 87 | - --no-default-features --features Ord 88 | - --no-default-features --features PartialOrd --features Ord 89 | - --no-default-features --features Hash 90 | - --no-default-features --features Default 91 | - --no-default-features --features Deref 92 | - --no-default-features --features DerefMut 93 | - --no-default-features --features Deref --features DerefMut 94 | - --no-default-features --features Into 95 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 96 | runs-on: ${{ matrix.os }} 97 | steps: 98 | - uses: actions/checkout@v4 99 | - uses: actions-rust-lang/setup-rust-toolchain@v1 100 | with: 101 | toolchain: ${{ matrix.toolchain }} 102 | - run: cargo test --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /tests/into_struct.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Into")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic_1() { 9 | #[derive(Educe)] 10 | #[educe(Into(u8))] 11 | struct Struct { 12 | f1: u8, 13 | } 14 | 15 | #[derive(Educe)] 16 | #[educe(Into(u8))] 17 | struct Struct2 { 18 | f1: u8, 19 | #[educe(Into(u8))] 20 | f2: u8, 21 | } 22 | 23 | #[derive(Educe)] 24 | #[educe(Into(u8))] 25 | struct Tuple(u8); 26 | 27 | #[derive(Educe)] 28 | #[educe(Into(u8))] 29 | struct Tuple2(u8, #[educe(Into(u8))] u8); 30 | 31 | let s1 = Struct { 32 | f1: 1 33 | }; 34 | 35 | let s2 = Struct2 { 36 | f1: 1, f2: 2 37 | }; 38 | 39 | let t1 = Tuple(1); 40 | let t2 = Tuple2(1, 2); 41 | 42 | assert_eq!(1u8, s1.into()); 43 | assert_eq!(2u8, s2.into()); 44 | 45 | assert_eq!(1u8, t1.into()); 46 | assert_eq!(2u8, t2.into()); 47 | } 48 | 49 | #[allow(dead_code)] 50 | #[test] 51 | fn basic_2() { 52 | #[derive(Copy, Clone, Educe)] 53 | #[educe(Into(u8), Into(u16))] 54 | struct Struct { 55 | f1: u8, 56 | f2: u16, 57 | } 58 | 59 | #[derive(Copy, Clone, Educe)] 60 | #[educe(Into(u8), Into(u16))] 61 | struct Struct2 { 62 | f1: u8, 63 | #[educe(Into(u8))] 64 | f2: u8, 65 | f3: u16, 66 | #[educe(Into(u16))] 67 | f4: u16, 68 | } 69 | 70 | #[derive(Copy, Clone, Educe)] 71 | #[educe(Into(u8), Into(u16))] 72 | struct Tuple(u8, u16); 73 | 74 | #[derive(Copy, Clone, Educe)] 75 | #[educe(Into(u8), Into(u16))] 76 | struct Tuple2(u8, #[educe(Into(u8))] u8, u16, #[educe(Into(u16))] u16); 77 | 78 | let s1 = Struct { 79 | f1: 1, f2: 2 80 | }; 81 | 82 | let s2 = Struct2 { 83 | f1: 1, f2: 2, f3: 3, f4: 4 84 | }; 85 | 86 | let t1 = Tuple(1, 2); 87 | let t2 = Tuple2(1, 2, 3, 4); 88 | 89 | assert_eq!(1u8, s1.into()); 90 | assert_eq!(2u16, s1.into()); 91 | assert_eq!(2u8, s2.into()); 92 | assert_eq!(4u16, s2.into()); 93 | 94 | assert_eq!(1u8, t1.into()); 95 | assert_eq!(2u16, t1.into()); 96 | assert_eq!(2u8, t2.into()); 97 | assert_eq!(4u16, t2.into()); 98 | } 99 | 100 | #[test] 101 | fn method_1() { 102 | fn into(v: u16) -> u8 { 103 | v as u8 104 | } 105 | 106 | #[derive(Educe)] 107 | #[educe(Into(u8))] 108 | struct Struct { 109 | #[educe(Into(u8, method = into))] 110 | f1: u16, 111 | } 112 | 113 | let s1 = Struct { 114 | f1: 1 115 | }; 116 | 117 | assert_eq!(1u8, s1.into()); 118 | } 119 | 120 | #[test] 121 | fn method_2() { 122 | fn into(v: u16) -> u8 { 123 | v as u8 124 | } 125 | 126 | #[derive(Educe)] 127 | #[educe(Into(u8))] 128 | struct Struct { 129 | #[educe(Into(u8, method(into)))] 130 | f1: u16, 131 | } 132 | 133 | let s1 = Struct { 134 | f1: 1 135 | }; 136 | 137 | assert_eq!(1u8, s1.into()); 138 | } 139 | 140 | #[test] 141 | fn bound_1() { 142 | #[derive(Educe)] 143 | #[educe(Into(u8))] 144 | struct Struct { 145 | #[educe(Into(u8))] 146 | f1: T, 147 | } 148 | 149 | let s1 = Struct { 150 | f1: 1 151 | }; 152 | 153 | assert_eq!(1u8, s1.into()); 154 | } 155 | 156 | #[test] 157 | fn bound_2() { 158 | #[derive(Educe)] 159 | #[educe(Into(u8, bound = "T: Into"))] 160 | struct Struct { 161 | #[educe(Into(u8))] 162 | f1: T, 163 | } 164 | 165 | let s1 = Struct { 166 | f1: 1 167 | }; 168 | 169 | assert_eq!(1u8, s1.into()); 170 | } 171 | 172 | #[test] 173 | fn bound_3() { 174 | #[derive(Educe)] 175 | #[educe(Into(u8, bound(T: Into)))] 176 | struct Struct { 177 | #[educe(Into(u8))] 178 | f1: T, 179 | } 180 | 181 | let s1 = Struct { 182 | f1: 1 183 | }; 184 | 185 | assert_eq!(1u8, s1.into()); 186 | } 187 | -------------------------------------------------------------------------------- /src/trait_handlers/eq/mod.rs: -------------------------------------------------------------------------------- 1 | mod models; 2 | 3 | use models::{FieldAttributeBuilder, TypeAttributeBuilder}; 4 | use quote::quote; 5 | use syn::{Data, DeriveInput, Meta}; 6 | 7 | use super::TraitHandler; 8 | use crate::Trait; 9 | 10 | pub(crate) struct EqHandler; 11 | 12 | impl TraitHandler for EqHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | #[cfg(feature = "PartialEq")] 21 | let contains_partial_eq = traits.contains(&Trait::PartialEq); 22 | 23 | #[cfg(not(feature = "PartialEq"))] 24 | let contains_partial_eq = false; 25 | 26 | let type_attribute = TypeAttributeBuilder { 27 | enable_flag: true, 28 | enable_bound: !contains_partial_eq, 29 | } 30 | .build_from_eq_meta(meta)?; 31 | 32 | let mut field_types = vec![]; 33 | 34 | // if `contains_partial_eq` is true, the implementation is handled by the `PartialEq` attribute, and field attributes is also handled by the `PartialEq` attribute 35 | if !contains_partial_eq { 36 | match &ast.data { 37 | Data::Struct(data) => { 38 | for field in data.fields.iter() { 39 | field_types.push(&field.ty); 40 | let _ = 41 | FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; 42 | } 43 | }, 44 | Data::Enum(data) => { 45 | for variant in data.variants.iter() { 46 | let _ = TypeAttributeBuilder { 47 | enable_flag: false, enable_bound: false 48 | } 49 | .build_from_attributes(&variant.attrs, traits)?; 50 | 51 | for field in variant.fields.iter() { 52 | field_types.push(&field.ty); 53 | let _ = FieldAttributeBuilder 54 | .build_from_attributes(&field.attrs, traits)?; 55 | } 56 | } 57 | }, 58 | Data::Union(data) => { 59 | for field in data.fields.named.iter() { 60 | field_types.push(&field.ty); 61 | let _ = 62 | FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; 63 | } 64 | }, 65 | } 66 | 67 | let ident = &ast.ident; 68 | 69 | /* 70 | #[derive(PartialEq)] 71 | struct B { 72 | f1: PhantomData, 73 | } 74 | 75 | impl Eq for B { 76 | 77 | } 78 | 79 | // The above code will throw a compile error because T have to be bound to `PartialEq`. However, it seems not to be necessary logically. 80 | */ 81 | let bound = 82 | type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 83 | &ast.generics.params, 84 | &syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(), 85 | &field_types, 86 | &[quote! {::core::cmp::PartialEq}], 87 | ); 88 | 89 | let mut generics = ast.generics.clone(); 90 | let where_clause = generics.make_where_clause(); 91 | 92 | for where_predicate in bound { 93 | where_clause.predicates.push(where_predicate); 94 | } 95 | 96 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 97 | 98 | token_stream.extend(quote! { 99 | impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { 100 | } 101 | }); 102 | } 103 | 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display, Formatter}; 2 | 3 | use proc_macro2::Span; 4 | use syn::{spanned::Spanned, Ident, Path, Variant}; 5 | 6 | use crate::{common::path::path_to_string, Trait}; 7 | 8 | struct DisplayStringSlice<'a>(&'a [&'static str]); 9 | 10 | impl<'a> Display for DisplayStringSlice<'a> { 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | if !self.0.is_empty() { 13 | f.write_str(", which should be reformatted as follows:")?; 14 | 15 | for &s in self.0 { 16 | f.write_str("\n ")?; 17 | f.write_str(s)?; 18 | } 19 | } 20 | 21 | Ok(()) 22 | } 23 | } 24 | 25 | struct DisplayTraits; 26 | 27 | impl Display for DisplayTraits { 28 | #[inline] 29 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 30 | for t in &Trait::VARIANTS[..Trait::VARIANTS.len() - 1] { 31 | f.write_str("\n ")?; 32 | f.write_fmt(format_args!("{t:?}"))?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[inline] 40 | pub(crate) fn derive_attribute_not_set_up_yet() -> syn::Error { 41 | syn::Error::new( 42 | Span::call_site(), 43 | "you are using `Educe` in the `derive` attribute, but it has not been set up yet", 44 | ) 45 | } 46 | 47 | #[inline] 48 | pub(crate) fn attribute_incorrect_place(name: &Ident) -> syn::Error { 49 | syn::Error::new(name.span(), format!("the `{name}` attribute cannot be placed here")) 50 | } 51 | 52 | #[inline] 53 | pub(crate) fn attribute_incorrect_format_with_span( 54 | name: &Ident, 55 | span: Span, 56 | correct_usage: &[&'static str], 57 | ) -> syn::Error { 58 | if correct_usage.is_empty() { 59 | attribute_incorrect_place(name) 60 | } else { 61 | syn::Error::new( 62 | span, 63 | format!( 64 | "you are using an incorrect format of the `{name}` attribute{}", 65 | DisplayStringSlice(correct_usage) 66 | ), 67 | ) 68 | } 69 | } 70 | 71 | #[inline] 72 | pub(crate) fn attribute_incorrect_format( 73 | name: &Ident, 74 | correct_usage: &[&'static str], 75 | ) -> syn::Error { 76 | attribute_incorrect_format_with_span(name, name.span(), correct_usage) 77 | } 78 | 79 | #[inline] 80 | pub(crate) fn parameter_reset(name: &Ident) -> syn::Error { 81 | syn::Error::new(name.span(), format!("you are trying to reset the `{name}` parameter")) 82 | } 83 | 84 | #[inline] 85 | pub(crate) fn educe_format_incorrect(name: &Ident) -> syn::Error { 86 | attribute_incorrect_format(name, &[stringify!(#[educe(Trait1, Trait2, ..., TraitN)])]) 87 | } 88 | 89 | #[inline] 90 | pub(crate) fn unsupported_trait(name: &Path) -> syn::Error { 91 | let span = name.span(); 92 | 93 | match name.get_ident() { 94 | Some(name) => syn::Error::new( 95 | span, 96 | format!("unsupported trait `{name}`, available traits:{DisplayTraits}"), 97 | ), 98 | None => { 99 | let name = path_to_string(name); 100 | 101 | syn::Error::new( 102 | span, 103 | format!("unsupported trait `{name}`, available traits:{DisplayTraits}"), 104 | ) 105 | }, 106 | } 107 | } 108 | 109 | #[inline] 110 | pub(crate) fn reuse_a_trait(name: &Ident) -> syn::Error { 111 | syn::Error::new(name.span(), format!("the trait `{name}` is used repeatedly")) 112 | } 113 | 114 | #[inline] 115 | pub(crate) fn trait_not_used(name: &Ident) -> syn::Error { 116 | syn::Error::new(name.span(), format!("the trait `{name}` is not used")) 117 | } 118 | 119 | #[inline] 120 | pub(crate) fn trait_not_support_union(name: &Ident) -> syn::Error { 121 | syn::Error::new(name.span(), format!("the trait `{name}` does not support to a union")) 122 | } 123 | 124 | #[inline] 125 | pub(crate) fn trait_not_support_unit_variant(name: &Ident, variant: &Variant) -> syn::Error { 126 | syn::Error::new( 127 | variant.span(), 128 | format!("the trait `{name}` cannot be implemented for an enum which has unit variants"), 129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /src/common/where_predicates_bool.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens}; 2 | use syn::{ 3 | parse::{Parse, ParseStream}, 4 | punctuated::Punctuated, 5 | spanned::Spanned, 6 | token::Comma, 7 | Expr, GenericParam, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate, 8 | }; 9 | 10 | use super::path::path_to_string; 11 | 12 | pub(crate) type WherePredicates = Punctuated; 13 | 14 | pub(crate) enum WherePredicatesOrBool { 15 | WherePredicates(WherePredicates), 16 | Bool(bool), 17 | All, 18 | } 19 | 20 | impl WherePredicatesOrBool { 21 | fn from_lit(lit: &Lit) -> syn::Result { 22 | Ok(match lit { 23 | Lit::Bool(lit) => Self::Bool(lit.value), 24 | Lit::Str(lit) => match lit.parse_with(WherePredicates::parse_terminated) { 25 | Ok(where_predicates) => Self::WherePredicates(where_predicates), 26 | Err(_) if lit.value().is_empty() => Self::Bool(false), 27 | Err(error) => return Err(error), 28 | }, 29 | other => { 30 | return Err(syn::Error::new( 31 | other.span(), 32 | "unexpected kind of literal (only boolean or string allowed)", 33 | )) 34 | }, 35 | }) 36 | } 37 | } 38 | 39 | impl Parse for WherePredicatesOrBool { 40 | #[inline] 41 | fn parse(input: ParseStream) -> syn::Result { 42 | if let Ok(lit) = input.parse::() { 43 | return Self::from_lit(&lit); 44 | } 45 | 46 | if let Ok(_star) = input.parse::() { 47 | return Ok(Self::All); 48 | } 49 | 50 | Ok(Self::WherePredicates(input.parse_terminated(WherePredicate::parse, Token![,])?)) 51 | } 52 | } 53 | 54 | #[inline] 55 | pub(crate) fn meta_name_value_2_where_predicates_bool( 56 | name_value: &MetaNameValue, 57 | ) -> syn::Result { 58 | if let Expr::Lit(lit) = &name_value.value { 59 | return WherePredicatesOrBool::from_lit(&lit.lit); 60 | } 61 | 62 | Err(syn::Error::new( 63 | name_value.value.span(), 64 | format!( 65 | "expected `{path} = \"where_predicates\"` or `{path} = false`", 66 | path = path_to_string(&name_value.path) 67 | ), 68 | )) 69 | } 70 | 71 | #[inline] 72 | pub(crate) fn meta_2_where_predicates(meta: &Meta) -> syn::Result { 73 | match &meta { 74 | Meta::NameValue(name_value) => meta_name_value_2_where_predicates_bool(name_value), 75 | Meta::List(list) => list.parse_args::(), 76 | Meta::Path(path) => Err(syn::Error::new( 77 | path.span(), 78 | format!( 79 | "expected `{path} = \"where_predicates\"`, `{path}(where_predicates)`, `{path} = \ 80 | false`, or `{path}(false)`", 81 | path = path.clone().into_token_stream() 82 | ), 83 | )), 84 | } 85 | } 86 | 87 | #[inline] 88 | pub(crate) fn create_where_predicates_from_all_generic_parameters( 89 | params: &Punctuated, 90 | bound_trait: &Path, 91 | ) -> WherePredicates { 92 | let mut where_predicates = Punctuated::new(); 93 | 94 | for param in params { 95 | if let GenericParam::Type(ty) = param { 96 | let ident = &ty.ident; 97 | 98 | where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap()); 99 | } 100 | } 101 | 102 | where_predicates 103 | } 104 | 105 | #[inline] 106 | pub(crate) fn create_where_predicates_from_generic_parameters_check_types( 107 | bound_trait: &Path, 108 | types: &[&Type], 109 | supertraits: &[proc_macro2::TokenStream], 110 | ) -> WherePredicates { 111 | let mut where_predicates = Punctuated::new(); 112 | 113 | for t in types { 114 | where_predicates.push(syn::parse2(quote! { #t: #bound_trait }).unwrap()); 115 | } 116 | 117 | for supertrait in supertraits { 118 | where_predicates.push(syn::parse2(quote! { Self: #supertrait }).unwrap()); 119 | } 120 | 121 | where_predicates 122 | } 123 | -------------------------------------------------------------------------------- /tests/into_enum.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Into")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[allow(dead_code)] 7 | #[test] 8 | fn basic_1() { 9 | #[derive(Educe)] 10 | #[educe(Into(u8))] 11 | enum Enum { 12 | Struct { f1: u8 }, 13 | Tuple(u8), 14 | } 15 | 16 | #[derive(Educe)] 17 | #[educe(Into(u8))] 18 | enum Enum2 { 19 | Struct { 20 | f1: u8, 21 | #[educe(Into(u8))] 22 | f2: u8, 23 | }, 24 | Tuple(u8, #[educe(Into(u8))] u8), 25 | } 26 | 27 | let s1 = Enum::Struct { 28 | f1: 1 29 | }; 30 | 31 | let s2 = Enum2::Struct { 32 | f1: 1, f2: 2 33 | }; 34 | 35 | let t1 = Enum::Tuple(1); 36 | let t2 = Enum2::Tuple(1, 2); 37 | 38 | assert_eq!(1u8, s1.into()); 39 | assert_eq!(2u8, s2.into()); 40 | 41 | assert_eq!(1u8, t1.into()); 42 | assert_eq!(2u8, t2.into()); 43 | } 44 | 45 | #[allow(dead_code)] 46 | #[test] 47 | fn basic_2() { 48 | #[derive(Copy, Clone, Educe)] 49 | #[educe(Into(u8), Into(u16))] 50 | enum Enum { 51 | Struct { f1: u8, f2: u16 }, 52 | Tuple(u8, u16), 53 | } 54 | 55 | #[derive(Copy, Clone, Educe)] 56 | #[educe(Into(u8), Into(u16))] 57 | enum Enum2 { 58 | Struct { 59 | f1: u8, 60 | #[educe(Into(u8))] 61 | f2: u8, 62 | f3: u8, 63 | #[educe(Into(u16))] 64 | f4: u8, 65 | }, 66 | Tuple(u8, #[educe(Into(u8))] u8, u16, #[educe(Into(u16))] u16), 67 | } 68 | 69 | let s1 = Enum::Struct { 70 | f1: 1, f2: 2 71 | }; 72 | 73 | let s2 = Enum2::Struct { 74 | f1: 1, f2: 2, f3: 3, f4: 4 75 | }; 76 | 77 | let t1 = Enum::Tuple(1, 2); 78 | let t2 = Enum2::Tuple(1, 2, 3, 4); 79 | 80 | assert_eq!(1u8, s1.into()); 81 | assert_eq!(2u16, s1.into()); 82 | assert_eq!(2u8, s2.into()); 83 | assert_eq!(4u16, s2.into()); 84 | 85 | assert_eq!(1u8, t1.into()); 86 | assert_eq!(2u16, t1.into()); 87 | assert_eq!(2u8, t2.into()); 88 | assert_eq!(4u16, t2.into()); 89 | } 90 | 91 | #[allow(dead_code)] 92 | fn method_1() { 93 | fn into(v: u16) -> u8 { 94 | v as u8 95 | } 96 | 97 | #[derive(Educe)] 98 | #[educe(Into(u8))] 99 | enum Enum { 100 | Struct { 101 | #[educe(Into(u8, method = into))] 102 | f1: u16, 103 | }, 104 | Tuple(u8), 105 | } 106 | 107 | let s1 = Enum::Struct { 108 | f1: 1 109 | }; 110 | 111 | assert_eq!(1u8, s1.into()); 112 | } 113 | 114 | #[allow(dead_code)] 115 | fn method_2() { 116 | fn into(v: u16) -> u8 { 117 | v as u8 118 | } 119 | 120 | #[derive(Educe)] 121 | #[educe(Into(u8))] 122 | enum Enum { 123 | Struct { 124 | #[educe(Into(u8, method(into)))] 125 | f1: u16, 126 | }, 127 | Tuple(u8), 128 | } 129 | 130 | let s1 = Enum::Struct { 131 | f1: 1 132 | }; 133 | 134 | assert_eq!(1u8, s1.into()); 135 | } 136 | 137 | #[test] 138 | fn bound_1() { 139 | #[derive(Educe)] 140 | #[educe(Into(u8))] 141 | enum Enum { 142 | Struct { 143 | #[educe(Into(u8))] 144 | f1: T, 145 | }, 146 | } 147 | 148 | let s1 = Enum::Struct { 149 | f1: 1 150 | }; 151 | 152 | assert_eq!(1u8, s1.into()); 153 | } 154 | 155 | #[test] 156 | fn bound_2() { 157 | #[derive(Educe)] 158 | #[educe(Into(u8, bound = "T: Into"))] 159 | enum Enum { 160 | Struct { 161 | #[educe(Into(u8))] 162 | f1: T, 163 | }, 164 | } 165 | 166 | let s1 = Enum::Struct { 167 | f1: 1 168 | }; 169 | 170 | assert_eq!(1u8, s1.into()); 171 | } 172 | 173 | #[test] 174 | fn bound_3() { 175 | #[derive(Educe)] 176 | #[educe(Into(u8, bound(T: Into)))] 177 | enum Enum { 178 | Struct { 179 | #[educe(Into(u8))] 180 | f1: T, 181 | }, 182 | } 183 | 184 | let s1 = Enum::Struct { 185 | f1: 1 186 | }; 187 | 188 | assert_eq!(1u8, s1.into()); 189 | } 190 | -------------------------------------------------------------------------------- /tests/copy_clone_struct.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Copy", feature = "Clone"))] 2 | #![no_std] 3 | #![allow(clippy::clone_on_copy)] 4 | 5 | use core::marker::PhantomData; 6 | 7 | use educe::Educe; 8 | 9 | #[test] 10 | fn empty() { 11 | #[allow(dead_code)] 12 | #[derive(Educe)] 13 | #[educe(Copy, Clone)] 14 | struct Struct {} 15 | 16 | #[allow(dead_code)] 17 | #[derive(Educe)] 18 | #[educe(Copy, Clone)] 19 | struct Tuple(); 20 | } 21 | 22 | #[test] 23 | fn basic() { 24 | #[derive(Educe)] 25 | #[educe(Copy, Clone)] 26 | struct Unit; 27 | 28 | #[derive(Educe)] 29 | #[educe(Copy, Clone)] 30 | struct Struct { 31 | f1: u8, 32 | } 33 | 34 | #[derive(Educe)] 35 | #[educe(Copy, Clone)] 36 | struct Tuple(u8); 37 | 38 | let u = Unit.clone(); 39 | let s = Struct { 40 | f1: 1 41 | } 42 | .clone(); 43 | let t = Tuple(1).clone(); 44 | 45 | assert!(matches!(u, Unit)); 46 | 47 | assert_eq!(1, s.f1); 48 | assert_eq!(1, t.0); 49 | } 50 | 51 | #[test] 52 | fn bound_1() { 53 | #[derive(Educe)] 54 | #[educe(Copy, Clone)] 55 | struct Struct { 56 | f1: T, 57 | } 58 | 59 | #[derive(Educe)] 60 | #[educe(Copy, Clone)] 61 | struct Tuple(T); 62 | 63 | let s = Struct { 64 | f1: 1 65 | } 66 | .clone(); 67 | let t = Tuple(1).clone(); 68 | 69 | assert_eq!(1, s.f1); 70 | assert_eq!(1, t.0); 71 | } 72 | 73 | #[test] 74 | fn bound_2() { 75 | #[derive(Educe)] 76 | #[educe(Copy, Clone(bound = "T: core::marker::Copy"))] 77 | struct Struct { 78 | f1: T, 79 | } 80 | 81 | #[derive(Educe)] 82 | #[educe(Copy, Clone(bound = "T: core::marker::Copy"))] 83 | struct Tuple(T); 84 | 85 | let s = Struct { 86 | f1: 1 87 | } 88 | .clone(); 89 | let t = Tuple(1).clone(); 90 | 91 | assert_eq!(1, s.f1); 92 | assert_eq!(1, t.0); 93 | } 94 | 95 | #[test] 96 | fn bound_3() { 97 | #[derive(Educe)] 98 | #[educe(Copy, Clone(bound(T: core::marker::Copy)))] 99 | struct Struct { 100 | f1: T, 101 | } 102 | 103 | #[derive(Educe)] 104 | #[educe(Copy, Clone(bound(T: core::marker::Copy)))] 105 | struct Tuple(T); 106 | 107 | let s = Struct { 108 | f1: 1 109 | } 110 | .clone(); 111 | let t = Tuple(1).clone(); 112 | 113 | assert_eq!(1, s.f1); 114 | assert_eq!(1, t.0); 115 | } 116 | 117 | #[test] 118 | fn bound_4() { 119 | #[derive(Educe)] 120 | #[educe(Copy, Clone)] 121 | struct Struct { 122 | f1: Option, 123 | f2: PhantomData, 124 | } 125 | 126 | #[derive(Educe)] 127 | #[educe(Copy, Clone)] 128 | struct Tuple(Option, PhantomData); 129 | 130 | let s = Struct { 131 | f1: Some(1), f2: PhantomData:: 132 | } 133 | .clone(); 134 | let t = Tuple(Some(1), PhantomData::).clone(); 135 | 136 | assert_eq!(Some(1), s.f1); 137 | assert_eq!(Some(1), t.0); 138 | } 139 | 140 | #[test] 141 | fn bound_5() { 142 | trait Suitable {} 143 | struct SuitableNotClone; 144 | impl Suitable for SuitableNotClone {} 145 | let phantom = PhantomData::; 146 | 147 | fn copy(t: &T) -> T { 148 | *t 149 | } 150 | 151 | #[derive(Educe)] 152 | #[educe(Copy)] 153 | struct Struct { 154 | f1: Option, 155 | f2: PhantomData, 156 | } 157 | 158 | impl Clone for Struct { 159 | fn clone(&self) -> Self { 160 | Struct { 161 | f1: self.f1.clone(), f2: PhantomData 162 | } 163 | } 164 | } 165 | 166 | #[derive(Educe)] 167 | #[educe(Copy)] 168 | struct Tuple(Option, PhantomData); 169 | 170 | impl Clone for Tuple { 171 | fn clone(&self) -> Self { 172 | Tuple(self.0.clone(), PhantomData) 173 | } 174 | } 175 | 176 | let s = copy(&Struct { 177 | f1: Some(1), f2: phantom 178 | }); 179 | 180 | let t = copy(&Tuple(Some(1), phantom)); 181 | 182 | assert_eq!(Some(1), s.f1); 183 | assert_eq!(Some(1), t.0); 184 | } 185 | -------------------------------------------------------------------------------- /src/common/expr.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens}; 2 | use syn::{spanned::Spanned, Expr, Lit, Meta, Type}; 3 | 4 | use super::path::path_to_string; 5 | 6 | const INT_TYPES: [&str; 12] = 7 | ["u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]; 8 | 9 | const FLOAT_TYPES: [&str; 2] = ["f32", "f64"]; 10 | 11 | #[inline] 12 | pub(crate) fn meta_2_expr(meta: &Meta) -> syn::Result { 13 | match &meta { 14 | Meta::NameValue(name_value) => Ok(name_value.value.clone()), 15 | Meta::List(list) => list.parse_args::(), 16 | Meta::Path(path) => Err(syn::Error::new( 17 | path.span(), 18 | format!("expected `{path} = Expr` or `{path}(Expr)`", path = path_to_string(path)), 19 | )), 20 | } 21 | } 22 | 23 | #[inline] 24 | pub(crate) fn auto_adjust_expr(expr: Expr, ty: Option<&Type>) -> Expr { 25 | match &expr { 26 | Expr::Lit(lit) => { 27 | match &lit.lit { 28 | Lit::Int(lit) => { 29 | if let Some(Type::Path(ty)) = ty { 30 | let ty_string = ty.into_token_stream().to_string(); 31 | 32 | if lit.suffix() == ty_string || INT_TYPES.contains(&ty_string.as_str()) { 33 | // don't call into 34 | return expr; 35 | } 36 | } 37 | }, 38 | Lit::Float(lit) => { 39 | if let Some(Type::Path(ty)) = ty { 40 | let ty_string = ty.into_token_stream().to_string(); 41 | 42 | if lit.suffix() == ty_string || FLOAT_TYPES.contains(&ty_string.as_str()) { 43 | // don't call into 44 | return expr; 45 | } 46 | } 47 | }, 48 | Lit::Str(_) => { 49 | if let Some(Type::Reference(ty)) = ty { 50 | let ty_string = ty.elem.clone().into_token_stream().to_string(); 51 | 52 | if ty_string == "str" { 53 | // don't call into 54 | return expr; 55 | } 56 | } 57 | }, 58 | Lit::Bool(_) => { 59 | if let Some(Type::Path(ty)) = ty { 60 | let ty_string = ty.into_token_stream().to_string(); 61 | 62 | if ty_string == "bool" { 63 | // don't call into 64 | return expr; 65 | } 66 | } 67 | }, 68 | Lit::Char(_) => { 69 | if let Some(Type::Path(ty)) = ty { 70 | let ty_string = ty.into_token_stream().to_string(); 71 | 72 | if ty_string == "char" { 73 | // don't call into 74 | return expr; 75 | } 76 | } 77 | }, 78 | Lit::Byte(_) => { 79 | if let Some(Type::Path(ty)) = ty { 80 | let ty_string = ty.into_token_stream().to_string(); 81 | 82 | if ty_string == "u8" { 83 | // don't call into 84 | return expr; 85 | } 86 | } 87 | }, 88 | Lit::ByteStr(_) => { 89 | if let Some(Type::Reference(ty)) = ty { 90 | if let Type::Array(ty) = ty.elem.as_ref() { 91 | if let Type::Path(ty) = ty.elem.as_ref() { 92 | let ty_string = ty.into_token_stream().to_string(); 93 | 94 | if ty_string == "u8" { 95 | // don't call into 96 | return expr; 97 | } 98 | } 99 | } 100 | } 101 | }, 102 | _ => (), 103 | } 104 | 105 | syn::parse2(quote!(::core::convert::Into::into(#expr))).unwrap() 106 | }, 107 | _ => expr, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/trait_handlers/clone/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; 2 | 3 | use crate::{common::path::meta_2_path, panic, supported_traits::Trait}; 4 | 5 | pub(crate) struct FieldAttribute { 6 | pub(crate) method: Option, 7 | } 8 | 9 | pub(crate) struct FieldAttributeBuilder { 10 | pub(crate) enable_method: bool, 11 | } 12 | 13 | impl FieldAttributeBuilder { 14 | pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result { 15 | debug_assert!(meta.path().is_ident("Clone")); 16 | 17 | let mut method = None; 18 | 19 | let correct_usage_for_clone_attribute = { 20 | let mut usage = vec![]; 21 | 22 | if self.enable_method { 23 | usage.push(stringify!(#[educe(Clone(method(path_to_method)))])); 24 | } 25 | 26 | usage 27 | }; 28 | 29 | match meta { 30 | Meta::Path(_) | Meta::NameValue(_) => { 31 | return Err(panic::attribute_incorrect_format( 32 | meta.path().get_ident().unwrap(), 33 | &correct_usage_for_clone_attribute, 34 | )); 35 | }, 36 | Meta::List(list) => { 37 | let result = 38 | list.parse_args_with(Punctuated::::parse_terminated)?; 39 | 40 | let mut method_is_set = false; 41 | 42 | let mut handler = |meta: Meta| -> syn::Result { 43 | if let Some(ident) = meta.path().get_ident() { 44 | if ident == "method" { 45 | if !self.enable_method { 46 | return Ok(false); 47 | } 48 | 49 | let v = meta_2_path(&meta)?; 50 | 51 | if method_is_set { 52 | return Err(panic::parameter_reset(ident)); 53 | } 54 | 55 | method_is_set = true; 56 | 57 | method = Some(v); 58 | 59 | return Ok(true); 60 | } 61 | } 62 | 63 | Ok(false) 64 | }; 65 | 66 | for p in result { 67 | if !handler(p)? { 68 | return Err(panic::attribute_incorrect_format( 69 | meta.path().get_ident().unwrap(), 70 | &correct_usage_for_clone_attribute, 71 | )); 72 | } 73 | } 74 | }, 75 | } 76 | 77 | Ok(FieldAttribute { 78 | method, 79 | }) 80 | } 81 | 82 | pub(crate) fn build_from_attributes( 83 | &self, 84 | attributes: &[Attribute], 85 | traits: &[Trait], 86 | ) -> syn::Result { 87 | let mut output = None; 88 | 89 | for attribute in attributes.iter() { 90 | let path = attribute.path(); 91 | 92 | if path.is_ident("educe") { 93 | if let Meta::List(list) = &attribute.meta { 94 | let result = 95 | list.parse_args_with(Punctuated::::parse_terminated)?; 96 | 97 | for meta in result { 98 | let path = meta.path(); 99 | 100 | let t = match Trait::from_path(path) { 101 | Some(t) => t, 102 | None => return Err(panic::unsupported_trait(meta.path())), 103 | }; 104 | 105 | if !traits.contains(&t) { 106 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 107 | } 108 | 109 | if t == Trait::Clone { 110 | if output.is_some() { 111 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 112 | } 113 | 114 | output = Some(self.build_from_clone_meta(&meta)?); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | Ok(output.unwrap_or(FieldAttribute { 122 | method: None 123 | })) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_ord/partial_ord_struct.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use quote::quote; 4 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Path, Type}; 5 | 6 | use super::{ 7 | models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, 8 | TraitHandler, 9 | }; 10 | use crate::{common::ident_index::IdentOrIndex, Trait}; 11 | 12 | pub(crate) struct PartialOrdStructHandler; 13 | 14 | impl TraitHandler for PartialOrdStructHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | let type_attribute = TypeAttributeBuilder { 23 | enable_flag: true, enable_bound: true 24 | } 25 | .build_from_partial_ord_meta(meta)?; 26 | 27 | let mut partial_ord_types: Vec<&Type> = Vec::new(); 28 | 29 | let mut partial_cmp_token_stream = proc_macro2::TokenStream::new(); 30 | 31 | if let Data::Struct(data) = &ast.data { 32 | let mut fields: BTreeMap = BTreeMap::new(); 33 | 34 | for (index, field) in data.fields.iter().enumerate() { 35 | let field_attribute = FieldAttributeBuilder { 36 | enable_ignore: true, 37 | enable_method: true, 38 | enable_rank: true, 39 | rank: isize::MIN + index as isize, 40 | } 41 | .build_from_attributes(&field.attrs, traits)?; 42 | 43 | if field_attribute.ignore { 44 | continue; 45 | } 46 | 47 | let rank = field_attribute.rank; 48 | 49 | if fields.contains_key(&rank) { 50 | return Err(super::panic::reuse_a_rank( 51 | field_attribute.rank_span.unwrap_or_else(|| field.span()), 52 | rank, 53 | )); 54 | } 55 | 56 | fields.insert(rank, (index, field, field_attribute)); 57 | } 58 | 59 | let built_in_partial_cmp: Path = 60 | syn::parse2(quote!(::core::cmp::PartialOrd::partial_cmp)).unwrap(); 61 | 62 | for (index, field, field_attribute) in fields.values() { 63 | let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), *index); 64 | 65 | let partial_cmp = field_attribute.method.as_ref().unwrap_or_else(|| { 66 | partial_ord_types.push(&field.ty); 67 | 68 | &built_in_partial_cmp 69 | }); 70 | 71 | partial_cmp_token_stream.extend(quote! { 72 | match #partial_cmp(&self.#field_name, &other.#field_name) { 73 | Some(::core::cmp::Ordering::Equal) => (), 74 | Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater), 75 | Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less), 76 | None => return None, 77 | } 78 | }); 79 | } 80 | } 81 | 82 | let ident = &ast.ident; 83 | 84 | let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 85 | &ast.generics.params, 86 | &syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(), 87 | &partial_ord_types, 88 | &[quote! {::core::cmp::PartialEq}], 89 | ); 90 | 91 | let mut generics = ast.generics.clone(); 92 | let where_clause = generics.make_where_clause(); 93 | 94 | for where_predicate in bound { 95 | where_clause.predicates.push(where_predicate); 96 | } 97 | 98 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 99 | 100 | token_stream.extend(quote! { 101 | impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { 102 | #[inline] 103 | fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { 104 | #partial_cmp_token_stream 105 | 106 | Some(::core::cmp::Ordering::Equal) 107 | } 108 | } 109 | }); 110 | 111 | Ok(()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/clone_enum.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Clone")] 2 | #![no_std] 3 | 4 | use educe::Educe; 5 | 6 | #[test] 7 | fn empty() { 8 | #[allow(dead_code)] 9 | #[derive(Educe)] 10 | #[educe(Clone)] 11 | enum Enum {} 12 | 13 | #[derive(Educe)] 14 | #[educe(Clone)] 15 | enum Enum2 { 16 | Struct {}, 17 | Tuple(), 18 | } 19 | 20 | let s = Enum2::Struct {}.clone(); 21 | let t = Enum2::Tuple().clone(); 22 | 23 | assert!(matches!(s, Enum2::Struct {})); 24 | assert!(matches!(t, Enum2::Tuple())); 25 | } 26 | 27 | #[test] 28 | fn basic() { 29 | #[derive(Educe)] 30 | #[educe(Clone)] 31 | enum Enum { 32 | Unit, 33 | Struct { f1: u8 }, 34 | Tuple(u8), 35 | } 36 | 37 | let u = Enum::Unit.clone(); 38 | let s = Enum::Struct { 39 | f1: 1 40 | } 41 | .clone(); 42 | let t = Enum::Tuple(1).clone(); 43 | 44 | assert!(matches!(u, Enum::Unit)); 45 | 46 | if let Enum::Struct { 47 | f1, 48 | } = s 49 | { 50 | assert_eq!(1, f1); 51 | } else { 52 | panic!(); 53 | } 54 | 55 | if let Enum::Tuple(f1) = t { 56 | assert_eq!(1, f1); 57 | } else { 58 | panic!(); 59 | } 60 | } 61 | 62 | #[test] 63 | fn method_1() { 64 | fn clone(v: &u8) -> u8 { 65 | v + 100 66 | } 67 | 68 | #[derive(Educe)] 69 | #[educe(Clone)] 70 | enum Enum { 71 | Struct { 72 | #[educe(Clone(method = clone))] 73 | f1: u8, 74 | }, 75 | Tuple(#[educe(Clone(method = clone))] u8), 76 | } 77 | 78 | let s = Enum::Struct { 79 | f1: 1 80 | } 81 | .clone(); 82 | let t = Enum::Tuple(1).clone(); 83 | 84 | if let Enum::Struct { 85 | f1, 86 | } = s 87 | { 88 | assert_eq!(101, f1); 89 | } else { 90 | panic!(); 91 | } 92 | 93 | if let Enum::Tuple(f1) = t { 94 | assert_eq!(101, f1); 95 | } else { 96 | panic!(); 97 | } 98 | } 99 | 100 | #[test] 101 | fn method_2() { 102 | fn clone(v: &u8) -> u8 { 103 | v + 100 104 | } 105 | 106 | #[derive(Educe)] 107 | #[educe(Clone)] 108 | enum Enum { 109 | Struct { 110 | #[educe(Clone(method(clone)))] 111 | f1: u8, 112 | }, 113 | Tuple(#[educe(Clone(method(clone)))] u8), 114 | } 115 | 116 | let s = Enum::Struct { 117 | f1: 1 118 | } 119 | .clone(); 120 | let t = Enum::Tuple(1).clone(); 121 | 122 | if let Enum::Struct { 123 | f1, 124 | } = s 125 | { 126 | assert_eq!(101, f1); 127 | } else { 128 | panic!(); 129 | } 130 | 131 | if let Enum::Tuple(f1) = t { 132 | assert_eq!(101, f1); 133 | } else { 134 | panic!(); 135 | } 136 | } 137 | 138 | #[test] 139 | fn bound_1() { 140 | #[derive(Educe)] 141 | #[educe(Clone)] 142 | enum Enum { 143 | Struct { f1: T }, 144 | Tuple(T), 145 | } 146 | 147 | let s = Enum::Struct { 148 | f1: 1 149 | } 150 | .clone(); 151 | let t = Enum::Tuple(1).clone(); 152 | 153 | if let Enum::Struct { 154 | f1, 155 | } = s 156 | { 157 | assert_eq!(1, f1); 158 | } else { 159 | panic!(); 160 | } 161 | 162 | if let Enum::Tuple(f1) = t { 163 | assert_eq!(1, f1); 164 | } else { 165 | panic!(); 166 | } 167 | } 168 | 169 | #[test] 170 | fn bound_2() { 171 | #[derive(Educe)] 172 | #[educe(Clone(bound = "T: core::clone::Clone"))] 173 | enum Enum { 174 | Struct { f1: T }, 175 | Tuple(T), 176 | } 177 | 178 | let s = Enum::Struct { 179 | f1: 1 180 | } 181 | .clone(); 182 | let t = Enum::Tuple(1).clone(); 183 | 184 | if let Enum::Struct { 185 | f1, 186 | } = s 187 | { 188 | assert_eq!(1, f1); 189 | } else { 190 | panic!(); 191 | } 192 | 193 | if let Enum::Tuple(f1) = t { 194 | assert_eq!(1, f1); 195 | } else { 196 | panic!(); 197 | } 198 | } 199 | 200 | #[test] 201 | fn bound_3() { 202 | #[derive(Educe)] 203 | #[educe(Clone(bound(T: core::clone::Clone)))] 204 | enum Enum { 205 | Struct { f1: T }, 206 | Tuple(T), 207 | } 208 | 209 | let s = Enum::Struct { 210 | f1: 1 211 | } 212 | .clone(); 213 | let t = Enum::Tuple(1).clone(); 214 | 215 | if let Enum::Struct { 216 | f1, 217 | } = s 218 | { 219 | assert_eq!(1, f1); 220 | } else { 221 | panic!(); 222 | } 223 | 224 | if let Enum::Tuple(f1) = t { 225 | assert_eq!(1, f1); 226 | } else { 227 | panic!(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/copy_clone_enum.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "Copy", feature = "Clone"))] 2 | #![no_std] 3 | #![allow(clippy::clone_on_copy)] 4 | 5 | use educe::Educe; 6 | 7 | #[allow(dead_code)] 8 | #[test] 9 | fn empty() { 10 | #[derive(Educe)] 11 | #[educe(Copy, Clone)] 12 | enum Enum {} 13 | 14 | #[derive(Educe)] 15 | #[educe(Copy, Clone)] 16 | enum Enum2 { 17 | Struct {}, 18 | Tuple(), 19 | } 20 | 21 | let s = Enum2::Struct {}.clone(); 22 | let t = Enum2::Tuple().clone(); 23 | 24 | assert!(matches!(s, Enum2::Struct {})); 25 | assert!(matches!(t, Enum2::Tuple())); 26 | } 27 | 28 | #[test] 29 | fn basic() { 30 | #[derive(Educe)] 31 | #[educe(Copy, Clone)] 32 | enum Enum { 33 | Unit, 34 | Struct { f1: u8 }, 35 | Tuple(u8), 36 | } 37 | 38 | let u = Enum::Unit.clone(); 39 | let s = Enum::Struct { 40 | f1: 1 41 | } 42 | .clone(); 43 | let t = Enum::Tuple(1).clone(); 44 | 45 | assert!(matches!(u, Enum::Unit)); 46 | 47 | if let Enum::Struct { 48 | f1, 49 | } = s 50 | { 51 | assert_eq!(1, f1); 52 | } else { 53 | panic!(); 54 | } 55 | 56 | if let Enum::Tuple(f1) = t { 57 | assert_eq!(1, f1); 58 | } else { 59 | panic!(); 60 | } 61 | } 62 | 63 | #[allow(clippy::non_canonical_clone_impl)] 64 | #[test] 65 | fn method() { 66 | fn clone(v: &u8) -> u8 { 67 | v + 100 68 | } 69 | 70 | #[derive(Educe)] 71 | #[educe(Copy, Clone)] 72 | enum Enum { 73 | Struct { 74 | #[educe(Clone(method = "clone"))] 75 | f1: u8, 76 | }, 77 | Tuple(#[educe(Clone(method = "clone"))] u8), 78 | } 79 | 80 | let s = Enum::Struct { 81 | f1: 1 82 | } 83 | .clone(); 84 | let t = Enum::Tuple(1).clone(); 85 | 86 | if let Enum::Struct { 87 | f1, 88 | } = s 89 | { 90 | assert_eq!(101, f1); 91 | } else { 92 | panic!(); 93 | } 94 | 95 | if let Enum::Tuple(f1) = t { 96 | assert_eq!(101, f1); 97 | } else { 98 | panic!(); 99 | } 100 | } 101 | 102 | #[test] 103 | fn bound_1() { 104 | #[derive(Educe)] 105 | #[educe(Copy, Clone)] 106 | enum Enum { 107 | Struct { f1: T }, 108 | Tuple(T), 109 | } 110 | 111 | let s = Enum::Struct { 112 | f1: 1 113 | } 114 | .clone(); 115 | let t = Enum::Tuple(1).clone(); 116 | 117 | if let Enum::Struct { 118 | f1, 119 | } = s 120 | { 121 | assert_eq!(1, f1); 122 | } else { 123 | panic!(); 124 | } 125 | 126 | if let Enum::Tuple(f1) = t { 127 | assert_eq!(1, f1); 128 | } else { 129 | panic!(); 130 | } 131 | } 132 | 133 | #[test] 134 | fn bound_2() { 135 | #[derive(Educe)] 136 | #[educe(Copy, Clone(bound = "T: core::marker::Copy"))] 137 | enum Enum { 138 | Struct { f1: T }, 139 | Tuple(T), 140 | } 141 | 142 | let s = Enum::Struct { 143 | f1: 1 144 | } 145 | .clone(); 146 | let t = Enum::Tuple(1).clone(); 147 | 148 | if let Enum::Struct { 149 | f1, 150 | } = s 151 | { 152 | assert_eq!(1, f1); 153 | } else { 154 | panic!(); 155 | } 156 | 157 | if let Enum::Tuple(f1) = t { 158 | assert_eq!(1, f1); 159 | } else { 160 | panic!(); 161 | } 162 | } 163 | 164 | #[test] 165 | fn bound_3() { 166 | #[derive(Educe)] 167 | #[educe(Copy, Clone(bound("T: core::marker::Copy")))] 168 | enum Enum { 169 | Struct { f1: T }, 170 | Tuple(T), 171 | } 172 | 173 | let s = Enum::Struct { 174 | f1: 1 175 | } 176 | .clone(); 177 | let t = Enum::Tuple(1).clone(); 178 | 179 | if let Enum::Struct { 180 | f1, 181 | } = s 182 | { 183 | assert_eq!(1, f1); 184 | } else { 185 | panic!(); 186 | } 187 | 188 | if let Enum::Tuple(f1) = t { 189 | assert_eq!(1, f1); 190 | } else { 191 | panic!(); 192 | } 193 | } 194 | 195 | #[test] 196 | fn bound_4() { 197 | #[derive(Educe)] 198 | #[educe(Copy, Clone(bound(T: core::marker::Copy)))] 199 | enum Enum { 200 | Struct { f1: T }, 201 | Tuple(T), 202 | } 203 | 204 | let s = Enum::Struct { 205 | f1: 1 206 | } 207 | .clone(); 208 | let t = Enum::Tuple(1).clone(); 209 | 210 | if let Enum::Struct { 211 | f1, 212 | } = s 213 | { 214 | assert_eq!(1, f1); 215 | } else { 216 | panic!(); 217 | } 218 | 219 | if let Enum::Tuple(f1) = t { 220 | assert_eq!(1, f1); 221 | } else { 222 | panic!(); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/trait_handlers/ord/ord_struct.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use quote::quote; 4 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Path, Type}; 5 | 6 | use super::{ 7 | models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, 8 | TraitHandler, 9 | }; 10 | use crate::{common::ident_index::IdentOrIndex, Trait}; 11 | 12 | pub(crate) struct OrdStructHandler; 13 | 14 | impl TraitHandler for OrdStructHandler { 15 | #[inline] 16 | fn trait_meta_handler( 17 | ast: &DeriveInput, 18 | token_stream: &mut proc_macro2::TokenStream, 19 | traits: &[Trait], 20 | meta: &Meta, 21 | ) -> syn::Result<()> { 22 | let type_attribute = TypeAttributeBuilder { 23 | enable_flag: true, enable_bound: true 24 | } 25 | .build_from_ord_meta(meta)?; 26 | 27 | let mut ord_types: Vec<&Type> = Vec::new(); 28 | 29 | let mut cmp_token_stream = proc_macro2::TokenStream::new(); 30 | 31 | if let Data::Struct(data) = &ast.data { 32 | let mut fields: BTreeMap = BTreeMap::new(); 33 | 34 | for (index, field) in data.fields.iter().enumerate() { 35 | let field_attribute = FieldAttributeBuilder { 36 | enable_ignore: true, 37 | enable_method: true, 38 | enable_rank: true, 39 | rank: isize::MIN + index as isize, 40 | } 41 | .build_from_attributes(&field.attrs, traits)?; 42 | 43 | if field_attribute.ignore { 44 | continue; 45 | } 46 | 47 | let rank = field_attribute.rank; 48 | 49 | if fields.contains_key(&rank) { 50 | return Err(super::panic::reuse_a_rank( 51 | field_attribute.rank_span.unwrap_or_else(|| field.span()), 52 | rank, 53 | )); 54 | } 55 | 56 | fields.insert(rank, (index, field, field_attribute)); 57 | } 58 | 59 | let built_in_cmp: Path = syn::parse2(quote!(::core::cmp::Ord::cmp)).unwrap(); 60 | 61 | for (index, field, field_attribute) in fields.values() { 62 | let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), *index); 63 | 64 | let cmp = field_attribute.method.as_ref().unwrap_or_else(|| { 65 | ord_types.push(&field.ty); 66 | 67 | &built_in_cmp 68 | }); 69 | 70 | cmp_token_stream.extend(quote! { 71 | match #cmp(&self.#field_name, &other.#field_name) { 72 | ::core::cmp::Ordering::Equal => (), 73 | ::core::cmp::Ordering::Greater => return ::core::cmp::Ordering::Greater, 74 | ::core::cmp::Ordering::Less => return ::core::cmp::Ordering::Less, 75 | } 76 | }); 77 | } 78 | } 79 | 80 | let ident = &ast.ident; 81 | 82 | let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( 83 | &ast.generics.params, 84 | &syn::parse2(quote!(::core::cmp::Ord)).unwrap(), 85 | &ord_types, 86 | &crate::trait_handlers::ord::supertraits(traits), 87 | ); 88 | 89 | let mut generics = ast.generics.clone(); 90 | let where_clause = generics.make_where_clause(); 91 | 92 | for where_predicate in bound { 93 | where_clause.predicates.push(where_predicate); 94 | } 95 | 96 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 97 | 98 | token_stream.extend(quote! { 99 | impl #impl_generics ::core::cmp::Ord for #ident #ty_generics #where_clause { 100 | #[inline] 101 | fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { 102 | #cmp_token_stream 103 | 104 | ::core::cmp::Ordering::Equal 105 | } 106 | } 107 | }); 108 | 109 | #[cfg(feature = "PartialOrd")] 110 | if traits.contains(&Trait::PartialOrd) { 111 | token_stream.extend(quote! { 112 | impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { 113 | #[inline] 114 | fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { 115 | Some(::core::cmp::Ord::cmp(self, other)) 116 | } 117 | } 118 | }); 119 | } 120 | 121 | Ok(()) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/clone_struct.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "Clone")] 2 | #![no_std] 3 | 4 | use core::marker::PhantomData; 5 | 6 | use educe::Educe; 7 | 8 | #[test] 9 | fn empty() { 10 | #[derive(Educe)] 11 | #[educe(Clone)] 12 | struct Struct {} 13 | 14 | #[derive(Educe)] 15 | #[educe(Clone)] 16 | struct Tuple(); 17 | 18 | let s = Struct {}.clone(); 19 | let t = Tuple().clone(); 20 | 21 | assert!(matches!(s, Struct {})); 22 | 23 | assert!(matches!(t, Tuple())); 24 | } 25 | 26 | #[test] 27 | fn basic() { 28 | #[derive(Educe)] 29 | #[educe(Clone)] 30 | struct Unit; 31 | 32 | #[derive(Educe)] 33 | #[educe(Clone)] 34 | struct Struct { 35 | f1: u8, 36 | } 37 | 38 | #[derive(Educe)] 39 | #[educe(Clone)] 40 | struct Tuple(u8); 41 | 42 | let u = Unit.clone(); 43 | let s = Struct { 44 | f1: 1 45 | } 46 | .clone(); 47 | let t = Tuple(1).clone(); 48 | 49 | assert!(matches!(u, Unit)); 50 | 51 | assert_eq!(1, s.f1); 52 | assert_eq!(1, t.0); 53 | } 54 | 55 | #[test] 56 | fn method_1() { 57 | fn clone(v: &u8) -> u8 { 58 | v + 100 59 | } 60 | 61 | #[derive(Educe)] 62 | #[educe(Clone)] 63 | struct Struct { 64 | #[educe(Clone(method = clone))] 65 | f1: u8, 66 | } 67 | 68 | #[derive(Educe)] 69 | #[educe(Clone)] 70 | struct Tuple(#[educe(Clone(method = clone))] u8); 71 | 72 | let s = Struct { 73 | f1: 1 74 | } 75 | .clone(); 76 | let t = Tuple(1).clone(); 77 | 78 | assert_eq!(101, s.f1); 79 | assert_eq!(101, t.0); 80 | } 81 | 82 | #[test] 83 | fn method_2() { 84 | fn clone(v: &u8) -> u8 { 85 | v + 100 86 | } 87 | 88 | #[derive(Educe)] 89 | #[educe(Clone)] 90 | struct Struct { 91 | #[educe(Clone(method(clone)))] 92 | f1: u8, 93 | } 94 | 95 | #[derive(Educe)] 96 | #[educe(Clone)] 97 | struct Tuple(#[educe(Clone(method(clone)))] u8); 98 | 99 | let s = Struct { 100 | f1: 1 101 | } 102 | .clone(); 103 | let t = Tuple(1).clone(); 104 | 105 | assert_eq!(101, s.f1); 106 | assert_eq!(101, t.0); 107 | } 108 | 109 | #[test] 110 | fn bound_1() { 111 | #[derive(Educe)] 112 | #[educe(Clone)] 113 | struct Struct { 114 | f1: T, 115 | } 116 | 117 | #[derive(Educe)] 118 | #[educe(Clone)] 119 | struct Tuple(T); 120 | 121 | let s = Struct { 122 | f1: 1 123 | } 124 | .clone(); 125 | let t = Tuple(1).clone(); 126 | 127 | assert_eq!(1, s.f1); 128 | assert_eq!(1, t.0); 129 | } 130 | 131 | #[test] 132 | fn bound_2() { 133 | #[derive(Educe)] 134 | #[educe(Clone(bound = "T: core::clone::Clone"))] 135 | struct Struct { 136 | f1: T, 137 | } 138 | 139 | #[derive(Educe)] 140 | #[educe(Clone(bound = "T: core::clone::Clone"))] 141 | struct Tuple(T); 142 | 143 | let s = Struct { 144 | f1: 1 145 | } 146 | .clone(); 147 | let t = Tuple(1).clone(); 148 | 149 | assert_eq!(1, s.f1); 150 | assert_eq!(1, t.0); 151 | } 152 | 153 | #[test] 154 | fn bound_3() { 155 | #[derive(Educe)] 156 | #[educe(Clone(bound(T: core::clone::Clone)))] 157 | struct Struct { 158 | f1: T, 159 | } 160 | 161 | #[derive(Educe)] 162 | #[educe(Clone(bound(T: core::clone::Clone)))] 163 | struct Tuple(T); 164 | 165 | let s = Struct { 166 | f1: 1 167 | } 168 | .clone(); 169 | let t = Tuple(1).clone(); 170 | 171 | assert_eq!(1, s.f1); 172 | assert_eq!(1, t.0); 173 | } 174 | 175 | #[test] 176 | fn bound_4() { 177 | struct NotClone; 178 | 179 | #[derive(Educe)] 180 | // without `bound(*)` we get E0034: multiple applicable items in scope 181 | // when we call Struct.clone(), since .clone() is then ambiguous 182 | #[educe(Clone(bound(*)))] 183 | struct Struct { 184 | f1: PhantomData, 185 | } 186 | 187 | trait ClashingFakeClone { 188 | fn clone(&self) {} 189 | } 190 | impl ClashingFakeClone for Struct {} 191 | 192 | let _: () = Struct { 193 | f1: PhantomData:: 194 | } 195 | .clone(); 196 | 197 | let _: Struct<_> = Struct { 198 | f1: PhantomData::<()> 199 | } 200 | .clone(); 201 | } 202 | 203 | #[cfg(feature = "Debug")] 204 | #[test] 205 | fn leaking_bounds() { 206 | use core::{fmt::Debug, marker::PhantomData}; 207 | 208 | #[derive(Educe)] 209 | #[educe(Debug(bound(T: Debug)), Clone(bound(T: Clone)))] 210 | struct Struct { 211 | x: PhantomData, 212 | } 213 | 214 | #[derive(Clone)] 215 | struct NotDebug; 216 | 217 | let a = Struct { 218 | x: PhantomData:: 219 | }; 220 | let _b = a.clone(); 221 | } 222 | -------------------------------------------------------------------------------- /src/trait_handlers/eq/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{common::bound::Bound, panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute { 6 | pub(crate) bound: Bound, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TypeAttributeBuilder { 11 | pub(crate) enable_flag: bool, 12 | pub(crate) enable_bound: bool, 13 | } 14 | 15 | impl TypeAttributeBuilder { 16 | pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("Eq")); 18 | 19 | let mut bound = Bound::Auto; 20 | 21 | let correct_usage_for_copy_attribute = { 22 | let mut usage = vec![]; 23 | 24 | if self.enable_flag { 25 | usage.push(stringify!(#[educe(Eq)])); 26 | } 27 | 28 | if self.enable_bound { 29 | usage.push(stringify!(#[educe(Eq(bound(where_predicates)))])); 30 | usage.push(stringify!(#[educe(Eq(bound = false))])); 31 | } 32 | 33 | usage 34 | }; 35 | 36 | match meta { 37 | Meta::Path(_) => { 38 | if !self.enable_flag { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_copy_attribute, 42 | )); 43 | } 44 | }, 45 | Meta::NameValue(_) => { 46 | return Err(panic::attribute_incorrect_format( 47 | meta.path().get_ident().unwrap(), 48 | &correct_usage_for_copy_attribute, 49 | )); 50 | }, 51 | Meta::List(list) => { 52 | let result = 53 | list.parse_args_with(Punctuated::::parse_terminated)?; 54 | 55 | let mut bound_is_set = false; 56 | 57 | let mut handler = |meta: Meta| -> syn::Result { 58 | if let Some(ident) = meta.path().get_ident() { 59 | if ident == "bound" { 60 | if !self.enable_bound { 61 | return Ok(false); 62 | } 63 | 64 | let v = Bound::from_meta(&meta)?; 65 | 66 | if bound_is_set { 67 | return Err(panic::parameter_reset(ident)); 68 | } 69 | 70 | bound_is_set = true; 71 | 72 | bound = v; 73 | 74 | return Ok(true); 75 | } 76 | } 77 | 78 | Ok(false) 79 | }; 80 | 81 | for p in result { 82 | if !handler(p)? { 83 | return Err(panic::attribute_incorrect_format( 84 | meta.path().get_ident().unwrap(), 85 | &correct_usage_for_copy_attribute, 86 | )); 87 | } 88 | } 89 | }, 90 | } 91 | 92 | Ok(TypeAttribute { 93 | bound, 94 | }) 95 | } 96 | 97 | pub(crate) fn build_from_attributes( 98 | &self, 99 | attributes: &[Attribute], 100 | traits: &[Trait], 101 | ) -> syn::Result { 102 | let mut output = None; 103 | 104 | for attribute in attributes.iter() { 105 | let path = attribute.path(); 106 | 107 | if path.is_ident("educe") { 108 | if let Meta::List(list) = &attribute.meta { 109 | let result = 110 | list.parse_args_with(Punctuated::::parse_terminated)?; 111 | 112 | for meta in result { 113 | let path = meta.path(); 114 | 115 | let t = match Trait::from_path(path) { 116 | Some(t) => t, 117 | None => return Err(panic::unsupported_trait(meta.path())), 118 | }; 119 | 120 | if !traits.contains(&t) { 121 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 122 | } 123 | 124 | if t == Trait::Eq { 125 | if output.is_some() { 126 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 127 | } 128 | 129 | output = Some(self.build_from_eq_meta(&meta)?); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | Ok(output.unwrap_or(TypeAttribute { 137 | bound: Bound::Auto 138 | })) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/trait_handlers/copy/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{common::bound::Bound, panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute { 6 | pub(crate) bound: Bound, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TypeAttributeBuilder { 11 | pub(crate) enable_flag: bool, 12 | pub(crate) enable_bound: bool, 13 | } 14 | 15 | impl TypeAttributeBuilder { 16 | pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("Copy")); 18 | 19 | let mut bound = Bound::Auto; 20 | 21 | let correct_usage_for_copy_attribute = { 22 | let mut usage = vec![]; 23 | 24 | if self.enable_flag { 25 | usage.push(stringify!(#[educe(Copy)])); 26 | } 27 | 28 | if self.enable_bound { 29 | usage.push(stringify!(#[educe(Copy(bound(where_predicates)))])); 30 | usage.push(stringify!(#[educe(Copy(bound = false))])); 31 | } 32 | 33 | usage 34 | }; 35 | 36 | match meta { 37 | Meta::Path(_) => { 38 | if !self.enable_flag { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_copy_attribute, 42 | )); 43 | } 44 | }, 45 | Meta::NameValue(_) => { 46 | return Err(panic::attribute_incorrect_format( 47 | meta.path().get_ident().unwrap(), 48 | &correct_usage_for_copy_attribute, 49 | )); 50 | }, 51 | Meta::List(list) => { 52 | let result = 53 | list.parse_args_with(Punctuated::::parse_terminated)?; 54 | 55 | let mut bound_is_set = false; 56 | 57 | let mut handler = |meta: Meta| -> syn::Result { 58 | if let Some(ident) = meta.path().get_ident() { 59 | if ident == "bound" { 60 | if !self.enable_bound { 61 | return Ok(false); 62 | } 63 | 64 | let v = Bound::from_meta(&meta)?; 65 | 66 | if bound_is_set { 67 | return Err(panic::parameter_reset(ident)); 68 | } 69 | 70 | bound_is_set = true; 71 | 72 | bound = v; 73 | 74 | return Ok(true); 75 | } 76 | } 77 | 78 | Ok(false) 79 | }; 80 | 81 | for p in result { 82 | if !handler(p)? { 83 | return Err(panic::attribute_incorrect_format( 84 | meta.path().get_ident().unwrap(), 85 | &correct_usage_for_copy_attribute, 86 | )); 87 | } 88 | } 89 | }, 90 | } 91 | 92 | Ok(TypeAttribute { 93 | bound, 94 | }) 95 | } 96 | 97 | pub(crate) fn build_from_attributes( 98 | &self, 99 | attributes: &[Attribute], 100 | traits: &[Trait], 101 | ) -> syn::Result { 102 | let mut output = None; 103 | 104 | for attribute in attributes.iter() { 105 | let path = attribute.path(); 106 | 107 | if path.is_ident("educe") { 108 | if let Meta::List(list) = &attribute.meta { 109 | let result = 110 | list.parse_args_with(Punctuated::::parse_terminated)?; 111 | 112 | for meta in result { 113 | let path = meta.path(); 114 | 115 | let t = match Trait::from_path(path) { 116 | Some(t) => t, 117 | None => return Err(panic::unsupported_trait(meta.path())), 118 | }; 119 | 120 | if !traits.contains(&t) { 121 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 122 | } 123 | 124 | if t == Trait::Copy { 125 | if output.is_some() { 126 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 127 | } 128 | 129 | output = Some(self.build_from_copy_meta(&meta)?); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | Ok(output.unwrap_or(TypeAttribute { 137 | bound: Bound::Auto 138 | })) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/trait_handlers/clone/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{common::bound::Bound, panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute { 6 | pub(crate) bound: Bound, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TypeAttributeBuilder { 11 | pub(crate) enable_flag: bool, 12 | pub(crate) enable_bound: bool, 13 | } 14 | 15 | impl TypeAttributeBuilder { 16 | pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("Clone")); 18 | 19 | let mut bound = Bound::Auto; 20 | 21 | let correct_usage_for_clone_attribute = { 22 | let mut usage = vec![]; 23 | 24 | if self.enable_flag { 25 | usage.push(stringify!(#[educe(Clone)])); 26 | } 27 | 28 | if self.enable_bound { 29 | usage.push(stringify!(#[educe(Clone(bound(where_predicates)))])); 30 | usage.push(stringify!(#[educe(Clone(bound = false))])); 31 | } 32 | 33 | usage 34 | }; 35 | 36 | match meta { 37 | Meta::Path(_) => { 38 | if !self.enable_flag { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_clone_attribute, 42 | )); 43 | } 44 | }, 45 | Meta::NameValue(_) => { 46 | return Err(panic::attribute_incorrect_format( 47 | meta.path().get_ident().unwrap(), 48 | &correct_usage_for_clone_attribute, 49 | )); 50 | }, 51 | Meta::List(list) => { 52 | let result = 53 | list.parse_args_with(Punctuated::::parse_terminated)?; 54 | 55 | let mut bound_is_set = false; 56 | 57 | let mut handler = |meta: Meta| -> syn::Result { 58 | if let Some(ident) = meta.path().get_ident() { 59 | if ident == "bound" { 60 | if !self.enable_bound { 61 | return Ok(false); 62 | } 63 | 64 | let v = Bound::from_meta(&meta)?; 65 | 66 | if bound_is_set { 67 | return Err(panic::parameter_reset(ident)); 68 | } 69 | 70 | bound_is_set = true; 71 | 72 | bound = v; 73 | 74 | return Ok(true); 75 | } 76 | } 77 | 78 | Ok(false) 79 | }; 80 | 81 | for p in result { 82 | if !handler(p)? { 83 | return Err(panic::attribute_incorrect_format( 84 | meta.path().get_ident().unwrap(), 85 | &correct_usage_for_clone_attribute, 86 | )); 87 | } 88 | } 89 | }, 90 | } 91 | 92 | Ok(TypeAttribute { 93 | bound, 94 | }) 95 | } 96 | 97 | pub(crate) fn build_from_attributes( 98 | &self, 99 | attributes: &[Attribute], 100 | traits: &[Trait], 101 | ) -> syn::Result { 102 | let mut output = None; 103 | 104 | for attribute in attributes.iter() { 105 | let path = attribute.path(); 106 | 107 | if path.is_ident("educe") { 108 | if let Meta::List(list) = &attribute.meta { 109 | let result = 110 | list.parse_args_with(Punctuated::::parse_terminated)?; 111 | 112 | for meta in result { 113 | let path = meta.path(); 114 | 115 | let t = match Trait::from_path(path) { 116 | Some(t) => t, 117 | None => return Err(panic::unsupported_trait(meta.path())), 118 | }; 119 | 120 | if !traits.contains(&t) { 121 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 122 | } 123 | 124 | if t == Trait::Clone { 125 | if output.is_some() { 126 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 127 | } 128 | 129 | output = Some(self.build_from_clone_meta(&meta)?); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | Ok(output.unwrap_or(TypeAttribute { 137 | bound: Bound::Auto 138 | })) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/trait_handlers/partial_ord/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{common::bound::Bound, panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute { 6 | pub(crate) bound: Bound, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TypeAttributeBuilder { 11 | pub(crate) enable_flag: bool, 12 | pub(crate) enable_bound: bool, 13 | } 14 | 15 | impl TypeAttributeBuilder { 16 | pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("PartialOrd")); 18 | 19 | let mut bound = Bound::Auto; 20 | 21 | let correct_usage_for_partial_eq_attribute = { 22 | let mut usage = vec![]; 23 | 24 | if self.enable_flag { 25 | usage.push(stringify!(#[educe(PartialOrd)])); 26 | } 27 | 28 | if self.enable_bound { 29 | usage.push(stringify!(#[educe(PartialOrd(bound(where_predicates)))])); 30 | usage.push(stringify!(#[educe(PartialOrd(bound = false))])); 31 | } 32 | 33 | usage 34 | }; 35 | 36 | match meta { 37 | Meta::Path(_) => { 38 | if !self.enable_flag { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_partial_eq_attribute, 42 | )); 43 | } 44 | }, 45 | Meta::NameValue(_) => { 46 | return Err(panic::attribute_incorrect_format( 47 | meta.path().get_ident().unwrap(), 48 | &correct_usage_for_partial_eq_attribute, 49 | )); 50 | }, 51 | Meta::List(list) => { 52 | let result = 53 | list.parse_args_with(Punctuated::::parse_terminated)?; 54 | 55 | let mut bound_is_set = false; 56 | 57 | let mut handler = |meta: Meta| -> syn::Result { 58 | if let Some(ident) = meta.path().get_ident() { 59 | if ident == "bound" { 60 | if !self.enable_bound { 61 | return Ok(false); 62 | } 63 | 64 | let v = Bound::from_meta(&meta)?; 65 | 66 | if bound_is_set { 67 | return Err(panic::parameter_reset(ident)); 68 | } 69 | 70 | bound_is_set = true; 71 | 72 | bound = v; 73 | 74 | return Ok(true); 75 | } 76 | } 77 | 78 | Ok(false) 79 | }; 80 | 81 | for p in result { 82 | if !handler(p)? { 83 | return Err(panic::attribute_incorrect_format( 84 | meta.path().get_ident().unwrap(), 85 | &correct_usage_for_partial_eq_attribute, 86 | )); 87 | } 88 | } 89 | }, 90 | } 91 | 92 | Ok(TypeAttribute { 93 | bound, 94 | }) 95 | } 96 | 97 | pub(crate) fn build_from_attributes( 98 | &self, 99 | attributes: &[Attribute], 100 | traits: &[Trait], 101 | ) -> syn::Result { 102 | let mut output = None; 103 | 104 | for attribute in attributes.iter() { 105 | let path = attribute.path(); 106 | 107 | if path.is_ident("educe") { 108 | if let Meta::List(list) = &attribute.meta { 109 | let result = 110 | list.parse_args_with(Punctuated::::parse_terminated)?; 111 | 112 | for meta in result { 113 | let path = meta.path(); 114 | 115 | let t = match Trait::from_path(path) { 116 | Some(t) => t, 117 | None => return Err(panic::unsupported_trait(meta.path())), 118 | }; 119 | 120 | if !traits.contains(&t) { 121 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 122 | } 123 | 124 | if t == Trait::PartialOrd { 125 | if output.is_some() { 126 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 127 | } 128 | 129 | output = Some(self.build_from_partial_ord_meta(&meta)?); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | Ok(output.unwrap_or(TypeAttribute { 137 | bound: Bound::Auto 138 | })) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/trait_handlers/deref_mut/deref_mut_enum.rs: -------------------------------------------------------------------------------- 1 | use quote::{format_ident, quote}; 2 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta}; 3 | 4 | use super::{ 5 | models::{FieldAttributeBuilder, TypeAttributeBuilder}, 6 | TraitHandler, 7 | }; 8 | use crate::{panic, supported_traits::Trait}; 9 | 10 | pub(crate) struct DerefMutEnumHandler; 11 | 12 | impl TraitHandler for DerefMutEnumHandler { 13 | #[inline] 14 | fn trait_meta_handler( 15 | ast: &DeriveInput, 16 | token_stream: &mut proc_macro2::TokenStream, 17 | traits: &[Trait], 18 | meta: &Meta, 19 | ) -> syn::Result<()> { 20 | let _ = TypeAttributeBuilder { 21 | enable_flag: true 22 | } 23 | .build_from_deref_mut_meta(meta)?; 24 | 25 | let mut arms_token_stream = proc_macro2::TokenStream::new(); 26 | 27 | if let Data::Enum(data) = &ast.data { 28 | type Variants<'a> = Vec<(&'a Ident, bool, usize, Ident)>; 29 | 30 | let mut variants: Variants = Vec::new(); 31 | 32 | for variant in data.variants.iter() { 33 | let _ = TypeAttributeBuilder { 34 | enable_flag: false 35 | } 36 | .build_from_attributes(&variant.attrs, traits)?; 37 | 38 | if let Fields::Unit = &variant.fields { 39 | return Err(panic::trait_not_support_unit_variant( 40 | meta.path().get_ident().unwrap(), 41 | variant, 42 | )); 43 | } 44 | 45 | let fields = &variant.fields; 46 | 47 | let (index, field) = if fields.len() == 1 { 48 | let field = fields.into_iter().next().unwrap(); 49 | 50 | let _ = FieldAttributeBuilder { 51 | enable_flag: true 52 | } 53 | .build_from_attributes(&field.attrs, traits)?; 54 | 55 | (0usize, field) 56 | } else { 57 | let mut deref_field: Option<(usize, &Field)> = None; 58 | 59 | for (index, field) in variant.fields.iter().enumerate() { 60 | let field_attribute = FieldAttributeBuilder { 61 | enable_flag: true 62 | } 63 | .build_from_attributes(&field.attrs, traits)?; 64 | 65 | if field_attribute.flag { 66 | if deref_field.is_some() { 67 | return Err(super::panic::multiple_deref_mut_fields_of_variant( 68 | field_attribute.span, 69 | variant, 70 | )); 71 | } 72 | 73 | deref_field = Some((index, field)); 74 | } 75 | } 76 | 77 | if let Some(deref_field) = deref_field { 78 | deref_field 79 | } else { 80 | return Err(super::panic::no_deref_mut_field_of_variant( 81 | meta.span(), 82 | variant, 83 | )); 84 | } 85 | }; 86 | 87 | let (field_name, is_tuple): (Ident, bool) = match field.ident.as_ref() { 88 | Some(ident) => (ident.clone(), false), 89 | None => (format_ident!("_{}", index), true), 90 | }; 91 | 92 | variants.push((&variant.ident, is_tuple, index, field_name)); 93 | } 94 | 95 | if variants.is_empty() { 96 | return Err(super::panic::no_deref_mut_field(meta.span())); 97 | } 98 | 99 | for (variant_ident, is_tuple, index, field_name) in variants { 100 | let mut pattern_token_stream = proc_macro2::TokenStream::new(); 101 | 102 | if is_tuple { 103 | for _ in 0..index { 104 | pattern_token_stream.extend(quote!(_,)); 105 | } 106 | 107 | pattern_token_stream.extend(quote!( #field_name, .. )); 108 | 109 | arms_token_stream.extend( 110 | quote!( Self::#variant_ident ( #pattern_token_stream ) => #field_name, ), 111 | ); 112 | } else { 113 | pattern_token_stream.extend(quote!( #field_name, .. )); 114 | 115 | arms_token_stream.extend( 116 | quote!( Self::#variant_ident { #pattern_token_stream } => #field_name, ), 117 | ); 118 | } 119 | } 120 | } 121 | 122 | let ident = &ast.ident; 123 | 124 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 125 | 126 | token_stream.extend(quote! { 127 | impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause { 128 | #[inline] 129 | fn deref_mut(&mut self) -> &mut Self::Target { 130 | match self { 131 | #arms_token_stream 132 | } 133 | } 134 | } 135 | }); 136 | 137 | Ok(()) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/trait_handlers/into/models/field_attribute.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; 4 | 5 | use crate::{ 6 | common::{path::meta_2_path, r#type::TypeWithPunctuatedMeta, tools::HashType}, 7 | panic, Trait, 8 | }; 9 | 10 | pub(crate) struct FieldAttribute { 11 | pub(crate) types: HashMap>, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub(crate) struct FieldAttributeBuilder { 16 | pub(crate) enable_types: bool, 17 | } 18 | 19 | impl FieldAttributeBuilder { 20 | pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result { 21 | debug_assert!(!meta.is_empty()); 22 | 23 | let mut types = HashMap::new(); 24 | 25 | for meta in meta { 26 | debug_assert!(meta.path().is_ident("Into")); 27 | 28 | let correct_usage_for_into_attribute = { 29 | let mut usage = vec![]; 30 | 31 | if self.enable_types { 32 | usage.push(stringify!(#[educe(Into(type))])); 33 | usage.push(stringify!(#[educe(Into(type, method(path_to_method)))])); 34 | } 35 | 36 | usage 37 | }; 38 | 39 | match meta { 40 | Meta::Path(_) | Meta::NameValue(_) => { 41 | return Err(panic::attribute_incorrect_format( 42 | meta.path().get_ident().unwrap(), 43 | &correct_usage_for_into_attribute, 44 | )); 45 | }, 46 | Meta::List(list) => { 47 | if !self.enable_types { 48 | return Err(panic::attribute_incorrect_format( 49 | meta.path().get_ident().unwrap(), 50 | &correct_usage_for_into_attribute, 51 | )); 52 | } 53 | 54 | let TypeWithPunctuatedMeta { 55 | ty, 56 | list: result, 57 | } = list.parse_args()?; 58 | 59 | let ty = super::super::common::to_hash_type(&ty); 60 | 61 | let mut method = None; 62 | let mut method_is_set = false; 63 | 64 | let mut handler = |meta: Meta| -> syn::Result { 65 | if let Some(ident) = meta.path().get_ident() { 66 | if ident == "method" { 67 | let v = meta_2_path(&meta)?; 68 | 69 | if method_is_set { 70 | return Err(panic::parameter_reset(ident)); 71 | } 72 | 73 | method_is_set = true; 74 | 75 | method = Some(v); 76 | 77 | return Ok(true); 78 | } 79 | } 80 | 81 | Ok(false) 82 | }; 83 | 84 | for p in result { 85 | if !handler(p)? { 86 | return Err(panic::attribute_incorrect_format( 87 | meta.path().get_ident().unwrap(), 88 | &correct_usage_for_into_attribute, 89 | )); 90 | } 91 | } 92 | 93 | if types.contains_key(&ty) { 94 | return Err(super::super::panic::reset_a_type(&ty)); 95 | } 96 | 97 | types.insert(ty, method); 98 | }, 99 | } 100 | } 101 | 102 | Ok(FieldAttribute { 103 | types, 104 | }) 105 | } 106 | 107 | pub(crate) fn build_from_attributes( 108 | &self, 109 | attributes: &[Attribute], 110 | traits: &[Trait], 111 | ) -> syn::Result { 112 | let mut output: Option = None; 113 | 114 | let mut v_meta = Vec::new(); 115 | 116 | for attribute in attributes.iter() { 117 | let path = attribute.path(); 118 | 119 | if path.is_ident("educe") { 120 | if let Meta::List(list) = &attribute.meta { 121 | let result = 122 | list.parse_args_with(Punctuated::::parse_terminated)?; 123 | 124 | for meta in result { 125 | let path = meta.path(); 126 | 127 | let t = match Trait::from_path(path) { 128 | Some(t) => t, 129 | None => return Err(panic::unsupported_trait(meta.path())), 130 | }; 131 | 132 | if !traits.contains(&t) { 133 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 134 | } 135 | 136 | if t == Trait::Into { 137 | v_meta.push(meta); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | if !v_meta.is_empty() { 145 | output = Some(self.build_from_into_meta(&v_meta)?); 146 | } 147 | 148 | Ok(output.unwrap_or_else(|| FieldAttribute { 149 | types: HashMap::new() 150 | })) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/trait_handlers/hash/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{ 4 | common::{bound::Bound, unsafe_punctuated_meta::UnsafePunctuatedMeta}, 5 | panic, Trait, 6 | }; 7 | 8 | pub(crate) struct TypeAttribute { 9 | pub(crate) has_unsafe: bool, 10 | pub(crate) bound: Bound, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub(crate) struct TypeAttributeBuilder { 15 | pub(crate) enable_flag: bool, 16 | pub(crate) enable_unsafe: bool, 17 | pub(crate) enable_bound: bool, 18 | } 19 | 20 | impl TypeAttributeBuilder { 21 | pub(crate) fn build_from_hash_meta(&self, meta: &Meta) -> syn::Result { 22 | debug_assert!(meta.path().is_ident("Hash")); 23 | 24 | let mut has_unsafe = false; 25 | let mut bound = Bound::Auto; 26 | 27 | let correct_usage_for_hash_attribute = { 28 | let mut usage = vec![]; 29 | 30 | if self.enable_flag { 31 | usage.push(stringify!(#[educe(Hash)])); 32 | } 33 | 34 | if self.enable_bound { 35 | usage.push(stringify!(#[educe(Hash(bound(where_predicates)))])); 36 | usage.push(stringify!(#[educe(Hash(bound = false))])); 37 | } 38 | 39 | usage 40 | }; 41 | 42 | match meta { 43 | Meta::Path(_) => { 44 | if !self.enable_flag { 45 | return Err(panic::attribute_incorrect_format( 46 | meta.path().get_ident().unwrap(), 47 | &correct_usage_for_hash_attribute, 48 | )); 49 | } 50 | }, 51 | Meta::NameValue(_) => { 52 | return Err(panic::attribute_incorrect_format( 53 | meta.path().get_ident().unwrap(), 54 | &correct_usage_for_hash_attribute, 55 | )); 56 | }, 57 | Meta::List(list) => { 58 | let result = if self.enable_unsafe { 59 | let result: UnsafePunctuatedMeta = list.parse_args()?; 60 | 61 | has_unsafe = result.has_unsafe; 62 | 63 | result.list 64 | } else { 65 | list.parse_args_with(Punctuated::::parse_terminated)? 66 | }; 67 | 68 | let mut bound_is_set = false; 69 | 70 | let mut handler = |meta: Meta| -> syn::Result { 71 | if let Some(ident) = meta.path().get_ident() { 72 | if ident == "bound" { 73 | if !self.enable_bound { 74 | return Ok(false); 75 | } 76 | 77 | let v = Bound::from_meta(&meta)?; 78 | 79 | if bound_is_set { 80 | return Err(panic::parameter_reset(ident)); 81 | } 82 | 83 | bound_is_set = true; 84 | 85 | bound = v; 86 | 87 | return Ok(true); 88 | } 89 | } 90 | 91 | Ok(false) 92 | }; 93 | 94 | for p in result { 95 | if !handler(p)? { 96 | return Err(panic::attribute_incorrect_format( 97 | meta.path().get_ident().unwrap(), 98 | &correct_usage_for_hash_attribute, 99 | )); 100 | } 101 | } 102 | }, 103 | } 104 | 105 | Ok(TypeAttribute { 106 | has_unsafe, 107 | bound, 108 | }) 109 | } 110 | 111 | pub(crate) fn build_from_attributes( 112 | &self, 113 | attributes: &[Attribute], 114 | traits: &[Trait], 115 | ) -> syn::Result { 116 | let mut output = None; 117 | 118 | for attribute in attributes.iter() { 119 | let path = attribute.path(); 120 | 121 | if path.is_ident("educe") { 122 | if let Meta::List(list) = &attribute.meta { 123 | let result = 124 | list.parse_args_with(Punctuated::::parse_terminated)?; 125 | 126 | for meta in result { 127 | let path = meta.path(); 128 | 129 | let t = match Trait::from_path(path) { 130 | Some(t) => t, 131 | None => return Err(panic::unsupported_trait(meta.path())), 132 | }; 133 | 134 | if !traits.contains(&t) { 135 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 136 | } 137 | 138 | if t == Trait::Hash { 139 | if output.is_some() { 140 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 141 | } 142 | 143 | output = Some(self.build_from_hash_meta(&meta)?); 144 | } 145 | } 146 | } 147 | } 148 | } 149 | 150 | Ok(output.unwrap_or(TypeAttribute { 151 | has_unsafe: false, bound: Bound::Auto 152 | })) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/trait_handlers/ord/models/type_attribute.rs: -------------------------------------------------------------------------------- 1 | use syn::{punctuated::Punctuated, Attribute, Meta, Token}; 2 | 3 | use crate::{common::bound::Bound, panic, Trait}; 4 | 5 | pub(crate) struct TypeAttribute { 6 | pub(crate) bound: Bound, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TypeAttributeBuilder { 11 | pub(crate) enable_flag: bool, 12 | pub(crate) enable_bound: bool, 13 | } 14 | 15 | impl TypeAttributeBuilder { 16 | pub(crate) fn build_from_ord_meta(&self, meta: &Meta) -> syn::Result { 17 | debug_assert!(meta.path().is_ident("Ord") || meta.path().is_ident("PartialOrd")); 18 | 19 | let mut bound = Bound::Auto; 20 | 21 | let correct_usage_for_partial_eq_attribute = { 22 | let mut usage = vec![]; 23 | 24 | if self.enable_flag { 25 | usage.push(stringify!(#[educe(Ord)])); 26 | } 27 | 28 | if self.enable_bound { 29 | usage.push(stringify!(#[educe(Ord(bound(where_predicates)))])); 30 | usage.push(stringify!(#[educe(Ord(bound = false))])); 31 | } 32 | 33 | usage 34 | }; 35 | 36 | match meta { 37 | Meta::Path(_) => { 38 | if !self.enable_flag { 39 | return Err(panic::attribute_incorrect_format( 40 | meta.path().get_ident().unwrap(), 41 | &correct_usage_for_partial_eq_attribute, 42 | )); 43 | } 44 | }, 45 | Meta::NameValue(_) => { 46 | return Err(panic::attribute_incorrect_format( 47 | meta.path().get_ident().unwrap(), 48 | &correct_usage_for_partial_eq_attribute, 49 | )); 50 | }, 51 | Meta::List(list) => { 52 | let result = 53 | list.parse_args_with(Punctuated::::parse_terminated)?; 54 | 55 | let mut bound_is_set = false; 56 | 57 | let mut handler = |meta: Meta| -> syn::Result { 58 | if let Some(ident) = meta.path().get_ident() { 59 | if ident == "bound" { 60 | if !self.enable_bound { 61 | return Ok(false); 62 | } 63 | 64 | let v = Bound::from_meta(&meta)?; 65 | 66 | if bound_is_set { 67 | return Err(panic::parameter_reset(ident)); 68 | } 69 | 70 | bound_is_set = true; 71 | 72 | bound = v; 73 | 74 | return Ok(true); 75 | } 76 | } 77 | 78 | Ok(false) 79 | }; 80 | 81 | for p in result { 82 | if !handler(p)? { 83 | return Err(panic::attribute_incorrect_format( 84 | meta.path().get_ident().unwrap(), 85 | &correct_usage_for_partial_eq_attribute, 86 | )); 87 | } 88 | } 89 | }, 90 | } 91 | 92 | Ok(TypeAttribute { 93 | bound, 94 | }) 95 | } 96 | 97 | pub(crate) fn build_from_attributes( 98 | &self, 99 | attributes: &[Attribute], 100 | traits: &[Trait], 101 | ) -> syn::Result { 102 | let mut output = None; 103 | 104 | for attribute in attributes.iter() { 105 | let path = attribute.path(); 106 | 107 | if path.is_ident("educe") { 108 | if let Meta::List(list) = &attribute.meta { 109 | let result = 110 | list.parse_args_with(Punctuated::::parse_terminated)?; 111 | 112 | for meta in result { 113 | let path = meta.path(); 114 | 115 | let t = match Trait::from_path(path) { 116 | Some(t) => t, 117 | None => return Err(panic::unsupported_trait(meta.path())), 118 | }; 119 | 120 | if !traits.contains(&t) { 121 | return Err(panic::trait_not_used(path.get_ident().unwrap())); 122 | } 123 | 124 | if t == Trait::Ord { 125 | if output.is_some() { 126 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 127 | } 128 | 129 | output = Some(self.build_from_ord_meta(&meta)?); 130 | } 131 | 132 | #[cfg(feature = "PartialOrd")] 133 | if traits.contains(&Trait::PartialOrd) && t == Trait::PartialOrd { 134 | if output.is_some() { 135 | return Err(panic::reuse_a_trait(path.get_ident().unwrap())); 136 | } 137 | 138 | output = Some(self.build_from_ord_meta(&meta)?); 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | Ok(output.unwrap_or(TypeAttribute { 146 | bound: Bound::Auto 147 | })) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/common/ident_bool.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | parse::{Parse, ParseStream}, 3 | spanned::Spanned, 4 | Expr, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, 5 | }; 6 | 7 | use super::path::path_to_string; 8 | 9 | #[derive(Debug)] 10 | pub(crate) enum IdentOrBool { 11 | Ident(Ident), 12 | Bool(bool), 13 | } 14 | 15 | impl Parse for IdentOrBool { 16 | #[inline] 17 | fn parse(input: ParseStream) -> syn::Result { 18 | if let Ok(lit) = input.parse::() { 19 | match lit { 20 | Lit::Bool(lit) => return Ok(Self::Bool(lit.value)), 21 | Lit::Str(lit) => { 22 | return match lit.parse::() { 23 | Ok(ident) => Ok(Self::Ident(ident)), 24 | Err(_) if lit.value().is_empty() => Ok(Self::Bool(false)), 25 | Err(error) => Err(error), 26 | } 27 | }, 28 | _ => (), 29 | } 30 | } 31 | 32 | Ok(Self::Ident(input.parse::()?)) 33 | } 34 | } 35 | 36 | #[inline] 37 | pub(crate) fn meta_name_value_2_ident(name_value: &MetaNameValue) -> syn::Result { 38 | match &name_value.value { 39 | Expr::Lit(lit) => { 40 | if let Lit::Str(lit) = &lit.lit { 41 | return lit.parse(); 42 | } 43 | }, 44 | Expr::Path(path) => { 45 | if let Some(ident) = path.path.get_ident() { 46 | return Ok(ident.clone()); 47 | } 48 | }, 49 | _ => (), 50 | } 51 | 52 | Err(syn::Error::new( 53 | name_value.value.span(), 54 | format!("expected `{path} = Ident`", path = path_to_string(&name_value.path)), 55 | )) 56 | } 57 | 58 | #[inline] 59 | pub(crate) fn meta_2_ident(meta: &Meta) -> syn::Result { 60 | match &meta { 61 | Meta::NameValue(name_value) => meta_name_value_2_ident(name_value), 62 | Meta::List(list) => { 63 | if let Ok(lit) = list.parse_args::() { 64 | lit.parse() 65 | } else { 66 | list.parse_args() 67 | } 68 | }, 69 | Meta::Path(path) => Err(syn::Error::new( 70 | path.span(), 71 | format!("expected `{path} = Ident` or `{path}(Ident)`", path = path_to_string(path)), 72 | )), 73 | } 74 | } 75 | 76 | #[inline] 77 | pub(crate) fn meta_name_value_2_bool(name_value: &MetaNameValue) -> syn::Result { 78 | if let Expr::Lit(lit) = &name_value.value { 79 | if let Lit::Bool(b) = &lit.lit { 80 | return Ok(b.value); 81 | } 82 | } 83 | 84 | Err(syn::Error::new( 85 | name_value.value.span(), 86 | format!("expected `{path} = false`", path = path_to_string(&name_value.path)), 87 | )) 88 | } 89 | 90 | #[inline] 91 | pub(crate) fn meta_2_bool(meta: &Meta) -> syn::Result { 92 | match &meta { 93 | Meta::NameValue(name_value) => meta_name_value_2_bool(name_value), 94 | Meta::List(list) => Ok(list.parse_args::()?.value), 95 | Meta::Path(path) => Err(syn::Error::new( 96 | path.span(), 97 | format!("expected `{path} = false` or `{path}(false)`", path = path_to_string(path)), 98 | )), 99 | } 100 | } 101 | 102 | #[inline] 103 | pub(crate) fn meta_2_bool_allow_path(meta: &Meta) -> syn::Result { 104 | match &meta { 105 | Meta::Path(_) => Ok(true), 106 | Meta::NameValue(name_value) => meta_name_value_2_bool(name_value), 107 | Meta::List(list) => Ok(list.parse_args::()?.value), 108 | } 109 | } 110 | 111 | #[inline] 112 | pub(crate) fn meta_name_value_2_ident_and_bool( 113 | name_value: &MetaNameValue, 114 | ) -> syn::Result { 115 | match &name_value.value { 116 | Expr::Lit(lit) => match &lit.lit { 117 | Lit::Str(lit) => match lit.parse::() { 118 | Ok(ident) => return Ok(IdentOrBool::Ident(ident)), 119 | Err(_) if lit.value().is_empty() => { 120 | return Ok(IdentOrBool::Bool(false)); 121 | }, 122 | Err(error) => { 123 | return Err(error); 124 | }, 125 | }, 126 | Lit::Bool(lit) => { 127 | return Ok(IdentOrBool::Bool(lit.value)); 128 | }, 129 | _ => (), 130 | }, 131 | Expr::Path(path) => { 132 | if let Some(ident) = path.path.get_ident() { 133 | return Ok(IdentOrBool::Ident(ident.clone())); 134 | } 135 | }, 136 | _ => (), 137 | } 138 | 139 | Err(syn::Error::new( 140 | name_value.value.span(), 141 | format!( 142 | "expected `{path} = Ident` or `{path} = false`", 143 | path = path_to_string(&name_value.path) 144 | ), 145 | )) 146 | } 147 | 148 | #[inline] 149 | pub(crate) fn meta_2_ident_and_bool(meta: &Meta) -> syn::Result { 150 | match &meta { 151 | Meta::NameValue(name_value) => meta_name_value_2_ident_and_bool(name_value), 152 | Meta::List(list) => list.parse_args::(), 153 | Meta::Path(path) => Err(syn::Error::new( 154 | path.span(), 155 | format!( 156 | "expected `{path} = Ident`, `{path}(Ident)`, `{path} = false`, or `{path}(false)`", 157 | path = path_to_string(path) 158 | ), 159 | )), 160 | } 161 | } 162 | --------------------------------------------------------------------------------