| // https://github.com/rust-lang/rust/issues/13101 |
| |
| use ast; |
| use attr; |
| use matcher; |
| use paths; |
| use proc_macro2; |
| use syn; |
| use utils; |
| |
| /// Derive `Eq` for `input`. |
| pub fn derive_eq(input: &ast::Input) -> proc_macro2::TokenStream { |
| let name = &input.ident; |
| |
| let eq_trait_path = eq_trait_path(); |
| let generics = utils::build_impl_generics( |
| input, |
| &eq_trait_path, |
| needs_eq_bound, |
| |field| field.eq_bound(), |
| |input| input.eq_bound(), |
| ); |
| let new_where_clause; |
| let (impl_generics, ty_generics, mut where_clause) = generics.split_for_impl(); |
| |
| if let Some(new_where_clause2) = |
| maybe_add_copy(input, where_clause, |f| !f.attrs.ignore_partial_eq()) |
| { |
| new_where_clause = new_where_clause2; |
| where_clause = Some(&new_where_clause); |
| } |
| |
| quote! { |
| #[allow(unused_qualifications)] |
| impl #impl_generics #eq_trait_path for #name #ty_generics #where_clause {} |
| } |
| } |
| |
| /// Derive `PartialEq` for `input`. |
| pub fn derive_partial_eq(input: &ast::Input) -> proc_macro2::TokenStream { |
| let discriminant_cmp = if let ast::Body::Enum(_) = input.body { |
| let discriminant_path = paths::discriminant_path(); |
| |
| quote!((#discriminant_path(&*self) == #discriminant_path(&*other))) |
| } else { |
| quote!(true) |
| }; |
| |
| let name = &input.ident; |
| |
| let partial_eq_trait_path = partial_eq_trait_path(); |
| let generics = utils::build_impl_generics( |
| input, |
| &partial_eq_trait_path, |
| needs_partial_eq_bound, |
| |field| field.partial_eq_bound(), |
| |input| input.partial_eq_bound(), |
| ); |
| let new_where_clause; |
| let (impl_generics, ty_generics, mut where_clause) = generics.split_for_impl(); |
| |
| let match_fields = if input.is_trivial_enum() { |
| quote!(true) |
| } else { |
| matcher::Matcher::new(matcher::BindingStyle::Ref, input.attrs.is_packed) |
| .with_field_filter(|f: &ast::Field| !f.attrs.ignore_partial_eq()) |
| .build_2_arms( |
| (quote!(*self), quote!(*other)), |
| (input, "__self"), |
| (input, "__other"), |
| |_, _, _, (left_variant, right_variant)| { |
| let cmp = left_variant.iter().zip(&right_variant).map(|(o, i)| { |
| let outer_name = &o.expr; |
| let inner_name = &i.expr; |
| |
| if o.field.attrs.ignore_partial_eq() { |
| None |
| } else if let Some(compare_fn) = o.field.attrs.partial_eq_compare_with() { |
| Some(quote!(&& #compare_fn(&#outer_name, &#inner_name))) |
| } else { |
| Some(quote!(&& &#outer_name == &#inner_name)) |
| } |
| }); |
| |
| quote!(true #(#cmp)*) |
| }, |
| ) |
| }; |
| |
| if let Some(new_where_clause2) = |
| maybe_add_copy(input, where_clause, |f| !f.attrs.ignore_partial_eq()) |
| { |
| new_where_clause = new_where_clause2; |
| where_clause = Some(&new_where_clause); |
| } |
| |
| quote! { |
| #[allow(unused_qualifications)] |
| #[allow(clippy::unneeded_field_pattern)] |
| impl #impl_generics #partial_eq_trait_path for #name #ty_generics #where_clause { |
| fn eq(&self, other: &Self) -> bool { |
| #discriminant_cmp && #match_fields |
| } |
| } |
| } |
| } |
| |
| /// Derive `PartialOrd` for `input`. |
| pub fn derive_partial_ord( |
| input: &ast::Input, |
| errors: &mut proc_macro2::TokenStream, |
| ) -> proc_macro2::TokenStream { |
| if let ast::Body::Enum(_) = input.body { |
| if !input.attrs.partial_ord_on_enum() { |
| let message = "can't use `#[derivative(PartialOrd)]` on an enumeration without \ |
| `feature_allow_slow_enum`; see the documentation for more details"; |
| errors.extend(syn::Error::new(input.span, message).to_compile_error()); |
| } |
| } |
| |
| let option_path = option_path(); |
| let ordering_path = ordering_path(); |
| |
| let body = matcher::Matcher::new(matcher::BindingStyle::Ref, input.attrs.is_packed) |
| .with_field_filter(|f: &ast::Field| !f.attrs.ignore_partial_ord()) |
| .build_arms(input, "__self", |_, n, _, _, _, outer_bis| { |
| let body = matcher::Matcher::new(matcher::BindingStyle::Ref, input.attrs.is_packed) |
| .with_field_filter(|f: &ast::Field| !f.attrs.ignore_partial_ord()) |
| .build_arms(input, "__other", |_, m, _, _, _, inner_bis| { |
| match n.cmp(&m) { |
| ::std::cmp::Ordering::Less => { |
| quote!(#option_path::Some(#ordering_path::Less)) |
| } |
| ::std::cmp::Ordering::Greater => { |
| quote!(#option_path::Some(#ordering_path::Greater)) |
| } |
| ::std::cmp::Ordering::Equal => { |
| let equal_path = quote!(#ordering_path::Equal); |
| outer_bis |
| .iter() |
| .rev() |
| .zip(inner_bis.into_iter().rev()) |
| .fold(quote!(#option_path::Some(#equal_path)), |acc, (o, i)| { |
| let outer_name = &o.expr; |
| let inner_name = &i.expr; |
| |
| if o.field.attrs.ignore_partial_ord() { |
| acc |
| } else { |
| let cmp_fn = o |
| .field |
| .attrs |
| .partial_ord_compare_with() |
| .map(|f| quote!(#f)) |
| .unwrap_or_else(|| { |
| let path = partial_ord_trait_path(); |
| quote!(#path::partial_cmp) |
| }); |
| |
| quote!(match #cmp_fn(&#outer_name, &#inner_name) { |
| #option_path::Some(#equal_path) => #acc, |
| __derive_ordering_other => __derive_ordering_other, |
| }) |
| } |
| }) |
| } |
| } |
| }); |
| |
| quote! { |
| match *other { |
| #body |
| } |
| |
| } |
| }); |
| |
| let name = &input.ident; |
| |
| let partial_ord_trait_path = partial_ord_trait_path(); |
| let generics = utils::build_impl_generics( |
| input, |
| &partial_ord_trait_path, |
| needs_partial_ord_bound, |
| |field| field.partial_ord_bound(), |
| |input| input.partial_ord_bound(), |
| ); |
| let new_where_clause; |
| let (impl_generics, ty_generics, mut where_clause) = generics.split_for_impl(); |
| |
| if let Some(new_where_clause2) = |
| maybe_add_copy(input, where_clause, |f| !f.attrs.ignore_partial_ord()) |
| { |
| new_where_clause = new_where_clause2; |
| where_clause = Some(&new_where_clause); |
| } |
| |
| quote! { |
| #[allow(unused_qualifications)] |
| #[allow(clippy::unneeded_field_pattern)] |
| impl #impl_generics #partial_ord_trait_path for #name #ty_generics #where_clause { |
| fn partial_cmp(&self, other: &Self) -> #option_path<#ordering_path> { |
| match *self { |
| #body |
| } |
| } |
| } |
| } |
| } |
| |
| /// Derive `Ord` for `input`. |
| pub fn derive_ord( |
| input: &ast::Input, |
| errors: &mut proc_macro2::TokenStream, |
| ) -> proc_macro2::TokenStream { |
| if let ast::Body::Enum(_) = input.body { |
| if !input.attrs.ord_on_enum() { |
| let message = "can't use `#[derivative(Ord)]` on an enumeration without \ |
| `feature_allow_slow_enum`; see the documentation for more details"; |
| errors.extend(syn::Error::new(input.span, message).to_compile_error()); |
| } |
| } |
| |
| let ordering_path = ordering_path(); |
| |
| let body = matcher::Matcher::new(matcher::BindingStyle::Ref, input.attrs.is_packed) |
| .with_field_filter(|f: &ast::Field| !f.attrs.ignore_ord()) |
| .build_arms(input, "__self", |_, n, _, _, _, outer_bis| { |
| let body = matcher::Matcher::new(matcher::BindingStyle::Ref, input.attrs.is_packed) |
| .with_field_filter(|f: &ast::Field| !f.attrs.ignore_ord()) |
| .build_arms(input, "__other", |_, m, _, _, _, inner_bis| { |
| match n.cmp(&m) { |
| ::std::cmp::Ordering::Less => quote!(#ordering_path::Less), |
| ::std::cmp::Ordering::Greater => quote!(#ordering_path::Greater), |
| ::std::cmp::Ordering::Equal => { |
| let equal_path = quote!(#ordering_path::Equal); |
| outer_bis |
| .iter() |
| .rev() |
| .zip(inner_bis.into_iter().rev()) |
| .fold(quote!(#equal_path), |acc, (o, i)| { |
| let outer_name = &o.expr; |
| let inner_name = &i.expr; |
| |
| if o.field.attrs.ignore_ord() { |
| acc |
| } else { |
| let cmp_fn = o |
| .field |
| .attrs |
| .ord_compare_with() |
| .map(|f| quote!(#f)) |
| .unwrap_or_else(|| { |
| let path = ord_trait_path(); |
| quote!(#path::cmp) |
| }); |
| |
| quote!(match #cmp_fn(&#outer_name, &#inner_name) { |
| #equal_path => #acc, |
| __derive_ordering_other => __derive_ordering_other, |
| }) |
| } |
| }) |
| } |
| } |
| }); |
| |
| quote! { |
| match *other { |
| #body |
| } |
| |
| } |
| }); |
| |
| let name = &input.ident; |
| |
| let ord_trait_path = ord_trait_path(); |
| let generics = utils::build_impl_generics( |
| input, |
| &ord_trait_path, |
| needs_ord_bound, |
| |field| field.ord_bound(), |
| |input| input.ord_bound(), |
| ); |
| let new_where_clause; |
| let (impl_generics, ty_generics, mut where_clause) = generics.split_for_impl(); |
| |
| if let Some(new_where_clause2) = maybe_add_copy(input, where_clause, |f| !f.attrs.ignore_ord()) |
| { |
| new_where_clause = new_where_clause2; |
| where_clause = Some(&new_where_clause); |
| } |
| |
| quote! { |
| #[allow(unused_qualifications)] |
| #[allow(clippy::unneeded_field_pattern)] |
| impl #impl_generics #ord_trait_path for #name #ty_generics #where_clause { |
| fn cmp(&self, other: &Self) -> #ordering_path { |
| match *self { |
| #body |
| } |
| } |
| } |
| } |
| } |
| |
| fn needs_partial_eq_bound(attrs: &attr::Field) -> bool { |
| !attrs.ignore_partial_eq() && attrs.partial_eq_bound().is_none() |
| } |
| |
| fn needs_partial_ord_bound(attrs: &attr::Field) -> bool { |
| !attrs.ignore_partial_ord() && attrs.partial_ord_bound().is_none() |
| } |
| |
| fn needs_ord_bound(attrs: &attr::Field) -> bool { |
| !attrs.ignore_ord() && attrs.ord_bound().is_none() |
| } |
| |
| fn needs_eq_bound(attrs: &attr::Field) -> bool { |
| !attrs.ignore_partial_eq() && attrs.eq_bound().is_none() |
| } |
| |
| /// Return the path of the `Eq` trait, that is `::std::cmp::Eq`. |
| fn eq_trait_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::cmp::Eq) |
| } else { |
| parse_quote!(::std::cmp::Eq) |
| } |
| } |
| |
| /// Return the path of the `PartialEq` trait, that is `::std::cmp::PartialEq`. |
| fn partial_eq_trait_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::cmp::PartialEq) |
| } else { |
| parse_quote!(::std::cmp::PartialEq) |
| } |
| } |
| |
| /// Return the path of the `PartialOrd` trait, that is `::std::cmp::PartialOrd`. |
| fn partial_ord_trait_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::cmp::PartialOrd) |
| } else { |
| parse_quote!(::std::cmp::PartialOrd) |
| } |
| } |
| |
| /// Return the path of the `Ord` trait, that is `::std::cmp::Ord`. |
| fn ord_trait_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::cmp::Ord) |
| } else { |
| parse_quote!(::std::cmp::Ord) |
| } |
| } |
| |
| /// Return the path of the `Option` trait, that is `::std::option::Option`. |
| fn option_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::option::Option) |
| } else { |
| parse_quote!(::std::option::Option) |
| } |
| } |
| |
| /// Return the path of the `Ordering` trait, that is `::std::cmp::Ordering`. |
| fn ordering_path() -> syn::Path { |
| if cfg!(feature = "use_core") { |
| parse_quote!(::core::cmp::Ordering) |
| } else { |
| parse_quote!(::std::cmp::Ordering) |
| } |
| } |
| |
| fn maybe_add_copy( |
| input: &ast::Input, |
| where_clause: Option<&syn::WhereClause>, |
| field_filter: impl Fn(&ast::Field) -> bool, |
| ) -> Option<syn::WhereClause> { |
| if input.attrs.is_packed && !input.body.is_empty() { |
| let mut new_where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { |
| where_token: parse_quote!(where), |
| predicates: Default::default(), |
| }); |
| |
| new_where_clause.predicates.extend( |
| input |
| .body |
| .all_fields() |
| .into_iter() |
| .filter(|f| field_filter(f)) |
| .map(|f| { |
| let ty = f.ty; |
| |
| let pred: syn::WherePredicate = parse_quote!(#ty: Copy); |
| pred |
| }), |
| ); |
| |
| Some(new_where_clause) |
| } else { |
| None |
| } |
| } |