praxis_stdlib/capability.rs
1//! The capability vocabulary (§5.4): the payload-free names of the structural
2//! properties the compiler decides about a type.
3//!
4//! This is deliberately the *names* and nothing else. A capability that carries
5//! a type — `Iterable(T, Item)`, `HasMethod(name, params, result)` — lives in
6//! `praxis_typeck::constraint`, which can name a `Type`; this crate cannot, and
7//! should not, because the method catalog's type *patterns* are written here and
8//! a pattern is not a type.
9//!
10//! **§5.4 forbids surfacing any of these names to the user.** A diagnostic says
11//! what the program did and why it cannot work — "a `Vec` can change after it is
12//! stored, so it cannot be found again as a key" — never "does not satisfy
13//! `HashStable`", never "capability", never "trait". The wording lives in
14//! `praxis_hir::diagnostics`.
15
16/// One structural property a type either has or does not (§5.4, §5.5).
17///
18/// The five are not independent; the two hash-shaped ones are a pair, see
19/// [`CapKind::HashStable`].
20#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
21pub enum CapKind {
22 /// Comparable with `==` / `!=` (§5.5). Scalars and `Unit` are; a composite
23 /// is iff every component is; functions never are.
24 Eq,
25 /// Has a total order — usable with `<`/`>`, in a heap, or as a sort key
26 /// (§5.4 `SupportsOrd`, ADR-045). The orderable types are exactly the
27 /// scalars whose descriptors carry a `compare` callback.
28 Ord,
29 /// Hashable: the runtime can compute a structural hash of the value. This
30 /// is [`CapKind::Eq`]'s companion — the descriptor's `hash` and `equals`
31 /// callbacks are defined together — and on its own it is **not** enough to
32 /// be a key.
33 Hash,
34 /// Hashable **and immutable**, which is what a `Map` key or `Set` element
35 /// must be (D4).
36 ///
37 /// A `Vec` is hashable: the runtime can hash its current contents. It is
38 /// not *stably* hashable, because `key.push(2)` after `table.insert(key,
39 /// v)` moves the entry's hash without moving the entry, and the value can
40 /// no longer be found. Python rejects `list`/`dict`/`set` as keys for
41 /// exactly this reason; Rust permits `HashMap<Vec<i32>, V>` only because
42 /// the borrow checker makes mutating a held key impossible, and Praxis has
43 /// `var` mutation and no borrow checker.
44 ///
45 /// The rule is **mutability**, not container-ness: scalars, `Text`, tuples,
46 /// records and enums are stable *structurally* — a tuple is a key iff every
47 /// component is — and the eight mutable collections are not.
48 HashStable,
49 /// Numeric: admits `+`, `-`, `*`, `/`, unary minus, and the numeric sinks
50 /// (`sum`, `product`). `Int`, `UInt`, `Byte` and `Float` are; nothing else
51 /// is. `%` is narrower still and is not this capability.
52 Numeric,
53}
54
55impl CapKind {
56 /// Every capability, for exhaustive sweeps (agreement tests, tables).
57 pub const ALL: &'static [CapKind] = &[
58 CapKind::Eq,
59 CapKind::Ord,
60 CapKind::Hash,
61 CapKind::HashStable,
62 CapKind::Numeric,
63 ];
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use std::collections::HashSet;
70
71 /// `ALL` is what every exhaustive sweep iterates, so a variant missing from
72 /// it is a capability no agreement test ever checks. The match is what makes
73 /// the omission impossible to introduce silently.
74 #[test]
75 fn all_lists_every_capability() {
76 let listed: HashSet<CapKind> = CapKind::ALL.iter().copied().collect();
77 assert_eq!(listed.len(), CapKind::ALL.len(), "no duplicates in ALL");
78 for kind in CapKind::ALL {
79 // An exhaustive match: adding a variant without adding it to `ALL`
80 // fails to compile here rather than passing quietly.
81 let named = match kind {
82 CapKind::Eq => CapKind::Eq,
83 CapKind::Ord => CapKind::Ord,
84 CapKind::Hash => CapKind::Hash,
85 CapKind::HashStable => CapKind::HashStable,
86 CapKind::Numeric => CapKind::Numeric,
87 };
88 assert!(listed.contains(&named));
89 }
90 }
91}