1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use darling::{
Error, FromDeriveInput, Result,
ast::Data,
util::{Flag, SpannedValue},
};
use syn::Ident;
use crate::types::{FieldOrderList, OrdField, OrdVariant, TraitConfig};
/// Top-level derive input parsed from the procedural macro attributes.
#[derive(Debug, Clone, FromDeriveInput)]
#[darling(attributes(ord), supports(struct_any, enum_any), and_then = Self::validate)]
pub struct OrdDerive {
/// The type identifier.
pub ident: Ident,
/// Generic parameters.
pub generics: syn::Generics,
/// The data (struct fields or enum variants).
pub data: Data<OrdVariant, OrdField>,
/// Reverse the entire comparison result.
#[darling(default)]
pub reverse: Flag,
/// Skip `PartialEq` implementation.
#[darling(default)]
pub skip_partial_eq: Flag,
/// Skip `Eq` implementation.
#[darling(default)]
pub skip_eq: Flag,
/// Skip `PartialOrd` implementation.
#[darling(default)]
pub skip_partial_ord: Flag,
/// Skip `Ord` implementation.
#[darling(default)]
pub skip_ord: Flag,
/// Skip `Hash` implementation.
#[darling(default)]
pub skip_hash: Flag,
/// Explicit field ordering via `#[ord(by = [field1(asc), field2(desc)])]`.
#[darling(default, rename = "by")]
pub field_order: Option<SpannedValue<FieldOrderList>>,
}
impl OrdDerive {
/// Returns the configuration for which traits to generate based on skip flags.
pub fn trait_config(&self) -> TraitConfig {
// Start with all traits enabled
let mut config = TraitConfig::all();
// Apply explicit skip flags and cascading constraints
if self.skip_partial_eq.is_present() {
config.partial_eq = false;
// Without PartialEq, we can't have Eq, PartialOrd, or Ord
config.eq = false;
config.partial_ord = false;
config.ord = false;
}
if self.skip_eq.is_present() {
config.eq = false;
// Without Eq, we can't have Ord or Hash
config.ord = false;
config.hash = false;
}
if self.skip_partial_ord.is_present() {
config.partial_ord = false;
// Without PartialOrd, we can't have Ord
config.ord = false;
}
if self.skip_ord.is_present() {
config.ord = false;
}
if self.skip_hash.is_present() {
config.hash = false;
}
config
}
/// Validates the derive input and returns accumulated errors.
fn validate(self) -> Result<Self> {
let mut errors = Error::accumulator();
match &self.data {
Data::Struct(fields) => {
self.validate_struct_fields(fields, &mut errors);
}
Data::Enum(variants) => {
self.validate_enum_variants(variants, &mut errors);
}
}
errors.finish_with(self)
}
fn validate_struct_fields(
&self,
fields: &darling::ast::Fields<OrdField>,
errors: &mut darling::error::Accumulator,
) {
// Check for conflicts between field_order and field-level attributes
if let Some(ref order_list) = self.field_order {
let ordered_names: std::collections::HashSet<_> =
order_list.0.iter().map(|e| e.ident.to_string()).collect();
for field in fields.iter() {
if let Some(ref ident) = field.ident {
let name = ident.to_string();
// Check for priority on fields when explicit ordering is used
if field.priority.is_some() && ordered_names.contains(&name) {
errors.push(
Error::custom(
"cannot use `priority` on fields when `by` is specified at struct level",
)
.with_span(ident),
);
}
// Check for order on fields when explicit ordering is used
if field.order.is_some() && ordered_names.contains(&name) {
errors.push(
Error::custom(
"cannot use `order` on fields listed in `by`; specify order in the `by` list instead",
)
.with_span(ident),
);
}
}
}
// Check that all fields in order_list exist
let field_names: std::collections::HashSet<_> =
fields.iter().filter_map(|f| f.ident.clone()).collect();
for entry in &order_list.0 {
if !field_names.contains(&entry.ident) {
errors.push(
Error::custom(format!("unknown field `{}`", entry.ident))
.with_span(&entry.ident),
);
}
}
}
// Check for conflicting skip and other attributes
for field in fields.iter() {
field.check_skipped(errors);
}
}
fn validate_enum_variants(
&self,
variants: &[OrdVariant],
errors: &mut darling::error::Accumulator,
) {
// Check for duplicate ranks
let mut ranks: std::collections::HashMap<i32, &Ident> = std::collections::HashMap::new();
for variant in variants {
if let Some(ref rank) = variant.rank {
let rank_value = **rank;
if let Some(existing) = ranks.get(&rank_value) {
errors.push(
Error::custom(format!(
"duplicate rank {rank_value} (also used by `{existing}`)"
))
.with_span(&variant.ident),
);
} else {
ranks.insert(rank_value, &variant.ident);
}
}
}
// Validate fields within each variant
for variant in variants {
for field in variant.fields.iter() {
field.check_skipped(errors);
}
}
// Check that field_order is not used with enums
if self.field_order.is_some() {
errors.push(
Error::custom("`by` attribute is not supported on enums").with_span(&self.ident),
);
}
}
}