partial_cmp_derive/lib.rs
1//! # partial-cmp-derive
2//!
3//! A procedural macro crate for deriving `PartialEq`, `Eq`, `PartialOrd`, and `Ord`
4//! with fine-grained control over field comparison behavior.
5//!
6//! ## Features
7//!
8//! - **Skip fields**: Use `#[ord(skip)]` to exclude fields from all comparisons.
9//! Skipped fields are ignored in both equality and ordering checks.
10//! - **Sort order**: Use `#[ord(order = "asc")]` or `#[ord(order = "desc")]` per field
11//! - **Explicit ordering**: Use `#[ord(by = [field1(desc), field2(asc)])]` at struct level
12//! - **Field priority**: Use `#[ord(priority = N)]` for implicit ordering (lower = first)
13//! - **Custom comparators**: Use `#[ord(compare_with = "path::to::fn")]` for ordering
14//! - **Custom equality**: Use `#[ord(eq_with = "path::to::fn")]` for equality checks
15//! - **Reverse all**: Use `#[ord(reverse)]` at struct level to reverse entire comparison
16//! - **Enum ranking**: Use `#[ord(rank = N)]` to control variant ordering
17//! - **Option handling**: Use `#[ord(none_order = "first")]` or `"last"`
18//! - **Trait selection**: Control which traits are generated with skip flags
19//!
20//! ## Trait Generation
21//!
22//! By default, all four comparison traits are generated: `PartialEq`, `Eq`,
23//! `PartialOrd`, and `Ord`. You can opt out of specific traits:
24//!
25//! - `#[ord(skip_partial_eq)]` — Don't generate `PartialEq` (implies no other traits)
26//! - `#[ord(skip_eq)]` — Don't generate `Eq` (implies no `Ord`)
27//! - `#[ord(skip_partial_ord)]` — Don't generate `PartialOrd` (implies no `Ord`)
28//! - `#[ord(skip_ord)]` — Don't generate `Ord`
29//!
30//! ## Example
31//!
32//! ```rust
33//! use partial_cmp_derive::PartialCmp;
34//!
35//! // Generates PartialEq, Eq, PartialOrd, and Ord
36//! #[derive(PartialCmp)]
37//! struct Point {
38//! x: i32,
39//! y: i32,
40//! }
41//!
42//! // Skipped fields are excluded from all comparisons
43//! // Use skip_eq and skip_ord when non-skipped fields don't implement Eq/Ord
44//! #[derive(Debug, PartialCmp)]
45//! #[ord(skip_eq, skip_ord)]
46//! struct Measurement {
47//! #[ord(skip)]
48//! raw_value: f32, // Ignored in eq and cmp
49//! timestamp: u64,
50//! }
51//!
52//! // Only generate PartialEq and PartialOrd (for types like f32)
53//! #[derive(Debug, PartialCmp)]
54//! #[ord(skip_eq, skip_ord)]
55//! struct PartialOnly {
56//! #[ord(compare_with = "cmp_f32")]
57//! value: f32,
58//! }
59//!
60//! fn cmp_f32(a: &f32, b: &f32) -> std::cmp::Ordering {
61//! a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
62//! }
63//! ```
64//!
65//! This generates consistent implementations where skipped fields are ignored
66//! in both equality and ordering comparisons.
67
68mod expand;
69mod types;
70
71use darling::FromDeriveInput;
72use proc_macro::TokenStream;
73use syn::{DeriveInput, parse_macro_input};
74
75use crate::expand::expand_derive;
76use crate::types::OrdDerive;
77
78/// Derives comparison traits with customizable field behavior.
79///
80/// By default, this macro generates implementations for `PartialEq`, `Eq`,
81/// `PartialOrd`, and `Ord`. All traits respect the same field configuration,
82/// ensuring consistent behavior.
83///
84/// # Struct-Level Attributes
85///
86/// - `#[ord(reverse)]` — Reverses the final comparison result
87/// - `#[ord(by = [field1(asc), field2(desc)])]` — Explicit field comparison order
88/// - `#[ord(skip_partial_eq)]` — Don't generate `PartialEq` (disables all other traits too)
89/// - `#[ord(skip_eq)]` — Don't generate `Eq` (also disables `Ord`)
90/// - `#[ord(skip_partial_ord)]` — Don't generate `PartialOrd` (also disables `Ord`)
91/// - `#[ord(skip_ord)]` — Don't generate `Ord`
92///
93/// # Field-Level Attributes
94///
95/// - `#[ord(skip)]` — Exclude this field from all comparisons (both eq and ord)
96/// - `#[ord(order = "asc")]` or `#[ord(order = "desc")]` — Sort direction
97/// - `#[ord(priority = N)]` — Comparison priority (lower = compared first)
98/// - `#[ord(compare_with = "path::to::fn")]` — Custom comparison function for ordering
99/// (signature: `fn(&T, &T) -> Ordering`)
100/// - `#[ord(eq_with = "path::to::fn")]` — Custom equality function
101/// (signature: `fn(&T, &T) -> bool`)
102/// - `#[ord(none_order = "first")]` or `#[ord(none_order = "last")]` — Option handling
103///
104/// # Variant-Level Attributes (Enums)
105///
106/// - `#[ord(rank = N)]` — Explicit variant ranking (lower = less than)
107///
108/// # Custom Comparison Functions
109///
110/// When using `compare_with`, you can optionally provide `eq_with` for a custom
111/// equality check. If `eq_with` is not provided, equality is derived from the
112/// comparison function (equal when `compare_with` returns `Ordering::Equal`).
113///
114/// ```rust
115/// use partial_cmp_derive::PartialCmp;
116/// use std::cmp::Ordering;
117///
118/// fn cmp_abs(a: &i32, b: &i32) -> Ordering {
119/// a.abs().cmp(&b.abs())
120/// }
121///
122/// fn eq_abs(a: &i32, b: &i32) -> bool {
123/// a.abs() == b.abs()
124/// }
125///
126/// #[derive(PartialCmp)]
127/// struct AbsValue {
128/// #[ord(compare_with = "cmp_abs", eq_with = "eq_abs")]
129/// value: i32,
130/// }
131/// ```
132///
133/// # Example
134///
135/// ```rust
136/// use partial_cmp_derive::PartialCmp;
137///
138/// #[derive(Debug, PartialCmp)]
139/// #[ord(by = [score(desc), name(asc)])]
140/// struct Player {
141/// id: u64, // Not compared (not in `by` list)
142/// name: String,
143/// score: u32,
144/// }
145/// ```
146#[proc_macro_derive(PartialCmp, attributes(ord))]
147pub fn partial_cmp_derive(input: TokenStream) -> TokenStream {
148 let input = parse_macro_input!(input as DeriveInput);
149
150 let ord_derive = match OrdDerive::from_derive_input(&input) {
151 Ok(v) => v,
152 Err(e) => return e.write_errors().into(),
153 };
154
155 expand_derive(&ord_derive).into()
156}