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(¶m.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(¶m.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}