Skip to main content

cove_schema/
builtins.rs

1//! What the builtin types declare about themselves.
2//!
3//! `Array<T>.get`, `Vector.of`, `Shared<T>.lock` and their neighbours are the
4//! language's own methods and associated functions rather than a host's, but
5//! they have the Host API schema's problem exactly: the compiler needs their
6//! signatures to check a call, the runtime needs their names to dispatch one,
7//! and the two crates cannot see each other. ADR 0004 wrote the table out
8//! twice and said so, "until a crate both can depend on exists". This is that
9//! crate, so this is that table, and there is now one of it.
10//!
11//! # Why this is not [`HostType`](crate::HostType)
12//!
13//! A Host API operation's signature is deliberately monomorphic:
14//! `documents.read(String) -> Result<String, Error>` names concrete types
15//! because a host is a boundary, and a boundary that took a type parameter
16//! would have nothing to instantiate it with. A builtin is the opposite.
17//! `Array<T>.get` answers in the element type of the receiver it was called
18//! on, `snapshot` answers in the receiver's own type, `Shared<T>.lock` takes
19//! a function and answers in whatever that function produces, and
20//! `Vector.of(items: T...)` binds a parameter of its own. None of that fits
21//! `HostType`, and widening `HostType` to hold it would put generics into
22//! every host signature that has no use for them.
23//!
24//! So there are two vocabularies here, on purpose:
25//! [`HostType`](crate::HostType) for what crosses the Host API boundary, and
26//! [`BuiltinType`] for what the language defines about itself. They overlap
27//! in the scalars and diverge exactly where the two kinds of signature
28//! differ — [`BuiltinType`] has [`BuiltinType::Param`],
29//! [`BuiltinType::SelfType`], and [`BuiltinType::Fn`], and `HostType` has
30//! [`Any`](crate::HostType::Any), which is a boundary's way of saying it does
31//! not look inside a value and means nothing for a method the language itself
32//! defines.
33//!
34//! # Two tables, because a builtin is not always called on something
35//!
36//! [`BUILTINS`] is keyed by a receiver, and most builtins have one:
37//! `items.length()` and `Vector.of(1)` are both reached through a type.
38//! `Ok(1)`, `Error("boom")`, and `assert(true)` are not — they are written
39//! bare, the way a declared function is — so [`FREE_BUILTINS`] is the second
40//! table, holding the five constructors and the two assertions with a name,
41//! a kind, and a signature each. They were the last builtins written out in
42//! both `cove-sema` and `cove-runtime`; [issue #50](https://github.com/myuon/cove/issues/50)
43//! is why they are here.
44//!
45//! # What a builtin type is made of, and not only what it answers
46//!
47//! A [`BuiltinSchema`] began as a name and a list of methods, which was
48//! enough for a call and not enough for anything else: `Option` is `Some` and
49//! `None`, `Result` is `Ok` and `Err`, an `Error` carries a `message`, and a
50//! `MapEntry` carries a `key` and a `value`, and none of that is a method. So
51//! an entry also declares its [`cases`](BuiltinSchema::cases) if it is an
52//! enum and its [`fields`](BuiltinSchema::fields) if it is a struct, and both
53//! ends read them: `match` exhaustiveness, the type a pattern's binding gets,
54//! the value the interpreter builds, and the field a program reads all come
55//! from here. [issue #53](https://github.com/myuon/cove/issues/53) is why,
56//! and it is the last of the four.
57//!
58//! # What is here and what is not
59//!
60//! The signatures are here; the implementations are not, and cannot be. A
61//! builtin's body is Rust that reaches into a `Value`, so it lives in
62//! `cove_runtime::builtins` beside the value model it walks. What this table
63//! removes is the *second description* of those bodies: `cove-sema` reads
64//! every signature from here rather than restating it, and the runtime reads
65//! from here every question it can answer from a name alone — which type
66//! names are namespaces, which methods take a `var self` receiver, which
67//! names are constructors, which are assertions, how many arguments each
68//! takes, which receivers are told that `count()` is spelled `length()`, and
69//! what each builtin enum's cases and each builtin struct's fields are
70//! called. `crates/cove-runtime/tests/builtin_schema.rs` closes the loop by
71//! driving every entry in both tables through a real interpreter, so an entry
72//! added here with no implementation behind it fails a test rather than a
73//! program.
74//!
75//! The variants of [`BuiltinType`] cover exactly the types the tables below
76//! use, on the same rule the host vocabulary follows: add one when a builtin
77//! needs it, because an unused variant is a type nobody can produce.
78
79use std::fmt;
80use std::sync::OnceLock;
81
82/// A type in a builtin's signature, written in Cove's source vocabulary.
83///
84/// Like [`HostType`](crate::HostType) this is a small enum rather than
85/// `cove_syntax::ast::Type`, because a builtin has no source to point a span
86/// at, and like `HostType` its [`fmt::Display`] produces the form the type
87/// would be written in Cove.
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum BuiltinType {
90    /// `Unit`, what a method that produces nothing answers.
91    Unit,
92    /// `Bool`.
93    Bool,
94    /// `Int`, a signed 64-bit integer.
95    Int,
96    /// `Float`, a 64-bit binary floating-point number.
97    Float,
98    /// `String`.
99    String,
100    /// `Error`, the builtin error struct.
101    Error,
102    /// `Duration`, a signed count of nanoseconds.
103    Duration,
104    /// `Array<T>`, the fixed-length immutable sequence.
105    Array(&'static BuiltinType),
106    /// `Vector<T>`, the growable one.
107    Vector(&'static BuiltinType),
108    /// `Set<T>`.
109    Set(&'static BuiltinType),
110    /// `Map<K, V>`.
111    Map(&'static BuiltinType, &'static BuiltinType),
112    /// `MapEntry<K, V>`, the one `key`/`value` pair `Map.of` collects.
113    MapEntry(&'static BuiltinType, &'static BuiltinType),
114    /// `Option<T>`.
115    Option(&'static BuiltinType),
116    /// `Result<T, E>`.
117    Result(&'static BuiltinType, &'static BuiltinType),
118    /// `Task<T>`, the handle `scope.spawn { ... }` hands back.
119    Task(&'static BuiltinType),
120    /// `Shared<T>`, the synchronized value `Shared(...)` wraps one in.
121    Shared(&'static BuiltinType),
122    /// `fn(A, B) -> R`: what a builtin that takes a callback declares, such
123    /// as `Shared<T>.lock` or `Scope.spawn`.
124    Fn(&'static [BuiltinType], &'static BuiltinType),
125    /// A type parameter, by name.
126    ///
127    /// It is bound either by the receiver — the `T` of the `Array<T>` a
128    /// method was called on — or by the signature itself, as
129    /// `Vector.of(items: T...)` binds one. Which of the two a name is comes
130    /// from where it is declared: [`BuiltinSchema::parameters`] for the
131    /// receiver's, [`MethodSchema::generics`] for the signature's.
132    Param(&'static str),
133    /// The receiver's own type, written `Self`.
134    ///
135    /// This is what `snapshot` answers, and it is one of the reasons a
136    /// builtin's signature cannot be written in the host vocabulary: an
137    /// immutable builtin snapshots to itself, so its result is not a type at
138    /// all until there is a receiver to read it off.
139    SelfType,
140}
141
142impl fmt::Display for BuiltinType {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            BuiltinType::Unit => f.write_str("Unit"),
146            BuiltinType::Bool => f.write_str("Bool"),
147            BuiltinType::Int => f.write_str("Int"),
148            BuiltinType::Float => f.write_str("Float"),
149            BuiltinType::String => f.write_str("String"),
150            BuiltinType::Error => f.write_str("Error"),
151            BuiltinType::Duration => f.write_str("Duration"),
152            BuiltinType::Array(item) => write!(f, "Array<{item}>"),
153            BuiltinType::Vector(item) => write!(f, "Vector<{item}>"),
154            BuiltinType::Set(item) => write!(f, "Set<{item}>"),
155            BuiltinType::Map(key, value) => write!(f, "Map<{key}, {value}>"),
156            BuiltinType::MapEntry(key, value) => write!(f, "MapEntry<{key}, {value}>"),
157            BuiltinType::Option(some) => write!(f, "Option<{some}>"),
158            BuiltinType::Result(ok, error) => write!(f, "Result<{ok}, {error}>"),
159            BuiltinType::Task(inner) => write!(f, "Task<{inner}>"),
160            BuiltinType::Shared(inner) => write!(f, "Shared<{inner}>"),
161            BuiltinType::Fn(params, ret) => {
162                let params: Vec<String> = params.iter().map(BuiltinType::to_string).collect();
163                write!(f, "fn({}) -> {ret}", params.join(", "))
164            }
165            BuiltinType::Param(name) => f.write_str(name),
166            BuiltinType::SelfType => f.write_str("Self"),
167        }
168    }
169}
170
171/// One parameter of a builtin's signature.
172///
173/// A host operation's parameters are positions and nothing else; a builtin's
174/// are labels a caller may write and a diagnostic does write, which is why
175/// this carries a name where
176/// [`OperationSchema::params`](crate::OperationSchema::params) carries a bare
177/// type.
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub struct ParamSchema {
180    /// The label, such as `index` or `fallback`.
181    pub name: &'static str,
182    /// The parameter's type.
183    pub ty: BuiltinType,
184}
185
186/// One case of a builtin enum.
187///
188/// A case is what a builtin enum is made of, the way a [`FieldSchema`] is
189/// what a builtin struct is made of, and the payload is written in the
190/// receiver's own type parameters: `Some` carries a `T` and `Err` carries an
191/// `E`. So a pattern reads its binding's type off the scrutinee exactly as a
192/// method reads its result off its receiver, and there is one description of
193/// what `Ok` carries rather than one on each side of the toolchain.
194///
195/// This is [`TypeSchema::cases`](crate::TypeSchema::cases) in the builtin
196/// vocabulary. A host's enum cases are bare names, because a boundary hands
197/// over data it has already made; a builtin's carry a payload the language
198/// itself binds.
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
200pub struct CaseSchema {
201    /// The name Cove source writes in a pattern or a call, such as `Ok`.
202    pub name: &'static str,
203    /// What the case carries, in order.
204    ///
205    /// Empty for a case that carries nothing, which is `None` and only
206    /// `None`: it is the one builtin case a program writes as a bare name
207    /// rather than as a call.
208    pub payload: &'static [BuiltinType],
209}
210
211impl CaseSchema {
212    /// The case, in the form a declaration would write it: `Ok(T)`, or
213    /// `None` for a case that carries nothing.
214    pub fn signature(&self) -> String {
215        if self.payload.is_empty() {
216            return self.name.to_string();
217        }
218        let payload: Vec<String> = self.payload.iter().map(BuiltinType::to_string).collect();
219        format!("{}({})", self.name, payload.join(", "))
220    }
221
222    /// The case as a pattern that binds nothing: `Ok(_)`, or `None` for a
223    /// case that carries nothing.
224    ///
225    /// This is how a diagnostic points *inside* a value: a host that
226    /// declares `Result<String, Error>` and hands back an `Ok(1)` is told
227    /// the mismatch is inside `Ok(_)`.
228    pub fn wildcard_pattern(&self) -> String {
229        if self.payload.is_empty() {
230            return self.name.to_string();
231        }
232        let payload: Vec<&str> = self.payload.iter().map(|_| "_").collect();
233        format!("{}({})", self.name, payload.join(", "))
234    }
235}
236
237/// One field of a builtin struct.
238///
239/// `Error` and `MapEntry` are structs the language builds rather than a
240/// module declares, and a program reads them the ordinary way, by field. The
241/// runtime has always built both — a `Value::Struct` with the fields below —
242/// so what this adds is the half the checker was missing: what the runtime
243/// builds, written down where the checker can read it.
244///
245/// This is [`FieldSchema`](crate::FieldSchema) in the builtin vocabulary,
246/// carrying a [`BuiltinType`] because a builtin struct may be generic:
247/// `MapEntry<K, V>`'s two fields are its two type parameters.
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub struct FieldSchema {
250    /// The label Cove source writes to read the field, such as `message`.
251    pub name: &'static str,
252    /// The field's type.
253    pub ty: BuiltinType,
254}
255
256/// One builtin method or associated function.
257///
258/// The two are the same shape and differ only in whether there is a receiver,
259/// which is what the field they are declared in says: a
260/// [`BuiltinSchema::methods`] entry is called on a value and a
261/// [`BuiltinSchema::associated`] entry is called on the type. An associated
262/// function therefore never names [`BuiltinType::SelfType`], never names the
263/// receiver's type parameters, and is never `mutating`.
264#[derive(Clone, Copy, Debug, PartialEq, Eq)]
265pub struct MethodSchema {
266    /// The name Cove source calls, such as `isEmpty`.
267    pub name: &'static str,
268    /// The type parameters this signature binds of its own, unified at the
269    /// call site exactly as a declared function's are.
270    pub generics: &'static [&'static str],
271    /// Parameters in declaration order.
272    pub params: &'static [ParamSchema],
273    /// Whether the last parameter takes the rest of the arguments, as
274    /// `Vector.of(items: T...)` does.
275    pub variadic: bool,
276    /// The type the call produces.
277    pub result: BuiltinType,
278    /// Whether the receiver is `var self`, so the call needs the caller's own
279    /// mutable place rather than a value.
280    pub mutating: bool,
281    /// Whether this call hands back a value nothing else holds a handle to —
282    /// freshly allocated storage, or a copy of somebody else's.
283    ///
284    /// `cove_sema::unique::creates` is the one reader, and this field is the
285    /// whole of what it trusts: `Vector.of(...)` allocates,
286    /// `Array.toVector()` copies an array's elements into storage nothing
287    /// else names, and `Vector.snapshot()` copies the vector's own graph, so
288    /// the three are `true`. Everything else here is `false`, including the
289    /// other types that share `snapshot`'s declaration —
290    /// `Array.snapshot()`, `Map.snapshot()`, `Set.snapshot()` and the rest
291    /// answer themselves, because the type is immutable and "itself" is
292    /// exactly the handle the caller already had, not a new one.
293    ///
294    /// # Who may say `true`, and why that is the whole boundary
295    ///
296    /// This table, and only this table. It is the compiler's own claim
297    /// about what the runtime allocates, not a declaration a program
298    /// writes, and `crates/cove-runtime/tests/builtin_schema.rs` drives
299    /// every entry through a real interpreter to hold the claim to account.
300    /// A Cove `fn` cannot make the same claim about its own `return` —
301    /// nothing checks that a body actually hands back unaliased storage —
302    /// so `unique::creates()` asks no question of a declared function at
303    /// all: it resolves a call to an entry in this table or it does not,
304    /// and a call to a declared function, however it is written or spelled,
305    /// simply has no entry to resolve to. `std.vector.filter`'s own
306    /// `out.freeze()` is proved the ordinary local way, from `Vector.of()`
307    /// a few lines above it in the same body; a *caller* of `filter` gets
308    /// no obligation-free `Vector` back, before or after this field
309    /// existed, because the field only ever answers a question about one
310    /// call's schema entry, and `filter` has none.
311    pub fresh: bool,
312}
313
314impl MethodSchema {
315    /// The signature, in the form it would be written in Cove source:
316    /// `inserted(key: K, value: V) -> Map<K, V>`.
317    pub fn signature(&self) -> String {
318        let mut params: Vec<String> = self
319            .params
320            .iter()
321            .map(|param| format!("{}: {}", param.name, param.ty))
322            .collect();
323        if self.variadic {
324            if let Some(last) = params.last_mut() {
325                last.push_str("...");
326            }
327        }
328        format!("{}({}) -> {}", self.name, params.join(", "), self.result)
329    }
330}
331
332/// One builtin type: its name, its type parameters, and what may be called on
333/// a value of it or on the name of it.
334#[derive(Clone, Copy, Debug, PartialEq, Eq)]
335pub struct BuiltinSchema {
336    /// The name Cove source writes, such as `Array`.
337    pub name: &'static str,
338    /// The type parameters the receiver binds, in the order they are written:
339    /// `["K", "V"]` for `Map<K, V>`.
340    ///
341    /// A method's signature names these, and a call site reads them off the
342    /// receiver the method was called on.
343    pub parameters: &'static [&'static str],
344    /// Whether the name may be written as a namespace, as in `Vector.of(...)`
345    /// or `Int.parse(...)`.
346    ///
347    /// This is not the same question as whether the type has associated
348    /// functions. `Array.something()` is a call on the builtin type `Array`
349    /// whatever `something` turns out to be, and answering "`Array` has no
350    /// associated function `something`" is better than treating `Array` as an
351    /// undeclared name. The types that say `false` are the ones no program
352    /// writes the name of: a `Task` comes from `scope.spawn`, a `Shared` from
353    /// the `Shared(...)` constructor, a `Scope` from `scope name { ... }`,
354    /// and a `Range` or a `Unit` from an expression that makes one.
355    /// `Duration` used to be in that list and is not any more — a duration
356    /// built from a number a program computed is written
357    /// `Duration.millis(n)`, so the name is one a program writes.
358    pub namespace: bool,
359    /// The cases, for a builtin enum. Empty for everything else.
360    ///
361    /// `Option` and `Result` are the two, and this is the one list of what
362    /// they are made of: `match` exhaustiveness, the sentence that names a
363    /// missing case, the type a pattern's binding gets, and the value the
364    /// interpreter builds all read it here.
365    pub cases: &'static [CaseSchema],
366    /// The fields, for a builtin struct. Empty for everything else.
367    ///
368    /// `Error` and `MapEntry` are the two. The order is the order an
369    /// initializer takes them in and a diagnostic reads them out.
370    pub fields: &'static [FieldSchema],
371    /// What may be called on a value of this type.
372    ///
373    /// The order is the order a diagnostic lists them in when it has to say
374    /// what does exist.
375    pub methods: &'static [MethodSchema],
376    /// What may be called on the type itself.
377    pub associated: &'static [MethodSchema],
378}
379
380impl BuiltinSchema {
381    /// The method `name`, if this type has one.
382    pub fn method(&self, name: &str) -> Option<&'static MethodSchema> {
383        self.methods.iter().find(|entry| entry.name == name)
384    }
385
386    /// The associated function `name`, if this type has one.
387    pub fn associated_function(&self, name: &str) -> Option<&'static MethodSchema> {
388        self.associated.iter().find(|entry| entry.name == name)
389    }
390
391    /// The case `name`, if this type declares one.
392    pub fn case(&self, name: &str) -> Option<&'static CaseSchema> {
393        self.cases.iter().find(|entry| entry.name == name)
394    }
395
396    /// The field `name`, if this type declares one.
397    pub fn field(&self, name: &str) -> Option<&'static FieldSchema> {
398        self.fields.iter().find(|entry| entry.name == name)
399    }
400
401    /// Whether this is a builtin enum, which is what having cases means.
402    pub fn is_enum(&self) -> bool {
403        !self.cases.is_empty()
404    }
405
406    /// Whether this is a builtin struct, which is what having fields means.
407    pub fn is_struct(&self) -> bool {
408        !self.fields.is_empty()
409    }
410}
411
412/// What a builtin that is called on nothing *is*.
413///
414/// The two kinds are not variations of one thing — a constructor makes a
415/// value and an assertion checks one — and both ends of the toolchain ask
416/// which is which before anything else: the interpreter dispatches an
417/// assertion through the one path that carries the source text of its
418/// arguments, and the checker gives an assertion's arity a different sentence
419/// than a constructor's. So the kind is in the table rather than derived from
420/// the name.
421#[derive(Clone, Copy, Debug, PartialEq, Eq)]
422pub enum FreeBuiltinKind {
423    /// `Ok(value)`, `Err(error)`, `Some(value)`, `Error("message")`, and
424    /// `Shared(value)`: a name that builds a builtin value out of one
425    /// payload.
426    Constructor,
427    /// `assert(condition)` and `assertEqual(actual, expected)`: a name a test
428    /// calls, which reports failure as an ordinary `Err`.
429    Assertion,
430}
431
432/// One builtin that is called on nothing.
433///
434/// A [`BuiltinSchema`] is keyed by a receiver, and these have none: `Ok(1)`
435/// and `assert(true)` are written bare, like a declared function and unlike
436/// `items.length()` or `Vector.of(1)`. Straining the receiver-keyed table to
437/// hold them would have meant inventing a receiver they do not have, so they
438/// have a table of their own, and it is close to the plainest thing that lets
439/// both ends stop restating each other: a name, which kind it is, and the
440/// parameters it takes.
441///
442/// The result is here too, because the checker reads it in both directions.
443/// A constructor's result is generic — `Ok(value: T) -> Result<T, E>` — and
444/// the type a call site expects is what settles `T` and `E`, so the one
445/// declaration that says what `Ok` produces is also the one that says what
446/// its payload must be. That is why this carries a signature rather than only
447/// an arity: the arity is what the runtime needs, and the signature is what
448/// stops the checker from writing the same five names out again to say what
449/// each of them makes.
450#[derive(Clone, Copy, Debug, PartialEq, Eq)]
451pub struct FreeBuiltinSchema {
452    /// The name Cove source calls, such as `Ok`.
453    pub name: &'static str,
454    /// Whether this builds a value or checks one.
455    pub kind: FreeBuiltinKind,
456    /// The type parameters this signature binds.
457    ///
458    /// Every type parameter a free builtin names is one it binds itself:
459    /// there is no receiver to read one off. A call site settles them from
460    /// the type it expects, and from the arguments where it expects nothing
461    /// in particular.
462    pub generics: &'static [&'static str],
463    /// Parameters in declaration order, labelled as a diagnostic names them.
464    pub params: &'static [ParamSchema],
465    /// The type the call produces.
466    pub result: BuiltinType,
467}
468
469impl FreeBuiltinSchema {
470    /// How many arguments a call must supply.
471    pub fn arity(&self) -> usize {
472        self.params.len()
473    }
474
475    /// The signature, in the form it would be written in Cove source:
476    /// `assertEqual(actual: T, expected: T) -> Result<Unit, Error>`.
477    pub fn signature(&self) -> String {
478        let params: Vec<String> = self
479            .params
480            .iter()
481            .map(|param| format!("{}: {}", param.name, param.ty))
482            .collect();
483        format!("{}({}) -> {}", self.name, params.join(", "), self.result)
484    }
485}
486
487/// Every builtin type the language defines.
488///
489/// The order is the order the associated functions read out in a diagnostic
490/// that has to list them, which is why the collections come first.
491pub static BUILTINS: &[BuiltinSchema] = &[
492    ARRAY, VECTOR, MAP, MAP_ENTRY, SET, STRING, RANGE, OPTION, RESULT, INT, FLOAT, BOOL, UNIT,
493    DURATION, ERROR, TASK, SHARED, SCOPE,
494];
495
496/// Every builtin type the language defines.
497pub fn builtins() -> &'static [BuiltinSchema] {
498    BUILTINS
499}
500
501/// The builtin type `name` describes itself with, if there is one.
502pub fn builtin(name: &str) -> Option<&'static BuiltinSchema> {
503    BUILTINS.iter().find(|entry| entry.name == name)
504}
505
506/// Whether `name` is a builtin type a program may write as a namespace, as in
507/// `Vector.of(...)`.
508pub fn is_builtin_type(name: &str) -> bool {
509    BUILTINS
510        .iter()
511        .any(|entry| entry.namespace && entry.name == name)
512}
513
514/// Whether `name` is a builtin method that takes a `var self` receiver, and
515/// so needs a mutable place at the call site rather than a value.
516///
517/// The question is asked by name alone, because that is what the call site
518/// has before it has evaluated a receiver. `push`, `set`, and `freeze` are
519/// the three, and no builtin type spells a mutating method the way another
520/// spells an immutable one.
521pub fn is_mutating_method(name: &str) -> bool {
522    mutating_methods().contains(&name)
523}
524
525/// Every `var self` method name the table declares, gathered once.
526///
527/// The table is still the list; this is that list read out of it on the first
528/// call rather than walked again on every later one. The question is asked at
529/// each method call a program makes, and walking eighteen types' methods to
530/// answer it was 2.3% of `examples/cq`'s run before this existed (issue #104).
531fn mutating_methods() -> &'static [&'static str] {
532    static NAMES: OnceLock<Vec<&'static str>> = OnceLock::new();
533    NAMES.get_or_init(|| {
534        BUILTINS
535            .iter()
536            .flat_map(|entry| entry.methods.iter())
537            .filter(|method| method.mutating)
538            .map(|method| method.name)
539            .collect()
540    })
541}
542
543/// Whether the builtin type `name` reports how many elements it holds.
544///
545/// This is the audience for the one diagnostic that teaches a spelling: a
546/// receiver that answers `length()` is a receiver a program might have
547/// written `count()` on, so `Array`, `Vector`, `String`, `Range`, `Map`, and
548/// `Set` are told what the spelling is and everything else is told it has no
549/// such method. Deriving the set from the table is the point — it used to be
550/// written out at both ends, and the two had drifted by two types.
551pub fn declares_length(name: &str) -> bool {
552    builtin(name).is_some_and(|entry| entry.method("length").is_some())
553}
554
555/// The builtin enum that declares the case `name`, if one does.
556///
557/// This is what lets a bare `Some(value)` arm say which enum a `match` is
558/// over without a list of its own: the two builtin enums are the two entries
559/// with cases, and a case name belongs to at most one of them.
560pub fn enum_declaring(name: &str) -> Option<&'static BuiltinSchema> {
561    BUILTINS.iter().find(|entry| entry.case(name).is_some())
562}
563
564// ------------------------------------------------------- the standard library
565//
566// A builtin's signature lives in [`BUILTINS`] whether its body is Rust or
567// Cove; what [`StdBinding`] adds is a second fact some methods carry, which
568// is *where the body is instead*. `Array.isEmpty` type-checks exactly as it
569// always did — the schema above still answers its signature — but there is
570// no Rust arm for it in either evaluator any more, because `isEmpty` is
571// `length() == 0` and saying so once, in Cove, replaced saying it twice, in
572// Rust.
573//
574// A table rather than a field on [`MethodSchema`], because a field would
575// mean touching all hundred-odd literals above to add one that is empty for
576// every one of them but this. The table only grows as a method migrates.
577
578/// Which of a builtin type's two call forms a [`StdBinding`] names.
579///
580/// A receiver and a name are not always one thing: `Duration` declares
581/// `micros`, `millis`, `seconds`, `minutes`, and `hours` as both a method —
582/// `d.millis()`, the reader — and an associated function — `Duration.millis(n)`,
583/// the builder — of the same name. `(receiver, name)` cannot key both without
584/// telling the two apart, so this is the third field of the key, and it is
585/// also why the two bindings for one name have to point at two different
586/// functions: a Cove module cannot declare `millis` twice.
587#[derive(Clone, Copy, Debug, PartialEq, Eq)]
588pub enum StdBindingKind {
589    /// `receiver.method(...)`, resolved by [`standard_binding`].
590    Method,
591    /// `Receiver.method(...)`, resolved by [`standard_associated_binding`].
592    Associated,
593}
594
595/// A builtin method or associated function whose implementation is Cove
596/// source rather than Rust.
597///
598/// This is the fact `cove_ir`'s lowering reads to turn `items.isEmpty()`
599/// into an ordinary call to a declared function instead of a
600/// `CallBuiltin`: the receiver and method name are what a call site already
601/// has, and the module and function name are where to send it.
602#[derive(Clone, Copy, Debug, PartialEq, Eq)]
603pub struct StdBinding {
604    /// Whether `method` names a method or an associated function of
605    /// `receiver`. See [`StdBindingKind`].
606    pub kind: StdBindingKind,
607    /// The builtin type the method is called on, such as `"Array"`.
608    pub receiver: &'static str,
609    /// The method or associated function name Cove source calls, such as
610    /// `"isEmpty"`.
611    pub method: &'static str,
612    /// The standard-library module the implementation lives in, such as
613    /// `"std.array"`.
614    pub module: &'static str,
615    /// The function within that module, such as `"isEmpty"`. Usually the
616    /// same spelling as `method`, but named separately because nothing
617    /// requires it to be — an associated binding in particular cannot share
618    /// its module function's name with its method counterpart, since a
619    /// module cannot declare the same name twice.
620    pub function: &'static str,
621}
622
623/// Every builtin method whose body has moved out of Rust and into the
624/// standard library.
625///
626/// Twenty-nine entries, and what is *not* here is as informative as what is.
627///
628/// `Result.mapError` is here, and it is the only one that needed a language
629/// change to arrive. While a callback's arity was adapted rather than
630/// matched, a program could write `mapError { ... }` with a trailing closure
631/// naming no parameter, and no Cove body can call such a closure —
632/// `body(error)` passes one argument always. ADR 0044 removed that
633/// exception, and this row is what it bought.
634///
635/// `Int.abs` is here too now, and it is the first entry that can fail: the
636/// least `Int` has no positive counterpart. It waited on
637/// [issue #258](https://github.com/myuon/cove/issues/258), which taught a
638/// trap raised from inside a standard-library body to name its caller
639/// instead of only the library's own line — without that, moving `abs`
640/// here would have moved its diagnostic out of the caller's source along
641/// with it.
642///
643/// Ten of the twenty-nine are `Duration`'s, and they are the first entries
644/// that come in pairs: `micros`, `millis`, `seconds`, `minutes`, and `hours`
645/// each name a method (`d.millis()`, the reader) and, separately, an
646/// associated function (`Duration.millis(n)`, the builder) — see
647/// [`StdBindingKind`]. `nanos` is not among them and never will be: it is
648/// the one primitive `Duration` keeps, because something has to know how a
649/// duration is actually stored. Before this migration all six readers
650/// shared one table-driven Rust function and so did all six builders; a
651/// reader now divides by its unit's constant and a builder multiplies by
652/// it, both written once in `std.duration` rather than once per backend.
653///
654/// # Checking ADR 0043's third condition before adding a row here
655///
656/// [ADR 0043](../../../docs/adr/0043-a-method-moves-if-it-is-total-and-takes-no-closure.md)'s
657/// third migration condition is that the Rust an entry deletes is
658/// **per-method**: a method whose implementation is shared with others
659/// moves nothing by moving, and counting schema entries instead of
660/// implementations is the mistake that ADR corrects — which is exactly what
661/// made `Duration` wait until its shared table-driven function could be
662/// deleted outright rather than merely bypassed for five of its six units.
663///
664/// The check is: delete the dispatch arm in `cove-runtime`'s
665/// `vm::builtins` that calls the Rust function, leave the function itself
666/// defined, and run
667/// `cargo clippy --workspace --all-targets --profile checked -- -D warnings`.
668/// If the condition holds, clippy reports the now-unreachable function as
669/// dead code and the build fails; a build that stays green says the
670/// function still has another caller, which is exactly the shared-Rust case
671/// the condition rules out.
672///
673/// This only works because `crates/cove-runtime/src/vm/mod.rs` carries no
674/// module-wide `#[allow(dead_code)]` — it did once, and while it did this
675/// check passed silently for every candidate, moved or not, which is how
676/// `Int.min` and `Int.max` in [PR #259](https://github.com/myuon/cove/pull/259)
677/// went unverified. [Issue #274](https://github.com/myuon/cove/issues/274)
678/// narrowed that allow to the individual items that need it, each with its
679/// own comment saying why it has no caller outside its own tests; deleting a
680/// dispatch arm anywhere else in `vm::builtins` now reaches exactly one of
681/// two outcomes — a clippy failure, or a genuinely shared implementation —
682/// and never the third, silent one this note used to have to warn about.
683pub static STANDARD_LIBRARY: &[StdBinding] = &[
684    StdBinding {
685        kind: StdBindingKind::Method,
686        receiver: "Array",
687        method: "isEmpty",
688        module: "std.array",
689        function: "isEmpty",
690    },
691    StdBinding {
692        kind: StdBindingKind::Method,
693        receiver: "Array",
694        method: "filter",
695        module: "std.array",
696        function: "filter",
697    },
698    StdBinding {
699        kind: StdBindingKind::Method,
700        receiver: "Array",
701        method: "fold",
702        module: "std.array",
703        function: "fold",
704    },
705    StdBinding {
706        kind: StdBindingKind::Method,
707        receiver: "Vector",
708        method: "isEmpty",
709        module: "std.vector",
710        function: "isEmpty",
711    },
712    StdBinding {
713        kind: StdBindingKind::Method,
714        receiver: "Vector",
715        method: "filter",
716        module: "std.vector",
717        function: "filter",
718    },
719    StdBinding {
720        kind: StdBindingKind::Method,
721        receiver: "Vector",
722        method: "fold",
723        module: "std.vector",
724        function: "fold",
725    },
726    StdBinding {
727        kind: StdBindingKind::Method,
728        receiver: "Map",
729        method: "isEmpty",
730        module: "std.map",
731        function: "isEmpty",
732    },
733    StdBinding {
734        kind: StdBindingKind::Method,
735        receiver: "Set",
736        method: "isEmpty",
737        module: "std.set",
738        function: "isEmpty",
739    },
740    StdBinding {
741        kind: StdBindingKind::Method,
742        receiver: "String",
743        method: "isEmpty",
744        module: "std.string",
745        function: "isEmpty",
746    },
747    StdBinding {
748        kind: StdBindingKind::Method,
749        receiver: "Option",
750        method: "isSome",
751        module: "std.option",
752        function: "isSome",
753    },
754    StdBinding {
755        kind: StdBindingKind::Method,
756        receiver: "Option",
757        method: "isNone",
758        module: "std.option",
759        function: "isNone",
760    },
761    StdBinding {
762        kind: StdBindingKind::Method,
763        receiver: "Option",
764        method: "unwrapOr",
765        module: "std.option",
766        function: "unwrapOr",
767    },
768    StdBinding {
769        kind: StdBindingKind::Method,
770        receiver: "Result",
771        method: "isOk",
772        module: "std.result",
773        function: "isOk",
774    },
775    StdBinding {
776        kind: StdBindingKind::Method,
777        receiver: "Result",
778        method: "isError",
779        module: "std.result",
780        function: "isError",
781    },
782    StdBinding {
783        kind: StdBindingKind::Method,
784        receiver: "Result",
785        method: "unwrapOr",
786        module: "std.result",
787        function: "unwrapOr",
788    },
789    StdBinding {
790        kind: StdBindingKind::Method,
791        receiver: "Result",
792        method: "mapError",
793        module: "std.result",
794        function: "mapError",
795    },
796    StdBinding {
797        kind: StdBindingKind::Method,
798        receiver: "Int",
799        method: "min",
800        module: "std.int",
801        function: "min",
802    },
803    StdBinding {
804        kind: StdBindingKind::Method,
805        receiver: "Int",
806        method: "max",
807        module: "std.int",
808        function: "max",
809    },
810    StdBinding {
811        kind: StdBindingKind::Method,
812        receiver: "Int",
813        method: "abs",
814        module: "std.int",
815        function: "abs",
816    },
817    // `Duration.nanos` is not here: it is the one primitive left, and both
818    // its forms — the reader and the builder — stay in the machine. Each of
819    // its five neighbours is bound twice, once as the method that reads it
820    // and once as the associated function that builds it, and the two point
821    // at different functions of `std.duration` because a module cannot
822    // declare `micros` twice.
823    StdBinding {
824        kind: StdBindingKind::Method,
825        receiver: "Duration",
826        method: "micros",
827        module: "std.duration",
828        function: "micros",
829    },
830    StdBinding {
831        kind: StdBindingKind::Method,
832        receiver: "Duration",
833        method: "millis",
834        module: "std.duration",
835        function: "millis",
836    },
837    StdBinding {
838        kind: StdBindingKind::Method,
839        receiver: "Duration",
840        method: "seconds",
841        module: "std.duration",
842        function: "seconds",
843    },
844    StdBinding {
845        kind: StdBindingKind::Method,
846        receiver: "Duration",
847        method: "minutes",
848        module: "std.duration",
849        function: "minutes",
850    },
851    StdBinding {
852        kind: StdBindingKind::Method,
853        receiver: "Duration",
854        method: "hours",
855        module: "std.duration",
856        function: "hours",
857    },
858    StdBinding {
859        kind: StdBindingKind::Associated,
860        receiver: "Duration",
861        method: "micros",
862        module: "std.duration",
863        function: "ofMicros",
864    },
865    StdBinding {
866        kind: StdBindingKind::Associated,
867        receiver: "Duration",
868        method: "millis",
869        module: "std.duration",
870        function: "ofMillis",
871    },
872    StdBinding {
873        kind: StdBindingKind::Associated,
874        receiver: "Duration",
875        method: "seconds",
876        module: "std.duration",
877        function: "ofSeconds",
878    },
879    StdBinding {
880        kind: StdBindingKind::Associated,
881        receiver: "Duration",
882        method: "minutes",
883        module: "std.duration",
884        function: "ofMinutes",
885    },
886    StdBinding {
887        kind: StdBindingKind::Associated,
888        receiver: "Duration",
889        method: "hours",
890        module: "std.duration",
891        function: "ofHours",
892    },
893];
894
895/// Every builtin method whose body lives in the standard library.
896pub fn standard_library() -> &'static [StdBinding] {
897    STANDARD_LIBRARY
898}
899
900/// The standard-library binding for `receiver.method(...)`, a call on a
901/// value of `receiver`, if that method's body has moved out of Rust.
902///
903/// Only [`StdBindingKind::Method`] entries answer here — an associated
904/// function of the same name, such as `Duration`'s builder half of
905/// `millis`, is a different binding and [`standard_associated_binding`]
906/// is what finds it.
907pub fn standard_binding(receiver: &str, method: &str) -> Option<&'static StdBinding> {
908    STANDARD_LIBRARY.iter().find(|entry| {
909        entry.kind == StdBindingKind::Method && entry.receiver == receiver && entry.method == method
910    })
911}
912
913/// The standard-library binding for `Receiver.method(...)`, a call on the
914/// type's own name, if that associated function's body has moved out of
915/// Rust.
916///
917/// Only [`StdBindingKind::Associated`] entries answer here, for the reason
918/// [`standard_binding`] gives.
919pub fn standard_associated_binding(receiver: &str, method: &str) -> Option<&'static StdBinding> {
920    STANDARD_LIBRARY.iter().find(|entry| {
921        entry.kind == StdBindingKind::Associated
922            && entry.receiver == receiver
923            && entry.method == method
924    })
925}
926
927// -------------------------------------------- the cases and the one field
928//
929// The four case names and the two structs' field names are what
930// [issue #53](https://github.com/myuon/cove/issues/53) was about: they were
931// written out in `cove-sema` for exhaustiveness and pattern types, and again
932// in `cove-runtime` for the values it builds. These are the constants both
933// ends name.
934
935/// `Some(T)`, the case an `Option` carries a value in.
936pub const SOME_CASE: CaseSchema = CaseSchema {
937    name: "Some",
938    payload: &[BuiltinType::Param("T")],
939};
940
941/// `None`, the empty case of `Option`.
942///
943/// It is the one builtin case with no payload, and therefore the one written
944/// as a bare name rather than as a call — which is why both ends ask for this
945/// constant by itself: the checker to give the name a type, the interpreter
946/// to build the value, and both to say that `None(...)` is a mistake.
947pub const NONE_CASE: CaseSchema = CaseSchema {
948    name: "None",
949    payload: &[],
950};
951
952/// `Ok(T)`, the success case of a `Result`.
953pub const OK_CASE: CaseSchema = CaseSchema {
954    name: "Ok",
955    payload: &[BuiltinType::Param("T")],
956};
957
958/// `Err(E)`, the failure case of a `Result`.
959pub const ERR_CASE: CaseSchema = CaseSchema {
960    name: "Err",
961    payload: &[BuiltinType::Param("E")],
962};
963
964/// `message: String`, the one field of the builtin `Error` struct.
965///
966/// The runtime has always built an `Error` with this field and served a read
967/// of it; the checker used to answer "`Error` has no field `message`" and
968/// suggest a method `Error` does not have. Declaring it here is what closed
969/// that gap.
970pub const MESSAGE_FIELD: FieldSchema = FieldSchema {
971    name: "message",
972    ty: BuiltinType::String,
973};
974
975/// Every builtin that is called on nothing: the constructors, then the
976/// assertions.
977///
978/// A name belongs to one kind or the other and never to both, so the order
979/// settles nothing at a call site. It is the order a reader meets them in:
980/// `Ok` and its neighbours are in every program, and `assert` is in every
981/// test.
982pub static FREE_BUILTINS: &[FreeBuiltinSchema] =
983    &[OK, ERR, SOME, ERROR_OF, SHARED_OF, ASSERT, ASSERT_EQUAL];
984
985/// Every builtin that is called on nothing.
986pub fn free_builtins() -> &'static [FreeBuiltinSchema] {
987    FREE_BUILTINS
988}
989
990/// The free builtin `name` describes itself with, if there is one.
991pub fn free_builtin(name: &str) -> Option<&'static FreeBuiltinSchema> {
992    FREE_BUILTINS.iter().find(|entry| entry.name == name)
993}
994
995// ------------------------------------------- the builtins called on nothing
996
997/// `Ok(value: T) -> Result<T, E>`.
998///
999/// The error type is the one thing a payload cannot say, so `E` is settled by
1000/// the type the call site expects and is unknown when it expects nothing.
1001pub const OK: FreeBuiltinSchema = FreeBuiltinSchema {
1002    name: "Ok",
1003    kind: FreeBuiltinKind::Constructor,
1004    generics: &["T", "E"],
1005    params: &[ParamSchema {
1006        name: "value",
1007        ty: BuiltinType::Param("T"),
1008    }],
1009    result: BuiltinType::Result(&BuiltinType::Param("T"), &BuiltinType::Param("E")),
1010};
1011
1012/// `Err(error: E) -> Result<T, E>`, the mirror of [`OK`].
1013pub const ERR: FreeBuiltinSchema = FreeBuiltinSchema {
1014    name: "Err",
1015    kind: FreeBuiltinKind::Constructor,
1016    generics: &["T", "E"],
1017    params: &[ParamSchema {
1018        name: "error",
1019        ty: BuiltinType::Param("E"),
1020    }],
1021    result: BuiltinType::Result(&BuiltinType::Param("T"), &BuiltinType::Param("E")),
1022};
1023
1024/// `Some(value: T) -> Option<T>`.
1025///
1026/// `None` has no entry here because it is not a call: it is the empty case
1027/// written as a bare name, and writing `None(...)` is a mistake both ends
1028/// name as one.
1029pub const SOME: FreeBuiltinSchema = FreeBuiltinSchema {
1030    name: "Some",
1031    kind: FreeBuiltinKind::Constructor,
1032    generics: &["T"],
1033    params: &[ParamSchema {
1034        name: "value",
1035        ty: BuiltinType::Param("T"),
1036    }],
1037    result: BuiltinType::Option(&BuiltinType::Param("T")),
1038};
1039
1040/// `Error(message: String) -> Error`, the one constructor whose payload has a
1041/// type of its own rather than one the call site settles.
1042///
1043/// Its one parameter *is* [`MESSAGE_FIELD`], the field the value it builds
1044/// carries, so the label a call writes and the label a read writes cannot
1045/// come apart.
1046///
1047/// The constant is not called `ERROR` because [`BuiltinType::Error`]'s type
1048/// table already is.
1049pub const ERROR_OF: FreeBuiltinSchema = FreeBuiltinSchema {
1050    name: "Error",
1051    kind: FreeBuiltinKind::Constructor,
1052    generics: &[],
1053    params: &[ParamSchema {
1054        name: MESSAGE_FIELD.name,
1055        ty: MESSAGE_FIELD.ty,
1056    }],
1057    result: BuiltinType::Error,
1058};
1059
1060/// `Shared(value: T) -> Shared<T>`.
1061///
1062/// This is the one constructor that can refuse its payload: what a `Shared`
1063/// wraps is reachable from every task it is given to, so it must be able to
1064/// cross a task boundary. That rule is not in this table — it is about what a
1065/// type *is*, not what a call takes — so the checker and the runtime each
1066/// enforce it with what they have, a type and a value.
1067pub const SHARED_OF: FreeBuiltinSchema = FreeBuiltinSchema {
1068    name: "Shared",
1069    kind: FreeBuiltinKind::Constructor,
1070    generics: &["T"],
1071    params: &[ParamSchema {
1072        name: "value",
1073        ty: BuiltinType::Param("T"),
1074    }],
1075    result: BuiltinType::Shared(&BuiltinType::Param("T")),
1076};
1077
1078/// `assert(condition: Bool) -> Result<Unit, Error>`.
1079///
1080/// A failing assertion is an expected failure rather than a broken invariant,
1081/// so it answers `Err` and `?` works on it inside a test.
1082pub const ASSERT: FreeBuiltinSchema = FreeBuiltinSchema {
1083    name: "assert",
1084    kind: FreeBuiltinKind::Assertion,
1085    generics: &[],
1086    params: &[ParamSchema {
1087        name: "condition",
1088        ty: BuiltinType::Bool,
1089    }],
1090    result: BuiltinType::Result(&BuiltinType::Unit, &BuiltinType::Error),
1091};
1092
1093/// `assertEqual(actual: T, expected: T) -> Result<Unit, Error>`.
1094///
1095/// One type parameter named twice is the whole rule: `assertEqual` compares
1096/// two values of one type, and comparing values of two types is the mistake
1097/// it catches. Both ends read that off the repeated `T` — the checker as
1098/// unification, the runtime as two values whose type names must agree.
1099pub const ASSERT_EQUAL: FreeBuiltinSchema = FreeBuiltinSchema {
1100    name: "assertEqual",
1101    kind: FreeBuiltinKind::Assertion,
1102    generics: &["T"],
1103    params: &[
1104        ParamSchema {
1105            name: "actual",
1106            ty: BuiltinType::Param("T"),
1107        },
1108        ParamSchema {
1109            name: "expected",
1110            ty: BuiltinType::Param("T"),
1111        },
1112    ],
1113    result: BuiltinType::Result(&BuiltinType::Unit, &BuiltinType::Error),
1114};
1115
1116// ----------------------------------------------------- the shared signatures
1117
1118/// `snapshot(self) -> Self`, the builtin `Snapshot` trait's one method.
1119///
1120/// Every builtin value type that has it is immutable and returns itself;
1121/// `Vector`, the one builtin with an independent mutable graph to copy,
1122/// returns a `Vector` again, and the runtime is what recursively snapshots
1123/// the elements. A closure, a task, a task scope, and a synchronized value
1124/// have no such graph this side of a lock and so do not have this method at
1125/// all. A struct or enum conforms the ordinary way, with an explicit `impl
1126/// Snapshot for Type`, so it is not a builtin.
1127const SNAPSHOT: MethodSchema = MethodSchema {
1128    name: "snapshot",
1129    generics: &[],
1130    params: &[],
1131    variadic: false,
1132    result: BuiltinType::SelfType,
1133    mutating: false,
1134    fresh: false,
1135};
1136
1137/// `length() -> Int`, how every sequence reports its element count. There is
1138/// no `count()`.
1139const LENGTH: MethodSchema = MethodSchema {
1140    name: "length",
1141    generics: &[],
1142    params: &[],
1143    variadic: false,
1144    result: BuiltinType::Int,
1145    mutating: false,
1146    fresh: false,
1147};
1148
1149/// `isEmpty() -> Bool`.
1150const IS_EMPTY: MethodSchema = MethodSchema {
1151    name: "isEmpty",
1152    generics: &[],
1153    params: &[],
1154    variadic: false,
1155    result: BuiltinType::Bool,
1156    mutating: false,
1157    fresh: false,
1158};
1159
1160// ------------------------------- the questions an ordered sequence answers
1161//
1162// `contains`, `indexOf` and `slice` are three questions a program asks a
1163// sequence constantly that `get`, `length` and the four higher-order
1164// operations below do not answer: whether a value is in there, where it is,
1165// and what a part of it is. They are declared once here for the same reason
1166// the four are — an `Array` and a `Vector` are one sequence with two storage
1167// rules, and a shared constant is what stops one name from becoming two
1168// signatures.
1169//
1170// # Where each belongs, and why the answer is not "on everything"
1171//
1172// `contains` goes on every collection, because membership is a question
1173// every collection can answer. `Map` and `Set` already answered it of a key
1174// and an element, and `String` answers it of a substring; this is the same
1175// question asked of a sequence's elements, so it is the same word.
1176//
1177// `indexOf` and `slice` go on the ordered types only — `String`, which
1178// already has both, and now `Array` and `Vector`. A position is a fact only
1179// where order is. A `Map` and a `Set` do keep their entries in ascending key
1180// order, but that is the collection's own storage rule rather than an
1181// ordering a caller chose, and `toArray()` is the operation that says "this
1182// ordering is mine now"; slicing or indexing what it answers is how a
1183// program means it.
1184//
1185// # What each answers where a caller might not expect one
1186//
1187// An empty receiver answers `false`, `None`, and `[]`. None of the three is
1188// a mistake to ask on an empty sequence, and none of them makes a caller
1189// check the length first.
1190
1191/// `contains(element: T) -> Bool`, whether the receiver holds a value equal
1192/// to `element`.
1193///
1194/// Equality is `==`'s, which is structural: an `Array<Point>` answers `true`
1195/// for a `Point` with equal fields whether or not it is the one that was put
1196/// in, exactly as `==` on the two would. An empty receiver answers `false`,
1197/// and there is no argument this can refuse — every value has an equality.
1198///
1199/// `Set.contains` is this same constant, so the one operation reads the same
1200/// on either. What differs is under the signature rather than in it: a `Set`
1201/// may only hold values that can be keys, so it compares keys, and a
1202/// sequence holds anything, so it compares values.
1203const CONTAINS: MethodSchema = MethodSchema {
1204    name: "contains",
1205    generics: &[],
1206    params: &[ParamSchema {
1207        name: "element",
1208        ty: BuiltinType::Param("T"),
1209    }],
1210    variadic: false,
1211    result: BuiltinType::Bool,
1212    mutating: false,
1213    fresh: false,
1214};
1215
1216/// `indexOf(element: T) -> Option<Int>`: the position of the **first**
1217/// element equal to `element`, or `None`.
1218///
1219/// `None` is the not-found answer and the empty-receiver answer both, which
1220/// is `String.indexOf`'s rule and `Array.get`'s: a question about a position
1221/// that is not there answers a value the caller opens rather than stopping
1222/// the run. Equality is [`CONTAINS`]'s, so `items.contains(x)` and
1223/// `items.indexOf(x)` are one question asked two ways and cannot disagree.
1224const INDEX_OF: MethodSchema = MethodSchema {
1225    name: "indexOf",
1226    generics: &[],
1227    params: &[ParamSchema {
1228        name: "element",
1229        ty: BuiltinType::Param("T"),
1230    }],
1231    variadic: false,
1232    result: BuiltinType::Option(&BuiltinType::Int),
1233    mutating: false,
1234    fresh: false,
1235};
1236
1237/// `slice(from: Int, to: Int) -> Array<T>`: the elements at indices `from`
1238/// up to but not including `to`.
1239///
1240/// This is `String.slice` on a sequence, down to the spelling and down to
1241/// what it does with an argument nobody would write on purpose. **Both
1242/// bounds are clamped into `0..length()`, and a `to` at or below `from`
1243/// answers `[]`** — so a negative bound, a bound past the end, and a
1244/// reversed pair are each answered rather than refused, and no argument can
1245/// stop a program. A prefix is `slice(0, n)` and a suffix is
1246/// `slice(n, items.length())`. There is no `take`/`drop` pair beside this:
1247/// two spellings of "part of a sequence" is what declaring these three
1248/// together was for avoiding, and `String` had already chosen this one.
1249///
1250/// It answers an `Array` from either receiver, which is the rule `map`,
1251/// `filter`, `fold` and `sorted` already follow and for their reason: a part
1252/// of a sequence is a finished sequence rather than a second handle to go on
1253/// appending to.
1254const SLICE: MethodSchema = MethodSchema {
1255    name: "slice",
1256    generics: &[],
1257    params: &[
1258        ParamSchema {
1259            name: "from",
1260            ty: BuiltinType::Int,
1261        },
1262        ParamSchema {
1263            name: "to",
1264            ty: BuiltinType::Int,
1265        },
1266    ],
1267    variadic: false,
1268    result: BuiltinType::Array(&BuiltinType::Param("T")),
1269    mutating: false,
1270    fresh: false,
1271};
1272
1273// ------------------------------- the higher-order methods a sequence shares
1274//
1275// `Array<T>` and `Vector<T>` both bind one parameter and both call it `T`,
1276// so one declaration serves both receivers the way [`LENGTH`] and
1277// [`IS_EMPTY`] already do. That is the point rather than a saving: an
1278// operation that walks a sequence with a closure is the same operation on
1279// either, and a shared constant is what stops the two from drifting into two
1280// signatures with one name.
1281//
1282// # What all four promise
1283//
1284// Each **answers an `Array`**, whichever receiver it was called on. What a
1285// walk produces is a finished sequence rather than a handle to go on
1286// appending to, and answering a `Vector` from a `Vector` would hand back a
1287// second mutable graph that nothing asked for. `Vector.toArray` already
1288// draws that line and these stand on the same side of it.
1289//
1290// Each **takes the elements once, before it calls anything**. A `Vector`
1291// shares its storage, so the walk has to settle what it walks rather than
1292// read the receiver as it goes; the elements are copied out first, so what
1293// is walked and what comes back are both settled before the first call.
1294// That is the same rule `cove_ir::lower` applies to a `for` — it reads a
1295// sequence's length once, with `Inst::Len`, and walks a snapshot — applied
1296// where the same question arises, and not a second answer to it.
1297//
1298// A callback cannot reach the receiver to test that, and that is the rule
1299// rather than a gap in it: a closure captures a copy of each binding and a
1300// captured binding is a read-only place, so `items.push(..)` inside a
1301// callback is refused by the checker. Issue #190 decided that it stays
1302// refused -- a callback that can push onto the vector it is filtering is a
1303// way to write a walk that does not end, and the read-only capture is what
1304// prevents it -- so this rule is checked through a `for` loop over the same
1305// vector, which is expressible and does walk it live.
1306//
1307// Each **visits every element exactly once, front to back**, in the
1308// receiver's own order.
1309//
1310// Each **answers nothing at all when its callback fails**. The result is
1311// built to the side and returned only on success, so a walk that stops
1312// leaves no half-built array and no half-sorted sequence for anything to
1313// observe, and the receiver is never written through. A `?` inside a
1314// callback returns from the callback, and the callback's result type is what
1315// the signature declares — a `Bool` for `filter` and `sorted` — so a `?` in
1316// one of those is a check-time mismatch rather than a runtime surprise.
1317//
1318// And each is **empty-safe by construction**: an empty receiver answers an
1319// empty `Array` — or, for `fold`, `initial` — and calls the callback zero
1320// times.
1321
1322/// `map(transform: fn(T) -> R) -> Array<R>`.
1323///
1324/// The constant is not called `MAP` because the `Map<K, V>` type table
1325/// already is, the way [`ERROR_OF`] is not called `ERROR`.
1326const MAP_EACH: MethodSchema = MethodSchema {
1327    name: "map",
1328    generics: &["R"],
1329    params: &[ParamSchema {
1330        name: "transform",
1331        ty: BuiltinType::Fn(&[BuiltinType::Param("T")], &BuiltinType::Param("R")),
1332    }],
1333    variadic: false,
1334    result: BuiltinType::Array(&BuiltinType::Param("R")),
1335    mutating: false,
1336    fresh: false,
1337};
1338
1339/// `filter(keep: fn(T) -> Bool) -> Array<T>`.
1340///
1341/// `keep` says whether an element belongs in the answer, so the elements it
1342/// answered `true` for come back in the order they were in.
1343const FILTER: MethodSchema = MethodSchema {
1344    name: "filter",
1345    generics: &[],
1346    params: &[ParamSchema {
1347        name: "keep",
1348        ty: BuiltinType::Fn(&[BuiltinType::Param("T")], &BuiltinType::Bool),
1349    }],
1350    variadic: false,
1351    result: BuiltinType::Array(&BuiltinType::Param("T")),
1352    mutating: false,
1353    fresh: false,
1354};
1355
1356/// `fold(initial: R, step: fn(R, T) -> R) -> R`.
1357///
1358/// The total is the left argument and the element the right, so
1359/// `fold(0, fn(total, item) { total + item })` sums. It is `fold` and not
1360/// `reduce` because the initial value is what makes an empty sequence answer
1361/// something: a `reduce` with no initial value has to answer an `Option`,
1362/// which is this with one extra case for the caller to open, and both would
1363/// be one operation written twice.
1364const FOLD: MethodSchema = MethodSchema {
1365    name: "fold",
1366    generics: &["R"],
1367    params: &[
1368        ParamSchema {
1369            name: "initial",
1370            ty: BuiltinType::Param("R"),
1371        },
1372        ParamSchema {
1373            name: "step",
1374            ty: BuiltinType::Fn(
1375                &[BuiltinType::Param("R"), BuiltinType::Param("T")],
1376                &BuiltinType::Param("R"),
1377            ),
1378        },
1379    ],
1380    variadic: false,
1381    result: BuiltinType::Param("R"),
1382    mutating: false,
1383    fresh: false,
1384};
1385
1386/// `sorted(by: fn(T, T) -> Bool) -> Array<T>`, a **stable** sort under the
1387/// caller's own ordering.
1388///
1389/// `by` is a strict less-than: `by(a, b)` answers whether `a` must come
1390/// before `b`, and answers `false` when they are equivalent. Two elements
1391/// neither of which comes before the other keep the order they were in,
1392/// which is what stable means and is what makes a sort by one field of a
1393/// record leave the rest of the ordering alone.
1394///
1395/// A three-way comparison was the alternative and would need an ordering
1396/// type to answer in, which the language does not have; a `Bool` needs
1397/// nothing new and is the shape `<` already has.
1398///
1399/// An ordering that contradicts itself — one where `by(a, b)` and `by(b, a)`
1400/// are both true, or one that answers differently on the same pair twice —
1401/// gets *some* permutation of the elements and no promise about which. It is
1402/// not a stopped run: the comparison is the program's to get right, and the
1403/// merge that implements this has no invariant of its own to break.
1404///
1405/// There is no natural-order `sorted()` beside this one. Ascending order is
1406/// `sorted(by: fn(a, b) { a < b })`, written in the operator the language
1407/// already defines for `Int`, `Float`, `Duration`, and `String`; a bare
1408/// `sorted()` would have to say which element types it accepts, and saying
1409/// so needs bounds, which the MVP does not have. `Map`'s own table already
1410/// declines that for the same reason.
1411const SORTED: MethodSchema = MethodSchema {
1412    name: "sorted",
1413    generics: &[],
1414    params: &[ParamSchema {
1415        name: "by",
1416        ty: BuiltinType::Fn(
1417            &[BuiltinType::Param("T"), BuiltinType::Param("T")],
1418            &BuiltinType::Bool,
1419        ),
1420    }],
1421    variadic: false,
1422    result: BuiltinType::Array(&BuiltinType::Param("T")),
1423    mutating: false,
1424    fresh: false,
1425};
1426
1427// ------------------------------------------------------------------- Array
1428
1429/// `Array<T>`: the fixed-length immutable sequence.
1430///
1431/// `get` answers an `Option` rather than trapping, so an index outside the
1432/// array is a value the caller has to open rather than a stopped program.
1433///
1434/// `contains`, `indexOf`, and `slice` are the three questions about the
1435/// order and the membership of a sequence, and `map`, `filter`, `fold`, and
1436/// `sorted` are the four it walks with a closure. All seven are declared
1437/// once above, because a `Vector` has the same seven.
1438///
1439/// `toVector` is the one direction `Vector` does not answer for itself, and
1440/// it is the inverse of `Vector.toArray`.
1441pub const ARRAY: BuiltinSchema = BuiltinSchema {
1442    name: "Array",
1443    parameters: &["T"],
1444    namespace: true,
1445    cases: &[],
1446    fields: &[],
1447    methods: &[
1448        MethodSchema {
1449            name: "get",
1450            generics: &[],
1451            params: &[ParamSchema {
1452                name: "index",
1453                ty: BuiltinType::Int,
1454            }],
1455            variadic: false,
1456            result: BuiltinType::Option(&BuiltinType::Param("T")),
1457            mutating: false,
1458            fresh: false,
1459        },
1460        LENGTH,
1461        IS_EMPTY,
1462        CONTAINS,
1463        INDEX_OF,
1464        SLICE,
1465        MAP_EACH,
1466        FILTER,
1467        FOLD,
1468        SORTED,
1469        // `toVector() -> Vector<T>`: a fresh growable copy of these
1470        // elements.
1471        //
1472        // This is `Vector.toArray` run backwards, down to the spelling, and
1473        // it is the beginning of the round trip the other two already have
1474        // the middle and the end of: an immutable `Array` is the state, a
1475        // `Vector` is the scratch, and `freeze()` or `toArray()` puts it
1476        // back. It answers a vector nothing else holds a handle to, so the
1477        // `freeze()` at the end of that trip is the O(1) one.
1478        //
1479        // **It is on `Array` and not on `Vector`.** A `Vector` already
1480        // answers an independent vector, and that answer is called
1481        // `snapshot()`; a second name for it is the drift the shared
1482        // constants exist to prevent.
1483        //
1484        // It copies the elements as they are, which is `toArray`'s rule and
1485        // deliberately not `snapshot`'s. A struct element is the field-wise
1486        // shallow copy an assignment makes, and a `Vector` element stays the
1487        // same handle, so `items.toVector()` separates the sequence and
1488        // nothing inside it. A program that wants the deep copy asks for it
1489        // by name.
1490        //
1491        // There is no bad argument, no empty-receiver question — an empty
1492        // array answers an empty vector — and the copy is O(n) either way,
1493        // so what this buys is an expression where a `for`/`push` loop was.
1494        // The `...` spread is not that expression: it fills a *declared*
1495        // function's variadic parameter and nothing else, so `Vector.of` —
1496        // an associated function of a builtin — collects nothing from one,
1497        // and `cove_ir` refuses to lower a `...` written there.
1498        MethodSchema {
1499            name: "toVector",
1500            generics: &[],
1501            params: &[],
1502            variadic: false,
1503            result: BuiltinType::Vector(&BuiltinType::Param("T")),
1504            mutating: false,
1505            // Copies the array's elements into storage nothing else names —
1506            // see `MethodSchema::fresh`.
1507            fresh: true,
1508        },
1509        SNAPSHOT,
1510    ],
1511    associated: &[],
1512};
1513
1514// ------------------------------------------------------------------ Vector
1515
1516/// `Vector<T>`: the growable sequence, and the one builtin with a mutable
1517/// graph of its own.
1518///
1519/// `push`, `set`, `pop`, `remove`, and `freeze` are the language's only
1520/// `var self` methods: one appends, one replaces, two take an element back
1521/// out, and the last consumes locally unique storage and hands back an
1522/// `Array` in O(1). `toArray` is the copying alternative, for a caller that
1523/// cannot give the storage up.
1524///
1525/// # The name of a mutation, and the name of an answer
1526///
1527/// A method that writes through the receiver is spelled as an imperative
1528/// verb — `push`, `set`, `pop`, `remove` — and a method that answers a new
1529/// collection is spelled as a past participle: `Map.inserted`,
1530/// `Set.removed`, and every sequence's `sorted`. That is why removal here is
1531/// `remove` and not `removed`, which is `Set`'s **non**-mutating answer and
1532/// would say the opposite of what this does. The parameter is `index` for
1533/// the same reason, because `get(index)` and `set(index, value)` already
1534/// call it that.
1535///
1536/// # There is no `clear`
1537///
1538/// Emptying a vector is `pop` in a loop, or — for scratch state that is
1539/// rebuilt each time round, which is what the examples write — rebinding
1540/// `var items = Vector.of()`, which costs nothing and hands back storage
1541/// nothing else can be holding. What a `clear` would add over those is a
1542/// bulk write *through* an alias, and a program that empties a vector
1543/// somebody else is holding is the case worth making a caller spell out.
1544///
1545/// It is also the only one of the three shrinking operations with nothing to
1546/// answer, so it is the only one that would need a decision of its own —
1547/// `Unit`, or the count it discarded — where `pop` and `remove` inherit
1548/// `get`'s answer whole. An operation whose only argument is convenience and
1549/// whose only question is new is the one to leave out.
1550///
1551/// # What a removal costs
1552///
1553/// `push`, `set`, and `pop` are O(1). `remove(index)` is O(n - index),
1554/// because the elements after `index` move down one; that is written down
1555/// for the reason `set` being O(1) is.
1556///
1557/// `contains`, `indexOf`, `slice`, `map`, `filter`, `fold`, and `sorted` are
1558/// the same seven an `Array` has, and the four that produce a sequence
1559/// answer an `Array` here too: `v.sorted(by:)` is `v.toArray().sorted(by:)`
1560/// and writes nothing through the handle, so an alias sees no change and no
1561/// walk can be disturbed by what happens during it.
1562///
1563/// Removal is checked against that rather than assumed under it, because a
1564/// walk that shrinks its own receiver is the case that would have found it
1565/// wrong. The check is written as a `for` rather than as a callback: a
1566/// closure captures a copy of each binding it reads and a captured binding
1567/// is a read-only place, so `items.pop()` inside a `filter` callback is
1568/// refused by the checker, and `for`, which walks the elements it asked for
1569/// once, is where the question can be asked at all.
1570pub const VECTOR: BuiltinSchema = BuiltinSchema {
1571    name: "Vector",
1572    parameters: &["T"],
1573    namespace: true,
1574    cases: &[],
1575    fields: &[],
1576    methods: &[
1577        MethodSchema {
1578            name: "get",
1579            generics: &[],
1580            params: &[ParamSchema {
1581                name: "index",
1582                ty: BuiltinType::Int,
1583            }],
1584            variadic: false,
1585            result: BuiltinType::Option(&BuiltinType::Param("T")),
1586            mutating: false,
1587            fresh: false,
1588        },
1589        LENGTH,
1590        IS_EMPTY,
1591        CONTAINS,
1592        INDEX_OF,
1593        SLICE,
1594        MAP_EACH,
1595        FILTER,
1596        FOLD,
1597        SORTED,
1598        MethodSchema {
1599            name: "push",
1600            generics: &[],
1601            params: &[ParamSchema {
1602                name: "value",
1603                ty: BuiltinType::Param("T"),
1604            }],
1605            variadic: false,
1606            result: BuiltinType::Unit,
1607            mutating: true,
1608            fresh: false,
1609        },
1610        // `set(index: Int, value: T) -> Option<T>`: replaces the element at
1611        // `index` and answers what was there.
1612        //
1613        // A `Vector` shares its storage, so replacing an element is a
1614        // mutation and not a new vector — which is why this is `var self`
1615        // like `push` and unlike `Map.inserted`, and why it needs the
1616        // caller's own place at the call site for the same reason `push`
1617        // does.
1618        //
1619        // **An index that is not already in the vector answers `None` and
1620        // writes nothing.** That is `get`'s answer to the same bad index,
1621        // in the shape a replacement can take, and it is deliberately not a
1622        // third thing: a negative index, an index at or past `length()`, and
1623        // a `set` on an empty vector all answer `None`, exactly as `get`
1624        // does, so a program can be written against one rule about indices
1625        // rather than two. `Some(previous)` is what the index held before,
1626        // so `v.set(i, x)` answers what `v.get(i)` would have.
1627        //
1628        // It replaces and never appends. A `set` at `length()` is out of
1629        // range, because a vector grows by `push` and a `set` that
1630        // sometimes grew would make the length depend on the index.
1631        MethodSchema {
1632            name: "set",
1633            generics: &[],
1634            params: &[
1635                ParamSchema {
1636                    name: "index",
1637                    ty: BuiltinType::Int,
1638                },
1639                ParamSchema {
1640                    name: "value",
1641                    ty: BuiltinType::Param("T"),
1642                },
1643            ],
1644            variadic: false,
1645            result: BuiltinType::Option(&BuiltinType::Param("T")),
1646            mutating: true,
1647            fresh: false,
1648        },
1649        // `pop() -> Option<T>`: takes the last element out and answers it,
1650        // or answers `None` and writes nothing when there is no last
1651        // element.
1652        //
1653        // The empty answer is not a decision of its own. `pop` is
1654        // `remove(length() - 1)`, and on an empty vector that index is `-1`,
1655        // which `get`, `set` and `remove` all answer `None` for; so the one
1656        // rule about indices covers this too, rather than a second rule
1657        // about emptiness sitting beside it.
1658        //
1659        // It is a mutation and so it is an imperative verb, like `push` and
1660        // `set` and unlike `Set.removed`. It writes through the shared
1661        // storage, so an alias observes the shrink, and it needs the
1662        // caller's own place at the call site for the reason `push` does.
1663        //
1664        // **Removal makes a promise about indices that replacement does
1665        // not.** After `v.set(3, x)` every index still names what it named;
1666        // after `v.pop()` the index `length() - 1` names nothing, and after
1667        // `v.remove(3)` every index above 3 names what the one above it
1668        // named. A cursor, a stored index, or an `indexOf` result computed
1669        // earlier is stale afterwards. That is what makes these different
1670        // operations from `set` rather than more of it.
1671        MethodSchema {
1672            name: "pop",
1673            generics: &[],
1674            params: &[],
1675            variadic: false,
1676            result: BuiltinType::Option(&BuiltinType::Param("T")),
1677            mutating: true,
1678            fresh: false,
1679        },
1680        // `remove(index: Int) -> Option<T>`: takes the element at `index`
1681        // out, moves everything after it down one, and answers what was
1682        // there.
1683        //
1684        // **An index that is not already in the vector answers `None` and
1685        // removes nothing**, which is `get`'s answer and `set`'s: a negative
1686        // index, an index at or past `length()`, and a `remove` on an empty
1687        // vector are one case with one answer. `Some(removed)` is what
1688        // `v.get(index)` would have answered a moment before.
1689        //
1690        // It is `remove` and not `removed` because it writes through the
1691        // receiver; the parameter is `index` and not `at` because `get` and
1692        // `set` already named it. Both are the vocabulary rather than a
1693        // choice made here — see this type's own documentation.
1694        MethodSchema {
1695            name: "remove",
1696            generics: &[],
1697            params: &[ParamSchema {
1698                name: "index",
1699                ty: BuiltinType::Int,
1700            }],
1701            variadic: false,
1702            result: BuiltinType::Option(&BuiltinType::Param("T")),
1703            mutating: true,
1704            fresh: false,
1705        },
1706        MethodSchema {
1707            name: "freeze",
1708            generics: &[],
1709            params: &[],
1710            variadic: false,
1711            result: BuiltinType::Array(&BuiltinType::Param("T")),
1712            mutating: true,
1713            fresh: false,
1714        },
1715        MethodSchema {
1716            name: "toArray",
1717            generics: &[],
1718            params: &[],
1719            variadic: false,
1720            result: BuiltinType::Array(&BuiltinType::Param("T")),
1721            mutating: false,
1722            fresh: false,
1723        },
1724        // `Vector` is the one `SNAPSHOT` user whose storage is not the
1725        // receiver's own already: the runtime copies its mutable graph
1726        // rather than handing the same handle back, so this override is
1727        // `fresh` where every other user of the shared constant is not —
1728        // see `MethodSchema::fresh`.
1729        MethodSchema {
1730            fresh: true,
1731            ..SNAPSHOT
1732        },
1733    ],
1734    associated: &[MethodSchema {
1735        name: "of",
1736        generics: &["T"],
1737        params: &[ParamSchema {
1738            name: "items",
1739            ty: BuiltinType::Param("T"),
1740        }],
1741        variadic: true,
1742        result: BuiltinType::Vector(&BuiltinType::Param("T")),
1743        mutating: false,
1744        // Allocates fresh storage — see `MethodSchema::fresh`.
1745        fresh: true,
1746    }],
1747};
1748
1749// --------------------------------------------------------------------- Map
1750
1751/// `Map<K, V>`: an immutable mapping, kept in ascending key order.
1752///
1753/// `inserted` and `removed` are past participles because the map they answer
1754/// with is a new one; nothing here writes through the receiver, unlike
1755/// `Vector`'s `push`. Which values may be keys is a runtime rule — a key's
1756/// equality must not be able to change — and stating it here would need
1757/// bounds, which the MVP does not have.
1758pub const MAP: BuiltinSchema = BuiltinSchema {
1759    name: "Map",
1760    parameters: &["K", "V"],
1761    namespace: true,
1762    cases: &[],
1763    fields: &[],
1764    methods: &[
1765        MethodSchema {
1766            name: "get",
1767            generics: &[],
1768            params: &[ParamSchema {
1769                name: "key",
1770                ty: BuiltinType::Param("K"),
1771            }],
1772            variadic: false,
1773            result: BuiltinType::Option(&BuiltinType::Param("V")),
1774            mutating: false,
1775            fresh: false,
1776        },
1777        LENGTH,
1778        IS_EMPTY,
1779        MethodSchema {
1780            name: "contains",
1781            generics: &[],
1782            params: &[ParamSchema {
1783                name: "key",
1784                ty: BuiltinType::Param("K"),
1785            }],
1786            variadic: false,
1787            result: BuiltinType::Bool,
1788            mutating: false,
1789            fresh: false,
1790        },
1791        MethodSchema {
1792            name: "keys",
1793            generics: &[],
1794            params: &[],
1795            variadic: false,
1796            result: BuiltinType::Array(&BuiltinType::Param("K")),
1797            mutating: false,
1798            fresh: false,
1799        },
1800        MethodSchema {
1801            name: "values",
1802            generics: &[],
1803            params: &[],
1804            variadic: false,
1805            result: BuiltinType::Array(&BuiltinType::Param("V")),
1806            mutating: false,
1807            fresh: false,
1808        },
1809        MethodSchema {
1810            name: "inserted",
1811            generics: &[],
1812            params: &[
1813                ParamSchema {
1814                    name: "key",
1815                    ty: BuiltinType::Param("K"),
1816                },
1817                ParamSchema {
1818                    name: "value",
1819                    ty: BuiltinType::Param("V"),
1820                },
1821            ],
1822            variadic: false,
1823            result: BuiltinType::Map(&BuiltinType::Param("K"), &BuiltinType::Param("V")),
1824            mutating: false,
1825            fresh: false,
1826        },
1827        MethodSchema {
1828            name: "removed",
1829            generics: &[],
1830            params: &[ParamSchema {
1831                name: "key",
1832                ty: BuiltinType::Param("K"),
1833            }],
1834            variadic: false,
1835            result: BuiltinType::Map(&BuiltinType::Param("K"), &BuiltinType::Param("V")),
1836            mutating: false,
1837            fresh: false,
1838        },
1839        SNAPSHOT,
1840    ],
1841    associated: &[MethodSchema {
1842        name: "of",
1843        generics: &["K", "V"],
1844        params: &[ParamSchema {
1845            name: "entries",
1846            ty: BuiltinType::MapEntry(&BuiltinType::Param("K"), &BuiltinType::Param("V")),
1847        }],
1848        variadic: true,
1849        result: BuiltinType::Map(&BuiltinType::Param("K"), &BuiltinType::Param("V")),
1850        mutating: false,
1851        fresh: false,
1852    }],
1853};
1854
1855// ---------------------------------------------------------------- MapEntry
1856
1857/// `MapEntry<K, V>`: the one `key`/`value` pair a `Map` is built from and
1858/// iterated as.
1859///
1860/// It is the second builtin *struct*, and the only builtin whose initializer
1861/// is labelled: `MapEntry(key: "a", value: 1)` is the synthesized labelled
1862/// call a declared struct gets, and its labels are the two fields below, read
1863/// by both ends rather than written out at each. It is not a namespace,
1864/// because nothing is called on the name — `Map.of` collects the pairs and a
1865/// `for` over a `Map` binds them.
1866pub const MAP_ENTRY: BuiltinSchema = BuiltinSchema {
1867    name: "MapEntry",
1868    parameters: &["K", "V"],
1869    namespace: false,
1870    cases: &[],
1871    fields: &[
1872        FieldSchema {
1873            name: "key",
1874            ty: BuiltinType::Param("K"),
1875        },
1876        FieldSchema {
1877            name: "value",
1878            ty: BuiltinType::Param("V"),
1879        },
1880    ],
1881    methods: &[],
1882    associated: &[],
1883};
1884
1885// --------------------------------------------------------------------- Set
1886
1887/// `Set<T>`: an immutable set, kept in ascending element order.
1888///
1889/// `contains` is the very declaration `Array` and `Vector` answer membership
1890/// with, shared rather than restated, because it is the same question about
1891/// a different container — [`ARRAY`] says the rest of it. There is no
1892/// `indexOf` or `slice` here: the ascending order is how a set is stored
1893/// rather than an order a caller chose, and `toArray()` is where a program
1894/// says it wants that order to be its own.
1895pub const SET: BuiltinSchema = BuiltinSchema {
1896    name: "Set",
1897    parameters: &["T"],
1898    namespace: true,
1899    cases: &[],
1900    fields: &[],
1901    methods: &[
1902        LENGTH,
1903        IS_EMPTY,
1904        MethodSchema {
1905            name: "toArray",
1906            generics: &[],
1907            params: &[],
1908            variadic: false,
1909            result: BuiltinType::Array(&BuiltinType::Param("T")),
1910            mutating: false,
1911            fresh: false,
1912        },
1913        CONTAINS,
1914        MethodSchema {
1915            name: "inserted",
1916            generics: &[],
1917            params: &[ParamSchema {
1918                name: "element",
1919                ty: BuiltinType::Param("T"),
1920            }],
1921            variadic: false,
1922            result: BuiltinType::Set(&BuiltinType::Param("T")),
1923            mutating: false,
1924            fresh: false,
1925        },
1926        MethodSchema {
1927            name: "removed",
1928            generics: &[],
1929            params: &[ParamSchema {
1930                name: "element",
1931                ty: BuiltinType::Param("T"),
1932            }],
1933            variadic: false,
1934            result: BuiltinType::Set(&BuiltinType::Param("T")),
1935            mutating: false,
1936            fresh: false,
1937        },
1938        SNAPSHOT,
1939    ],
1940    associated: &[MethodSchema {
1941        name: "of",
1942        generics: &["T"],
1943        params: &[ParamSchema {
1944            name: "items",
1945            ty: BuiltinType::Param("T"),
1946        }],
1947        variadic: true,
1948        result: BuiltinType::Set(&BuiltinType::Param("T")),
1949        mutating: false,
1950        fresh: false,
1951    }],
1952};
1953
1954// ------------------------------------------------------------------ String
1955
1956/// `String`: an immutable sequence of characters, whose `length` counts
1957/// characters rather than bytes — and every other index this type takes or
1958/// answers, in `chars`, `slice`, and `indexOf`, counts the same way, so an
1959/// API that mixed characters and bytes never has the chance to become a trap.
1960///
1961/// Three operations count in **bytes** instead, and every one of them says so
1962/// in its name: `byteLength`, `codePointAtByte` and `sliceBytes`. They exist
1963/// because the representation is UTF-8 and a scanner that walks it should not
1964/// have to allocate a one-character `String` per character to do so
1965/// ([issue #292](https://github.com/myuon/cove/issues/292)). The rule that
1966/// keeps them from becoming the trap the paragraph above avoids is the naming
1967/// one: a byte offset is not a `String` index, it is a value one of these
1968/// three answered and another takes back, and no method without `Byte` in its
1969/// name accepts one. Mixing the two index spaces in the same call is
1970/// therefore something a reader can see rather than something the types allow
1971/// silently.
1972///
1973/// `join` lives here rather than on `Array<String>`, so that `", ".join(names)`
1974/// reads receiver-first with the separator: [`BuiltinType`] has no way to
1975/// constrain a receiver's type parameter, so an `Array<T>.join` would either
1976/// have to accept an `Array<Int>` too or need a bound the MVP cannot express,
1977/// and putting the method on `String` instead sidesteps the bound rather than
1978/// needing it.
1979///
1980/// `split` and `replace` both search for a piece of text that may not be
1981/// empty: an empty needle would match between every character rather than
1982/// answer either method's question, so both refuse it at run time and point
1983/// at `chars()`, which is the operation that actually means that.
1984pub const STRING: BuiltinSchema = BuiltinSchema {
1985    name: "String",
1986    parameters: &[],
1987    namespace: true,
1988    cases: &[],
1989    fields: &[],
1990    methods: &[
1991        LENGTH,
1992        IS_EMPTY,
1993        MethodSchema {
1994            name: "words",
1995            generics: &[],
1996            params: &[],
1997            variadic: false,
1998            result: BuiltinType::Array(&BuiltinType::String),
1999            mutating: false,
2000            fresh: false,
2001        },
2002        // One element per character, each a `String` of length 1 — the
2003        // decomposition `for` cannot do itself, since `for` refuses a
2004        // `String`.
2005        MethodSchema {
2006            name: "chars",
2007            generics: &[],
2008            params: &[],
2009            variadic: false,
2010            result: BuiltinType::Array(&BuiltinType::String),
2011            mutating: false,
2012            fresh: false,
2013        },
2014        // Every occurrence of `separator` separates, so adjacent separators
2015        // produce an empty part and text with none produces one part that is
2016        // the whole text; an empty `separator` is refused, as the type's own
2017        // doc comment says.
2018        MethodSchema {
2019            name: "split",
2020            generics: &[],
2021            params: &[ParamSchema {
2022                name: "separator",
2023                ty: BuiltinType::String,
2024            }],
2025            variadic: false,
2026            result: BuiltinType::Array(&BuiltinType::String),
2027            mutating: false,
2028            fresh: false,
2029        },
2030        // The receiver is the separator; see the type's own doc comment for
2031        // why this is not `Array<T>.join`.
2032        MethodSchema {
2033            name: "join",
2034            generics: &[],
2035            params: &[ParamSchema {
2036                name: "parts",
2037                ty: BuiltinType::Array(&BuiltinType::String),
2038            }],
2039            variadic: false,
2040            result: BuiltinType::String,
2041            mutating: false,
2042            fresh: false,
2043        },
2044        // The characters at indices `from` up to but not including `to`.
2045        // Both bounds are clamped into `0..length()`, and a `to` at or below
2046        // `from` answers `""`, so — the same choice `Array.get` makes by
2047        // answering an `Option`, in the form a substring can take — no
2048        // argument can stop a program.
2049        MethodSchema {
2050            name: "slice",
2051            generics: &[],
2052            params: &[
2053                ParamSchema {
2054                    name: "from",
2055                    ty: BuiltinType::Int,
2056                },
2057                ParamSchema {
2058                    name: "to",
2059                    ty: BuiltinType::Int,
2060                },
2061            ],
2062            variadic: false,
2063            result: BuiltinType::String,
2064            mutating: false,
2065            fresh: false,
2066        },
2067        // Leading and trailing whitespace removed, where whitespace is
2068        // Unicode whitespace as Rust's own `str::trim` sees it; `words()`
2069        // still splits on ASCII whitespace only, and this does not change
2070        // that.
2071        MethodSchema {
2072            name: "trim",
2073            generics: &[],
2074            params: &[],
2075            variadic: false,
2076            result: BuiltinType::String,
2077            mutating: false,
2078            fresh: false,
2079        },
2080        MethodSchema {
2081            name: "contains",
2082            generics: &[],
2083            params: &[ParamSchema {
2084                name: "text",
2085                ty: BuiltinType::String,
2086            }],
2087            variadic: false,
2088            result: BuiltinType::Bool,
2089            mutating: false,
2090            fresh: false,
2091        },
2092        MethodSchema {
2093            name: "startsWith",
2094            generics: &[],
2095            params: &[ParamSchema {
2096                name: "prefix",
2097                ty: BuiltinType::String,
2098            }],
2099            variadic: false,
2100            result: BuiltinType::Bool,
2101            mutating: false,
2102            fresh: false,
2103        },
2104        MethodSchema {
2105            name: "endsWith",
2106            generics: &[],
2107            params: &[ParamSchema {
2108                name: "suffix",
2109                ty: BuiltinType::String,
2110            }],
2111            variadic: false,
2112            result: BuiltinType::Bool,
2113            mutating: false,
2114            fresh: false,
2115        },
2116        // The character index `text` first occurs at, or `None`; an empty
2117        // `text` occurs at 0.
2118        MethodSchema {
2119            name: "indexOf",
2120            generics: &[],
2121            params: &[ParamSchema {
2122                name: "text",
2123                ty: BuiltinType::String,
2124            }],
2125            variadic: false,
2126            result: BuiltinType::Option(&BuiltinType::Int),
2127            mutating: false,
2128            fresh: false,
2129        },
2130        // Every non-overlapping occurrence of `old`, scanning left to right;
2131        // an empty `old` is refused for the same reason `split`'s empty
2132        // `separator` is.
2133        MethodSchema {
2134            name: "replace",
2135            generics: &[],
2136            params: &[
2137                ParamSchema {
2138                    name: "old",
2139                    ty: BuiltinType::String,
2140                },
2141                ParamSchema {
2142                    name: "new",
2143                    ty: BuiltinType::String,
2144                },
2145            ],
2146            variadic: false,
2147            result: BuiltinType::String,
2148            mutating: false,
2149            fresh: false,
2150        },
2151        // Unicode-aware, by Rust's own `str::to_uppercase`.
2152        MethodSchema {
2153            name: "toUpper",
2154            generics: &[],
2155            params: &[],
2156            variadic: false,
2157            result: BuiltinType::String,
2158            mutating: false,
2159            fresh: false,
2160        },
2161        // Unicode-aware, by Rust's own `str::to_lowercase`.
2162        MethodSchema {
2163            name: "toLower",
2164            generics: &[],
2165            params: &[],
2166            variadic: false,
2167            result: BuiltinType::String,
2168            mutating: false,
2169            fresh: false,
2170        },
2171        // The number of UTF-8 bytes, which is what the object actually
2172        // holds — and, in the linear-memory backend, the object header's own
2173        // length field rather than anything read out of the payload.
2174        // `length()` beside it still counts characters and still walks them.
2175        MethodSchema {
2176            name: "byteLength",
2177            generics: &[],
2178            params: &[],
2179            variadic: false,
2180            result: BuiltinType::Int,
2181            mutating: false,
2182            fresh: false,
2183        },
2184        // The Unicode scalar value beginning at `offset` bytes, or `None`.
2185        //
2186        // `None` means every way the offset does not begin a character: at or
2187        // past the end, negative, or the interior of one. Those are
2188        // deliberately one answer rather than several. A scanner that starts
2189        // at 0 and advances by the width of what it read never produces the
2190        // last two, and the width is a function of the scalar — a `String` is
2191        // valid UTF-8 in its shortest form, so a value below 0x80 occupies
2192        // one byte, below 0x800 two, below 0x10000 three, and four otherwise.
2193        // That is why there is no `nextByteOffset` here to pair with it, and
2194        // why this can answer a bare `Int`: the caller can already say where
2195        // the next one starts.
2196        //
2197        // It is total. Nothing written over it inherits a trap.
2198        // The byte at an offset, as an `Int` in `0..=255`.
2199        //
2200        // It refuses an offset outside the string rather than answering
2201        // `Option`, which is `sliceBytes`'s rule and not `get`'s: a byte
2202        // offset out of range is one this type never handed out, where an
2203        // index out of range is arithmetic a caller did about a sequence it
2204        // can count. `byteLength()` is how a caller knows the range, and a
2205        // scan that walks bytes already reads it once.
2206        //
2207        // The wrapper is what the rule is really about. A lexer asks this
2208        // once per byte of its input, and an `Option` around one byte was
2209        // measured costing more than the read: `benches/scanshape` puts a
2210        // byte scan at 78 ns against 122 with the wrapper, over the same
2211        // bytes answering the same question.
2212        MethodSchema {
2213            name: "byteAt",
2214            generics: &[],
2215            params: &[ParamSchema {
2216                name: "offset",
2217                ty: BuiltinType::Int,
2218            }],
2219            variadic: false,
2220            result: BuiltinType::Int,
2221            mutating: false,
2222            fresh: false,
2223        },
2224        MethodSchema {
2225            name: "codePointAtByte",
2226            generics: &[],
2227            params: &[ParamSchema {
2228                name: "offset",
2229                ty: BuiltinType::Int,
2230            }],
2231            variadic: false,
2232            result: BuiltinType::Option(&BuiltinType::Int),
2233            mutating: false,
2234            fresh: false,
2235        },
2236        // The text between two byte offsets, which must both begin a
2237        // character and be in range and in order.
2238        //
2239        // It refuses rather than clamps, which is the one place it parts from
2240        // `slice`. `slice` clamps because a character position out of range
2241        // is the caller's arithmetic about a sequence it can count; a byte
2242        // offset out of range or inside a character is an offset this type
2243        // never handed out, and quietly moving it to the nearest legal one
2244        // would produce a `String` the caller did not ask for. The invariant
2245        // that a `String` is valid UTF-8 is the thing being kept, and it is
2246        // kept here rather than anywhere downstream.
2247        MethodSchema {
2248            name: "sliceBytes",
2249            generics: &[],
2250            params: &[
2251                ParamSchema {
2252                    name: "from",
2253                    ty: BuiltinType::Int,
2254                },
2255                ParamSchema {
2256                    name: "to",
2257                    ty: BuiltinType::Int,
2258                },
2259            ],
2260            variadic: false,
2261            result: BuiltinType::Result(&BuiltinType::String, &BuiltinType::Error),
2262            mutating: false,
2263            fresh: false,
2264        },
2265        SNAPSHOT,
2266    ],
2267    // `String`'s first associated function, and the answer to the question
2268    // `chars()` leaves open in the other direction: `chars()` takes a string
2269    // apart into one-character strings, and this builds one of those from
2270    // the number that names it. There is no `Character` type to build
2271    // instead — a character in Cove is a `String` of length 1, which is what
2272    // `chars()` already answers an array of.
2273    //
2274    // It answers a `Result` for `Float.toInt`'s reason rather than
2275    // `Array.get`'s: this is a conversion between two domains where the
2276    // source has values the target has no room for, and a code point past
2277    // `0x10FFFF`, a negative one, and a surrogate half are three ways a
2278    // number can name no character. A program that read the number out of a
2279    // file is the program that should be told which.
2280    associated: &[MethodSchema {
2281        name: "fromCodePoint",
2282        generics: &[],
2283        params: &[ParamSchema {
2284            name: "codePoint",
2285            ty: BuiltinType::Int,
2286        }],
2287        variadic: false,
2288        result: BuiltinType::Result(&BuiltinType::String, &BuiltinType::Error),
2289        mutating: false,
2290        fresh: false,
2291    }],
2292};
2293
2294// ------------------------------------------------------------------- Range
2295
2296/// `Range`: what `0..n` and `0..=n` produce.
2297pub const RANGE: BuiltinSchema = BuiltinSchema {
2298    name: "Range",
2299    parameters: &[],
2300    namespace: false,
2301    cases: &[],
2302    fields: &[],
2303    methods: &[
2304        LENGTH,
2305        IS_EMPTY,
2306        MethodSchema {
2307            name: "contains",
2308            generics: &[],
2309            params: &[ParamSchema {
2310                name: "value",
2311                ty: BuiltinType::Int,
2312            }],
2313            variadic: false,
2314            result: BuiltinType::Bool,
2315            mutating: false,
2316            fresh: false,
2317        },
2318        SNAPSHOT,
2319    ],
2320    associated: &[],
2321};
2322
2323// ------------------------------------------------------------------ Option
2324
2325/// `Option<T>`: `Some(value)` or `None`.
2326///
2327/// It has no `snapshot`: whether a copy of an `Option` is independent is
2328/// decided by what it wraps, and the MVP has no bound to say that with.
2329pub const OPTION: BuiltinSchema = BuiltinSchema {
2330    name: "Option",
2331    parameters: &["T"],
2332    namespace: true,
2333    cases: &[SOME_CASE, NONE_CASE],
2334    fields: &[],
2335    methods: &[
2336        MethodSchema {
2337            name: "isSome",
2338            generics: &[],
2339            params: &[],
2340            variadic: false,
2341            result: BuiltinType::Bool,
2342            mutating: false,
2343            fresh: false,
2344        },
2345        MethodSchema {
2346            name: "isNone",
2347            generics: &[],
2348            params: &[],
2349            variadic: false,
2350            result: BuiltinType::Bool,
2351            mutating: false,
2352            fresh: false,
2353        },
2354        MethodSchema {
2355            name: "unwrapOr",
2356            generics: &[],
2357            params: &[ParamSchema {
2358                name: "fallback",
2359                ty: BuiltinType::Param("T"),
2360            }],
2361            variadic: false,
2362            result: BuiltinType::Param("T"),
2363            mutating: false,
2364            fresh: false,
2365        },
2366    ],
2367    associated: &[],
2368};
2369
2370// ------------------------------------------------------------------ Result
2371
2372/// `Result<T, E>`: `Ok(value)` or `Err(error)`.
2373pub const RESULT: BuiltinSchema = BuiltinSchema {
2374    name: "Result",
2375    parameters: &["T", "E"],
2376    namespace: true,
2377    cases: &[OK_CASE, ERR_CASE],
2378    fields: &[],
2379    methods: &[
2380        MethodSchema {
2381            name: "isOk",
2382            generics: &[],
2383            params: &[],
2384            variadic: false,
2385            result: BuiltinType::Bool,
2386            mutating: false,
2387            fresh: false,
2388        },
2389        MethodSchema {
2390            name: "isError",
2391            generics: &[],
2392            params: &[],
2393            variadic: false,
2394            result: BuiltinType::Bool,
2395            mutating: false,
2396            fresh: false,
2397        },
2398        // `Option.unwrapOr`'s sibling, and deliberately the same signature
2399        // word for word: the fallback is the type the value would have
2400        // carried, and the result is that type whichever case the receiver
2401        // is. It sits here, after the two queries, because that is where
2402        // `Option` puts it — the methods that ask, and then the one that
2403        // takes the value out. What it does *not* do is see the error, which
2404        // is `mapError`'s job below.
2405        MethodSchema {
2406            name: "unwrapOr",
2407            generics: &[],
2408            params: &[ParamSchema {
2409                name: "fallback",
2410                ty: BuiltinType::Param("T"),
2411            }],
2412            variadic: false,
2413            result: BuiltinType::Param("T"),
2414            mutating: false,
2415            fresh: false,
2416        },
2417        // Declares `fn(E) -> F`, matched exactly like any other callback in
2418        // the language (ADR 0044). A callback that does not want the error
2419        // still names its parameter — `_` is not a parameter name — so
2420        // `result.mapError(fn(error) { ... })` is the one shape there is.
2421        MethodSchema {
2422            name: "mapError",
2423            generics: &["F"],
2424            params: &[ParamSchema {
2425                name: "body",
2426                ty: BuiltinType::Fn(&[BuiltinType::Param("E")], &BuiltinType::Param("F")),
2427            }],
2428            variadic: false,
2429            result: BuiltinType::Result(&BuiltinType::Param("T"), &BuiltinType::Param("F")),
2430            mutating: false,
2431            fresh: false,
2432        },
2433    ],
2434    associated: &[],
2435};
2436
2437// --------------------------------------------------------------------- Int
2438
2439/// `Int`: a signed 64-bit integer.
2440///
2441/// Parsing fails on text that is not one, which is an expected failure and so
2442/// a `Result` rather than a trap.
2443pub const INT: BuiltinSchema = BuiltinSchema {
2444    name: "Int",
2445    parameters: &[],
2446    namespace: true,
2447    cases: &[],
2448    fields: &[],
2449    methods: &[
2450        // The nearest `Float`. This is exact for magnitudes below 2^53 and
2451        // rounds to the nearest representable value above it, which is what
2452        // IEEE 754 does and what any lossless-only conversion would have to
2453        // refuse instead.
2454        MethodSchema {
2455            name: "toFloat",
2456            generics: &[],
2457            params: &[],
2458            variadic: false,
2459            result: BuiltinType::Float,
2460            mutating: false,
2461            fresh: false,
2462        },
2463        // The magnitude. `Int` is two's complement, so the most negative
2464        // `Int` has no positive counterpart; that case is an overflow, and
2465        // it stops the run with the same kind of error `+` reports, because
2466        // the Language Card already calls integer overflow a broken
2467        // invariant rather than a wrapped result.
2468        MethodSchema {
2469            name: "abs",
2470            generics: &[],
2471            params: &[],
2472            variadic: false,
2473            result: BuiltinType::Int,
2474            mutating: false,
2475            fresh: false,
2476        },
2477        // The lesser of the receiver and `other`.
2478        MethodSchema {
2479            name: "min",
2480            generics: &[],
2481            params: &[ParamSchema {
2482                name: "other",
2483                ty: BuiltinType::Int,
2484            }],
2485            variadic: false,
2486            result: BuiltinType::Int,
2487            mutating: false,
2488            fresh: false,
2489        },
2490        // The greater of the receiver and `other`.
2491        MethodSchema {
2492            name: "max",
2493            generics: &[],
2494            params: &[ParamSchema {
2495                name: "other",
2496                ty: BuiltinType::Int,
2497            }],
2498            variadic: false,
2499            result: BuiltinType::Int,
2500            mutating: false,
2501            fresh: false,
2502        },
2503        SNAPSHOT,
2504    ],
2505    associated: &[
2506        MethodSchema {
2507            name: "parse",
2508            generics: &[],
2509            params: &[ParamSchema {
2510                name: "text",
2511                ty: BuiltinType::String,
2512            }],
2513            variadic: false,
2514            result: BuiltinType::Result(&BuiltinType::Int, &BuiltinType::Error),
2515            mutating: false,
2516            fresh: false,
2517        },
2518        // The same reading in a base other than ten, and a second function
2519        // rather than a second parameter on `parse`. A builtin's parameters
2520        // are a name and a type and nothing else — there is nowhere in this
2521        // vocabulary to write a default, and every `ParamSig` the checker
2522        // builds from one says `has_default: false` — so a radix on `parse`
2523        // would have to be an argument every caller supplies, which would
2524        // change what `Int.parse(text)` means. It does not change: `parse`
2525        // is decimal, and this is the one that asks.
2526        //
2527        // `radix` is 2 through 36, which is as many digits as the letters
2528        // afford. A radix outside that names no notation, so it stops the
2529        // run rather than answering `Err` — the same line `String.split`
2530        // draws at an empty separator, and the line is between an argument
2531        // the program got wrong and text the data got wrong.
2532        MethodSchema {
2533            name: "parseRadix",
2534            generics: &[],
2535            params: &[
2536                ParamSchema {
2537                    name: "text",
2538                    ty: BuiltinType::String,
2539                },
2540                ParamSchema {
2541                    name: "radix",
2542                    ty: BuiltinType::Int,
2543                },
2544            ],
2545            variadic: false,
2546            result: BuiltinType::Result(&BuiltinType::Int, &BuiltinType::Error),
2547            mutating: false,
2548            fresh: false,
2549        },
2550    ],
2551};
2552
2553// ------------------------------------------------------------------- Float
2554
2555/// `Float`: a 64-bit binary floating-point number.
2556///
2557/// `parse` fails on text that is not one, which is an expected failure and so
2558/// a `Result`, exactly as `Int.parse` is. `toInt` is a `Result` for a second
2559/// reason on top of that one: `NaN`, an infinity, and a magnitude `Int` has no
2560/// room for are three ways a conversion can have nothing to answer, and a
2561/// program that read the number out of a file is the program that should be
2562/// handling them.
2563///
2564/// `Float` is IEEE 754 and stops at nothing, so nothing here traps: `abs`,
2565/// `round`, `sqrt`, `min`, and `max` are total. `format` is the one
2566/// exception, and what it refuses is its own argument rather than the
2567/// value.
2568pub const FLOAT: BuiltinSchema = BuiltinSchema {
2569    name: "Float",
2570    parameters: &[],
2571    namespace: true,
2572    cases: &[],
2573    fields: &[],
2574    methods: &[
2575        // Truncated toward zero. This is a `Result` rather than a trap
2576        // because the value usually came from outside the program: `NaN`, an
2577        // infinity, and anything whose truncation falls outside `Int`'s
2578        // range are all expected failures, and the error names which of them
2579        // happened.
2580        MethodSchema {
2581            name: "toInt",
2582            generics: &[],
2583            params: &[],
2584            variadic: false,
2585            result: BuiltinType::Result(&BuiltinType::Int, &BuiltinType::Error),
2586            mutating: false,
2587            fresh: false,
2588        },
2589        // The nearest whole `Float`, halfway cases rounded away from zero —
2590        // Rust's own `f64::round`.
2591        MethodSchema {
2592            name: "round",
2593            generics: &[],
2594            params: &[],
2595            variadic: false,
2596            result: BuiltinType::Float,
2597            mutating: false,
2598            fresh: false,
2599        },
2600        // The magnitude.
2601        MethodSchema {
2602            name: "abs",
2603            generics: &[],
2604            params: &[],
2605            variadic: false,
2606            result: BuiltinType::Float,
2607            mutating: false,
2608            fresh: false,
2609        },
2610        // The square root. IEEE 754 requires a correctly-rounded `sqrt`,
2611        // so this is exact on every conforming machine — unlike a
2612        // trigonometric or exponential function, which the standard leaves
2613        // free to differ in the last bit between two implementations. That
2614        // exactness is the entire reason this method exists (issue #250)
2615        // rather than a wider set of maths functions.
2616        //
2617        // It traps on nothing, the same as `abs` and `round`: a negative
2618        // operand answers `NaN`, matching Rust's `f64::sqrt` and IEEE 754
2619        // both, with one exception IEEE 754 carves out and Rust follows —
2620        // `(-0.0).sqrt()` is `-0.0`, not `NaN`. `Float`'s `NaN` and
2621        // signed-zero semantics are otherwise still undecided (issue #254),
2622        // and this does not decide them: it answers what the hardware
2623        // answers and nothing more.
2624        MethodSchema {
2625            name: "sqrt",
2626            generics: &[],
2627            params: &[],
2628            variadic: false,
2629            result: BuiltinType::Float,
2630            mutating: false,
2631            fresh: false,
2632        },
2633        // The lesser of the receiver and `other`. Rust's own `f64::min`
2634        // answers whichever operand is not `NaN`, and answers `NaN` only
2635        // when both are; this does the same rather than deciding anything of
2636        // its own.
2637        MethodSchema {
2638            name: "min",
2639            generics: &[],
2640            params: &[ParamSchema {
2641                name: "other",
2642                ty: BuiltinType::Float,
2643            }],
2644            variadic: false,
2645            result: BuiltinType::Float,
2646            mutating: false,
2647            fresh: false,
2648        },
2649        // The greater of the receiver and `other`, by the same rule as
2650        // `min`: Rust's own `f64::max` answers whichever operand is not
2651        // `NaN`, and `NaN` only when both are.
2652        MethodSchema {
2653            name: "max",
2654            generics: &[],
2655            params: &[ParamSchema {
2656                name: "other",
2657                ty: BuiltinType::Float,
2658            }],
2659            variadic: false,
2660            result: BuiltinType::Float,
2661            mutating: false,
2662            fresh: false,
2663        },
2664        // The value written with exactly `digits` digits after the decimal
2665        // point. `digits` outside `0..=17` is a runtime error: a `Float`
2666        // carries at most 17 significant decimal digits, so anything beyond
2667        // that is padding rather than precision, and a negative count names
2668        // nothing.
2669        MethodSchema {
2670            name: "format",
2671            generics: &[],
2672            params: &[ParamSchema {
2673                name: "digits",
2674                ty: BuiltinType::Int,
2675            }],
2676            variadic: false,
2677            result: BuiltinType::String,
2678            mutating: false,
2679            fresh: false,
2680        },
2681        SNAPSHOT,
2682    ],
2683    // Mirrors `Int.parse` exactly in shape: `Ok` or an `Error` whose message
2684    // says the text is not a `Float`. It accepts what Rust's `f64::from_str`
2685    // accepts, which includes `inf`, `-inf`, and `NaN` — consistent with the
2686    // Language Card's "Float is IEEE 754 and stops at nothing" — and, exactly
2687    // like `Int.parse` today, it does not accept the `_` digit separators a
2688    // literal may be written with. That is `Int.parse`'s existing behaviour,
2689    // not a new choice made here.
2690    associated: &[MethodSchema {
2691        name: "parse",
2692        generics: &[],
2693        params: &[ParamSchema {
2694            name: "text",
2695            ty: BuiltinType::String,
2696        }],
2697        variadic: false,
2698        result: BuiltinType::Result(&BuiltinType::Float, &BuiltinType::Error),
2699        mutating: false,
2700        fresh: false,
2701    }],
2702};
2703
2704// -------------------------------------------------------------------- Bool
2705
2706/// `Bool`.
2707pub const BOOL: BuiltinSchema = BuiltinSchema {
2708    name: "Bool",
2709    parameters: &[],
2710    namespace: true,
2711    cases: &[],
2712    fields: &[],
2713    methods: &[SNAPSHOT],
2714    associated: &[],
2715};
2716
2717// -------------------------------------------------------------------- Unit
2718
2719/// `Unit`, written `()`: what an expression that produces nothing produces.
2720pub const UNIT: BuiltinSchema = BuiltinSchema {
2721    name: "Unit",
2722    parameters: &[],
2723    namespace: false,
2724    cases: &[],
2725    fields: &[],
2726    methods: &[SNAPSHOT],
2727    associated: &[],
2728};
2729
2730// ---------------------------------------------------------------- Duration
2731
2732/// `Duration`: a signed count of nanoseconds.
2733///
2734/// # One function per literal suffix, in both directions
2735///
2736/// `500ms` is a literal and was, until these existed, the only way to have a
2737/// `Duration` at all: a program that read `250` out of a manifest, an
2738/// argument, or the environment had no expression that turned it into the
2739/// `Duration` `clock.sleep` takes (issue #146). The six associated functions
2740/// are that expression, and there is **exactly one per suffix the lexer
2741/// accepts** — `ns`, `us`, `ms`, `s`, `m`, `h` — so that
2742/// `Duration.seconds(1)` and `1s` are the same value and the reader has one
2743/// table to learn rather than two. Nothing about a literal changes: `1s` is
2744/// still 1,000,000,000 nanoseconds, written the way it always was.
2745///
2746/// The six methods are the same table read backwards, which is what lets a
2747/// duration be *reported* as well as built: `d.millis()` is the whole number
2748/// of milliseconds in `d`. That direction is not free of a choice, so it is
2749/// written down — see the entries.
2750///
2751/// A unit is a function name rather than an argument because a builtin
2752/// parameter is a name and a type and nothing else: a `Duration.of(count,
2753/// unit)` would need a unit type to pass, which would be a seventh builtin
2754/// enum existing only to be an argument. This is `Int.parse`/`Int.parseRadix`
2755/// answering the same pressure the same way.
2756///
2757/// Scalar multiplication — `Duration * Int` — was the other shape and is not
2758/// this one. It is a smaller change and it can only build: there is no
2759/// expression made of `*` that reads a count back out, and issue #146 asks
2760/// for both directions because a timeout that can be configured is a timeout
2761/// that gets reported.
2762///
2763/// # What a builder does with a count it cannot hold
2764///
2765/// **A negative count is a negative duration and nothing else.** A
2766/// `Duration` is *signed* nanoseconds, `-1h` is already a value a program can
2767/// write, and `Duration.hours(-1)` is that value. A builder that refused one
2768/// would be narrower than the literal it mirrors.
2769///
2770/// **A count whose nanoseconds do not fit in an `Int` stops the run**, in the
2771/// words `Duration` arithmetic already stops it in. The Language Card calls
2772/// integer overflow a broken invariant rather than a wrapped result, and
2773/// `1h + 1h` past the end already trapped; a builder that answered a
2774/// `Result` instead would make the same overflow two different kinds of
2775/// event depending on how the duration was reached.
2776pub const DURATION: BuiltinSchema = BuiltinSchema {
2777    name: "Duration",
2778    parameters: &[],
2779    // A program now writes the name: `Duration.millis(n)` is how a computed
2780    // duration is built.
2781    namespace: true,
2782    cases: &[],
2783    fields: &[],
2784    methods: &[
2785        // Each answers the whole number of its unit in the receiver,
2786        // **truncated toward zero**, which is what `Int` division already
2787        // does and is why `1500ms.seconds()` is 1 and `-1500ms.seconds()`
2788        // is -1. Truncating rather than rounding is what makes
2789        // `d.seconds()` and `d.nanos() / 1_000_000_000` the same number, so
2790        // a program that reads a duration one way and one that reads it the
2791        // other cannot disagree.
2792        //
2793        // None of the six can fail: every unit divides into a count that
2794        // fits where the nanoseconds already did.
2795        DURATION_NANOS,
2796        DURATION_MICROS,
2797        DURATION_MILLIS,
2798        DURATION_SECONDS,
2799        DURATION_MINUTES,
2800        DURATION_HOURS,
2801        SNAPSHOT,
2802    ],
2803    associated: &[
2804        DURATION_OF_NANOS,
2805        DURATION_OF_MICROS,
2806        DURATION_OF_MILLIS,
2807        DURATION_OF_SECONDS,
2808        DURATION_OF_MINUTES,
2809        DURATION_OF_HOURS,
2810    ],
2811};
2812
2813/// `nanos(count: Int) -> Duration`, which is `count` written `<count>ns`.
2814const DURATION_OF_NANOS: MethodSchema = duration_builder("nanos");
2815/// `micros(count: Int) -> Duration`, which is `count` written `<count>us`.
2816const DURATION_OF_MICROS: MethodSchema = duration_builder("micros");
2817/// `millis(count: Int) -> Duration`, which is `count` written `<count>ms`.
2818const DURATION_OF_MILLIS: MethodSchema = duration_builder("millis");
2819/// `seconds(count: Int) -> Duration`, which is `count` written `<count>s`.
2820const DURATION_OF_SECONDS: MethodSchema = duration_builder("seconds");
2821/// `minutes(count: Int) -> Duration`, which is `count` written `<count>m`.
2822const DURATION_OF_MINUTES: MethodSchema = duration_builder("minutes");
2823/// `hours(count: Int) -> Duration`, which is `count` written `<count>h`.
2824const DURATION_OF_HOURS: MethodSchema = duration_builder("hours");
2825
2826/// `nanos() -> Int`, the receiver's whole nanoseconds. This one is exact.
2827const DURATION_NANOS: MethodSchema = duration_reader("nanos");
2828/// `micros() -> Int`, the receiver's whole microseconds, toward zero.
2829const DURATION_MICROS: MethodSchema = duration_reader("micros");
2830/// `millis() -> Int`, the receiver's whole milliseconds, toward zero.
2831const DURATION_MILLIS: MethodSchema = duration_reader("millis");
2832/// `seconds() -> Int`, the receiver's whole seconds, toward zero.
2833const DURATION_SECONDS: MethodSchema = duration_reader("seconds");
2834/// `minutes() -> Int`, the receiver's whole minutes, toward zero.
2835const DURATION_MINUTES: MethodSchema = duration_reader("minutes");
2836/// `hours() -> Int`, the receiver's whole hours, toward zero.
2837const DURATION_HOURS: MethodSchema = duration_reader("hours");
2838
2839/// One of the six `Duration.<unit>(count)` associated functions.
2840///
2841/// Written as a function rather than six literals because the six differ in
2842/// one word: a table where the entries differ only in a name is a table one
2843/// of whose entries can be wrong on its own.
2844const fn duration_builder(name: &'static str) -> MethodSchema {
2845    MethodSchema {
2846        name,
2847        generics: &[],
2848        params: &[ParamSchema {
2849            name: "count",
2850            ty: BuiltinType::Int,
2851        }],
2852        variadic: false,
2853        result: BuiltinType::Duration,
2854        mutating: false,
2855        fresh: false,
2856    }
2857}
2858
2859/// One of the six `duration.<unit>()` methods, the builders read backwards.
2860const fn duration_reader(name: &'static str) -> MethodSchema {
2861    MethodSchema {
2862        name,
2863        generics: &[],
2864        params: &[],
2865        variadic: false,
2866        result: BuiltinType::Int,
2867        mutating: false,
2868        fresh: false,
2869    }
2870}
2871
2872// ------------------------------------------------------------------- Error
2873
2874/// `Error`, the builtin error struct.
2875///
2876/// It is a namespace because a program writes the name — `Error("message")`
2877/// builds one — so a mistyped `Error.something()` should be told what `Error`
2878/// is rather than that the name is undeclared. There is nothing to call on
2879/// it: the message is read as a field, and [`MESSAGE_FIELD`] is that field.
2880pub const ERROR: BuiltinSchema = BuiltinSchema {
2881    name: "Error",
2882    parameters: &[],
2883    namespace: true,
2884    cases: &[],
2885    fields: &[MESSAGE_FIELD],
2886    methods: &[],
2887    associated: &[],
2888};
2889
2890// -------------------------------------------------------------------- Task
2891
2892/// `Task<T>`: the handle `scope.spawn { ... }` hands back.
2893///
2894/// `cancel` only asks. A cancelled task stops at its next safepoint, and
2895/// whether it stopped or had already finished is known only once something
2896/// waits for it.
2897pub const TASK: BuiltinSchema = BuiltinSchema {
2898    name: "Task",
2899    parameters: &["T"],
2900    namespace: false,
2901    cases: &[],
2902    fields: &[],
2903    methods: &[
2904        MethodSchema {
2905            name: "await",
2906            generics: &[],
2907            params: &[],
2908            variadic: false,
2909            result: BuiltinType::Param("T"),
2910            mutating: false,
2911            fresh: false,
2912        },
2913        MethodSchema {
2914            name: "cancel",
2915            generics: &[],
2916            params: &[],
2917            variadic: false,
2918            result: BuiltinType::Unit,
2919            mutating: false,
2920            fresh: false,
2921        },
2922    ],
2923    associated: &[],
2924};
2925
2926// ------------------------------------------------------------------ Shared
2927
2928/// `Shared<T>`: mutable state more than one task may reach.
2929///
2930/// `lock` is its only operation, and there is no `get` and no `set` by
2931/// design: every access is scoped, so a read-modify-write is one expression
2932/// and cannot be split into two that race. The closure receives the wrapped
2933/// value and `lock` produces whatever the closure does.
2934pub const SHARED: BuiltinSchema = BuiltinSchema {
2935    name: "Shared",
2936    parameters: &["T"],
2937    namespace: false,
2938    cases: &[],
2939    fields: &[],
2940    methods: &[MethodSchema {
2941        name: "lock",
2942        generics: &["R"],
2943        params: &[ParamSchema {
2944            name: "body",
2945            ty: BuiltinType::Fn(&[BuiltinType::Param("T")], &BuiltinType::Param("R")),
2946        }],
2947        variadic: false,
2948        result: BuiltinType::Param("R"),
2949        mutating: false,
2950        fresh: false,
2951    }],
2952    associated: &[],
2953};
2954
2955// ------------------------------------------------------------------- Scope
2956
2957/// `Scope`: the value `scope name { ... }` binds.
2958///
2959/// `spawn` takes its body as a trailing closure and hands back a handle to
2960/// the value that body produces.
2961pub const SCOPE: BuiltinSchema = BuiltinSchema {
2962    name: "Scope",
2963    parameters: &[],
2964    namespace: false,
2965    cases: &[],
2966    fields: &[],
2967    methods: &[MethodSchema {
2968        name: "spawn",
2969        generics: &["T"],
2970        params: &[ParamSchema {
2971            name: "body",
2972            ty: BuiltinType::Fn(&[], &BuiltinType::Param("T")),
2973        }],
2974        variadic: false,
2975        result: BuiltinType::Task(&BuiltinType::Param("T")),
2976        mutating: false,
2977        fresh: false,
2978    }],
2979    associated: &[],
2980};
2981
2982#[cfg(test)]
2983mod tests {
2984    use super::*;
2985
2986    #[test]
2987    fn types_render_in_cove_source_form() {
2988        assert_eq!(BuiltinType::Int.to_string(), "Int");
2989        assert_eq!(
2990            BuiltinType::Option(&BuiltinType::Param("T")).to_string(),
2991            "Option<T>"
2992        );
2993        assert_eq!(
2994            BuiltinType::Map(&BuiltinType::Param("K"), &BuiltinType::Param("V")).to_string(),
2995            "Map<K, V>"
2996        );
2997        assert_eq!(
2998            BuiltinType::MapEntry(&BuiltinType::Param("K"), &BuiltinType::Param("V")).to_string(),
2999            "MapEntry<K, V>"
3000        );
3001        assert_eq!(BuiltinType::SelfType.to_string(), "Self");
3002        assert_eq!(
3003            BuiltinType::Fn(&[BuiltinType::Param("T")], &BuiltinType::Param("R")).to_string(),
3004            "fn(T) -> R"
3005        );
3006    }
3007
3008    #[test]
3009    fn signatures_read_like_source() {
3010        assert_eq!(
3011            MAP.method("inserted").unwrap().signature(),
3012            "inserted(key: K, value: V) -> Map<K, V>"
3013        );
3014        assert_eq!(
3015            VECTOR.associated_function("of").unwrap().signature(),
3016            "of(items: T...) -> Vector<T>"
3017        );
3018        assert_eq!(
3019            SHARED.method("lock").unwrap().signature(),
3020            "lock(body: fn(T) -> R) -> R"
3021        );
3022        assert_eq!(
3023            ARRAY.method("snapshot").unwrap().signature(),
3024            "snapshot() -> Self"
3025        );
3026    }
3027
3028    /// Every method both sequences declare is one declaration, reached
3029    /// through either.
3030    ///
3031    /// A `Vector` that answered a question with a different signature than
3032    /// an `Array` is the drift these share constants to prevent, and
3033    /// comparing the two tables entry for entry is what makes the sharing a
3034    /// fact rather than an intention. It is stated over *every* shared name
3035    /// rather than over a list written here, so a method added to one and
3036    /// then to the other with a slip in it fails this without anybody
3037    /// remembering to extend it.
3038    #[test]
3039    fn a_sequence_answers_with_the_same_signature_whichever_it_is() {
3040        let shared: Vec<&str> = ARRAY
3041            .methods
3042            .iter()
3043            .filter(|method| VECTOR.method(method.name).is_some())
3044            .map(|method| method.name)
3045            .collect();
3046        assert_eq!(
3047            shared,
3048            [
3049                "get", "length", "isEmpty", "contains", "indexOf", "slice", "map", "filter",
3050                "fold", "sorted", "snapshot"
3051            ]
3052        );
3053        for name in shared {
3054            let array = ARRAY.method(name).expect("`Array` declares it");
3055            let vector = VECTOR.method(name).expect("`Vector` declares it");
3056            if name == "snapshot" {
3057                // The one declared difference: `Vector` copies its own
3058                // mutable graph, so its `snapshot` hands back storage
3059                // nothing else names and `Array`'s does not, because an
3060                // immutable sequence's `snapshot` returns itself. Comparing
3061                // the rest of the signature still catches any other drift —
3062                // see `MethodSchema::fresh`.
3063                assert!(vector.fresh, "`Vector.snapshot` should be fresh");
3064                assert!(!array.fresh, "`Array.snapshot` should not be fresh");
3065                assert_eq!(
3066                    MethodSchema {
3067                        fresh: false,
3068                        ..*vector
3069                    },
3070                    *array,
3071                    "`{name}`, apart from `fresh`"
3072                );
3073                continue;
3074            }
3075            assert_eq!(array, vector, "`{name}`");
3076        }
3077        // `Set` answers membership with the sequences' own declaration,
3078        // because it is the sequences' own question.
3079        assert_eq!(SET.method("contains"), ARRAY.method("contains"));
3080        assert_eq!(
3081            ARRAY.method("contains").unwrap().signature(),
3082            "contains(element: T) -> Bool"
3083        );
3084        assert_eq!(
3085            ARRAY.method("indexOf").unwrap().signature(),
3086            "indexOf(element: T) -> Option<Int>"
3087        );
3088        assert_eq!(
3089            VECTOR.method("slice").unwrap().signature(),
3090            "slice(from: Int, to: Int) -> Array<T>"
3091        );
3092        assert_eq!(
3093            ARRAY.method("sorted").unwrap().signature(),
3094            "sorted(by: fn(T, T) -> Bool) -> Array<T>"
3095        );
3096        assert_eq!(
3097            ARRAY.method("map").unwrap().signature(),
3098            "map(transform: fn(T) -> R) -> Array<R>"
3099        );
3100        assert_eq!(
3101            ARRAY.method("filter").unwrap().signature(),
3102            "filter(keep: fn(T) -> Bool) -> Array<T>"
3103        );
3104        assert_eq!(
3105            VECTOR.method("fold").unwrap().signature(),
3106            "fold(initial: R, step: fn(R, T) -> R) -> R"
3107        );
3108    }
3109
3110    /// The names a program may write before a dot with no value in front of
3111    /// them. Both the compiler and the runtime ask this question, and both
3112    /// ask it here.
3113    #[test]
3114    fn the_namespaces_are_the_builtin_types_a_program_writes_the_name_of() {
3115        let namespaces: Vec<&str> = BUILTINS
3116            .iter()
3117            .filter(|entry| entry.namespace)
3118            .map(|entry| entry.name)
3119            .collect();
3120        assert_eq!(
3121            namespaces,
3122            [
3123                "Array", "Vector", "Map", "Set", "String", "Option", "Result", "Int", "Float",
3124                "Bool", "Duration", "Error"
3125            ]
3126        );
3127        assert!(is_builtin_type("Vector"));
3128        // `Duration` joined the list when it gained the six builders that
3129        // turn a number a program computed into one.
3130        assert!(is_builtin_type("Duration"));
3131        assert!(!is_builtin_type("Task"));
3132    }
3133
3134    /// One `Duration.<unit>(count)` per suffix a duration literal may be
3135    /// written with, and one `duration.<unit>()` reading it back.
3136    ///
3137    /// The pairing is the point: a unit a program can build in and cannot
3138    /// report in would make a configured timeout unprintable, and a unit
3139    /// the lexer accepts and no function names would make `1s` and
3140    /// `Duration.seconds(1)` two vocabularies.
3141    #[test]
3142    fn a_duration_is_built_and_read_in_the_units_a_literal_is_written_in() {
3143        let built: Vec<&str> = DURATION.associated.iter().map(|f| f.name).collect();
3144        assert_eq!(
3145            built,
3146            ["nanos", "micros", "millis", "seconds", "minutes", "hours"]
3147        );
3148        for name in &built {
3149            let builder = DURATION
3150                .associated_function(name)
3151                .expect("a builder for the unit");
3152            assert_eq!(
3153                builder.signature(),
3154                format!("{name}(count: Int) -> Duration")
3155            );
3156            let reader = DURATION.method(name).expect("a reader for the same unit");
3157            assert_eq!(reader.signature(), format!("{name}() -> Int"));
3158        }
3159        // `snapshot` is the one method that is not a unit, so the readers
3160        // and the builders are otherwise the same list.
3161        let read: Vec<&str> = DURATION
3162            .methods
3163            .iter()
3164            .map(|method| method.name)
3165            .filter(|name| *name != SNAPSHOT.name)
3166            .collect();
3167        assert_eq!(read, built);
3168    }
3169
3170    /// `push`, `set`, `pop`, `remove`, and `freeze` are the language's only
3171    /// `var self` methods, and the call site asks by name because it has no
3172    /// receiver type yet.
3173    ///
3174    /// Every one of them is an imperative verb, which is the half of the
3175    /// naming rule this can state: a past participle in this list would be a
3176    /// name that says it answers a new collection while writing through the
3177    /// receiver.
3178    #[test]
3179    fn the_mutating_methods_are_the_ones_that_write_through_the_receiver() {
3180        let mut mutating: Vec<&str> = BUILTINS
3181            .iter()
3182            .flat_map(|entry| entry.methods)
3183            .filter(|method| method.mutating)
3184            .map(|method| method.name)
3185            .collect();
3186        mutating.sort_unstable();
3187        assert_eq!(mutating, ["freeze", "pop", "push", "remove", "set"]);
3188        assert!(is_mutating_method("push"));
3189        assert!(is_mutating_method("set"));
3190        assert!(is_mutating_method("pop"));
3191        assert!(is_mutating_method("remove"));
3192        assert!(!is_mutating_method("get"));
3193        assert!(!is_mutating_method("toArray"));
3194        // The past participles answer a new collection and write through
3195        // nothing, so none of them is here — `Vector.remove` and
3196        // `Set.removed` are two operations and the names say which is which.
3197        assert!(!is_mutating_method("removed"));
3198        assert!(!is_mutating_method("inserted"));
3199        assert!(!is_mutating_method("sorted"));
3200    }
3201
3202    /// The round trip a program makes between the immutable sequence and the
3203    /// mutable one is spelled in two names, one per direction.
3204    #[test]
3205    fn a_sequence_converts_to_the_other_kind_in_one_name_each_way() {
3206        assert_eq!(
3207            ARRAY.method("toVector").unwrap().signature(),
3208            "toVector() -> Vector<T>"
3209        );
3210        assert_eq!(
3211            VECTOR.method("toArray").unwrap().signature(),
3212            "toArray() -> Array<T>"
3213        );
3214        // Neither type answers its own kind: an independent `Vector` from a
3215        // `Vector` is `snapshot()`, and an `Array` is already immutable.
3216        assert!(VECTOR.method("toVector").is_none());
3217        assert!(ARRAY.method("toArray").is_none());
3218    }
3219
3220    /// What a vector answers when an element is taken out of it, and what it
3221    /// answers when there is none to take.
3222    #[test]
3223    fn a_vector_shrinks_by_the_same_rule_about_indices_that_it_reads_by() {
3224        assert_eq!(
3225            VECTOR.method("pop").unwrap().signature(),
3226            "pop() -> Option<T>"
3227        );
3228        assert_eq!(
3229            VECTOR.method("remove").unwrap().signature(),
3230            "remove(index: Int) -> Option<T>"
3231        );
3232        // `get`, `set`, `pop` and `remove` all answer an `Option<T>`, which
3233        // is the one rule about indices written four times rather than four
3234        // rules.
3235        for name in ["get", "set", "pop", "remove"] {
3236            assert_eq!(
3237                VECTOR.method(name).unwrap().result,
3238                BuiltinType::Option(&BuiltinType::Param("T")),
3239                "`{name}`"
3240            );
3241        }
3242        // There is no `clear`: see this type's own documentation for why the
3243        // one operation with nothing to answer is the one left out.
3244        assert!(VECTOR.method("clear").is_none());
3245    }
3246
3247    /// A type parameter a signature names is either one the receiver binds or
3248    /// one the signature binds itself. A third kind would be a name nothing
3249    /// at the call site could instantiate.
3250    #[test]
3251    fn every_type_parameter_a_signature_names_is_bound() {
3252        fn named(ty: &BuiltinType, found: &mut Vec<&'static str>) {
3253            match ty {
3254                BuiltinType::Param(name) => found.push(name),
3255                BuiltinType::Array(inner)
3256                | BuiltinType::Vector(inner)
3257                | BuiltinType::Set(inner)
3258                | BuiltinType::Option(inner)
3259                | BuiltinType::Task(inner)
3260                | BuiltinType::Shared(inner) => named(inner, found),
3261                BuiltinType::Map(left, right)
3262                | BuiltinType::MapEntry(left, right)
3263                | BuiltinType::Result(left, right) => {
3264                    named(left, found);
3265                    named(right, found);
3266                }
3267                BuiltinType::Fn(inputs, ret) => {
3268                    for input in *inputs {
3269                        named(input, found);
3270                    }
3271                    named(ret, found);
3272                }
3273                _ => {}
3274            }
3275        }
3276
3277        for entry in BUILTINS {
3278            let signatures = entry
3279                .methods
3280                .iter()
3281                .map(|method| (method, entry.parameters))
3282                .chain(entry.associated.iter().map(|method| (method, &[][..])));
3283            for (method, receiver) in signatures {
3284                let mut found = Vec::new();
3285                for param in method.params {
3286                    named(&param.ty, &mut found);
3287                }
3288                named(&method.result, &mut found);
3289                for name in found {
3290                    assert!(
3291                        receiver.contains(&name) || method.generics.contains(&name),
3292                        "`{}.{}` names `{name}`, which nothing binds",
3293                        entry.name,
3294                        method.name
3295                    );
3296                }
3297            }
3298        }
3299
3300        // A case's payload and a field's type name only the receiver's
3301        // parameters: there is no signature of their own to bind one.
3302        for entry in BUILTINS {
3303            let mut found = Vec::new();
3304            for case in entry.cases {
3305                for payload in case.payload {
3306                    named(payload, &mut found);
3307                }
3308            }
3309            for field in entry.fields {
3310                named(&field.ty, &mut found);
3311            }
3312            for name in found {
3313                assert!(
3314                    entry.parameters.contains(&name),
3315                    "`{}` names `{name}`, which nothing binds",
3316                    entry.name
3317                );
3318            }
3319        }
3320
3321        // A free builtin has no receiver, so every name it uses is one it
3322        // binds itself.
3323        for entry in FREE_BUILTINS {
3324            let mut found = Vec::new();
3325            for param in entry.params {
3326                named(&param.ty, &mut found);
3327            }
3328            named(&entry.result, &mut found);
3329            for name in found {
3330                assert!(
3331                    entry.generics.contains(&name),
3332                    "`{}` names `{name}`, which nothing binds",
3333                    entry.name
3334                );
3335            }
3336        }
3337    }
3338
3339    /// The two kinds of builtin that are called on nothing, in the order
3340    /// both ends ask about them.
3341    #[test]
3342    fn the_free_builtins_are_five_constructors_and_two_assertions() {
3343        let constructors: Vec<&str> = FREE_BUILTINS
3344            .iter()
3345            .filter(|entry| entry.kind == FreeBuiltinKind::Constructor)
3346            .map(|entry| entry.name)
3347            .collect();
3348        assert_eq!(constructors, ["Ok", "Err", "Some", "Error", "Shared"]);
3349        let assertions: Vec<&str> = FREE_BUILTINS
3350            .iter()
3351            .filter(|entry| entry.kind == FreeBuiltinKind::Assertion)
3352            .map(|entry| entry.name)
3353            .collect();
3354        assert_eq!(assertions, ["assert", "assertEqual"]);
3355        // `None` is the one name that reads like a constructor and is not:
3356        // it is the empty case written bare, and a call is a mistake both
3357        // ends name as one.
3358        assert!(free_builtin("None").is_none());
3359    }
3360
3361    /// A constructor carries one value; an assertion takes what it compares.
3362    #[test]
3363    fn a_free_builtin_reads_like_source_and_knows_its_arity() {
3364        assert_eq!(
3365            free_builtin("Ok").unwrap().signature(),
3366            "Ok(value: T) -> Result<T, E>"
3367        );
3368        assert_eq!(
3369            free_builtin("Error").unwrap().signature(),
3370            "Error(message: String) -> Error"
3371        );
3372        assert_eq!(
3373            free_builtin("Shared").unwrap().signature(),
3374            "Shared(value: T) -> Shared<T>"
3375        );
3376        assert_eq!(
3377            free_builtin("assertEqual").unwrap().signature(),
3378            "assertEqual(actual: T, expected: T) -> Result<Unit, Error>"
3379        );
3380        for entry in FREE_BUILTINS {
3381            assert_eq!(entry.arity(), entry.params.len(), "`{}`", entry.name);
3382            if entry.kind == FreeBuiltinKind::Constructor {
3383                assert_eq!(entry.arity(), 1, "`{}` carries one value", entry.name);
3384            }
3385        }
3386    }
3387
3388    /// The receivers a `count()` call is taught the spelling on are the ones
3389    /// that answer `length()`, which is what closed the drift between the
3390    /// checker's list and the runtime's.
3391    #[test]
3392    fn the_sequences_are_the_builtin_types_that_declare_length() {
3393        let sequences: Vec<&str> = BUILTINS
3394            .iter()
3395            .filter(|entry| declares_length(entry.name))
3396            .map(|entry| entry.name)
3397            .collect();
3398        assert_eq!(
3399            sequences,
3400            ["Array", "Vector", "Map", "Set", "String", "Range"]
3401        );
3402        assert!(!declares_length("Option"));
3403        assert!(!declares_length("Nothing"));
3404    }
3405
3406    /// The builtin enums are two, and these are the four names that used to
3407    /// be written out in `cove-sema` and again in `cove-runtime`.
3408    #[test]
3409    fn the_builtin_enums_are_option_and_result() {
3410        let enums: Vec<(&str, Vec<&str>)> = BUILTINS
3411            .iter()
3412            .filter(|entry| entry.is_enum())
3413            .map(|entry| {
3414                (
3415                    entry.name,
3416                    entry.cases.iter().map(|case| case.name).collect(),
3417                )
3418            })
3419            .collect();
3420        assert_eq!(
3421            enums,
3422            [
3423                ("Option", vec!["Some", "None"]),
3424                ("Result", vec!["Ok", "Err"]),
3425            ]
3426        );
3427        assert_eq!(OPTION.case("Some").unwrap().signature(), "Some(T)");
3428        assert_eq!(OPTION.case("None").unwrap().signature(), "None");
3429        assert_eq!(RESULT.case("Err").unwrap().signature(), "Err(E)");
3430        assert!(RESULT.case("Nothing").is_none());
3431    }
3432
3433    /// A case name belongs to one builtin enum, which is what lets a bare
3434    /// `Some(value)` arm say which enum a `match` is over.
3435    #[test]
3436    fn a_case_name_names_one_builtin_enum() {
3437        assert_eq!(
3438            enum_declaring("Some").map(|entry| entry.name),
3439            Some("Option")
3440        );
3441        assert_eq!(
3442            enum_declaring("None").map(|entry| entry.name),
3443            Some("Option")
3444        );
3445        assert_eq!(enum_declaring("Ok").map(|entry| entry.name), Some("Result"));
3446        assert_eq!(
3447            enum_declaring("Err").map(|entry| entry.name),
3448            Some("Result")
3449        );
3450        assert!(enum_declaring("Confirmed").is_none());
3451        let mut seen: Vec<&str> = Vec::new();
3452        for entry in BUILTINS {
3453            for case in entry.cases {
3454                assert!(
3455                    !seen.contains(&case.name),
3456                    "`{}` is a case of two builtin enums",
3457                    case.name
3458                );
3459                seen.push(case.name);
3460            }
3461        }
3462    }
3463
3464    /// `None` is the one builtin case that carries nothing, which is why it
3465    /// is the one written as a bare name rather than as a call.
3466    #[test]
3467    fn none_is_the_only_builtin_case_that_carries_nothing() {
3468        let empty: Vec<&str> = BUILTINS
3469            .iter()
3470            .flat_map(|entry| entry.cases)
3471            .filter(|case| case.payload.is_empty())
3472            .map(|case| case.name)
3473            .collect();
3474        assert_eq!(empty, ["None"]);
3475        assert!(free_builtin(NONE_CASE.name).is_none());
3476    }
3477
3478    /// The builtin structs are two, and their fields are what the runtime
3479    /// has always built and the checker used to deny.
3480    #[test]
3481    fn the_builtin_structs_are_error_and_map_entry() {
3482        let structs: Vec<(&str, Vec<String>)> = BUILTINS
3483            .iter()
3484            .filter(|entry| entry.is_struct())
3485            .map(|entry| {
3486                (
3487                    entry.name,
3488                    entry
3489                        .fields
3490                        .iter()
3491                        .map(|field| format!("{}: {}", field.name, field.ty))
3492                        .collect(),
3493                )
3494            })
3495            .collect();
3496        assert_eq!(
3497            structs,
3498            [
3499                (
3500                    "MapEntry",
3501                    vec!["key: K".to_string(), "value: V".to_string()]
3502                ),
3503                ("Error", vec!["message: String".to_string()]),
3504            ]
3505        );
3506        assert!(ERROR.field("code").is_none());
3507    }
3508
3509    /// `Error("boom")` takes the field the value it builds carries, so the
3510    /// label a call writes and the label a read writes are one word.
3511    #[test]
3512    fn the_error_constructor_takes_the_field_an_error_carries() {
3513        let param = ERROR_OF.params.first().expect("`Error` takes a message");
3514        let field = ERROR.fields.first().expect("an `Error` carries one");
3515        assert_eq!(param.name, field.name);
3516        assert_eq!(param.ty, field.ty);
3517    }
3518
3519    /// A builtin is a struct or an enum or neither, never both: a value has
3520    /// cases to match or fields to read, and nothing in the language has
3521    /// each.
3522    #[test]
3523    fn no_builtin_type_has_both_cases_and_fields() {
3524        for entry in BUILTINS {
3525            assert!(
3526                !(entry.is_enum() && entry.is_struct()),
3527                "`{}` declares both cases and fields",
3528                entry.name
3529            );
3530        }
3531    }
3532
3533    /// An associated function is called on the type, so there is no receiver
3534    /// for `Self` to mean and none to mutate.
3535    #[test]
3536    fn an_associated_function_has_no_receiver_to_name() {
3537        for entry in BUILTINS {
3538            for method in entry.associated {
3539                assert!(!method.mutating, "`{}.{}`", entry.name, method.name);
3540                assert_ne!(
3541                    method.result,
3542                    BuiltinType::SelfType,
3543                    "`{}.{}`",
3544                    entry.name,
3545                    method.name
3546                );
3547            }
3548        }
3549    }
3550
3551    /// A variadic signature's last parameter is the one that repeats, so a
3552    /// signature with no parameters cannot be variadic.
3553    #[test]
3554    fn a_variadic_signature_has_a_parameter_to_repeat() {
3555        for entry in BUILTINS {
3556            for method in entry.methods.iter().chain(entry.associated) {
3557                assert!(
3558                    !method.variadic || !method.params.is_empty(),
3559                    "`{}.{}`",
3560                    entry.name,
3561                    method.name
3562                );
3563            }
3564        }
3565    }
3566
3567    /// A [`StdBinding`] names a receiver and a method or an associated
3568    /// function, and it has to be real: a table can name one that does not
3569    /// exist where a field on [`MethodSchema`] could not, because a field is
3570    /// only ever read off a method that is already there. This is the test
3571    /// that closes that gap — and, since [`StdBindingKind`] split one name
3572    /// into two possible call forms, it holds a [`StdBindingKind::Method`]
3573    /// entry to naming a method and a [`StdBindingKind::Associated`] entry
3574    /// to naming an associated function, rather than either satisfying the
3575    /// other: `Duration.millis(n)` existing does not mean `d.millis()`
3576    /// does, and a table that only checked "some function by this name"
3577    /// would not catch the two bindings landing on the wrong list.
3578    #[test]
3579    fn every_std_binding_names_a_method_that_exists() {
3580        for entry in STANDARD_LIBRARY {
3581            let receiver = builtin(entry.receiver)
3582                .unwrap_or_else(|| panic!("`{}` is not a builtin type", entry.receiver));
3583            match entry.kind {
3584                StdBindingKind::Method => assert!(
3585                    receiver.method(entry.method).is_some(),
3586                    "`{}` has no method `{}`",
3587                    entry.receiver,
3588                    entry.method
3589                ),
3590                StdBindingKind::Associated => assert!(
3591                    receiver.associated_function(entry.method).is_some(),
3592                    "`{}` has no associated function `{}`",
3593                    entry.receiver,
3594                    entry.method
3595                ),
3596            }
3597        }
3598    }
3599}