partial_cmp_derive/lib.rs
1//! # partial-cmp-derive
2//!
3//! A procedural macro crate for deriving `PartialOrd` and `Ord` with fine-grained
4//! control over field comparison behavior.
5//!
6//! ## Features
7//!
8//! - **Skip fields**: Use `#[ord(skip)]` to exclude fields from comparison.
9//! When any field is skipped, only `PartialOrd` is implemented (not `Ord`),
10//! allowing skipped fields to contain types that don't implement `Ord` or `Eq` (like `f32`).
11//! - **Sort order**: Use `#[ord(order = "asc")]` or `#[ord(order = "desc")]` per field
12//! - **Explicit ordering**: Use `#[ord(by = [field1(desc), field2(asc)])]` at struct level
13//! - **Field priority**: Use `#[ord(priority = N)]` for implicit ordering (lower = first)
14//! - **Custom comparators**: Use `#[ord(compare_with = "path::to::fn")]`
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//! - **Skip Ord**: Use `#[ord(skip_ord)]` to only implement `PartialOrd` without `Ord`
19//!
20//! ## Example
21//!
22//! ```rust
23//! use partial_cmp_derive::PartialCmp;
24//!
25//! // When no fields are skipped, both PartialOrd and Ord are implemented
26//! #[derive(PartialEq, Eq, PartialCmp)]
27//! struct Point {
28//! x: i32,
29//! y: i32,
30//! }
31//!
32//! // When fields are skipped, only PartialOrd is implemented
33//! // This allows using types like f32 that don't implement Ord
34//! #[derive(Debug, PartialEq, PartialCmp)]
35//! struct Measurement {
36//! #[ord(skip)]
37//! raw_value: f32, // f32 doesn't implement Ord, but it's skipped
38//! timestamp: u64,
39//! }
40//! ```
41//!
42//! This generates `PartialOrd` (and `Ord` when no fields are skipped) implementations
43//! that compare fields in the specified order, ignoring skipped fields entirely.
44
45mod expand;
46mod types;
47
48use darling::FromDeriveInput;
49use proc_macro::TokenStream;
50use syn::{DeriveInput, parse_macro_input};
51
52use crate::expand::expand_derive;
53use crate::types::OrdDerive;
54
55/// Derives `PartialOrd` and `Ord` with customizable field comparison behavior.
56///
57/// # Struct-Level Attributes
58///
59/// - `#[ord(reverse)]` — Reverses the final comparison result
60/// - `#[ord(by = [field1(asc), field2(desc)])]` — Explicit field comparison order
61/// - `#[ord(skip_ord)]` — Only implement `PartialOrd`, not `Ord`
62///
63/// # Field-Level Attributes
64///
65/// - `#[ord(skip)]` — Exclude this field from comparison. **Note:** When any field
66/// is skipped, only `PartialOrd` is implemented (not `Ord`), allowing skipped
67/// fields to contain types that don't implement `Ord` or `Eq` (like `f32`).
68/// - `#[ord(order = "asc")]` or `#[ord(order = "desc")]` — Sort direction
69/// - `#[ord(priority = N)]` — Comparison priority (lower = compared first)
70/// - `#[ord(compare_with = "path::to::fn")]` — Custom comparison function
71/// - `#[ord(none_order = "first")]` or `#[ord(none_order = "last")]` — Option handling
72///
73/// # Variant-Level Attributes (Enums)
74///
75/// - `#[ord(rank = N)]` — Explicit variant ranking (lower = less than)
76///
77/// # Requirements
78///
79/// - The type must also derive or implement `PartialEq` (and `Eq` if `Ord` is generated)
80/// - All compared fields must implement `Ord` (or provide `compare_with`)
81/// - Skipped fields have no trait requirements
82///
83/// # Example
84///
85/// ```rust
86/// use partial_cmp_derive::PartialCmp;
87///
88/// #[derive(PartialEq, Eq, PartialCmp)]
89/// #[ord(by = [score(desc), name(asc)])]
90/// struct Player {
91/// id: u64, // Not compared (not in `by` list)
92/// name: String,
93/// score: u32,
94/// }
95/// ```
96#[proc_macro_derive(PartialCmp, attributes(ord))]
97pub fn partial_cmp_derive(input: TokenStream) -> TokenStream {
98 let input = parse_macro_input!(input as DeriveInput);
99
100 let ord_derive = match OrdDerive::from_derive_input(&input) {
101 Ok(v) => v,
102 Err(e) => return e.write_errors().into(),
103 };
104
105 match expand_derive(&ord_derive) {
106 Ok(tokens) => tokens.into(),
107 Err(e) => e.write_errors().into(),
108 }
109}