1use std::collections::BTreeSet;
23
24use indexmap::IndexSet;
25use serde::{Deserialize, Serialize};
26
27use crate::domain::RegexPattern;
28use crate::fingerprint::Fingerprint;
29use crate::value::{SelectionItem, Value};
30
31fn dedup_partial_eq<T: PartialEq>(xs: &mut Vec<T>) {
41 let mut i = 1;
42 while i < xs.len() {
43 if xs[i] == xs[i - 1] {
44 xs.remove(i);
45 } else {
46 i += 1;
47 }
48 }
49}
50
51const CANONICAL_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
64
65fn write_u32_le(out: &mut Vec<u8>, v: u32) {
66 out.extend_from_slice(&v.to_le_bytes());
67}
68
69fn write_i64_le(out: &mut Vec<u8>, v: i64) {
70 out.extend_from_slice(&v.to_le_bytes());
71}
72
73fn write_f64_le_canonical(out: &mut Vec<u8>, v: f64) {
74 let v = if v.is_nan() { f64::from_bits(CANONICAL_NAN_BITS) } else { v };
77 out.extend_from_slice(&v.to_le_bytes());
78}
79
80fn write_str_len_prefixed(out: &mut Vec<u8>, s: &str) {
81 let len = u32::try_from(s.len()).expect("string length fits in u32");
82 write_u32_le(out, len);
83 out.extend_from_slice(s.as_bytes());
84}
85
86fn write_i64_set(out: &mut Vec<u8>, values: &BTreeSet<i64>) {
87 write_u32_le(out, u32::try_from(values.len()).expect("set size fits in u32"));
88 for v in values {
89 write_i64_le(out, *v);
90 }
91}
92
93fn write_string_set(out: &mut Vec<u8>, values: &BTreeSet<String>) {
94 write_u32_le(out, u32::try_from(values.len()).expect("set size fits in u32"));
95 for v in values {
96 write_str_len_prefixed(out, v);
97 }
98}
99
100fn write_selection_item_set(out: &mut Vec<u8>, values: &BTreeSet<SelectionItem>) {
101 write_u32_le(out, u32::try_from(values.len()).expect("set size fits in u32"));
102 for v in values {
103 write_str_len_prefixed(out, v.as_str());
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
117#[serde(tag = "op", rename_all = "snake_case")]
118pub enum IntConstraint {
119 Always,
121 Never,
123 Min {
125 n: i64,
127 },
128 Max {
130 n: i64,
132 },
133 Range {
135 min: i64,
137 max: i64,
139 },
140 InSet {
142 values: BTreeSet<i64>,
144 },
145 NotInSet {
147 values: BTreeSet<i64>,
149 },
150 Multiple {
152 n: i64,
154 },
155 And {
157 children: Vec<Self>,
159 },
160 Or {
162 children: Vec<Self>,
164 },
165 Not {
167 child: Box<Self>,
169 },
170}
171
172impl IntConstraint {
173 #[must_use]
175 pub const fn multiple(n: i64) -> Option<Self> {
176 if n == 0 {
177 None
178 } else {
179 Some(Self::Multiple { n })
180 }
181 }
182
183 #[must_use]
185 pub fn test(&self, value: i64) -> bool {
186 match self {
187 Self::Always => true,
188 Self::Never => false,
189 Self::Min { n } => value >= *n,
190 Self::Max { n } => value <= *n,
191 Self::Range { min, max } => value >= *min && value <= *max,
192 Self::InSet { values } => values.contains(&value),
193 Self::NotInSet { values } => !values.contains(&value),
194 Self::Multiple { n } => *n != 0 && value % *n == 0,
195 Self::And { children } => children.iter().all(|c| c.test(value)),
196 Self::Or { children } => children.iter().any(|c| c.test(value)),
197 Self::Not { child } => !child.test(value),
198 }
199 }
200
201 #[must_use]
203 pub fn and(self, rhs: Self) -> Self {
204 let mut children = Vec::new();
205 absorb_and_int(&mut children, self);
206 absorb_and_int(&mut children, rhs);
207 Self::And { children }
208 }
209
210 #[must_use]
212 pub fn or(self, rhs: Self) -> Self {
213 let mut children = Vec::new();
214 absorb_or_int(&mut children, self);
215 absorb_or_int(&mut children, rhs);
216 Self::Or { children }
217 }
218
219 #[must_use]
223 pub fn canonicalize(self) -> Self {
224 match self {
225 Self::Not { child } => match child.canonicalize() {
226 Self::Not { child: inner } => *inner,
227 Self::Always => Self::Never,
228 Self::Never => Self::Always,
229 other => Self::Not {
230 child: Box::new(other),
231 },
232 },
233 Self::And { children } => {
234 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
235 for c in children {
236 match c.canonicalize() {
237 Self::Always => {}
238 Self::Never => return Self::Never,
239 Self::And { children: sub } => flat.extend(sub),
240 other => flat.push(other),
241 }
242 }
243 flat.sort_by_cached_key(Self::canonical_bytes);
244 dedup_partial_eq(&mut flat);
245 match flat.len() {
246 0 => Self::Always,
247 1 => flat.pop().expect("len == 1"),
248 _ => Self::And { children: flat },
249 }
250 }
251 Self::Or { children } => {
252 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
253 for c in children {
254 match c.canonicalize() {
255 Self::Never => {}
256 Self::Always => return Self::Always,
257 Self::Or { children: sub } => flat.extend(sub),
258 other => flat.push(other),
259 }
260 }
261 flat.sort_by_cached_key(Self::canonical_bytes);
262 dedup_partial_eq(&mut flat);
263 match flat.len() {
264 0 => Self::Never,
265 1 => flat.pop().expect("len == 1"),
266 _ => Self::Or { children: flat },
267 }
268 }
269 leaf => leaf,
270 }
271 }
272
273 #[must_use]
279 pub fn canonical_bytes(&self) -> Vec<u8> {
280 let mut out = Vec::new();
281 self.write_canonical(&mut out);
282 out
283 }
284
285 #[must_use]
288 pub fn fingerprint(&self) -> Fingerprint {
289 let canonical = self.clone().canonicalize();
290 Fingerprint::of(&canonical.canonical_bytes())
291 }
292
293 fn write_canonical(&self, out: &mut Vec<u8>) {
294 const T_ALWAYS: u8 = 0x10;
295 const T_NEVER: u8 = 0x11;
296 const T_MIN: u8 = 0x12;
297 const T_MAX: u8 = 0x13;
298 const T_RANGE: u8 = 0x14;
299 const T_INSET: u8 = 0x15;
300 const T_NOTINSET: u8 = 0x16;
301 const T_MULTIPLE: u8 = 0x17;
302 const T_AND: u8 = 0x18;
303 const T_OR: u8 = 0x19;
304 const T_NOT: u8 = 0x1A;
305
306 match self {
307 Self::Always => out.push(T_ALWAYS),
308 Self::Never => out.push(T_NEVER),
309 Self::Min { n } => {
310 out.push(T_MIN);
311 write_i64_le(out, *n);
312 }
313 Self::Max { n } => {
314 out.push(T_MAX);
315 write_i64_le(out, *n);
316 }
317 Self::Range { min, max } => {
318 out.push(T_RANGE);
319 write_i64_le(out, *min);
320 write_i64_le(out, *max);
321 }
322 Self::InSet { values } => {
323 out.push(T_INSET);
324 write_i64_set(out, values);
325 }
326 Self::NotInSet { values } => {
327 out.push(T_NOTINSET);
328 write_i64_set(out, values);
329 }
330 Self::Multiple { n } => {
331 out.push(T_MULTIPLE);
332 write_i64_le(out, *n);
333 }
334 Self::And { children } => {
335 out.push(T_AND);
336 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
337 for c in children {
338 c.write_canonical(out);
339 }
340 }
341 Self::Or { children } => {
342 out.push(T_OR);
343 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
344 for c in children {
345 c.write_canonical(out);
346 }
347 }
348 Self::Not { child } => {
349 out.push(T_NOT);
350 child.write_canonical(out);
351 }
352 }
353 }
354}
355
356impl std::ops::Not for IntConstraint {
357 type Output = Self;
358 fn not(self) -> Self {
359 Self::Not {
360 child: Box::new(self),
361 }
362 }
363}
364
365fn absorb_and_int(into: &mut Vec<IntConstraint>, c: IntConstraint) {
366 match c {
367 IntConstraint::And { children } => into.extend(children),
368 other => into.push(other),
369 }
370}
371
372fn absorb_or_int(into: &mut Vec<IntConstraint>, c: IntConstraint) {
373 match c {
374 IntConstraint::Or { children } => into.extend(children),
375 other => into.push(other),
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
388#[serde(tag = "op", rename_all = "snake_case")]
389pub enum DoubleConstraint {
390 Always,
392 Never,
394 Min {
396 n: f64,
398 },
399 Max {
401 n: f64,
403 },
404 Range {
406 min: f64,
408 max: f64,
410 },
411 And {
413 children: Vec<Self>,
415 },
416 Or {
418 children: Vec<Self>,
420 },
421 Not {
423 child: Box<Self>,
425 },
426}
427
428impl DoubleConstraint {
429 #[must_use]
431 pub fn test(&self, value: f64) -> bool {
432 if value.is_nan() {
433 return false;
434 }
435 match self {
436 Self::Always => true,
437 Self::Never => false,
438 Self::Min { n } => value >= *n,
439 Self::Max { n } => value <= *n,
440 Self::Range { min, max } => value >= *min && value <= *max,
441 Self::And { children } => children.iter().all(|c| c.test(value)),
442 Self::Or { children } => children.iter().any(|c| c.test(value)),
443 Self::Not { child } => !child.test(value),
444 }
445 }
446
447 #[must_use]
449 pub fn and(self, rhs: Self) -> Self {
450 let mut children = Vec::new();
451 absorb_and_double(&mut children, self);
452 absorb_and_double(&mut children, rhs);
453 Self::And { children }
454 }
455
456 #[must_use]
458 pub fn or(self, rhs: Self) -> Self {
459 let mut children = Vec::new();
460 absorb_or_double(&mut children, self);
461 absorb_or_double(&mut children, rhs);
462 Self::Or { children }
463 }
464
465 #[must_use]
467 pub fn canonicalize(self) -> Self {
468 match self {
469 Self::Not { child } => match child.canonicalize() {
470 Self::Not { child: inner } => *inner,
471 Self::Always => Self::Never,
472 Self::Never => Self::Always,
473 other => Self::Not {
474 child: Box::new(other),
475 },
476 },
477 Self::And { children } => {
478 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
479 for c in children {
480 match c.canonicalize() {
481 Self::Always => {}
482 Self::Never => return Self::Never,
483 Self::And { children: sub } => flat.extend(sub),
484 other => flat.push(other),
485 }
486 }
487 flat.sort_by_cached_key(Self::canonical_bytes);
488 dedup_partial_eq(&mut flat);
489 match flat.len() {
490 0 => Self::Always,
491 1 => flat.pop().expect("len == 1"),
492 _ => Self::And { children: flat },
493 }
494 }
495 Self::Or { children } => {
496 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
497 for c in children {
498 match c.canonicalize() {
499 Self::Never => {}
500 Self::Always => return Self::Always,
501 Self::Or { children: sub } => flat.extend(sub),
502 other => flat.push(other),
503 }
504 }
505 flat.sort_by_cached_key(Self::canonical_bytes);
506 dedup_partial_eq(&mut flat);
507 match flat.len() {
508 0 => Self::Never,
509 1 => flat.pop().expect("len == 1"),
510 _ => Self::Or { children: flat },
511 }
512 }
513 leaf => leaf,
514 }
515 }
516
517 #[must_use]
519 pub fn canonical_bytes(&self) -> Vec<u8> {
520 let mut out = Vec::new();
521 self.write_canonical(&mut out);
522 out
523 }
524
525 #[must_use]
528 pub fn fingerprint(&self) -> Fingerprint {
529 let canonical = self.clone().canonicalize();
530 Fingerprint::of(&canonical.canonical_bytes())
531 }
532
533 fn write_canonical(&self, out: &mut Vec<u8>) {
534 const T_ALWAYS: u8 = 0x20;
535 const T_NEVER: u8 = 0x21;
536 const T_MIN: u8 = 0x22;
537 const T_MAX: u8 = 0x23;
538 const T_RANGE: u8 = 0x24;
539 const T_AND: u8 = 0x25;
540 const T_OR: u8 = 0x26;
541 const T_NOT: u8 = 0x27;
542
543 match self {
544 Self::Always => out.push(T_ALWAYS),
545 Self::Never => out.push(T_NEVER),
546 Self::Min { n } => {
547 out.push(T_MIN);
548 write_f64_le_canonical(out, *n);
549 }
550 Self::Max { n } => {
551 out.push(T_MAX);
552 write_f64_le_canonical(out, *n);
553 }
554 Self::Range { min, max } => {
555 out.push(T_RANGE);
556 write_f64_le_canonical(out, *min);
557 write_f64_le_canonical(out, *max);
558 }
559 Self::And { children } => {
560 out.push(T_AND);
561 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
562 for c in children {
563 c.write_canonical(out);
564 }
565 }
566 Self::Or { children } => {
567 out.push(T_OR);
568 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
569 for c in children {
570 c.write_canonical(out);
571 }
572 }
573 Self::Not { child } => {
574 out.push(T_NOT);
575 child.write_canonical(out);
576 }
577 }
578 }
579}
580
581impl std::ops::Not for DoubleConstraint {
582 type Output = Self;
583 fn not(self) -> Self {
584 Self::Not {
585 child: Box::new(self),
586 }
587 }
588}
589
590fn absorb_and_double(into: &mut Vec<DoubleConstraint>, c: DoubleConstraint) {
591 match c {
592 DoubleConstraint::And { children } => into.extend(children),
593 other => into.push(other),
594 }
595}
596
597fn absorb_or_double(into: &mut Vec<DoubleConstraint>, c: DoubleConstraint) {
598 match c {
599 DoubleConstraint::Or { children } => into.extend(children),
600 other => into.push(other),
601 }
602}
603
604#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
614#[serde(tag = "op", rename_all = "snake_case")]
615pub enum BoolConstraint {
616 Always,
618 Never,
620 EqTo {
622 b: bool,
624 },
625 Not {
627 child: Box<Self>,
629 },
630}
631
632impl BoolConstraint {
633 #[must_use]
635 pub fn test(&self, value: bool) -> bool {
636 match self {
637 Self::Always => true,
638 Self::Never => false,
639 Self::EqTo { b } => value == *b,
640 Self::Not { child } => !child.test(value),
641 }
642 }
643
644 #[must_use]
646 pub fn canonicalize(self) -> Self {
647 match self {
648 Self::Not { child } => match child.canonicalize() {
649 Self::Not { child: inner } => *inner,
650 Self::Always => Self::Never,
651 Self::Never => Self::Always,
652 Self::EqTo { b } => Self::EqTo { b: !b },
653 },
654 other => other,
655 }
656 }
657
658 #[must_use]
660 pub fn canonical_bytes(&self) -> Vec<u8> {
661 let mut out = Vec::new();
662 self.write_canonical(&mut out);
663 out
664 }
665
666 #[must_use]
668 pub fn fingerprint(&self) -> Fingerprint {
669 let canonical = self.clone().canonicalize();
670 Fingerprint::of(&canonical.canonical_bytes())
671 }
672
673 fn write_canonical(&self, out: &mut Vec<u8>) {
674 const T_ALWAYS: u8 = 0x30;
675 const T_NEVER: u8 = 0x31;
676 const T_EQTO: u8 = 0x32;
677 const T_NOT: u8 = 0x33;
678
679 match self {
680 Self::Always => out.push(T_ALWAYS),
681 Self::Never => out.push(T_NEVER),
682 Self::EqTo { b } => {
683 out.push(T_EQTO);
684 out.push(u8::from(*b));
685 }
686 Self::Not { child } => {
687 out.push(T_NOT);
688 child.write_canonical(out);
689 }
690 }
691 }
692}
693
694impl std::ops::Not for BoolConstraint {
695 type Output = Self;
696 fn not(self) -> Self {
697 Self::Not {
698 child: Box::new(self),
699 }
700 }
701}
702
703#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
709#[serde(tag = "op", rename_all = "snake_case")]
710pub enum StringConstraint {
711 Always,
713 Never,
715 Regex {
717 pattern: RegexPattern,
719 },
720 LengthRange {
722 min: u32,
724 max: u32,
726 },
727 NonEmpty,
729 InSet {
731 values: BTreeSet<String>,
733 },
734 NotInSet {
736 values: BTreeSet<String>,
738 },
739 And {
741 children: Vec<Self>,
743 },
744 Or {
746 children: Vec<Self>,
748 },
749 Not {
751 child: Box<Self>,
753 },
754}
755
756impl StringConstraint {
757 #[must_use]
759 pub fn test(&self, value: &str) -> bool {
760 match self {
761 Self::Always => true,
762 Self::Never => false,
763 Self::Regex { pattern } => pattern.is_match(value),
764 Self::LengthRange { min, max } => {
765 let len = u32::try_from(value.len()).unwrap_or(u32::MAX);
766 len >= *min && len <= *max
767 }
768 Self::NonEmpty => !value.is_empty(),
769 Self::InSet { values } => values.contains(value),
770 Self::NotInSet { values } => !values.contains(value),
771 Self::And { children } => children.iter().all(|c| c.test(value)),
772 Self::Or { children } => children.iter().any(|c| c.test(value)),
773 Self::Not { child } => !child.test(value),
774 }
775 }
776
777 #[must_use]
779 pub fn and(self, rhs: Self) -> Self {
780 let mut children = Vec::new();
781 absorb_and_string(&mut children, self);
782 absorb_and_string(&mut children, rhs);
783 Self::And { children }
784 }
785
786 #[must_use]
788 pub fn or(self, rhs: Self) -> Self {
789 let mut children = Vec::new();
790 absorb_or_string(&mut children, self);
791 absorb_or_string(&mut children, rhs);
792 Self::Or { children }
793 }
794
795 #[must_use]
797 pub fn canonicalize(self) -> Self {
798 match self {
799 Self::Not { child } => match child.canonicalize() {
800 Self::Not { child: inner } => *inner,
801 Self::Always => Self::Never,
802 Self::Never => Self::Always,
803 other => Self::Not {
804 child: Box::new(other),
805 },
806 },
807 Self::And { children } => {
808 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
809 for c in children {
810 match c.canonicalize() {
811 Self::Always => {}
812 Self::Never => return Self::Never,
813 Self::And { children: sub } => flat.extend(sub),
814 other => flat.push(other),
815 }
816 }
817 flat.sort_by_cached_key(Self::canonical_bytes);
818 dedup_partial_eq(&mut flat);
819 match flat.len() {
820 0 => Self::Always,
821 1 => flat.pop().expect("len == 1"),
822 _ => Self::And { children: flat },
823 }
824 }
825 Self::Or { children } => {
826 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
827 for c in children {
828 match c.canonicalize() {
829 Self::Never => {}
830 Self::Always => return Self::Always,
831 Self::Or { children: sub } => flat.extend(sub),
832 other => flat.push(other),
833 }
834 }
835 flat.sort_by_cached_key(Self::canonical_bytes);
836 dedup_partial_eq(&mut flat);
837 match flat.len() {
838 0 => Self::Never,
839 1 => flat.pop().expect("len == 1"),
840 _ => Self::Or { children: flat },
841 }
842 }
843 leaf => leaf,
844 }
845 }
846
847 #[must_use]
849 pub fn canonical_bytes(&self) -> Vec<u8> {
850 let mut out = Vec::new();
851 self.write_canonical(&mut out);
852 out
853 }
854
855 #[must_use]
858 pub fn fingerprint(&self) -> Fingerprint {
859 let canonical = self.clone().canonicalize();
860 Fingerprint::of(&canonical.canonical_bytes())
861 }
862
863 fn write_canonical(&self, out: &mut Vec<u8>) {
864 const T_ALWAYS: u8 = 0x40;
865 const T_NEVER: u8 = 0x41;
866 const T_REGEX: u8 = 0x42;
867 const T_LENGTHRANGE: u8 = 0x43;
868 const T_NONEMPTY: u8 = 0x44;
869 const T_INSET: u8 = 0x45;
870 const T_NOTINSET: u8 = 0x46;
871 const T_AND: u8 = 0x47;
872 const T_OR: u8 = 0x48;
873 const T_NOT: u8 = 0x49;
874
875 match self {
876 Self::Always => out.push(T_ALWAYS),
877 Self::Never => out.push(T_NEVER),
878 Self::Regex { pattern } => {
879 out.push(T_REGEX);
880 write_str_len_prefixed(out, pattern.as_str());
881 }
882 Self::LengthRange { min, max } => {
883 out.push(T_LENGTHRANGE);
884 write_u32_le(out, *min);
885 write_u32_le(out, *max);
886 }
887 Self::NonEmpty => out.push(T_NONEMPTY),
888 Self::InSet { values } => {
889 out.push(T_INSET);
890 write_string_set(out, values);
891 }
892 Self::NotInSet { values } => {
893 out.push(T_NOTINSET);
894 write_string_set(out, values);
895 }
896 Self::And { children } => {
897 out.push(T_AND);
898 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
899 for c in children {
900 c.write_canonical(out);
901 }
902 }
903 Self::Or { children } => {
904 out.push(T_OR);
905 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
906 for c in children {
907 c.write_canonical(out);
908 }
909 }
910 Self::Not { child } => {
911 out.push(T_NOT);
912 child.write_canonical(out);
913 }
914 }
915 }
916}
917
918impl std::ops::Not for StringConstraint {
919 type Output = Self;
920 fn not(self) -> Self {
921 Self::Not {
922 child: Box::new(self),
923 }
924 }
925}
926
927fn absorb_and_string(into: &mut Vec<StringConstraint>, c: StringConstraint) {
928 match c {
929 StringConstraint::And { children } => into.extend(children),
930 other => into.push(other),
931 }
932}
933
934fn absorb_or_string(into: &mut Vec<StringConstraint>, c: StringConstraint) {
935 match c {
936 StringConstraint::Or { children } => into.extend(children),
937 other => into.push(other),
938 }
939}
940
941#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
947#[serde(tag = "op", rename_all = "snake_case")]
948pub enum SelectionConstraint {
949 Always,
951 Never,
953 RequireAll {
955 items: BTreeSet<SelectionItem>,
957 },
958 RequireAny {
960 items: BTreeSet<SelectionItem>,
962 },
963 ForbidAll {
965 items: BTreeSet<SelectionItem>,
967 },
968 MinSize {
970 n: u32,
972 },
973 MaxSize {
975 n: u32,
977 },
978 And {
980 children: Vec<Self>,
982 },
983 Or {
985 children: Vec<Self>,
987 },
988 Not {
990 child: Box<Self>,
992 },
993}
994
995impl SelectionConstraint {
996 #[must_use]
998 pub fn test(&self, selection: &IndexSet<SelectionItem>) -> bool {
999 match self {
1000 Self::Always => true,
1001 Self::Never => false,
1002 Self::RequireAll { items } => items.iter().all(|i| selection.contains(i)),
1003 Self::RequireAny { items } => items.iter().any(|i| selection.contains(i)),
1004 Self::ForbidAll { items } => !items.iter().any(|i| selection.contains(i)),
1005 Self::MinSize { n } => selection.len() >= *n as usize,
1006 Self::MaxSize { n } => selection.len() <= *n as usize,
1007 Self::And { children } => children.iter().all(|c| c.test(selection)),
1008 Self::Or { children } => children.iter().any(|c| c.test(selection)),
1009 Self::Not { child } => !child.test(selection),
1010 }
1011 }
1012
1013 #[must_use]
1015 pub fn and(self, rhs: Self) -> Self {
1016 let mut children = Vec::new();
1017 absorb_and_selection(&mut children, self);
1018 absorb_and_selection(&mut children, rhs);
1019 Self::And { children }
1020 }
1021
1022 #[must_use]
1024 pub fn or(self, rhs: Self) -> Self {
1025 let mut children = Vec::new();
1026 absorb_or_selection(&mut children, self);
1027 absorb_or_selection(&mut children, rhs);
1028 Self::Or { children }
1029 }
1030
1031 #[must_use]
1033 pub fn canonicalize(self) -> Self {
1034 match self {
1035 Self::Not { child } => match child.canonicalize() {
1036 Self::Not { child: inner } => *inner,
1037 Self::Always => Self::Never,
1038 Self::Never => Self::Always,
1039 other => Self::Not {
1040 child: Box::new(other),
1041 },
1042 },
1043 Self::And { children } => {
1044 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
1045 for c in children {
1046 match c.canonicalize() {
1047 Self::Always => {}
1048 Self::Never => return Self::Never,
1049 Self::And { children: sub } => flat.extend(sub),
1050 other => flat.push(other),
1051 }
1052 }
1053 flat.sort_by_cached_key(Self::canonical_bytes);
1054 dedup_partial_eq(&mut flat);
1055 match flat.len() {
1056 0 => Self::Always,
1057 1 => flat.pop().expect("len == 1"),
1058 _ => Self::And { children: flat },
1059 }
1060 }
1061 Self::Or { children } => {
1062 let mut flat: Vec<Self> = Vec::with_capacity(children.len());
1063 for c in children {
1064 match c.canonicalize() {
1065 Self::Never => {}
1066 Self::Always => return Self::Always,
1067 Self::Or { children: sub } => flat.extend(sub),
1068 other => flat.push(other),
1069 }
1070 }
1071 flat.sort_by_cached_key(Self::canonical_bytes);
1072 dedup_partial_eq(&mut flat);
1073 match flat.len() {
1074 0 => Self::Never,
1075 1 => flat.pop().expect("len == 1"),
1076 _ => Self::Or { children: flat },
1077 }
1078 }
1079 leaf => leaf,
1080 }
1081 }
1082
1083 #[must_use]
1085 pub fn canonical_bytes(&self) -> Vec<u8> {
1086 let mut out = Vec::new();
1087 self.write_canonical(&mut out);
1088 out
1089 }
1090
1091 #[must_use]
1093 pub fn fingerprint(&self) -> Fingerprint {
1094 let canonical = self.clone().canonicalize();
1095 Fingerprint::of(&canonical.canonical_bytes())
1096 }
1097
1098 fn write_canonical(&self, out: &mut Vec<u8>) {
1099 const T_ALWAYS: u8 = 0x50;
1100 const T_NEVER: u8 = 0x51;
1101 const T_REQUIREALL: u8 = 0x52;
1102 const T_REQUIREANY: u8 = 0x53;
1103 const T_FORBIDALL: u8 = 0x54;
1104 const T_MINSIZE: u8 = 0x55;
1105 const T_MAXSIZE: u8 = 0x56;
1106 const T_AND: u8 = 0x57;
1107 const T_OR: u8 = 0x58;
1108 const T_NOT: u8 = 0x59;
1109
1110 match self {
1111 Self::Always => out.push(T_ALWAYS),
1112 Self::Never => out.push(T_NEVER),
1113 Self::RequireAll { items } => {
1114 out.push(T_REQUIREALL);
1115 write_selection_item_set(out, items);
1116 }
1117 Self::RequireAny { items } => {
1118 out.push(T_REQUIREANY);
1119 write_selection_item_set(out, items);
1120 }
1121 Self::ForbidAll { items } => {
1122 out.push(T_FORBIDALL);
1123 write_selection_item_set(out, items);
1124 }
1125 Self::MinSize { n } => {
1126 out.push(T_MINSIZE);
1127 write_u32_le(out, *n);
1128 }
1129 Self::MaxSize { n } => {
1130 out.push(T_MAXSIZE);
1131 write_u32_le(out, *n);
1132 }
1133 Self::And { children } => {
1134 out.push(T_AND);
1135 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
1136 for c in children {
1137 c.write_canonical(out);
1138 }
1139 }
1140 Self::Or { children } => {
1141 out.push(T_OR);
1142 write_u32_le(out, u32::try_from(children.len()).expect("fits in u32"));
1143 for c in children {
1144 c.write_canonical(out);
1145 }
1146 }
1147 Self::Not { child } => {
1148 out.push(T_NOT);
1149 child.write_canonical(out);
1150 }
1151 }
1152 }
1153}
1154
1155impl std::ops::Not for SelectionConstraint {
1156 type Output = Self;
1157 fn not(self) -> Self {
1158 Self::Not {
1159 child: Box::new(self),
1160 }
1161 }
1162}
1163
1164fn absorb_and_selection(into: &mut Vec<SelectionConstraint>, c: SelectionConstraint) {
1165 match c {
1166 SelectionConstraint::And { children } => into.extend(children),
1167 other => into.push(other),
1168 }
1169}
1170
1171fn absorb_or_selection(into: &mut Vec<SelectionConstraint>, c: SelectionConstraint) {
1172 match c {
1173 SelectionConstraint::Or { children } => into.extend(children),
1174 other => into.push(other),
1175 }
1176}
1177
1178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1184#[serde(tag = "kind", rename_all = "snake_case")]
1185pub enum Constraint {
1186 Integer(IntConstraint),
1188 Double(DoubleConstraint),
1190 Boolean(BoolConstraint),
1192 String(StringConstraint),
1194 Selection(SelectionConstraint),
1196}
1197
1198impl Constraint {
1199 #[must_use]
1202 pub fn test(&self, value: &Value) -> bool {
1203 match (self, value) {
1204 (Self::Integer(c), Value::Integer(v)) => c.test(v.value),
1205 (Self::Double(c), Value::Double(v)) => c.test(v.value),
1206 (Self::Boolean(c), Value::Boolean(v)) => c.test(v.value),
1207 (Self::String(c), Value::String(v)) => c.test(&v.value),
1208 (Self::Selection(c), Value::Selection(v)) => c.test(&v.items),
1209 _ => false,
1210 }
1211 }
1212
1213 #[must_use]
1215 pub fn canonicalize(self) -> Self {
1216 match self {
1217 Self::Integer(c) => Self::Integer(c.canonicalize()),
1218 Self::Double(c) => Self::Double(c.canonicalize()),
1219 Self::Boolean(c) => Self::Boolean(c.canonicalize()),
1220 Self::String(c) => Self::String(c.canonicalize()),
1221 Self::Selection(c) => Self::Selection(c.canonicalize()),
1222 }
1223 }
1224
1225 #[must_use]
1229 pub fn canonical_bytes(&self) -> Vec<u8> {
1230 match self {
1231 Self::Integer(c) => c.canonical_bytes(),
1232 Self::Double(c) => c.canonical_bytes(),
1233 Self::Boolean(c) => c.canonical_bytes(),
1234 Self::String(c) => c.canonical_bytes(),
1235 Self::Selection(c) => c.canonical_bytes(),
1236 }
1237 }
1238
1239 #[must_use]
1242 pub fn fingerprint(&self) -> Fingerprint {
1243 match self {
1244 Self::Integer(c) => c.fingerprint(),
1245 Self::Double(c) => c.fingerprint(),
1246 Self::Boolean(c) => c.fingerprint(),
1247 Self::String(c) => c.fingerprint(),
1248 Self::Selection(c) => c.fingerprint(),
1249 }
1250 }
1251}
1252
1253#[cfg(test)]
1258mod tests {
1259 use std::ops::Not;
1260
1261 use super::*;
1262 use crate::names::ParameterName;
1263
1264 fn pname(s: &str) -> ParameterName {
1265 ParameterName::new(s).unwrap()
1266 }
1267
1268 #[test]
1271 fn int_min_and_max_test() {
1272 assert!(IntConstraint::Min { n: 5 }.test(5));
1273 assert!(IntConstraint::Min { n: 5 }.test(100));
1274 assert!(!IntConstraint::Min { n: 5 }.test(4));
1275 assert!(IntConstraint::Max { n: 5 }.test(5));
1276 assert!(IntConstraint::Max { n: 5 }.test(-1));
1277 assert!(!IntConstraint::Max { n: 5 }.test(6));
1278 }
1279
1280 #[test]
1281 fn int_range_and_multiple_test() {
1282 let c = IntConstraint::Range { min: 1, max: 10 };
1283 assert!(c.test(1));
1284 assert!(c.test(10));
1285 assert!(!c.test(11));
1286 let m = IntConstraint::multiple(3).unwrap();
1287 assert!(m.test(9));
1288 assert!(!m.test(10));
1289 assert!(!IntConstraint::Multiple { n: 0 }.test(0));
1291 }
1292
1293 #[test]
1294 fn int_inset_notinset_test() {
1295 let s: BTreeSet<i64> = [1, 2, 3].into_iter().collect();
1296 let in_set = IntConstraint::InSet { values: s.clone() };
1297 let not_in = IntConstraint::NotInSet { values: s };
1298 assert!(in_set.test(2));
1299 assert!(!in_set.test(4));
1300 assert!(not_in.test(4));
1301 assert!(!not_in.test(2));
1302 }
1303
1304 #[test]
1305 fn int_and_or_not_flatten_and_test() {
1306 let c = IntConstraint::Min { n: 0 }
1307 .and(IntConstraint::Max { n: 10 })
1308 .and(IntConstraint::multiple(2).unwrap());
1309 match &c {
1311 IntConstraint::And { children } => assert_eq!(children.len(), 3),
1312 other => panic!("expected And, got {other:?}"),
1313 }
1314 assert!(c.test(4));
1315 assert!(!c.test(5));
1316 assert!(!c.test(-1));
1317
1318 let n = IntConstraint::Min { n: 0 }.not();
1319 assert!(n.test(-1));
1320 assert!(!n.test(0));
1321 }
1322
1323 #[test]
1324 fn int_canonicalize_collapses_identities() {
1325 let c = IntConstraint::Always
1327 .and(IntConstraint::Min { n: 0 })
1328 .canonicalize();
1329 assert_eq!(c, IntConstraint::Min { n: 0 });
1330
1331 let c = IntConstraint::Never
1333 .and(IntConstraint::Min { n: 0 })
1334 .canonicalize();
1335 assert_eq!(c, IntConstraint::Never);
1336
1337 let c = IntConstraint::Always
1339 .or(IntConstraint::Min { n: 0 })
1340 .canonicalize();
1341 assert_eq!(c, IntConstraint::Always);
1342
1343 let c = IntConstraint::Never
1345 .or(IntConstraint::Min { n: 0 })
1346 .canonicalize();
1347 assert_eq!(c, IntConstraint::Min { n: 0 });
1348 }
1349
1350 #[test]
1351 fn int_canonicalize_flattens_nested() {
1352 let c = IntConstraint::And {
1353 children: vec![
1354 IntConstraint::Min { n: 0 },
1355 IntConstraint::And {
1356 children: vec![
1357 IntConstraint::Max { n: 10 },
1358 IntConstraint::multiple(2).unwrap(),
1359 ],
1360 },
1361 ],
1362 }
1363 .canonicalize();
1364 match c {
1365 IntConstraint::And { children } => assert_eq!(children.len(), 3),
1366 other => panic!("expected And, got {other:?}"),
1367 }
1368 }
1369
1370 #[test]
1371 fn int_canonicalize_peels_double_negation() {
1372 let c = IntConstraint::Min { n: 0 }.not().not().canonicalize();
1373 assert_eq!(c, IntConstraint::Min { n: 0 });
1374 }
1375
1376 #[test]
1377 fn int_canonicalize_dedups_adjacent() {
1378 let c = IntConstraint::And {
1379 children: vec![
1380 IntConstraint::Min { n: 0 },
1381 IntConstraint::Min { n: 0 },
1382 IntConstraint::Max { n: 10 },
1383 ],
1384 }
1385 .canonicalize();
1386 match c {
1387 IntConstraint::And { children } => assert_eq!(children.len(), 2),
1388 other => panic!("expected And, got {other:?}"),
1389 }
1390 }
1391
1392 #[test]
1393 fn int_canonicalize_single_child_collapses() {
1394 let c = IntConstraint::And {
1395 children: vec![IntConstraint::Min { n: 0 }],
1396 }
1397 .canonicalize();
1398 assert_eq!(c, IntConstraint::Min { n: 0 });
1399
1400 let c = IntConstraint::Or {
1401 children: vec![],
1402 }
1403 .canonicalize();
1404 assert_eq!(c, IntConstraint::Never);
1405
1406 let c = IntConstraint::And {
1407 children: vec![],
1408 }
1409 .canonicalize();
1410 assert_eq!(c, IntConstraint::Always);
1411 }
1412
1413 #[test]
1414 fn int_canonicalize_not_folds_identities() {
1415 assert_eq!(
1416 IntConstraint::Always.not().canonicalize(),
1417 IntConstraint::Never
1418 );
1419 assert_eq!(
1420 IntConstraint::Never.not().canonicalize(),
1421 IntConstraint::Always
1422 );
1423 }
1424
1425 #[test]
1428 fn double_range_test_and_nan_safe() {
1429 let c = DoubleConstraint::Range {
1430 min: 0.0,
1431 max: 1.0,
1432 };
1433 assert!(c.test(0.5));
1434 assert!(!c.test(1.5));
1435 assert!(!c.test(f64::NAN)); }
1437
1438 #[test]
1439 fn double_canonicalize_double_negation() {
1440 let c = DoubleConstraint::Min { n: 0.0 }
1441 .not()
1442 .not()
1443 .canonicalize();
1444 assert_eq!(c, DoubleConstraint::Min { n: 0.0 });
1445 }
1446
1447 #[test]
1450 fn bool_eq_to_test() {
1451 assert!(BoolConstraint::EqTo { b: true }.test(true));
1452 assert!(!BoolConstraint::EqTo { b: true }.test(false));
1453 }
1454
1455 #[test]
1456 fn bool_not_flips_eq_to_under_canonicalize() {
1457 let c = BoolConstraint::EqTo { b: true }.not().canonicalize();
1458 assert_eq!(c, BoolConstraint::EqTo { b: false });
1459 }
1460
1461 #[test]
1464 fn string_regex_test() {
1465 let c = StringConstraint::Regex {
1466 pattern: RegexPattern::new("^[a-z]+$").unwrap(),
1467 };
1468 assert!(c.test("abc"));
1469 assert!(!c.test("abc1"));
1470 }
1471
1472 #[test]
1473 fn string_length_range_and_nonempty_test() {
1474 let lr = StringConstraint::LengthRange { min: 1, max: 5 };
1475 assert!(lr.test("a"));
1476 assert!(lr.test("abcde"));
1477 assert!(!lr.test(""));
1478 assert!(!lr.test("abcdef"));
1479 assert!(StringConstraint::NonEmpty.test("x"));
1480 assert!(!StringConstraint::NonEmpty.test(""));
1481 }
1482
1483 #[test]
1484 fn string_inset_test() {
1485 let set: BTreeSet<String> = ["red".into(), "blue".into()].into_iter().collect();
1486 let c = StringConstraint::InSet { values: set };
1487 assert!(c.test("red"));
1488 assert!(!c.test("green"));
1489 }
1490
1491 #[test]
1492 fn string_and_or_not_combine() {
1493 let c = StringConstraint::NonEmpty
1494 .and(StringConstraint::LengthRange { min: 1, max: 3 });
1495 assert!(c.test("ab"));
1496 assert!(!c.test(""));
1497 assert!(!c.test("abcd"));
1498 }
1499
1500 fn sel(xs: &[&str]) -> IndexSet<SelectionItem> {
1503 xs.iter().map(|s| SelectionItem::new(*s).unwrap()).collect()
1504 }
1505
1506 fn sitems(xs: &[&str]) -> BTreeSet<SelectionItem> {
1507 xs.iter().map(|s| SelectionItem::new(*s).unwrap()).collect()
1508 }
1509
1510 #[test]
1511 fn selection_require_all_and_any() {
1512 let c = SelectionConstraint::RequireAll {
1513 items: sitems(&["a", "b"]),
1514 };
1515 assert!(c.test(&sel(&["a", "b", "c"])));
1516 assert!(!c.test(&sel(&["a", "c"])));
1517
1518 let c = SelectionConstraint::RequireAny {
1519 items: sitems(&["a", "b"]),
1520 };
1521 assert!(c.test(&sel(&["b"])));
1522 assert!(!c.test(&sel(&["x"])));
1523 }
1524
1525 #[test]
1526 fn selection_forbid_all_and_sizes() {
1527 let c = SelectionConstraint::ForbidAll {
1528 items: sitems(&["z"]),
1529 };
1530 assert!(c.test(&sel(&["a"])));
1531 assert!(!c.test(&sel(&["z"])));
1532
1533 let c = SelectionConstraint::MinSize { n: 2 };
1534 assert!(c.test(&sel(&["a", "b"])));
1535 assert!(!c.test(&sel(&["a"])));
1536
1537 let c = SelectionConstraint::MaxSize { n: 2 };
1538 assert!(c.test(&sel(&["a", "b"])));
1539 assert!(!c.test(&sel(&["a", "b", "c"])));
1540 }
1541
1542 #[test]
1545 fn outer_dispatches_by_kind() {
1546 let c = Constraint::Integer(IntConstraint::Min { n: 0 });
1547 let good = Value::integer(pname("n"), 5, None);
1548 let bad = Value::integer(pname("n"), -1, None);
1549 let wrong_kind = Value::boolean(pname("n"), true, None);
1550 assert!(c.test(&good));
1551 assert!(!c.test(&bad));
1552 assert!(!c.test(&wrong_kind));
1553 }
1554
1555 #[test]
1556 fn outer_canonicalize_delegates() {
1557 let c = Constraint::Integer(IntConstraint::Min { n: 0 }.not().not());
1558 let canonical = c.canonicalize();
1559 assert_eq!(
1560 canonical,
1561 Constraint::Integer(IntConstraint::Min { n: 0 })
1562 );
1563 }
1564
1565 #[test]
1568 fn int_constraint_serde_roundtrip() {
1569 let c = IntConstraint::Min { n: 0 }
1570 .and(IntConstraint::Max { n: 10 })
1571 .not();
1572 let json = serde_json::to_string(&c).unwrap();
1573 let back: IntConstraint = serde_json::from_str(&json).unwrap();
1574 assert_eq!(c, back);
1575 }
1576
1577 #[test]
1578 fn string_constraint_with_regex_serde_roundtrip() {
1579 let c = StringConstraint::Regex {
1580 pattern: RegexPattern::new("^foo$").unwrap(),
1581 };
1582 let json = serde_json::to_string(&c).unwrap();
1583 let back: StringConstraint = serde_json::from_str(&json).unwrap();
1584 assert_eq!(c, back);
1585 }
1586
1587 #[test]
1588 fn outer_constraint_serde_roundtrip() {
1589 let c = Constraint::Boolean(BoolConstraint::EqTo { b: true });
1590 let json = serde_json::to_string(&c).unwrap();
1591 let back: Constraint = serde_json::from_str(&json).unwrap();
1592 assert_eq!(c, back);
1593 }
1594
1595 #[test]
1598 fn int_canonical_bytes_deterministic() {
1599 let a = IntConstraint::Range { min: 1, max: 10 }.canonical_bytes();
1600 let b = IntConstraint::Range { min: 1, max: 10 }.canonical_bytes();
1601 assert_eq!(a, b);
1602 }
1603
1604 #[test]
1605 fn int_canonical_bytes_distinguish_variants() {
1606 let min = IntConstraint::Min { n: 5 }.canonical_bytes();
1607 let max = IntConstraint::Max { n: 5 }.canonical_bytes();
1608 assert_ne!(min, max);
1609 }
1610
1611 #[test]
1612 fn int_fingerprint_stable_under_child_reordering() {
1613 let ab = IntConstraint::Min { n: 0 }
1614 .and(IntConstraint::Max { n: 10 })
1615 .fingerprint();
1616 let ba = IntConstraint::Max { n: 10 }
1617 .and(IntConstraint::Min { n: 0 })
1618 .fingerprint();
1619 assert_eq!(ab, ba, "canonicalise should reorder And children");
1620 }
1621
1622 #[test]
1623 fn int_fingerprint_stable_under_nested_and_flattening() {
1624 let flat = IntConstraint::Min { n: 0 }
1625 .and(IntConstraint::Max { n: 10 })
1626 .and(IntConstraint::multiple(2).unwrap())
1627 .fingerprint();
1628 let nested = IntConstraint::Min { n: 0 }
1629 .and(IntConstraint::Max { n: 10 }.and(IntConstraint::multiple(2).unwrap()))
1630 .fingerprint();
1631 assert_eq!(flat, nested);
1632 }
1633
1634 #[test]
1635 fn int_fingerprint_stable_under_double_negation() {
1636 let a = IntConstraint::Min { n: 0 }.fingerprint();
1637 let b = IntConstraint::Min { n: 0 }.not().not().fingerprint();
1638 assert_eq!(a, b);
1639 }
1640
1641 #[test]
1642 fn int_fingerprint_distinguishes_different_constraints() {
1643 let a = IntConstraint::Min { n: 0 }.fingerprint();
1644 let b = IntConstraint::Min { n: 1 }.fingerprint();
1645 assert_ne!(a, b);
1646 }
1647
1648 #[test]
1649 fn double_fingerprint_nan_normalises() {
1650 let a = DoubleConstraint::Min { n: f64::NAN }.fingerprint();
1651 let b = DoubleConstraint::Min {
1652 n: f64::from_bits(f64::NAN.to_bits() ^ 1),
1653 }
1654 .fingerprint();
1655 assert_eq!(a, b);
1656 }
1657
1658 #[test]
1659 fn canonicalize_sorts_and_dedups_and_children() {
1660 let c = IntConstraint::And {
1661 children: vec![
1662 IntConstraint::Max { n: 10 },
1663 IntConstraint::Min { n: 0 },
1664 IntConstraint::Max { n: 10 }, ],
1666 }
1667 .canonicalize();
1668 let children = match c {
1669 IntConstraint::And { children } => children,
1670 other => panic!("expected And, got {other:?}"),
1671 };
1672 assert_eq!(children.len(), 2);
1673 match &children[0] {
1676 IntConstraint::Min { n: 0 } => {}
1677 other => panic!("expected Min {{ n: 0 }} first, got {other:?}"),
1678 }
1679 }
1680
1681 #[test]
1682 fn bool_fingerprint_double_negation_matches() {
1683 let a = BoolConstraint::EqTo { b: true }.fingerprint();
1684 let b = BoolConstraint::EqTo { b: true }.not().not().fingerprint();
1685 assert_eq!(a, b);
1686 }
1687
1688 #[test]
1689 fn string_fingerprint_regex_hashes_source() {
1690 let a = StringConstraint::Regex {
1691 pattern: RegexPattern::new("^abc$").unwrap(),
1692 }
1693 .fingerprint();
1694 let b = StringConstraint::Regex {
1695 pattern: RegexPattern::new("^abc$").unwrap(),
1696 }
1697 .fingerprint();
1698 assert_eq!(a, b);
1699 let c = StringConstraint::Regex {
1700 pattern: RegexPattern::new("^abd$").unwrap(),
1701 }
1702 .fingerprint();
1703 assert_ne!(a, c);
1704 }
1705
1706 #[test]
1707 fn selection_fingerprint_stable_under_and_reordering() {
1708 let req = SelectionConstraint::RequireAll {
1709 items: sitems(&["a", "b"]),
1710 };
1711 let max = SelectionConstraint::MaxSize { n: 3 };
1712 let ab = req.clone().and(max.clone()).fingerprint();
1713 let ba = max.and(req).fingerprint();
1714 assert_eq!(ab, ba);
1715 }
1716
1717 #[test]
1718 fn outer_constraint_fingerprint_delegates() {
1719 let int_c = IntConstraint::Min { n: 0 };
1720 let direct = int_c.fingerprint();
1721 let outer = Constraint::Integer(int_c).fingerprint();
1722 assert_eq!(direct, outer);
1723 }
1724
1725 #[test]
1726 fn outer_constraint_kinds_do_not_collide() {
1727 let i = Constraint::Integer(IntConstraint::Always).fingerprint();
1730 let d = Constraint::Double(DoubleConstraint::Always).fingerprint();
1731 let b = Constraint::Boolean(BoolConstraint::Always).fingerprint();
1732 let s = Constraint::String(StringConstraint::Always).fingerprint();
1733 let sel = Constraint::Selection(SelectionConstraint::Always).fingerprint();
1734 let all = [i, d, b, s, sel];
1735 for (a_i, a) in all.iter().enumerate() {
1736 for (b_i, b) in all.iter().enumerate() {
1737 if a_i != b_i {
1738 assert_ne!(a, b, "kinds {a_i} and {b_i} collided");
1739 }
1740 }
1741 }
1742 }
1743}