Skip to main content

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}