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