use proc_macro2::TokenStream;
use quote::quote;
use crate::{data::SimpleType, Data, DeriveTrait, Item};
pub fn build_ord_signature(item: &Item, trait_: &DeriveTrait, body: &TokenStream) -> TokenStream {
use DeriveTrait::*;
let mut equal = quote! { ::core::cmp::Ordering::Equal };
if let PartialOrd = trait_ {
equal = quote! { ::core::option::Option::Some(#equal) };
}
match item {
item if item.is_incomparable() => {
quote! { ::core::option::Option::None }
}
Item::Enum { variants, .. } if variants.len() > 1 => {
let body = if item.is_empty(**trait_) {
quote! { #equal }
}
else if variants
.iter()
.any(|variant| variant.is_empty(**trait_) && !variant.is_incomparable())
{
quote! {
match (self, __other) {
#body
_ => #equal,
}
}
}
else {
#[cfg(not(feature = "safe"))]
let rest = quote! { unsafe { ::core::hint::unreachable_unchecked() } };
#[cfg(feature = "safe")]
let rest = quote! { ::core::unreachable!("comparing variants yielded unexpected results") };
quote! {
match (self, __other) {
#body
_ => #rest,
}
}
};
let incomparable = build_incomparable_pattern(variants);
let mut comparable = variants.iter().filter(|v| !v.is_incomparable());
if let (Some(comparable), None) = (comparable.next(), comparable.next()) {
let incomparable = incomparable.expect("there should be > 1 variants");
let equal = if comparable.is_empty(**trait_) {
equal
} else {
body
};
quote! {
if ::core::matches!(self, #incomparable) || ::core::matches!(__other, #incomparable) {
::core::option::Option::None
} else {
#equal
}
}
} else {
let incomparable = incomparable.into_iter();
let incomparable = quote! {
#(if ::core::matches!(self, #incomparable) || ::core::matches!(__other, #incomparable) {
return ::core::option::Option::None;
})*
};
#[cfg(any(feature = "nightly", not(feature = "safe")))]
{
let path = trait_.path();
let method = match trait_ {
PartialOrd => quote! { partial_cmp },
Ord => quote! { cmp },
_ => unreachable!("unsupported trait in `prepare_ord`"),
};
#[cfg(feature = "nightly")]
quote! {
#incomparable
let __self_disc = ::core::intrinsics::discriminant_value(self);
let __other_disc = ::core::intrinsics::discriminant_value(__other);
if __self_disc == __other_disc {
#body
} else {
#path::#method(&__self_disc, &__other_disc)
}
}
#[cfg(not(feature = "nightly"))]
quote! {
#incomparable
let __self_disc = ::core::mem::discriminant(self);
let __other_disc = ::core::mem::discriminant(__other);
if __self_disc == __other_disc {
#body
} else {
#path::#method(
&unsafe { ::core::mem::transmute::<_, isize>(__self_disc) },
&unsafe { ::core::mem::transmute::<_, isize>(__other_disc) },
)
}
}
}
#[cfg(all(not(feature = "nightly"), feature = "safe"))]
{
use syn::PatOr;
let mut less = quote! { ::core::cmp::Ordering::Less };
let mut greater = quote! { ::core::cmp::Ordering::Greater };
if let PartialOrd = trait_ {
less = quote! { ::core::option::Option::Some(#less) };
greater = quote! { ::core::option::Option::Some(#greater) };
}
let mut different = Vec::with_capacity(variants.len());
let variants: Vec<_> =
variants.iter().filter(|v| !v.is_incomparable()).collect();
for (index, variant) in variants.iter().enumerate() {
let pattern = &variant.self_pattern();
if index == 0 {
different.push(quote! {
#pattern => #less,
})
}
else if index == variants.len() - 1 {
different.push(quote! {
#pattern => #greater,
})
}
else {
let cases = variants
.iter()
.enumerate()
.filter(|(index_other, _)| *index_other < index)
.map(|(_, variant_other)| {
variant_other.other_pattern_skip().clone()
})
.collect();
let pattern_less = PatOr {
attrs: Vec::new(),
leading_vert: None,
cases,
};
different.push(quote! {
#pattern => match __other {
#pattern_less => #greater,
_ => #less,
},
});
}
}
let rest = if incomparable.is_empty() {
quote!()
} else {
quote!(_ => unreachable!("incomparable variants should have already returned"),)
};
quote! {
#incomparable
let __self_disc = ::core::mem::discriminant(self);
let __other_disc = ::core::mem::discriminant(__other);
if __self_disc == __other_disc {
#body
} else {
match self {
#(#different)*
#rest
}
}
}
}
}
}
item if item.is_empty(**trait_) => {
quote! { #equal }
}
_ => {
quote! {
match (self, __other) {
#body
}
}
}
}
}
pub fn build_ord_body(trait_: &DeriveTrait, data: &Data) -> TokenStream {
use DeriveTrait::*;
let path = trait_.path();
let mut equal = quote! { ::core::cmp::Ordering::Equal };
let method = match trait_ {
PartialOrd => {
equal = quote! { ::core::option::Option::Some(#equal) };
quote! { partial_cmp }
}
Ord => quote! { cmp },
_ => unreachable!("unsupported trait in `build_ord`"),
};
let mut body = quote! { #equal };
for (field_temp, field_other) in data
.iter_self_ident(**trait_)
.rev()
.zip(data.iter_other_ident(**trait_).rev())
{
body = quote! {
match #path::#method(#field_temp, #field_other) {
#equal => #body,
__cmp => __cmp,
}
};
}
body
}
pub fn build_incomparable_pattern(variants: &[Data]) -> Option<TokenStream> {
let mut incomparable = variants
.iter()
.filter(|variant| variant.is_incomparable())
.map(|variant @ Data { path, .. }| match variant.simple_type() {
SimpleType::Struct(_) => quote!(#path{..}),
SimpleType::Tuple(_) => quote!(#path(..)),
SimpleType::Union(_) => unreachable!("enum variants cannot be unions"),
SimpleType::Unit(_) => quote!(#path),
})
.peekable();
if incomparable.peek().is_some() {
Some(quote! {
#(#incomparable)|*
})
} else {
None
}
}