Skip to main content

bynk_check/
kernel_methods.rs

1//! v0.30.2 (ADR 0063): the enumerable kernel-method registry.
2//!
3//! The value methods of the built-in kernels (`List`/`Map`/`Option`/`Result`/
4//! `String`/`Int`/`Float`) are dispatched by the checker in
5//! [`crate::checker`]'s `check_*_kernel_method` functions, where the method
6//! names live in `match` arms — authoritative for *typing*, but not
7//! enumerable. This module is the enumerable view the LSP reads for `.`-member
8//! completion: per-kernel `(name, signature)` tables and a [`methods_for`]
9//! mapping from a receiver [`Ty`] to its methods.
10//!
11//! The signatures are human-readable Bynk-surface display strings (generic in
12//! the element/key/value type), for completion `detail` — not the checker's
13//! `Ty`-typed signatures. A drift test (`kernel_registry_pins_dispatch`)
14//! drives every listed method through the real checker and asserts none is
15//! rejected as `method_not_found`, so the table can't list a phantom method.
16
17use crate::checker::{NamedKind, Ty, TyId, Types};
18use bynk_syntax::ast::BaseType;
19
20/// One built-in kernel method: its name and a display signature.
21#[derive(Debug, Clone, Copy)]
22pub struct KernelMethod {
23    pub name: &'static str,
24    pub signature: &'static str,
25}
26
27const fn m(name: &'static str, signature: &'static str) -> KernelMethod {
28    KernelMethod { name, signature }
29}
30
31/// `List[T]` (v0.20b; v0.88 adds the ADR 0116 query/collection vocabulary).
32pub const LIST_METHODS: &[KernelMethod] = &[
33    m("length", "length() -> Int"),
34    m("get", "get(index: Int) -> Option[T]"),
35    m("prepend", "prepend(item: T) -> List[T]"),
36    m("fold", "fold(init: U, step: (U, T) -> U) -> U"),
37    m(
38        "foldEff",
39        "foldEff(init: U, step: (U, T) -> Effect[U]) -> Effect[U]",
40    ),
41    // v0.146 (ADR 0170): run an effectful step for each element, in order.
42    m("forEach", "forEach(f: T -> Effect[()]) -> Effect[()]"),
43    // v0.147 (ADR 0171): run an effectful step for every element concurrently.
44    m(
45        "parTraverse",
46        "parTraverse(f: T -> Effect[()]) -> Effect[()]",
47    ),
48    // v0.148 (ADR 0172): collect every outcome (no short-circuit), sequential
49    // and concurrent.
50    m(
51        "traverseAll",
52        "traverseAll(f: T -> Effect[Result[U, E]]) -> Effect[List[Result[U, E]]]",
53    ),
54    m(
55        "parTraverseAll",
56        "parTraverseAll(f: T -> Effect[Result[U, E]]) -> Effect[List[Result[U, E]]]",
57    ),
58    // v0.150 (ADR 0174): short-circuit collect — stop at the first `Err`.
59    m(
60        "traverseTry",
61        "traverseTry(f: T -> Effect[Result[U, E]]) -> Effect[Result[List[U], E]]",
62    ),
63    m(
64        "parTraverseTry",
65        "parTraverseTry(f: T -> Effect[Result[U, E]]) -> Effect[Result[List[U], E]]",
66    ),
67    // v0.88 (ADR 0116): eager in-memory builders + terminals.
68    m("map", "map(f: T -> U) -> List[U]"),
69    m("filter", "filter(p: T -> Bool) -> List[T]"),
70    m("flatMap", "flatMap(f: T -> List[U]) -> List[U]"),
71    m("sortBy", "sortBy(key: T -> K) -> List[T]"),
72    m("take", "take(n: Int) -> List[T]"),
73    m("skip", "skip(n: Int) -> List[T]"),
74    m("distinct", "distinct() -> List[T]"),
75    m("distinctBy", "distinctBy(key: T -> K) -> List[T]"),
76    m("count", "count() -> Int"),
77    m("any", "any(p: T -> Bool) -> Bool"),
78    m("all", "all(p: T -> Bool) -> Bool"),
79    m("first", "first() -> Option[T]"),
80    m("firstOrElse", "firstOrElse(default: T) -> T"),
81    m("sum", "sum(key: T -> K) -> K"),
82    m("min", "min(key: T -> K) -> Option[K]"),
83    m("max", "max(key: T -> K) -> Option[K]"),
84    m("average", "average(key: T -> K) -> Option[Float]"),
85    // v0.94 (ADR 0116/0120): joins & grouping, combiner form (eager — mirrors
86    // QUERY_METHODS' lazy `Query[V]` forms below, but returns `List[V]`).
87    m(
88        "joinOn",
89        "joinOn(other: List[U], on: (T, U) -> Bool, into: (T, U) -> V) -> List[V]",
90    ),
91    m(
92        "leftJoin",
93        "leftJoin(other: List[U], on: (T, U) -> Bool, into: (T, Option[U]) -> V) -> List[V]",
94    ),
95    m(
96        "join",
97        "join(other: List[U], on: (T, U) -> Bool, into: (T, U) -> V) -> List[V]",
98    ),
99    m(
100        "groupBy",
101        "groupBy(key: T -> K, into: (K, List[T]) -> V) -> List[V]",
102    ),
103];
104
105/// `Map[K, V]` (v0.20b).
106pub const MAP_METHODS: &[KernelMethod] = &[
107    m("length", "length() -> Int"),
108    m("keys", "keys() -> List[K]"),
109    m("values", "values() -> List[V]"),
110    m("get", "get(key: K) -> Option[V]"),
111    m("insert", "insert(key: K, value: V) -> Map[K, V]"),
112];
113
114/// `Query[T]` (v0.91, ADR 0115; joins/grouping v0.94, ADR 0116/0120) — the lazy
115/// storage-query vocabulary a `store` collection lifts into (a `store Map`'s
116/// `.entries`/`.keys`/`.values`, a `store Log`'s time-window roots, or a bare
117/// `store Map` used as a value). Builders stay lazy (`Query[U]`); terminals
118/// fold into the agent's storage capability (`Effect[T]`). Mirrors
119/// [`LIST_METHODS`]'s eager vocabulary; dispatched in
120/// `crate::checker::kernels::check_query_kernel_method`, which this table pins
121/// (`kernel_registry_pins_dispatch`).
122pub const QUERY_METHODS: &[KernelMethod] = &[
123    m("map", "map(f: T -> U) -> Query[U]"),
124    m("filter", "filter(p: T -> Bool) -> Query[T]"),
125    m("flatMap", "flatMap(f: T -> Query[U]) -> Query[U]"),
126    m("sortBy", "sortBy(key: T -> K) -> Query[T]"),
127    m("take", "take(n: Int) -> Query[T]"),
128    m("skip", "skip(n: Int) -> Query[T]"),
129    m("distinct", "distinct() -> Query[T]"),
130    m("distinctBy", "distinctBy(key: T -> K) -> Query[T]"),
131    m(
132        "joinOn",
133        "joinOn(other: Query[U], on: (T, U) -> Bool, into: (T, U) -> V) -> Query[V]",
134    ),
135    m(
136        "leftJoin",
137        "leftJoin(other: Query[U], on: (T, U) -> Bool, into: (T, Option[U]) -> V) -> Query[V]",
138    ),
139    m(
140        "join",
141        "join(other: Query[U], on: (T, U) -> Bool, into: (T, U) -> V) -> Query[V]",
142    ),
143    m(
144        "groupBy",
145        "groupBy(key: T -> K, into: (K, Query[T]) -> V) -> Query[V]",
146    ),
147    m("collect", "collect() -> Effect[List[T]]"),
148    m("first", "first() -> Effect[Option[T]]"),
149    m("firstOrElse", "firstOrElse(default: T) -> Effect[T]"),
150    m("count", "count() -> Effect[Int]"),
151    m("fold", "fold(init: U, step: (U, T) -> U) -> Effect[U]"),
152    m("any", "any(p: T -> Bool) -> Effect[Bool]"),
153    m("all", "all(p: T -> Bool) -> Effect[Bool]"),
154    m("sum", "sum(key: T -> K) -> Effect[K]"),
155    m("min", "min(key: T -> K) -> Effect[Option[K]]"),
156    m("max", "max(key: T -> K) -> Effect[Option[K]]"),
157    m("average", "average(key: T -> K) -> Effect[Option[Float]]"),
158    m("forEach", "forEach(f: T -> Effect[()]) -> Effect[()]"),
159    m(
160        "parTraverse",
161        "parTraverse(f: T -> Effect[()]) -> Effect[()]",
162    ),
163    m(
164        "traverseAll",
165        "traverseAll(f: T -> Effect[Result[U, E]]) -> Effect[List[Result[U, E]]]",
166    ),
167    m(
168        "parTraverseAll",
169        "parTraverseAll(f: T -> Effect[Result[U, E]]) -> Effect[List[Result[U, E]]]",
170    ),
171    m(
172        "traverseTry",
173        "traverseTry(f: T -> Effect[Result[U, E]]) -> Effect[Result[List[U], E]]",
174    ),
175    m(
176        "parTraverseTry",
177        "parTraverseTry(f: T -> Effect[Result[U, E]]) -> Effect[Result[List[U], E]]",
178    ),
179];
180
181/// `Option[T]` combinators (v0.22a).
182pub const OPTION_METHODS: &[KernelMethod] = &[
183    m("map", "map(f: T -> U) -> Option[U]"),
184    m("andThen", "andThen(f: T -> Option[U]) -> Option[U]"),
185    m("getOrElse", "getOrElse(default: T) -> T"),
186    m("isSome", "isSome() -> Bool"),
187    m("okOr", "okOr(err: E) -> Result[T, E]"),
188];
189
190/// `Result[T, E]` combinators (v0.22a).
191pub const RESULT_METHODS: &[KernelMethod] = &[
192    m("map", "map(f: T -> U) -> Result[U, E]"),
193    m("andThen", "andThen(f: T -> Result[U, E]) -> Result[U, E]"),
194    m("mapErr", "mapErr(f: E -> F) -> Result[T, F]"),
195    m("getOrElse", "getOrElse(default: T) -> T"),
196    m("isOk", "isOk() -> Bool"),
197];
198/// The `Effect[Result[T, E]]` combinators (v0.152, ADR 0176 — design doc
199/// §2.8.3): the compiler-synthesised methods on the universal cross-context
200/// shape. `mapOk`/`mapErr` transform the success/error value; `flatMapOk`
201/// chains a further effectful-fallible step; `flatMapErr` attempts an effectful
202/// recovery.
203pub const EFFECT_RESULT_METHODS: &[KernelMethod] = &[
204    m("mapOk", "mapOk(f: T -> U) -> Effect[Result[U, E]]"),
205    m("mapErr", "mapErr(f: E -> F) -> Effect[Result[T, F]]"),
206    m(
207        "flatMapOk",
208        "flatMapOk(f: T -> Effect[Result[U, E]]) -> Effect[Result[U, E]]",
209    ),
210    m(
211        "flatMapErr",
212        "flatMapErr(f: E -> Effect[Result[T, F]]) -> Effect[Result[T, F]]",
213    ),
214];
215
216/// The `String` kernel (v0.22a; UTF-16 code units, except `chars`).
217pub const STRING_METHODS: &[KernelMethod] = &[
218    m("length", "length() -> Int"),
219    m("split", "split(sep: String) -> List[String]"),
220    m("trim", "trim() -> String"),
221    m("toUpper", "toUpper() -> String"),
222    m("toLower", "toLower() -> String"),
223    m("contains", "contains(s: String) -> Bool"),
224    m("startsWith", "startsWith(s: String) -> Bool"),
225    m("endsWith", "endsWith(s: String) -> Bool"),
226    m("replace", "replace(from: String, to: String) -> String"),
227    m("slice", "slice(start: Int, end: Int) -> String"),
228    m("indexOf", "indexOf(s: String) -> Option[Int]"),
229    m("chars", "chars() -> List[String]"),
230    m("concat", "concat(s: String) -> String"),
231];
232
233/// The `Int` numeric kernel (v0.21).
234pub const INT_METHODS: &[KernelMethod] = &[
235    m("toFloat", "toFloat() -> Float"),
236    m("toString", "toString() -> String"),
237    m("abs", "abs() -> Int"),
238    m("min", "min(other: Int) -> Int"),
239    m("max", "max(other: Int) -> Int"),
240    m("clamp", "clamp(lo: Int, hi: Int) -> Int"),
241];
242
243/// The `Float` numeric kernel (v0.21).
244pub const FLOAT_METHODS: &[KernelMethod] = &[
245    m("round", "round() -> Int"),
246    m("floor", "floor() -> Int"),
247    m("ceil", "ceil() -> Int"),
248    m("truncate", "truncate() -> Int"),
249    m("toString", "toString() -> String"),
250    m("abs", "abs() -> Float"),
251    m("min", "min(other: Float) -> Float"),
252    m("max", "max(other: Float) -> Float"),
253    m("clamp", "clamp(lo: Float, hi: Float) -> Float"),
254    m("isNaN", "isNaN() -> Bool"),
255    m("isFinite", "isFinite() -> Bool"),
256];
257
258/// The `Duration` kernel (v0.86, ADR 0112). Comparison/arithmetic are operators
259/// (D3/D4); the kernel is the explicit escape to raw milliseconds (D5).
260pub const DURATION_METHODS: &[KernelMethod] = &[
261    m("toMillis", "toMillis() -> Int"),
262    m("toString", "toString() -> String"),
263];
264
265/// The `Instant` kernel (v0.90, ADR 0114). Comparison/arithmetic are operators
266/// (D3); the kernel is the explicit escape to raw epoch milliseconds (D6).
267pub const INSTANT_METHODS: &[KernelMethod] = &[
268    m("toEpochMillis", "toEpochMillis() -> Int"),
269    m("toString", "toString() -> String"),
270];
271
272/// The `Bytes` kernel (v0.110, ADR 0142). Equality is an operator (D4, content
273/// compare); the kernel is length plus the String-interop bridge (D3). No
274/// ordering/arithmetic/concat/slice in v1 (deferred follow-ons).
275pub const BYTES_METHODS: &[KernelMethod] = &[
276    m("length", "length() -> Int"),
277    m("toBase64", "toBase64() -> String"),
278    m("decodeUtf8", "decodeUtf8() -> Option[String]"),
279];
280
281/// The value methods of a receiver type, or `&[]` for a type with no kernel
282/// methods (record/sum named types, `Bool`, `Effect`, …). Record *fields* are
283/// resolved separately by the LSP (they need the type declaration).
284pub fn methods_for(ty: TyId, tys: &Types) -> &'static [KernelMethod] {
285    match &*tys.get(ty) {
286        Ty::Base(BaseType::Int) => INT_METHODS,
287        Ty::Base(BaseType::Float) => FLOAT_METHODS,
288        Ty::Base(BaseType::Duration) => DURATION_METHODS,
289        Ty::Base(BaseType::Instant) => INSTANT_METHODS,
290        Ty::Base(BaseType::Bytes) => BYTES_METHODS,
291        Ty::Base(BaseType::String) => STRING_METHODS,
292        Ty::List(_) => LIST_METHODS,
293        Ty::Map(_, _) => MAP_METHODS,
294        Ty::Query(_) => QUERY_METHODS,
295        Ty::Option(_) => OPTION_METHODS,
296        Ty::Result(_, _) => RESULT_METHODS,
297        // §2.8.3: an `Effect[Result[T, E]]` receiver offers the cross-context
298        // combinators; any other `Effect[_]` has no kernel methods.
299        Ty::Effect(inner) if matches!(&*tys.get(*inner), Ty::Result(_, _)) => EFFECT_RESULT_METHODS,
300        // #561: a refined receiver inherits its base type's read-only kernel
301        // methods (after its own declared methods, which the completion layer
302        // merges in), so `.`-member completion offers them. `Bool` has no
303        // kernel; opaque types deliberately do not widen, so both surface none.
304        Ty::Named {
305            kind: NamedKind::Refined(base),
306            ..
307        } => match base {
308            BaseType::Int => INT_METHODS,
309            BaseType::Float => FLOAT_METHODS,
310            BaseType::Duration => DURATION_METHODS,
311            BaseType::Instant => INSTANT_METHODS,
312            BaseType::Bytes => BYTES_METHODS,
313            BaseType::String => STRING_METHODS,
314            BaseType::Bool => &[],
315        },
316        _ => &[],
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    fn refined(tys: &Types, base: BaseType) -> TyId {
325        tys.intern(Ty::Named {
326            name: "R".to_string(),
327            kind: NamedKind::Refined(base),
328            args: Vec::new(),
329        })
330    }
331
332    #[test]
333    fn refined_receiver_inherits_base_kernel_methods() {
334        let tys = &Types::new();
335        // #561: completion on a refined receiver offers the base kernel.
336        assert_eq!(
337            methods_for(refined(tys, BaseType::String), tys).len(),
338            STRING_METHODS.len()
339        );
340        assert_eq!(
341            methods_for(refined(tys, BaseType::Int), tys).len(),
342            INT_METHODS.len()
343        );
344        assert_eq!(
345            methods_for(refined(tys, BaseType::Float), tys).len(),
346            FLOAT_METHODS.len()
347        );
348        // `Bool` has no kernel, so a `Bool`-based refinement inherits nothing.
349        assert!(methods_for(refined(tys, BaseType::Bool), tys).is_empty());
350        // Opaque types do not widen — no inherited kernel.
351        let opaque = tys.intern(Ty::Named {
352            name: "O".to_string(),
353            kind: NamedKind::Opaque(BaseType::String),
354            args: Vec::new(),
355        });
356        assert!(methods_for(opaque, tys).is_empty());
357    }
358
359    /// #596: a `Query[T]` receiver (a `store Map`'s `.entries`/`.keys`/
360    /// `.values`, or a bare store map used as a value, ADR 0120) offers the
361    /// full lazy query vocabulary — previously unmapped, so `.`-member
362    /// completion on any `Query` offered nothing.
363    #[test]
364    fn query_receiver_offers_the_query_vocabulary() {
365        let tys = &Types::new();
366        let int = tys.intern(Ty::Base(BaseType::Int));
367        let query = tys.intern(Ty::Query(int));
368        assert_eq!(methods_for(query, tys).len(), QUERY_METHODS.len());
369        assert!(methods_for(query, tys).iter().any(|m| m.name == "filter"));
370        assert!(methods_for(query, tys).iter().any(|m| m.name == "collect"));
371    }
372}