derive-ex-0.1.8/.cargo_vcs_info.json0000644000000001470000000000100127340ustar { "git": { "sha1": "b314b9a3ae48c5883f1165a30ac7804b2e720747" }, "path_in_vcs": "derive-ex" }derive-ex-0.1.8/Cargo.toml0000644000000021570000000000100107350ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "derive-ex" version = "0.1.8" authors = ["frozenlib"] description = "Improved version of the macro to implement the traits defined in the standard library." documentation = "https://docs.rs/derive-ex/" readme = "README.md" keywords = ["derive"] categories = ["rust-patterns"] license = "MIT OR Apache-2.0" repository = "https://github.com/frozenlib/derive-ex" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.81" [dependencies.quote] version = "1.0.36" [dependencies.structmeta] version = "0.3.0" [dependencies.syn] version = "2.0.60" features = [ "full", "extra-traits", "visit", "visit-mut", ] derive-ex-0.1.8/Cargo.toml.orig000064400000000000000000000013000072674642500144330ustar 00000000000000[package] name = "derive-ex" version = "0.1.8" edition = "2021" authors = ["frozenlib"] license = "MIT OR Apache-2.0" readme = "../README.md" repository = "https://github.com/frozenlib/derive-ex" documentation = "https://docs.rs/derive-ex/" keywords = ["derive"] categories = ["rust-patterns"] description = "Improved version of the macro to implement the traits defined in the standard library." # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] proc-macro = true [dependencies] syn = { version = "2.0.60", features = [ "full", "extra-traits", "visit", "visit-mut", ] } quote = "1.0.36" proc-macro2 = "1.0.81" structmeta = "0.3.0" derive-ex-0.1.8/README.md000064400000000000000000000047460072674642500130440ustar 00000000000000# derive-ex [![Crates.io](https://img.shields.io/crates/v/derive-ex.svg)](https://crates.io/crates/derive-ex) [![Docs.rs](https://docs.rs/derive-ex/badge.svg)](https://docs.rs/derive-ex/) [![Actions Status](https://github.com/frozenlib/derive-ex/workflows/CI/badge.svg)](https://github.com/frozenlib/derive-ex/actions) Improved version of the macro to implement the traits defined in the standard library. ## Documentation See [`#[derive_ex]` documentation](https://docs.rs/derive-ex/latest/derive_ex/attr.derive_ex.html) for details. ## Differences from standard derive macros - A trait bound that is automatically generated is smarter. - You can specify trait bound manually. - You can specify default values for each field. - You can specify comparison method for each field. - You can specify ignored field with the derivation of `Debug`. - Support derive `Clone::clone_from`. - Support derive operators. (`Add`, `AddAssign`, `Not`, `Deref`, etc.) ## Supported traits - `Copy` - `Clone` - `Debug` - `Default` - `Ord`, `PartialOrd`, `Eq`, `PartialEq`, `Hash` - operators - Add-like (`Add`, `Sub`, `Mul`, `Shl`, etc.) - AddAssign-like (`AddAssign`, `SubAssign`, `MulAssign`, `ShlAssign`, etc.) - Not-like (`Not`, `Neg`) - `Deref`, `DerefMut` ## Unsupported traits The following traits are not supported as more suitable crates exist. | trait | crate | | -------------------- | --------------------------------------------------------- | | `Display`, `FromStr` | [`parse-display`](https://crates.io/crates/parse-display) | | `Error` | [`thiserror`](https://crates.io/crates/thiserror) | ## Install Add this to your Cargo.toml: ```toml [dependencies] derive-ex = "0.1.7" ``` ## Example ```rust use derive_ex::derive_ex; #[derive(Eq, PartialEq, Debug)] #[derive_ex(Add, AddAssign, Clone, Default)] struct X { #[default(10)] a: u32, } assert_eq!(X { a: 1 } + X { a: 2 }, X { a: 3 }); assert_eq!(X::default(), X { a: 10 }); #[derive(Eq, PartialEq, Debug)] #[derive_ex(Clone, Default)] enum Y { A, #[default] B, } assert_eq!(Y::default(), Y::B); ``` ## License This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. derive-ex-0.1.8/src/bound.rs000064400000000000000000000060310072674642500140160ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; use structmeta::{NameArgs, ToTokens}; use syn::{ parse::{discouraged::Speculative, Parse, ParseStream}, Field, Generics, Result, Token, Type, WherePredicate, }; use crate::syn_utils::GenericParamSet; #[derive(Clone, ToTokens, Debug)] pub enum Bound { Type(Type), Pred(WherePredicate), Default(Token![..]), } impl Parse for Bound { fn parse(input: ParseStream) -> Result { if input.peek(Token![..]) { return Ok(Self::Default(input.parse()?)); } let fork = input.fork(); match fork.parse() { Ok(p) => { input.advance_to(&fork); Ok(Self::Pred(p)) } Err(e) => { if let Ok(ty) = input.parse() { Ok(Self::Type(ty)) } else { Err(e) } } } } } #[derive(Debug)] pub struct Bounds { pub ty: Vec, pub pred: Vec, pub default: bool, } impl Default for Bounds { fn default() -> Self { Self::new() } } impl Bounds { pub const fn new() -> Self { Self { ty: Vec::new(), pred: Vec::new(), default: true, } } pub fn from(bound: &Option>>) -> Self { let mut this = Self::new(); if let Some(bound) = bound { this.default = false; for b in &bound.args { this.push(b.clone()); } } this } fn push(&mut self, bound: Bound) { match bound { Bound::Type(ty) => self.ty.push(ty), Bound::Pred(pred) => self.pred.push(pred), Bound::Default(_) => self.default = true, } } } pub struct WhereClauseBuilder { types: Vec, preds: Vec, gps: GenericParamSet, } impl WhereClauseBuilder { pub fn new(generics: &Generics) -> Self { let (_, _, where_g) = generics.split_for_impl(); let mut preds = Vec::new(); if let Some(where_g) = where_g { preds.extend(where_g.predicates.iter().cloned()); } Self { types: Vec::new(), preds, gps: GenericParamSet::new(generics), } } pub fn push_bounds(&mut self, bounds: &Bounds) -> bool { self.preds.extend(bounds.pred.iter().cloned()); self.types.extend(bounds.ty.iter().cloned()); bounds.default } pub fn push_bounds_for_field(&mut self, field: &Field) { if self.gps.contains_in_type(&field.ty) { self.types.push(field.ty.clone()); } } pub fn build(self, f: impl Fn(&Type) -> TokenStream) -> TokenStream { let mut ws = Vec::new(); for ty in &self.types { ws.push(f(ty)); } for p in self.preds { ws.push(quote!(#p)); } if ws.is_empty() { quote!() } else { quote!(where #(#ws,)*) } } } derive-ex-0.1.8/src/common.rs000064400000000000000000000031660072674642500142050ustar 00000000000000#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum BinaryOp { Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub, } impl BinaryOp { pub fn from_str(s: &str) -> Option { Some(match s { "Add" => Self::Add, "BitAnd" => Self::BitAnd, "BitOr" => Self::BitOr, "BitXor" => Self::BitXor, "Div" => Self::Div, "Mul" => Self::Mul, "Rem" => Self::Rem, "Shl" => Self::Shl, "Shr" => Self::Shr, "Sub" => Self::Sub, _ => return None, }) } pub fn to_str(self) -> &'static str { match self { Self::Add => "Add", Self::BitAnd => "BitAnd", Self::BitOr => "BitOr", Self::BitXor => "BitXor", Self::Div => "Div", Self::Mul => "Mul", Self::Rem => "Rem", Self::Shl => "Shl", Self::Shr => "Shr", Self::Sub => "Sub", } } pub fn to_func_name(self) -> &'static str { match self { Self::Add => "add", Self::BitAnd => "bitand", Self::BitOr => "bitor", Self::BitXor => "bitxor", Self::Div => "div", Self::Mul => "mul", Self::Rem => "rem", Self::Shl => "shl", Self::Shr => "shr", Self::Sub => "sub", } } } impl std::fmt::Display for BinaryOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.to_str()) } } derive-ex-0.1.8/src/item_impl.rs000064400000000000000000000215010072674642500146650ustar 00000000000000use crate::{common::BinaryOp, syn_utils::expand_self}; use proc_macro2::{Span, TokenStream}; use quote::quote; use std::fmt::Display; use structmeta::StructMeta; use syn::{ parse2, parse_quote, spanned::Spanned, Error, GenericArgument, Ident, ImplItem, ItemImpl, Path, PathArguments, PathSegment, Result, Type, }; #[derive(StructMeta, Debug)] #[struct_meta(name_filter = "snake_case")] struct ArgList { #[struct_meta(unnamed)] items: Vec, dump: bool, } struct Args { dump: bool, make_binary: bool, make_assign: bool, } impl Args { fn from_attr_args(attr: TokenStream, op: Op) -> Result { let args: ArgList = parse2(attr)?; let mut make_binary = false; let mut make_assign = false; for item in &args.items { let target_op = Op::from_ident(item)?; if target_op.op != op.op { bail!( item.span(), "expected `{}` or `{}`", Op::new(op.op, OpForm::Binary), Op::new(op.op, OpForm::Assign) ); } match target_op.form { OpForm::Binary => make_binary = true, OpForm::Assign => make_assign = true, } } Ok(Self { dump: args.dump, make_binary, make_assign, }) } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] struct Op { op: BinaryOp, form: OpForm, } impl Op { fn new(op: BinaryOp, form: OpForm) -> Self { Self { op, form } } fn from_str(mut s: &str) -> Option { let suffix = "Assign"; let mut form = OpForm::Binary; if s.ends_with(suffix) { s = &s[..s.len() - suffix.len()]; form = OpForm::Assign; } Some(Self::new(BinaryOp::from_str(s)?, form)) } fn from_ident(s: &Ident) -> Result { let s = s.to_string(); if let Some(op) = Self::from_str(&s) { Ok(op) } else { bail!(s.span(), "`{}` is not supported for `#[derive_ex]`", s); } } fn to_func_ident(self) -> Ident { let name = self.op.to_func_name(); let assign = if self.form == OpForm::Assign { "_assign" } else { "" }; Ident::new(&format!("{name}{assign}"), Span::call_site()) } fn to_trait_ident(self) -> Ident { Ident::new(&self.to_string(), Span::call_site()) } fn to_trait_path(self) -> Path { let ident = self.to_trait_ident(); parse_quote!(::core::ops::#ident) } } impl Display for Op { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.op)?; if self.form == OpForm::Assign { write!(f, "Assign")?; } Ok(()) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum OpForm { Binary, Assign, } pub fn build_by_item_impl(attr: TokenStream, item_impl: &ItemImpl) -> Result { let span = Span::call_site(); let message = "must be used with `impl {Trait} for {Type}`"; let t = item_impl .trait_ .as_ref() .ok_or_else(|| Error::new(span, message))?; if t.0.is_some() { bail!(span, "cannot use with negative trait"); } let s = t.1.segments .last() .ok_or_else(|| Error::new(span, message))?; let this_orig = &item_impl.self_ty; let (this, this_is_ref) = to_ref_elem(this_orig); let rhs_orig = to_rhs(s, this_orig); let (rhs, rhs_is_ref) = to_ref_elem(&rhs_orig); let g = expand_self(&item_impl.generics, this_orig); let (impl_g, _, where_g) = &g.split_for_impl(); let op = Op::from_ident(&s.ident)?; let args = Args::from_attr_args(attr, op)?; let binary_op = Op::new(op.op, OpForm::Binary); let binary_func = binary_op.to_func_ident(); let binary_trait = binary_op.to_trait_path(); let assign_op = Op::new(op.op, OpForm::Assign); let assign_func = assign_op.to_func_ident(); let assign_trait = assign_op.to_trait_path(); let mut ts = TokenStream::new(); match op.form { OpForm::Binary => { let output = expand_self(find_output_type(item_impl)?, this_orig); let impl_binary = |impl_l_ref: bool, impl_r_ref: bool, call_l_ref: bool, call_r_ref: bool| { if impl_l_ref == call_l_ref && impl_r_ref == call_r_ref { return quote!(); } let impl_this = ref_type_with(&this, impl_l_ref); let impl_rhs = ref_type_with(&rhs, impl_r_ref); let l = ref_type_with(&this, call_l_ref); let r = ref_type_with(&rhs, call_r_ref); let l_expr = change_owned(quote!(self), &this, impl_l_ref, call_l_ref); let r_expr = change_owned(quote!(rhs), &rhs, impl_r_ref, call_r_ref); quote! { #[automatically_derived] impl #impl_g #binary_trait<#impl_rhs> for #impl_this #where_g { type Output = #output; fn #binary_func(self, rhs: #impl_rhs) -> Self::Output { <#l as #binary_trait<#r>>::#binary_func(#l_expr, #r_expr) } } } }; let impl_assign = |rhs: &Type, call_l_ref: bool| { let l = ref_type_with(&this, call_l_ref); let l_expr = change_owned(quote!(self), &this, true, call_l_ref); quote! { #[automatically_derived] impl #impl_g #assign_trait<#rhs> for #this #where_g { fn #assign_func(&mut self, rhs: #rhs) { *self = <#l as #binary_trait<#rhs>>::#binary_func(#l_expr, rhs) } } } }; if args.make_binary { for this in [false, true] { for rhs in [false, true] { ts.extend(impl_binary(this, rhs, this_is_ref, rhs_is_ref)); } } } if args.make_assign { if args.make_binary { ts.extend(impl_assign(&rhs, true)); ts.extend(impl_assign(&ref_type(&rhs), true)); } else { ts.extend(impl_assign(&rhs_orig, this_is_ref)); } } } OpForm::Assign => { if args.make_assign { bail!( _, "`#[derive_ex({})]` can be used only with `impl {} for T`", Op::new(op.op, OpForm::Assign), Op::new(op.op, OpForm::Binary), ); } if args.make_binary { let this = this_orig; let rhs = &rhs_orig; ts.extend(quote! { #[automatically_derived] impl #impl_g #binary_trait<#rhs> for #this #where_g { type Output = #this; fn #binary_func(mut self, rhs: #rhs) -> Self::Output { <#this as #assign_trait<#rhs>>::#assign_func(&mut self, rhs); self } } }); } } } if args.dump { bail!(_, "{}", format!("dump:\n{ts}")); } Ok(ts) } fn find_output_type(item_impl: &ItemImpl) -> Result<&Type> { for item in &item_impl.items { if let ImplItem::Type(t) = item { if t.ident == "Output" { return Ok(&t.ty); } } } bail!(_, "cannot find associate type `Output`"); } fn to_ref_elem(ty: &Type) -> (Type, bool) { if let Type::Reference(tr) = ty { if tr.lifetime.is_none() && tr.mutability.is_none() { return (tr.elem.as_ref().clone(), true); } } (ty.clone(), false) } fn to_rhs(s: &PathSegment, self_ty: &Type) -> Type { if let PathArguments::AngleBracketed(args) = &s.arguments { if args.args.len() == 1 { if let GenericArgument::Type(ty) = &args.args[0] { return expand_self(ty, self_ty); } } } self_ty.clone() } fn ref_type(ty: &Type) -> Type { parse_quote!(&#ty) } fn ref_type_with(ty: &Type, is_ref: bool) -> Type { if is_ref { ref_type(ty) } else { ty.clone() } } fn change_owned(expr: TokenStream, ty: &Type, input_ref: bool, output_ref: bool) -> TokenStream { match (input_ref, output_ref) { (true, false) => quote!(<#ty as ::core::clone::Clone>::clone(#expr)), (false, true) => quote!(&#expr), _ => expr, } } derive-ex-0.1.8/src/item_type/compare_op.rs000064400000000000000000001106560072674642500170430ustar 00000000000000use std::iter::once; use crate::bound::{Bounds, WhereClauseBuilder}; use proc_macro2::{Span, TokenStream, TokenTree}; use quote::{quote, quote_spanned, ToTokens}; use structmeta::{Flag, ToTokens}; use syn::{ parse::Parse, parse2, parse_quote, spanned::Spanned, Attribute, Expr, Generics, Ident, ItemEnum, ItemStruct, Result, Type, }; use super::{ parse_single, ArgsForCompareOp, AttributeTarget, CompareOp, DeriveEntry, DeriveItemKind, FieldEntry, HelperAttributeKinds, HelperAttributes, VariantEntry, }; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum ItemSourceKind { Struct, Enum, } impl ItemSourceKind { fn self_of(self, field: &FieldEntry) -> TokenStream { let span = field.span(); match self { ItemSourceKind::Struct => { let member = field.member(); quote_spanned!(span=> (self.#member)) } ItemSourceKind::Enum => { let ident = field.make_ident("_self"); quote_spanned!(span=> (*#ident)) } } } fn this_of(self, field: &FieldEntry) -> TokenStream { let span = field.span(); match self { ItemSourceKind::Struct => { let member = field.member(); quote_spanned!(span=> (this.#member)) } ItemSourceKind::Enum => { let ident = field.make_ident("_this"); quote_spanned!(span=> (*#ident)) } } } fn other_of(self, field: &FieldEntry) -> TokenStream { let span = field.span(); match self { ItemSourceKind::Struct => { let member = field.member(); quote_spanned!(span=> (other.#member)) } ItemSourceKind::Enum => { let ident = field.make_ident("_other"); quote_spanned!(span=> (*#ident)) } } } } #[derive(Copy, Clone)] enum ItemSource<'a> { Struct { item: &'a ItemStruct, fields: &'a [FieldEntry<'a>], }, Enum { item: &'a ItemEnum, variants: &'a [VariantEntry<'a>], }, } impl<'a> ItemSource<'a> { fn generics(&self) -> &Generics { match self { Self::Struct { item, .. } => &item.generics, Self::Enum { item, .. } => &item.generics, } } fn ident(&self) -> &Ident { match self { Self::Struct { item, .. } => &item.ident, Self::Enum { item, .. } => &item.ident, } } fn kind(&self) -> ItemSourceKind { match self { Self::Struct { .. } => ItemSourceKind::Struct, Self::Enum { .. } => ItemSourceKind::Enum, } } } pub(super) fn build_compare_op_for_struct( op: CompareOp, item: &ItemStruct, e: &DeriveEntry, hattrs: &HelperAttributes, fields: &[FieldEntry], ) -> Result { build_compare_op(op, ItemSource::Struct { item, fields }, e, hattrs) } pub(super) fn build_compare_op_for_enum( op: CompareOp, item: &ItemEnum, e: &DeriveEntry, hattrs: &HelperAttributes, variants: &[VariantEntry], ) -> Result { build_compare_op(op, ItemSource::Enum { item, variants }, e, hattrs) } fn build_compare_op( op: CompareOp, source: ItemSource, e: &DeriveEntry, hattrs: &HelperAttributes, ) -> Result { let kind = DeriveItemKind::CompareOp(op); let (impl_g, type_g, _) = source.generics().split_for_impl(); let this_ty_ident = source.ident(); let this_ty: Type = parse_quote!(#this_ty_ident #type_g); let trait_ = kind.to_path(); let mut wcb = WhereClauseBuilder::new(source.generics()); let use_bounds = e.push_bounds_to_with(hattrs, kind, &mut wcb); let body = match op { CompareOp::PartialEq => build_partial_eq_body(source, use_bounds, &mut wcb)?, CompareOp::Eq => build_eq_body(source, use_bounds, &mut wcb)?, CompareOp::PartialOrd => build_partial_ord_body(source, use_bounds, &mut wcb)?, CompareOp::Ord => build_ord_body(source, use_bounds, &mut wcb)?, CompareOp::Hash => build_hash_body(source, use_bounds, &mut wcb)?, }; let wheres = wcb.build(|ty| quote!(#ty : #trait_)); let (body, checker) = match op { CompareOp::PartialEq | CompareOp::PartialOrd | CompareOp::Ord | CompareOp::Hash => { (body, quote!()) } CompareOp::Eq => ( quote!(), quote! { const _: () = { #[allow(clippy::double_parens)] #[allow(unused_parens)] fn _f #impl_g (this: &#this_ty) #wheres { #body } }; }, ), }; Ok(quote! { #[automatically_derived] #[allow(clippy::double_parens)] #[allow(unused_parens)] impl #impl_g #trait_ for #this_ty #wheres { #body } #checker }) } fn build_partial_eq_body( source: ItemSource, use_bounds: bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::PartialEq; let kind = DeriveItemKind::CompareOp(op); let build_from_fields = |fields: &[FieldEntry], use_bounds: bool, wcb: &mut WhereClauseBuilder| -> Result { let mut exprs = Vec::new(); for field in fields { if field.hattrs.cmp.is_ignore(op)? { continue; } let mut field_used = false; let mut use_bounds = use_bounds; exprs.push(build_partial_eq_expr( source.kind(), field, &mut field_used, &mut use_bounds, wcb, )?); use_bounds = field .hattrs .push_bounds_to_without_helper(use_bounds, kind, wcb); if use_bounds && field_used { wcb.push_bounds_for_field(field.field); } } Ok(if exprs.is_empty() { quote!(true) } else { quote!(#(#exprs)&&*) }) }; let body = match source { ItemSource::Struct { fields, .. } => build_from_fields(fields, use_bounds, wcb)?, ItemSource::Enum { variants, .. } => { let mut arms = Vec::new(); for variant in variants { let use_bounds = variant.hattrs.push_bounds_to(use_bounds, kind, wcb); let body = build_from_fields(&variant.fields, use_bounds, wcb)?; let pat_this = variant.make_pat("_self"); let pat_other = variant.make_pat("_other"); arms.push(quote!((#pat_this, #pat_other) => { #body })) } quote! { match (self, other) { #(#arms)* _ => false, } } } }; Ok(quote! { fn eq(&self, other: &Self) -> bool { #body } }) } fn build_partial_eq_expr( source: ItemSourceKind, field: &FieldEntry, field_used: &mut bool, use_bounds: &mut bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::PartialEq; let ty = &field.field.ty; let fn_ident = field.make_ident("__eq_"); let this = source.self_of(field); let other = source.other_of(field); let cmp = &field.hattrs.cmp; cmp.partial_eq.push_bounds_to(use_bounds, wcb); let build_expr_by_eq = |by: &Expr| { quote! { { fn #fn_ident(this: &#ty, other: &#ty, eq: impl ::core::ops::Fn(&#ty, &#ty) -> bool) -> bool { eq(this, other) } #fn_ident(&#this, &#other, #by) } } }; if let Some(by) = &cmp.partial_eq.by { return Ok(build_expr_by_eq(by)); } if let Some(key) = &cmp.partial_eq.key { return Ok(key.build_eq_expr(this, other)); } cmp.eq.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.eq.by { return Ok(build_expr_by_eq(by)); } if let Some(key) = &cmp.eq.key { return Ok(key.build_eq_expr(this, other)); } cmp.partial_ord.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.partial_ord.by { return Ok(quote! { { fn #fn_ident(this: &#ty, other: &#ty, partial_cmp: impl Fn(&#ty, &#ty) -> ::core::option::Option<::core::cmp::Ordering>) -> bool { partial_cmp(this, other) == ::core::option::Option::Some(::core::cmp::Ordering::Equal) } #fn_ident(&#this, &#other, #by) } }); } if let Some(key) = &cmp.partial_ord.key { return Ok(key.build_eq_expr(this, other)); } cmp.ord.push_bounds_to(use_bounds, wcb); if let Some(by) = &field.hattrs.cmp.ord.by { return Ok(quote! { { fn #fn_ident(this: &#ty, other: &#ty, cmp: impl ::core::ops::Fn(&#ty, &#ty) -> ::core::cmp::Ordering) -> bool { cmp(this, other) == ::core::cmp::Ordering::Equal } #fn_ident(&#this, &#other, #by) } }); } if let Some(key) = &field.hattrs.cmp.ord.key { return Ok(key.build_eq_expr(this, other)); } if let Some(bad) = cmp.bad_attr() { return bad_attr( op, bad, &[ "partial_eq(key = ...)", "partial_eq(by = ...)", "eq(key = ...)", "eq(by = ...)", "partial_ord(key = ...)", "partial_ord(by = ...)", "ord(key = ...)", "ord(by = ...)", ], ); } *field_used = true; Ok(quote_spanned!(field.span()=> ::core::cmp::PartialEq::eq(&(#this), &(#other)))) } fn build_eq_body( source: ItemSource, use_bounds: bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Eq; let kind = DeriveItemKind::CompareOp(op); let build_from_fields = |fields: &[FieldEntry], use_bounds: bool, wcb: &mut WhereClauseBuilder| -> Result { let mut exprs = Vec::new(); for field in fields { if field.hattrs.cmp.is_ignore(op)? { continue; } let mut field_used = false; let mut use_bounds = use_bounds; exprs.push(build_eq_expr( source.kind(), field, &mut field_used, &mut use_bounds, wcb, )?); use_bounds = field .hattrs .push_bounds_to_without_helper(use_bounds, kind, wcb); if use_bounds && field_used { wcb.push_bounds_for_field(field.field); } } Ok(quote!(#(#exprs)*)) }; match source { ItemSource::Struct { fields, .. } => build_from_fields(fields, use_bounds, wcb), ItemSource::Enum { variants, .. } => { let mut arms = Vec::new(); for variant in variants { let use_bounds = variant.hattrs.push_bounds_to(use_bounds, kind, wcb); let body = build_from_fields(&variant.fields, use_bounds, wcb)?; let pat_this = variant.make_pat_with_self_path("_this", source.ident()); arms.push(quote!(#pat_this => { #body })); } Ok(quote! { match this { #(#arms)* _ => { } } }) } } } fn build_eq_expr( source: ItemSourceKind, field: &FieldEntry, field_used: &mut bool, use_bounds: &mut bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Eq; let cmp = &field.hattrs.cmp; let this = source.this_of(field); cmp.eq.push_bounds_to(use_bounds, wcb); if cmp.eq.by.is_some() { return Ok(quote!()); } if let Some(key) = &cmp.eq.key { return Ok(key.build_eq_checker(this)); } cmp.ord.push_bounds_to(use_bounds, wcb); if cmp.ord.by.is_some() { return Ok(quote!()); } if let Some(key) = &cmp.ord.key { return Ok(key.build_eq_checker(this)); } if let Some(bad) = cmp.bad_attr() { return bad_attr( op, bad, &[ "eq(key = ...)", "eq(by = ...)", "ord(key = ...)", "ord(by = ...)", ], ); } *field_used = true; Ok(build_eq_checker(this)) } fn build_partial_ord_body( source: ItemSource, use_bounds: bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::PartialOrd; let kind = DeriveItemKind::CompareOp(op); let build_from_fields = |fields: &[FieldEntry], use_bounds: bool, wcb: &mut WhereClauseBuilder| -> Result { let mut body = TokenStream::new(); for field in fields { if field.hattrs.cmp.is_ignore(op)? { continue; } let mut field_used = false; let mut use_bounds = use_bounds; let mut expr = build_partial_ord_expr( source.kind(), field, &mut field_used, &mut use_bounds, wcb, )?; if field.hattrs.cmp.is_reverse(op)? { expr = quote!(::core::option::Option::map(#expr, ::core::cmp::Ordering::reverse)); } body.extend(quote! { match #expr { ::core::option::Option::Some(::core::cmp::Ordering::Equal) => {} o => return o, } }); use_bounds = field .hattrs .push_bounds_to_without_helper(use_bounds, kind, wcb); if use_bounds && field_used { wcb.push_bounds_for_field(field.field); } } Ok(quote! { #body ::core::option::Option::Some(::core::cmp::Ordering::Equal) }) }; let body = match source { ItemSource::Struct { fields, .. } => build_from_fields(fields, use_bounds, wcb)?, ItemSource::Enum { variants, .. } => { let mut arms = Vec::new(); for variant in variants { let use_bounds = variant.hattrs.push_bounds_to(use_bounds, kind, wcb); let body = build_from_fields(&variant.fields, use_bounds, wcb)?; let pat_this = variant.make_pat("_self"); let pat_other = variant.make_pat("_other"); arms.push(quote!((#pat_this, #pat_other) => { #body })); } let to_index_fn = build_to_index_fn(variants); quote! { match (self, other) { #(#arms)* (this, other) => { #to_index_fn ::core::cmp::PartialOrd::partial_cmp(&to_index(this), &to_index(other)) }, } } } }; Ok(quote! { fn partial_cmp(&self, other: &Self) -> ::core::option::Option<::core::cmp::Ordering> { #body } }) } fn build_partial_ord_expr( source: ItemSourceKind, field: &FieldEntry, field_used: &mut bool, use_bounds: &mut bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::PartialOrd; let ty = &field.field.ty; let fn_ident = field.make_ident("__partial_ord_"); let this = source.self_of(field); let other = source.other_of(field); let cmp = &field.hattrs.cmp; cmp.partial_ord.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.partial_ord.by { return Ok(quote! { { fn #fn_ident( this: &#ty, other: &#ty, partial_cmp: impl Fn(&#ty, &#ty) -> ::core::option::Option<::core::cmp::Ordering>) -> ::core::option::Option<::core::cmp::Ordering> { partial_cmp(this, other) } #fn_ident(&#this, &#other, #by) } }); } if let Some(key) = &cmp.partial_ord.key { return Ok(key.build_partial_cmp_expr(this, other)); } cmp.ord.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.ord.by { return Ok(quote! { { fn #fn_ident( this: &#ty, other: &#ty, cmp: impl Fn(&#ty, &#ty) -> ::core::cmp::Ordering) -> ::core::option::Option<::core::cmp::Ordering> { ::core::option::Option::Some(cmp(this, other)) } #fn_ident(&#this, &#other, #by) } }); } if let Some(key) = &cmp.ord.key { return Ok(key.build_partial_cmp_expr(this, other)); } if let Some(bad) = cmp.bad_attr() { return bad_attr( op, bad, &[ "partial_ord(key = ...)", "partial_ord(by = ...)", "ord(key = ...)", "ord(by = ...)", ], ); } *field_used = true; Ok(quote_spanned!(field.span()=> ::core::cmp::PartialOrd::partial_cmp(&(#this), &(#other)))) } fn build_ord_body( source: ItemSource, use_bounds: bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Ord; let kind = DeriveItemKind::CompareOp(op); let build_from_fields = |fields: &[FieldEntry], use_bounds: bool, wcb: &mut WhereClauseBuilder| -> Result { let mut body = TokenStream::new(); for field in fields { if field.hattrs.cmp.is_ignore(op)? { continue; } let mut field_used = false; let mut use_bounds = use_bounds; let mut expr = build_ord_expr(source.kind(), field, &mut field_used, &mut use_bounds, wcb)?; if field.hattrs.cmp.is_reverse(op)? { expr = quote!(::core::cmp::Ordering::reverse(#expr)); } body.extend(quote! { match #expr { ::core::cmp::Ordering::Equal => {} o => return o, } }); use_bounds = field .hattrs .push_bounds_to_without_helper(use_bounds, kind, wcb); if use_bounds && field_used { wcb.push_bounds_for_field(field.field); } } Ok(quote! { #body ::core::cmp::Ordering::Equal }) }; let body = match source { ItemSource::Struct { fields, .. } => build_from_fields(fields, use_bounds, wcb)?, ItemSource::Enum { variants, .. } => { let mut arms = Vec::new(); for variant in variants { let use_bounds = variant.hattrs.push_bounds_to(use_bounds, kind, wcb); let body = build_from_fields(&variant.fields, use_bounds, wcb)?; let pat_this = variant.make_pat("_self"); let pat_other = variant.make_pat("_other"); arms.push(quote!((#pat_this, #pat_other) => { #body })); } let to_index_fn = build_to_index_fn(variants); quote! { match (self, other) { #(#arms)* (this, other) => { #to_index_fn ::core::cmp::Ord::cmp(&to_index(this), &to_index(other)) }, } } } }; Ok(quote! { fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { #body } }) } fn build_ord_expr( source: ItemSourceKind, field: &FieldEntry, field_used: &mut bool, use_bounds: &mut bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Ord; let ty = &field.field.ty; let fn_ident = field.make_ident("__ord_"); let this = source.self_of(field); let other = source.other_of(field); let cmp = &field.hattrs.cmp; cmp.ord.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.ord.by { return Ok(quote! { { fn #fn_ident( this: &#ty, other: &#ty, cmp: impl Fn(&#ty, &#ty) -> ::core::cmp::Ordering) -> ::core::cmp::Ordering { cmp(this, other) } #fn_ident(&#this, &#other, #by) } }); } if let Some(key) = &cmp.ord.key { return Ok(key.build_cmp_expr(this, other)); } if let Some(bad) = cmp.bad_attr() { return bad_attr(op, bad, &["ord(key = ...)", "ord(by = ...)"]); } *field_used = true; Ok(quote_spanned!(field.span()=> ::core::cmp::Ord::cmp(&(#this), &(#other)))) } fn build_hash_body( source: ItemSource, use_bounds: bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Hash; let kind = DeriveItemKind::CompareOp(op); let build_from_fields = |fields: &[FieldEntry], use_bounds: bool, wcb: &mut WhereClauseBuilder| -> Result { let mut exprs = Vec::new(); for field in fields { if field.hattrs.cmp.is_ignore(op)? { continue; } let mut field_used = false; let mut use_bounds = use_bounds; exprs.push(build_hash_expr( source.kind(), field, &mut field_used, &mut use_bounds, wcb, )?); use_bounds = field .hattrs .push_bounds_to_without_helper(use_bounds, kind, wcb); if use_bounds && field_used { wcb.push_bounds_for_field(field.field); } } Ok(quote!(#(#exprs)*)) }; let body = match source { ItemSource::Struct { fields, .. } => build_from_fields(fields, use_bounds, wcb)?, ItemSource::Enum { variants, .. } => { let mut arms = Vec::new(); for variant in variants { let use_bounds = variant.hattrs.push_bounds_to(use_bounds, kind, wcb); let body = build_from_fields(&variant.fields, use_bounds, wcb)?; let pat_self = variant.make_pat("_self"); arms.push(quote!(#pat_self => { #body })); } quote! { match self { #(#arms)* _ => unreachable!(), } } } }; Ok(quote! { fn hash(&self, state: &mut H) { #body } }) } fn build_hash_expr( source: ItemSourceKind, field: &FieldEntry, field_used: &mut bool, use_bounds: &mut bool, wcb: &mut WhereClauseBuilder, ) -> Result { let op = CompareOp::Hash; let ty = &field.field.ty; let fn_ident = field.make_ident("__hash_"); let this = source.self_of(field); let cmp = &field.hattrs.cmp; cmp.hash.push_bounds_to(use_bounds, wcb); if let Some(by) = &cmp.hash.by { return Ok(quote! { { fn #fn_ident( this: &#ty, state: &mut H, hash: impl Fn(&#ty, &mut H)) { hash(this, state) } #fn_ident(&#this, state, #by) } }); } if let Some(key) = &cmp.hash.key { return Ok(key.build_hash_stmt(this)); } cmp.eq.push_bounds_to(use_bounds, wcb); if let Some(key) = &cmp.eq.key { return Ok(key.build_hash_stmt(this)); } cmp.ord.push_bounds_to(use_bounds, wcb); if let Some(key) = &cmp.ord.key { return Ok(key.build_hash_stmt(this)); } if let Some(bad) = cmp.bad_attr() { return bad_attr( op, bad, &[ "hash(key = ...)", "hash(by = ...)", "eq(key = ...)", "ord(key = ...)", ], ); } *field_used = true; Ok(quote_spanned!(field.span()=> ::core::hash::Hash::hash(&(#this), state);)) } pub(super) struct HelperAttributesForCompareOp { ord: HelperAttributeForCompareOp, partial_ord: HelperAttributeForCompareOp, eq: HelperAttributeForCompareOp, partial_eq: HelperAttributeForCompareOp, hash: HelperAttributeForCompareOp, } impl HelperAttributesForCompareOp { pub fn from_attrs(attrs: &[Attribute], kinds: &HelperAttributeKinds) -> Result { let ord = if kinds.is_match_cmp_attr(CompareOp::Ord) { HelperAttributeForCompareOp::from_attrs(attrs, CompareOp::Ord)? } else { HelperAttributeForCompareOp::default() }; let partial_ord = if kinds.is_match_cmp_attr(CompareOp::PartialOrd) { HelperAttributeForCompareOp::from_attrs(attrs, CompareOp::PartialOrd)? } else { HelperAttributeForCompareOp::default() }; let eq = if kinds.is_match_cmp_attr(CompareOp::Eq) { HelperAttributeForCompareOp::from_attrs(attrs, CompareOp::Eq)? } else { HelperAttributeForCompareOp::default() }; let partial_eq = if kinds.is_match_cmp_attr(CompareOp::PartialEq) { HelperAttributeForCompareOp::from_attrs(attrs, CompareOp::PartialEq)? } else { HelperAttributeForCompareOp::default() }; let hash = if kinds.is_match_cmp_attr(CompareOp::Hash) { HelperAttributeForCompareOp::from_attrs(attrs, CompareOp::Hash)? } else { HelperAttributeForCompareOp::default() }; Ok(Self { ord, partial_ord, eq, partial_eq, hash, }) } pub fn push_bounds(&self, op: CompareOp, wcb: &mut WhereClauseBuilder) -> bool { let mut use_bounds = true; for &source in CompareOp::VARIANTS { if source.is_effects_to(op) && use_bounds { use_bounds = wcb.push_bounds(&self.get(source).bounds); } } use_bounds } fn get(&self, op: CompareOp) -> &HelperAttributeForCompareOp { match op { CompareOp::Ord => &self.ord, CompareOp::PartialOrd => &self.partial_ord, CompareOp::Eq => &self.eq, CompareOp::PartialEq => &self.partial_eq, CompareOp::Hash => &self.hash, } } fn is_ignore(&self, op: CompareOp) -> Result { let bad_flag = |bad: CompareOp, good: CompareOp| -> Result<()> { if let Some(span) = self.get(bad).ignore.span { let good = good.to_str_snake_case(); let bad = bad.to_str_snake_case(); bad_attr_1( span, op, &format!("{bad}(ignore)"), &format!("{good}(ignore)"), ) } else { Ok(()) } }; match op { CompareOp::Ord => { if self.ord.ignore.value() { return Ok(true); } bad_flag(CompareOp::PartialOrd, CompareOp::Ord)?; bad_flag(CompareOp::PartialEq, CompareOp::Ord)?; bad_flag(CompareOp::Eq, CompareOp::Ord)?; Ok(false) } CompareOp::PartialOrd => { if self.partial_ord.ignore.value() || self.ord.ignore.value() { return Ok(true); } bad_flag(CompareOp::PartialEq, CompareOp::PartialOrd)?; bad_flag(CompareOp::Eq, CompareOp::Ord)?; Ok(false) } CompareOp::Eq => { if self.eq.ignore.value() || self.ord.ignore.value() { return Ok(true); } bad_flag(CompareOp::PartialEq, CompareOp::Eq)?; bad_flag(CompareOp::PartialOrd, CompareOp::Ord)?; Ok(false) } CompareOp::PartialEq => Ok(self.partial_eq.ignore.value() || self.eq.ignore.value() || self.partial_ord.ignore.value() || self.ord.ignore.value()), CompareOp::Hash => { if self.hash.ignore.value() || self.eq.ignore.value() || self.ord.ignore.value() { return Ok(true); } bad_flag(CompareOp::PartialEq, CompareOp::Eq)?; bad_flag(CompareOp::PartialOrd, CompareOp::Ord)?; Ok(false) } } } fn is_reverse(&self, op: CompareOp) -> Result { match op { CompareOp::Ord => { if let Some(span) = self.partial_ord.reverse.span { return bad_attr_1(span, op, "partial_ord(reverse)", "ord(reverse)"); } Ok(self.ord.reverse.value()) } CompareOp::PartialOrd => { Ok(self.partial_ord.reverse.value() || self.ord.reverse.value()) } CompareOp::Eq | CompareOp::PartialEq | CompareOp::Hash => { unreachable!() } } } fn bad_attr(&self) -> Option<(String, Span)> { for &op in CompareOp::VARIANTS { if let Some((bad, span)) = self.get(op).bad_attr() { let op = op.to_str_snake_case(); return Some((format!("{op}({bad})"), span)); } } None } pub(crate) fn verify(&self, target: AttributeTarget) -> Result<()> { for &op in CompareOp::VARIANTS { self.get(op).verify(target)?; } Ok(()) } } #[derive(Default)] struct HelperAttributeForCompareOp { ignore: Flag, reverse: Flag, by: Option, key: Option