beck_diag/
index.rs

1//! The error index: every code the compiler can emit, in one place.
2//!
3//! [`crate::Diagnostic::code`] promised "a stable code (`B0101`) so that tests, docs and the
4//! eventual error index agree". This is that index, and the agreement is a test rather than an
5//! intention: `beck-cli/tests/docs.rs` scans every non-test source file in the workspace for a
6//! `"Bnnnn"` literal and fails if the set it finds differs from [`INDEX`] in either direction. A
7//! code cannot be added without an entry, and an entry cannot outlive its code.
8//!
9//! # What an entry says, and where it comes from
10//!
11//! Every diagnostic site in the compiler already carries a label, usually a note and often a
12//! fix-it — prose the project wrote next to the condition that raises it.
13//! [`CodeEntry::explain`] is that prose, condensed. Nothing here is a second, independent account
14//! of what the compiler does, because a second account is a thing that goes out of date.
15//!
16//! Ranges are not decorative: the first two digits say which stage refused the program, which is
17//! usually the first question.
18//!
19//! | Range | Stage |
20//! |---|---|
21//! | `B01xx` | Reading the source — lexer, S-expression reader, parser |
22//! | `B02xx` | Macro expansion, and the `ui` block it desugars |
23//! | `B03xx` | Names, types and effects — the checker |
24//! | `B04xx` | Placement (`B040x`) and security (`B041x`) |
25//! | `B05xx` | The signal graph and the slicer |
26//! | `B06xx` | Modules, interfaces and separate compilation |
27//! | `B07xx` | Tests written in Beck |
28
29/// The stage that raises a code.
30#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
31pub enum Stage {
32    Syntax,
33    Macros,
34    Types,
35    Placement,
36    Security,
37    Signals,
38    Modules,
39    Tests,
40}
41
42impl Stage {
43    pub fn title(&self) -> &'static str {
44        match self {
45            Stage::Syntax => "Reading the source",
46            Stage::Macros => "Macro expansion",
47            Stage::Types => "Names, types and effects",
48            Stage::Placement => "Placement",
49            Stage::Security => "Placement and security",
50            Stage::Signals => "The signal graph and the slicer",
51            Stage::Modules => "Modules and interfaces",
52            Stage::Tests => "Tests written in Beck",
53        }
54    }
55
56    /// Every stage, in the order the compiler runs them — which is the order the index is read in.
57    pub fn all() -> &'static [Stage] {
58        &[
59            Stage::Syntax,
60            Stage::Macros,
61            Stage::Types,
62            Stage::Placement,
63            Stage::Security,
64            Stage::Signals,
65            Stage::Modules,
66            Stage::Tests,
67        ]
68    }
69}
70
71/// One code, as the index describes it.
72#[derive(Copy, Clone, Debug, PartialEq, Eq)]
73pub struct CodeEntry {
74    pub code: &'static str,
75    pub stage: Stage,
76    /// The diagnostic's message, with the parts that vary at the call site generalised.
77    pub title: &'static str,
78    /// Why the compiler refuses this, in the terms the diagnostic itself uses.
79    pub explain: &'static str,
80    /// True for the codes raised as warnings rather than errors.
81    pub warning: bool,
82}
83
84/// Look a code up. The lookup `beck explain error B0301` and the rendered index both use.
85pub fn lookup(code: &str) -> Option<&'static CodeEntry> {
86    INDEX.iter().find(|e| e.code.eq_ignore_ascii_case(code))
87}
88
89/// Every code in one stage, in code order.
90pub fn in_stage(stage: Stage) -> impl Iterator<Item = &'static CodeEntry> {
91    INDEX.iter().filter(move |e| e.stage == stage)
92}
93
94const fn e(
95    code: &'static str,
96    stage: Stage,
97    title: &'static str,
98    explain: &'static str,
99) -> CodeEntry {
100    CodeEntry {
101        code,
102        stage,
103        title,
104        explain,
105        warning: false,
106    }
107}
108
109const fn w(
110    code: &'static str,
111    stage: Stage,
112    title: &'static str,
113    explain: &'static str,
114) -> CodeEntry {
115    CodeEntry {
116        code,
117        stage,
118        title,
119        explain,
120        warning: true,
121    }
122}
123
124/// Every code the compiler can emit, in code order.
125///
126/// Sorted, and the test asserts it: an index a reader has to search is an index nobody reads.
127pub const INDEX: &[CodeEntry] = &[
128    // ------------------------------------------------------------------ B01xx: reading the source
129    e(
130        "B0100",
131        Stage::Syntax,
132        "unrecognised character",
133        "The character is not a Beck token. Lexing continues past it so that one stray character \
134         does not hide every other problem in the file.",
135    ),
136    e(
137        "B0101",
138        Stage::Syntax,
139        "inconsistent indentation",
140        "This line's indentation matches no enclosing block. Indentation is significant, and \
141         §2.6 fixes it as spaces only, four per level — a tab is a lint rather than a width \
142         question. Blank and comment-only lines have no indentation at all, so a comment at \
143         column zero does not close a block.",
144    ),
145    e(
146        "B0102",
147        Stage::Syntax,
148        "an invisible control character in the source",
149        "A bidirectional formatting character (UTS #39 §4.1; CVE-2021-42574) or a zero-width \
150         no-break space away from the start of the file. These change how the text after them is \
151         *displayed* without changing what it means, so a reviewer and the compiler can read the \
152         same file differently. Write `\\u{...}` if a string genuinely needs one.",
153    ),
154    e(
155        "B0103",
156        Stage::Syntax,
157        "an identifier outside the ASCII profile",
158        "Beck's identifiers are `[A-Za-z_][A-Za-z0-9_]*`, which is UTS #39's ASCII-Only \
159         restriction level. Confusable and mixed-script identifiers are therefore not something \
160         the compiler checks for; they are something a program cannot contain.",
161    ),
162    e(
163        "B0110",
164        Stage::Syntax,
165        "expected a single form",
166        "`beck ast` and the macro-debugging paths read exactly one S-expression. A file with a \
167         second form at top level is a module, and is read with the module reader instead.",
168    ),
169    e(
170        "B0111",
171        Stage::Syntax,
172        "unbalanced closing delimiter",
173        "A `)`, `]` or `}` with no matching opening delimiter.",
174    ),
175    e(
176        "B0112",
177        Stage::Syntax,
178        "unclosed list",
179        "A list opened here and the file ended before it closed. The span points at the opening \
180         delimiter, which is the one worth looking at.",
181    ),
182    e(
183        "B0113",
184        Stage::Syntax,
185        "mismatched closing delimiter",
186        "The closing delimiter is not the one this form opened with — `(a]`. Both spans are \
187         reported: where it opened, and where it was closed wrongly.",
188    ),
189    e(
190        "B0114",
191        Stage::Syntax,
192        "empty application",
193        "`()` has no meaning: an application's head is its first element, and there is none. \
194         Write `unit` for the unit value.",
195    ),
196    e(
197        "B0115",
198        Stage::Syntax,
199        "unclosed string",
200        "A string literal opened and the line or file ended before the closing quote. Beck string \
201         literals do not span lines.",
202    ),
203    e(
204        "B0116",
205        Stage::Syntax,
206        "unreadable character",
207        "The S-expression reader cannot begin an atom with this character.",
208    ),
209    e(
210        "B0120",
211        Stage::Syntax,
212        "unexpected token",
213        "The Python-surface parser expected something else here; the message names what. One \
214         error per item: the parser recovers to the next top-level item, so a bad line does not \
215         make the rest of the file unparseable.",
216    ),
217    e(
218        "B0121",
219        Stage::Syntax,
220        "nesting is too deep to read",
221        "The source nests deeper than the front end follows — `beck_diag::depth::MAX_NESTING` \
222         levels of brackets, indentation or S-expression lists. The bound is a fixed count rather \
223         than a reading of the stack, so the same file is accepted or refused identically in every \
224         build; without it, deep enough input aborted the process with no span at all.",
225    ),
226    e(
227        "B0122",
228        Stage::Syntax,
229        "an expression chains more operators than the reader will follow",
230        "A left-associative chain — `1 + 1 + 1 + …` — is flat in source and builds a left-leaning \
231         tree of the same depth, one level per operator. The Pratt loop that reads it does not \
232         recurse, so none of the parser's recursion counters sees the depth, and a long enough \
233         chain reached the end of the host stack in whatever walked the tree afterwards. The bound \
234         is `beck_diag::depth::MAX_BLOCK` — the same ceiling a block of sequential bindings takes, \
235         because it is the same axis: a flat run of things that costs one tree level each.",
236    ),
237    // ------------------------------------------------------------------------ B02xx: macros
238    e(
239        "B0200",
240        Stage::Macros,
241        "macro is defined twice",
242        "Two macros in one module share a name, and a later definition would silently win.",
243    ),
244    e(
245        "B0201",
246        Stage::Macros,
247        "macro expansion did not terminate",
248        "Expansion ran past the depth limit — usually a macro that expands to a call to itself.",
249    ),
250    e(
251        "B0202",
252        Stage::Macros,
253        "macro expects an argument",
254        "A parameter of the macro received nothing at this call site. The macro's definition is \
255         reported as a secondary span.",
256    ),
257    e(
258        "B0203",
259        Stage::Macros,
260        "too many arguments for macro",
261        "More arguments than the macro declares parameters.",
262    ),
263    e(
264        "B0204",
265        Stage::Macros,
266        "macro returns nothing",
267        "A macro body ends without `return quote: …`, so there is no template to instantiate.",
268    ),
269    e(
270        "B0205",
271        Stage::Macros,
272        "unsupported statement in a macro body",
273        "Phase 1 macro bodies are `let` bindings and a final `return quote: …`. Arbitrary \
274         compile-time computation arrives with the macro interpreter, which is not built.",
275    ),
276    e(
277        "B0206",
278        Stage::Macros,
279        "unquoting an unbound name",
280        "`$x` inside a template names something that is not a parameter of this macro.",
281    ),
282    e(
283        "B0210",
284        Stage::Macros,
285        "`ui` needs an indented block",
286        "Write `ui:` followed by an indented element.",
287    ),
288    e(
289        "B0211",
290        Stage::Macros,
291        "`ui` block is empty",
292        "A view must produce exactly one root element.",
293    ),
294    e(
295        "B0212",
296        Stage::Macros,
297        "`ui` block has more than one root",
298        "An `Html` value is a single tree. Wrap the elements in one — a `div:` or `main:` block.",
299    ),
300    e(
301        "B0213",
302        Stage::Macros,
303        "the form nests too deep to expand",
304        "The expander walks a form's arguments as deeply as they nest, and stops at the count the \
305         reader stops at. This is not `B0201`: nothing here says a macro failed to terminate — the \
306         two were one counter until they were separated, and a deep expression with no macros in \
307         it reported the wrong one.",
308    ),
309    // ------------------------------------------------------- B03xx: names, types and effects
310    e(
311        "B0300",
312        Stage::Types,
313        "not a tier",
314        "`@on(…)` takes `client`, `server`, `data` or `any`.",
315    ),
316    w(
317        "B0301",
318        Stage::Types,
319        "unsupported decorator",
320        "The compiler understands `@on(client|server|data|any)` and `@signal`. A warning rather \
321         than an error, so an unknown decorator does not stop a build — but it does nothing.",
322    ),
323    e(
324        "B0302",
325        Stage::Types,
326        "type is declared twice",
327        "Two declarations in this module share a type name.",
328    ),
329    e(
330        "B0303",
331        Stage::Types,
332        "a top-level parameter needs a type annotation",
333        "Inference is intra-module and boundaries are declared (§3.6): a top-level definition's \
334         parameters are part of its published signature, so they are written rather than guessed.",
335    ),
336    e(
337        "B0304",
338        Stage::Types,
339        "needs a return type",
340        "Same reason as B0303: a top-level definition's result type is part of its contract.",
341    ),
342    e(
343        "B0305",
344        Stage::Types,
345        "neither an effect nor a row",
346        "A `uses` clause names effect atoms and row aliases, and this is neither. `beck doc \
347         reference` lists the atom set; a `row Name = …` declaration in the module is what makes a \
348         name for a bundle of them.",
349    ),
350    e(
351        "B0306",
352        Stage::Types,
353        "is not a rendering mode",
354        "`@render` takes `server` (Mode A: the server renders and the wire carries DOM patches) \
355         or `client` (Mode B: the browser renders and the wire carries the state).",
356    ),
357    e(
358        "B0307",
359        Stage::Types,
360        "unsupported top-level item",
361        "This form is not something a module may contain at top level.",
362    ),
363    e(
364        "B0308",
365        Stage::Types,
366        "expected a type",
367        "A type position holds something that is not a type expression.",
368    ),
369    e(
370        "B0310",
371        Stage::Types,
372        "cannot find type",
373        "No declaration, import or builtin of that name is in scope.",
374    ),
375    e(
376        "B0311",
377        Stage::Types,
378        "wrong number of type arguments",
379        "A mention of a type carries one argument per declared parameter — `union Tree[T]` is \
380         written `Tree[Int]`, never bare `Tree`.",
381    ),
382    e(
383        "B0312",
384        Stage::Types,
385        "a type alias defined in terms of itself, or a name the language reserves",
386        "An alias is transparent, so a self-referential one describes no type — a `union` may be \
387         recursive, an alias may not. The same code covers a definition named after a reserved \
388         form such as `record`, which would compile and never be reachable.",
389    ),
390    e(
391        "B0313",
392        Stage::Types,
393        "a type parameter takes no type arguments",
394        "A type parameter of the definition or declaration being read names an unknown type, so it \
395         has no structure to apply arguments to: `T[Int]` says nothing, whatever `T` turns out to \
396         be at the call site.",
397    ),
398    e(
399        "B0314",
400        Stage::Types,
401        "a type parameter shadows an existing type",
402        "A type parameter is a name the definition or declaration invents, and one that shadowed \
403         an existing type would make its fields or its signature read as though they mentioned \
404         that type.",
405    ),
406    e(
407        "B0315",
408        Stage::Types,
409        "a type parameter is repeated",
410        "The same name appears twice in one type-parameter list, where the second would silently \
411         shadow the first.",
412    ),
413    e(
414        "B0316",
415        Stage::Types,
416        "a declaration cannot bound its type parameter",
417        "A bound says what a body may call, and a `model`, a `union`, a `newtype` and a `type` have \
418         no body. The definitions that take the type apart are where the bound belongs.",
419    ),
420    e(
421        "B0317",
422        Stage::Types,
423        "a declaration shadows a builtin type",
424        "`Int`, `Str`, `Bool`, `Float`, `Unit`, `Html`, `Attr`, `list`, `Map`, `Stream`, `Signal`, \
425         `Envelope`, `secret` and `internal` are the language's own type names, and a `model`, \
426         `union`, `newtype` or `type` may not take one. B0314 has refused the same shadowing for a \
427         type *parameter* since docs/36; a declaration was the production it did not cover, so the \
428         name meant the builtin in one signature and the declaration in the next (docs/87 §87.4).",
429    ),
430    e(
431        "B0320",
432        Stage::Types,
433        "type mismatch",
434        "Unification failed; the message names what was being unified — an argument, a field, a \
435         result, or the two branches of an `if`. The branches of an `if` are reported as two \
436         alternatives rather than as actual-and-expected: typing one as the other's expectation \
437         is what refused SICP exercise 1.43 (docs/27 §27.3).",
438    ),
439    w(
440        "B0330",
441        Stage::Types,
442        "statements after `return` are unreachable",
443        "A `return` ends its block; anything after it never runs.",
444    ),
445    e(
446        "B0331",
447        Stage::Types,
448        "loops are not available in Phase 1",
449        "Everything is an expression and `var` is not yet mutable, so a loop has nothing to \
450         accumulate into. Use `map_list`, `filter_list` or `fold`.",
451    ),
452    e(
453        "B0332",
454        Stage::Types,
455        "a `quote` survived macro expansion",
456        "A template reached the checker, which means it was never instantiated. Quoted forms are \
457         data for a macro, not code.",
458    ),
459    e(
460        "B0333",
461        Stage::Types,
462        "a keyword argument outside a call",
463        "`name=value` is call syntax and has no meaning as an expression on its own.",
464    ),
465    e(
466        "B0334",
467        Stage::Types,
468        "unsupported expression",
469        "This form has no meaning in expression position.",
470    ),
471    e(
472        "B0335",
473        Stage::Types,
474        "has no body",
475        "A bodyless `def` is a declaration, which is what a `.becki` interface file is made of; \
476         an ordinary module has to define what it declares.",
477    ),
478    e(
479        "B0340",
480        Stage::Types,
481        "cannot find name in this scope",
482        "No local, parameter, top-level definition, import or prelude name matches — after \
483         hygiene, which means a name a macro introduced is not visible to code the macro did not \
484         write.",
485    ),
486    e(
487        "B0341",
488        Stage::Types,
489        "match is not exhaustive",
490        "The missing variants are listed. This is deliberate and load-bearing: adding a variant \
491         must break every fold that consumes it, which is what makes a missed migration a compile \
492         error rather than a 3 a.m. page.",
493    ),
494    e(
495        "B0342",
496        Stage::Types,
497        "unsupported pattern",
498        "A `case` arm's pattern is a variant applied to field bindings, or `_`. This form is \
499         neither.",
500    ),
501    e(
502        "B0343",
503        Stage::Types,
504        "not a constructor",
505        "The head of a pattern must be a variant of the union being matched.",
506    ),
507    e(
508        "B0344",
509        Stage::Types,
510        "cannot tell which field this binds or sets",
511        "A pattern or record update names a field the compiler cannot resolve to the type in hand.",
512    ),
513    e(
514        "B0345",
515        Stage::Types,
516        "the tail of a list pattern is a name, not a pattern",
517        "The tail of a list pattern binds the rest of the list, so it takes a name or `_`. \
518         `[a, *[b, c]]` is `[a, b, c]` written twice over.",
519    ),
520    e(
521        "B0346",
522        Stage::Types,
523        "cannot tell which model this record builds",
524        "A record literal's type comes from what is expected of it, and nothing here says which \
525         model it is.",
526    ),
527    e(
528        "B0347",
529        Stage::Types,
530        "expected a field or method name",
531        "The right-hand side of a `.` must be a name.",
532    ),
533    e(
534        "B0348",
535        Stage::Types,
536        "`with` takes named fields",
537        "Functional record update is written `t.with(done=…)`.",
538    ),
539    e(
540        "B0349",
541        Stage::Types,
542        "no such field on this type",
543        "The field named in a `with` is not declared on the model being updated.",
544    ),
545    e(
546        "B0350",
547        Stage::Types,
548        "no field or function for this type",
549        "A `.` reached neither a field of the receiver nor a function that could take it.",
550    ),
551    e(
552        "B0351",
553        Stage::Types,
554        "wrong number of arguments",
555        "A call passes a different number of arguments than the function or constructor takes.",
556    ),
557    e(
558        "B0352",
559        Stage::Types,
560        "not callable",
561        "The callee's type is not a function type.",
562    ),
563    e(
564        "B0353",
565        Stage::Types,
566        "no such variant",
567        "The union being constructed or matched has no variant of that name. The type is named in \
568         the message.",
569    ),
570    e(
571        "B0354",
572        Stage::Types,
573        "cannot construct this type",
574        "The name is a type, but not one with a constructor — an alias or a builtin.",
575    ),
576    e(
577        "B0355",
578        Stage::Types,
579        "this case can never match",
580        "Every value the arm matches is already matched by an arm above it, so it cannot run. A \
581         warning rather than an error: `case _` written after every variant of a union is a habit \
582         rather than a mistake.",
583    ),
584    e(
585        "B0356",
586        Stage::Types,
587        "the alternatives of an or-pattern bind different names",
588        "Every alternative of `a | b` has to bind the same names at the same types, because the \
589         body reads them without knowing which one matched.",
590    ),
591    e(
592        "B0357",
593        Stage::Types,
594        "`|` and `@` are only meaningful in a `case` pattern",
595        "Beck has no bitwise operators. `|` separates the alternatives of an or-pattern, `@` names \
596         the value a pattern takes apart, and neither means anything anywhere else.",
597    ),
598    e(
599        "B0358",
600        Stage::Types,
601        "the left of `@` is a name",
602        "`whole @ Circle(r)` binds `whole` to the value the pattern matched. What stands to the \
603         left of `@` is the name being bound.",
604    ),
605    e(
606        "B0359",
607        Stage::Types,
608        "an identity declaration this compiler cannot derive a deployment from",
609        "`identity = external(issuer=\"https://login.acme.com\")` and `identity = managed()` are \
610         the two forms, and a program declares at most one. An external issuer is an `https` URL — \
611         the key set's only integrity protection is the transport it arrives over — and its host \
612         has to be one an egress rule could name, because §6.5 derives the cluster's rule from it. \
613         `managed()` takes no arguments: it provisions a provider into the object graph, and the \
614         issuer is a Service the deployment names rather than the program.",
615    ),
616    e(
617        "B0360",
618        Stage::Types,
619        "cannot be called inside a fold",
620        "A fold must be replay-pure, and this would make replay non-deterministic. Time is data on \
621         the envelope (`env.at`) and entity ids are minted at the edge: mint the id in the \
622         client's command and read it from the event.",
623    ),
624    e(
625        "B0370",
626        Stage::Types,
627        "performs more than its signature declares",
628        "The undeclared atoms are listed. A `uses` clause is the published bound, and widening it \
629         is a breaking API change — so the compiler will not widen it for you.",
630    ),
631    // -------------------------------------------------------------------- B038x: traits
632    e(
633        "B0380",
634        Stage::Types,
635        "a trait cannot be declared here",
636        "The name already belongs to a type, the trait is declared twice, or the file is a \
637         `.becki` — a trait does not cross a module boundary, so an interface may not hold one.",
638    ),
639    e(
640        "B0381",
641        Stage::Types,
642        "a trait declaration is wrong",
643        "A trait holds `def` signatures with no bodies, each mentioning `Self` in a parameter so \
644         that a call has something to dispatch on. A method name belongs to one trait only.",
645    ),
646    e(
647        "B0382",
648        Stage::Types,
649        "an impl does not match its trait",
650        "An impl writes bodies and parameter *names*; the types, the return type and the effect \
651         row are the trait's. Every method must be implemented, exactly once, and no others.",
652    ),
653    e(
654        "B0383",
655        Stage::Types,
656        "cannot find the trait or the type this impl names",
657        "Both halves of `impl Trait for Type` have to resolve before the impl can be registered.",
658    ),
659    e(
660        "B0384",
661        Stage::Types,
662        "conflicting implementations",
663        "Coherence: one impl per trait per type constructor, and no blanket impl over a type \
664         parameter — so what a call means never depends on which impls happen to be in scope.",
665    ),
666    e(
667        "B0385",
668        Stage::Types,
669        "orphan impl",
670        "An impl belongs with the trait or with the type. Implementing somebody else's trait for \
671         somebody else's type is what lets two modules supply one and disagree.",
672    ),
673    e(
674        "B0386",
675        Stage::Types,
676        "no implementation can be chosen here",
677        "An implementation comes from a concrete type or from a bound on a type parameter — write \
678         `[T: Trait]` to say the parameter has one. A trait method and a bounded definition are \
679         both called rather than passed: the implementation is supplied at the call site, so a \
680         reference that is never called has nowhere to receive it.",
681    ),
682    e(
683        "B0387",
684        Stage::Types,
685        "the type does not implement the trait",
686        "There is no `impl Trait for Type` in scope for the receiver's type.",
687    ),
688    e(
689        "B0389",
690        Stage::Types,
691        "a block has more statements than the checker will follow",
692        "A block is a chain of `let`s however flat it looks in source, so the checker recurses once \
693         per statement and a long enough body reaches the end of the host stack. The bound is \
694         `beck_diag::depth::MAX_BLOCK` — much larger than the nesting ceiling, because 256 levels \
695         of nesting is pathological and 256 sequential bindings is merely a long function. It is a \
696         fixed count rather than a reading of the stack, so the same file is accepted or refused \
697         identically in a debug and a release build; without it, a long enough body aborted the \
698         process with no span at all.",
699    ),
700    e(
701        "B0390",
702        Stage::Types,
703        "the expression nests too deep to check",
704        "The checker walks an expression and a type as deeply as they nest, and stops at \
705         `beck_diag::depth::MAX_NESTING` levels — the same count the reader stops at, because the \
706         checker can be handed a tree a macro produced rather than one anybody typed. Everything \
707         downstream walks the `Core` this pass built, so it is bounded by the same number.",
708    ),
709    e(
710        "B0391",
711        Stage::Types,
712        "a raised value must have a declared type",
713        "`raise` performs `raises(T)`, and the atom names `T` so that a handler can say what it \
714         catches. A builtin will not do: `raises(Int)` would make every integer failure in a \
715         program the same failure, and a handler could not tell them apart.",
716    ),
717    e(
718        "B0392",
719        Stage::Types,
720        "nothing here can fail, and nothing says what this would catch",
721        "A `try:` reifies one failure into a `Result`, and it takes the error type from the \
722         enclosing signature's `Result[T, E]` where there is one and from the block's own row \
723         where there is not. Neither said anything here. Either the call you meant to make is not \
724         there, or the `try:` is left over from a signature that has stopped failing — which is \
725         the good case, and the diagnostic is how you find out.",
726    ),
727    e(
728        "B0393",
729        Stage::Types,
730        "the block can fail in more than one way, and nothing says which to catch",
731        "A `Result[T, E]` has one error type, and a `try:` catches one — the rest keep travelling, \
732         which is what makes a handler composable. Here nothing named which: give the enclosing \
733         definition a `Result[T, E]` return type, and the handler catches that `E`.",
734    ),
735    e(
736        "B0394",
737        Stage::Types,
738        "the row is declared twice",
739        "Two `row Name = …` declarations with the same name. A row alias is a name for a bundle of \
740         effect atoms, and a second one would make every `uses` clause mentioning it ambiguous.",
741    ),
742    e(
743        "B0395",
744        Stage::Types,
745        "the host of an outbound call has to be written at the call site",
746        "`http_fetch` performs `net.out(host)`, and §6.5 derives the cluster's egress policy from \
747         that atom and nothing else. A host computed at run time is an outbound call the \
748         deployment cannot be told about, so the argument is read where it is written. Compute \
749         the path, the port, the headers and the body; or take a closure, so the caller names its \
750         own host and the row carries the atom out.",
751    ),
752    e(
753        "B0396",
754        Stage::Types,
755        "that is not a host an outbound call can name",
756        "The host becomes a NetworkPolicy peer and a `uses net.out(…)` clause, both of which are \
757         written as bare DNS labels — so a scheme, a port or a path in it is a name neither could \
758         carry. `origin` is refused for a different reason: it is the one outbound atom a client \
759         tier discharges, and a client reaches its own server over the command channel.",
760    ),
761    e(
762        "B0397",
763        Stage::Types,
764        "a parallel scope has fewer than two children",
765        "A `parallel:` scope runs its bindings as children. With one there is nothing to run it \
766         alongside, and with none there is nothing to run — either way the form is claiming a \
767         concurrency it does not have, and an ordinary block says the same thing without the \
768         claim. The tail is everything after the last binding, so a scope written with its work \
769         in the tail has no children either.",
770    ),
771    e(
772        "B0398",
773        Stage::Types,
774        "a child of a parallel scope names another child",
775        "The children of a `parallel:` scope run together, so none of them can see another's \
776         result — a child that could would have to run second, and then it is not a child but a \
777         next line. Move the reader into the scope's tail, which runs after the join with every \
778         child's result in scope, or out into a second scope below this one.",
779    ),
780    e(
781        "B0399",
782        Stage::Types,
783        "a child of a parallel scope performs an effect another child could observe",
784        "The claim a `parallel:` scope makes is that its answer does not depend on the order its \
785         children ran in, and an effect on state the program holds — the log, the document, the \
786         merge point, a file, an external store — breaks it: two children appending to the log in \
787         the other order is a different log. `net.out(host)` is not on that list and is the case \
788         the form exists for. Do the shared-state part in the tail, which runs once, after the \
789         join.",
790    ),
791    // -------------------------------------------------------------------- B04xx: placement
792    e(
793        "B0400",
794        Stage::Placement,
795        "performs effects no single tier can discharge",
796        "Each tier discharges a fixed set (§3.3). A row no tier covers has to be split across \
797         definitions that can each be placed.",
798    ),
799    e(
800        "B0401",
801        Stage::Placement,
802        "placed on a tier that cannot discharge an effect it performs",
803        "The written `@on(…)` and the inferred row disagree. The diagnostic names the atom, the \
804         tier, and the tiers that could discharge it. `ingress` is the merge point and only the \
805         server holds it; `durable` is the data tier's; `dom` is the browser's.",
806    ),
807    e(
808        "B0402",
809        Stage::Placement,
810        "a fold function must be replay-pure",
811        "The function reached by a `fold` performs effects that replay would not reproduce. Both \
812         the fold and the definition are reported.",
813    ),
814    e(
815        "B0403",
816        Stage::Placement,
817        "a program has exactly one merge point",
818        "A second `merge_clients()`. The merge point is where time and nondeterminism enter; two \
819         of them would mean two total orders, and replay would no longer be a function of the log.",
820    ),
821    e(
822        "B0404",
823        Stage::Placement,
824        "cannot be unplaced",
825        "`@on(any)` means every tier, and an atom in the row is not discharged on every tier. The \
826         fix-it names the tiers that can.",
827    ),
828    e(
829        "B0405",
830        Stage::Placement,
831        "only a component can say where it renders",
832        "`@render` was written on something that is not a `Signal[Html]`. A definition is unplaced \
833         code compiled to every tier that needs it (§3.3); rendering is decided per component.",
834    ),
835    e(
836        "B0410",
837        Stage::Security,
838        "runs on the client, so its value must be Sendable",
839        "This value crosses to the browser, and §3.5's claim is that the compiler proves a secret \
840         cannot. The offending field and the path that reaches it are both named — `beck explain \
841         flow <Type>` prints the same walk.",
842    ),
843    e(
844        "B0411",
845        Stage::Security,
846        "durable, so its state must be storable",
847        "The log is the only description of this program's history; a value it cannot read back \
848         is a state replay would not reproduce.",
849    ),
850    e(
851        "B0412",
852        Stage::Security,
853        "requires a capability nothing can discharge",
854        "A `Session` reaches exactly one place in a Beck program: the validator `decide` is given, \
855         which is the only function handed a `Proposal`. Authority is one chokepoint (§3.5), so a \
856         capability required outside it has no holder.",
857    ),
858    // ------------------------------------------------------ B05xx: the signal graph and slicer
859    e(
860        "B0500",
861        Stage::Signals,
862        "this program has no merge point",
863        "A Beck application is a fold over an event stream, and the stream starts at \
864         `merge_clients()`. Not an error for a library: this code, with B0501 and B0505, is what \
865         says a module is a domain module rather than an application.",
866    ),
867    e(
868        "B0501",
869        Stage::Signals,
870        "this program has no durable state",
871        "`durable(fold(f, init, s))` is what makes the log a database.",
872    ),
873    e(
874        "B0502",
875        Stage::Signals,
876        "`durable` must wrap a `fold`",
877        "Only a fold has an accumulator to persist.",
878    ),
879    e(
880        "B0504",
881        Stage::Signals,
882        "events must come from `decide`",
883        "The fold has no chokepoint upstream of it. `decide` is the sole consumer of ingress and \
884         the one place a command becomes an event — §3.5's \"authority is one chokepoint\".",
885    ),
886    e(
887        "B0505",
888        Stage::Signals,
889        "no signal is placed on the client",
890        "`page` is the tier crossing: a `Signal[Html]` the browser subscribes to.",
891    ),
892    e(
893        "B0506",
894        Stage::Signals,
895        "not a signal",
896        "A signal's inputs are other signals. A function is applied *through* a construct — \
897         `signal_map(s, f)` — rather than named as an input.",
898    ),
899    e(
900        "B0507",
901        Stage::Signals,
902        "not a signal construct",
903        "§3.7's signal vocabulary is `merge_clients`, `filter_map`, `fold`, `durable`, \
904         `signal_map`, `map2`, `per_session` and `decide`.",
905    ),
906    e(
907        "B0508",
908        Stage::Signals,
909        "unsupported signal expression",
910        "A signal is a node in the dataflow, not a computation. The computation goes in a `def` \
911         and the signal names it: `summary: Signal[Summary] = signal_map(counts, summarise)`.",
912    ),
913    e(
914        "B0509",
915        Stage::Signals,
916        "a signal defined in terms of itself",
917        "The cycle is printed. A cycle through a `fold` is sound — an accumulator is a value, so \
918         the recursion has a bottom, which is why `events → todos → events` is legal. One with no \
919         fold in it has no first value to compute from.",
920    ),
921    e(
922        "B0510",
923        Stage::Signals,
924        "two signals are the page, and there is no router yet",
925        "The slicer will slice both; the runtime serves one document per connection, and choosing \
926         between them is routing — a Phase 3 client bullet that is not built.",
927    ),
928    e(
929        "B0511",
930        Stage::Signals,
931        "a program has one authority chokepoint",
932        "A second `decide`. §3.5 rests on validation being one place: two of them are two answers \
933         to \"may this actor do this\", and the log would record whichever ran.",
934    ),
935    e(
936        "B0512",
937        Stage::Signals,
938        "the chokepoint does not read a durable fold",
939        "`decide` threads the accumulator through validation, so what it reads has to be one — \
940         that is what makes first-writer-wins and ownership decidable (§3.7).",
941    ),
942    e(
943        "B0513",
944        Stage::Signals,
945        "a fold that is not durable",
946        "Its accumulator has nowhere to live across a restart. The log is what survives, and \
947         `durable` is what says an accumulator is folded from it.",
948    ),
949    e(
950        "B0514",
951        Stage::Signals,
952        "renders differently for each session, so it cannot render on the client",
953        "The page reads the session as well as the state, so it filters, scopes or hides by \
954         identity. `@render(client)` sends the browser the state rather than the page, which \
955         would hand every actor what the filter was removing (docs/94 §94.2).",
956    ),
957    e(
958        "B0515",
959        Stage::Signals,
960        "the chokepoint reads `presence`, which is not in the log",
961        "Who was connected when an event was recorded is written down nowhere, so a `validate` \
962         that decided from the roster would decide one thing now and another on replay. Record the \
963         fact instead: propose a command when a client arrives, and decide from the state that \
964         fold produces.",
965    ),
966    e(
967        "B0516",
968        Stage::Signals,
969        "reads `presence`, so it cannot render on the client",
970        "`@render(client)` sends the browser the accumulator, and who is connected is in neither \
971         the accumulator nor the log — it is a fact the server holds about its own sockets \
972         (docs/96 §96.4).",
973    ),
974    // --------------------------------------------------------- B06xx: modules and interfaces
975    e(
976        "B0600",
977        Stage::Modules,
978        "has a body, so it is not a signature",
979        "A `.becki` publishes what a module offers, not how it does it. Regenerate it with \
980         `beck iface`.",
981    ),
982    e(
983        "B0601",
984        Stage::Modules,
985        "defined in more than one module",
986        "Phase 2 links modules into one namespace and has no qualified reference to tell two \
987         definitions apart, so a clash is an error rather than a shadowing rule.",
988    ),
989    e(
990        "B0602",
991        Stage::Modules,
992        "a module imports itself, directly or through a cycle",
993        "A module's interface is derived from its body, so a cycle would mean each module needed \
994         the other's contract before either had one. The cycle is printed.",
995    ),
996    e(
997        "B0603",
998        Stage::Modules,
999        "cannot find module",
1000        "The loader looked for `<name>.becki` and `<name>.beck` beside the root module, and for a \
1001         standard-library module of that name, and found neither.",
1002    ),
1003    e(
1004        "B0604",
1005        Stage::Modules,
1006        "has an interface but no implementation",
1007        "An interface is enough to compile against and never enough to run.",
1008    ),
1009    e(
1010        "B0605",
1011        Stage::Modules,
1012        "does not match its published interface",
1013        "The checked-in `.becki` and the module compile to different digests. Regenerate it with \
1014         `beck iface`, and review the diff — the difference is an API change.",
1015    ),
1016    // ------------------------------------------------------------ B07xx: tests written in Beck
1017    e(
1018        "B0700",
1019        Stage::Tests,
1020        "a test block performs effects",
1021        "A test block's own row must be empty: an expectation is a pure question about a state, a \
1022         log and a page. Effects belong to the *subject*, and §21.3 stubs those.",
1023    ),
1024    e(
1025        "B0701",
1026        Stage::Tests,
1027        "a property parameter needs a type",
1028        "The generator is type-directed, so it works from the parameter's declared type. A \
1029         `property` with no types has nothing to generate.",
1030    ),
1031    e(
1032        "B0702",
1033        Stage::Tests,
1034        "not a tier, or not an effect atom",
1035        "`expect place(name) == tier` takes a tier, and a `stub` names an effect atom.",
1036    ),
1037    e(
1038        "B0703",
1039        Stage::Tests,
1040        "not something a stub can stand in for",
1041        "Time, ids and persistence are not stubbed in Beck and there is nothing to write: the \
1042         clock is data on the envelope, ids are minted at the edge, and the durable fold is real \
1043         and in memory.",
1044    ),
1045    e(
1046        "B0704",
1047        Stage::Tests,
1048        "nothing in this program performs this atom",
1049        "The stub would never be reached. The complete list of what a program touches is its \
1050         effect rows, and this atom is not among them.",
1051    ),
1052    e(
1053        "B0705",
1054        Stage::Tests,
1055        "only `given`, `when`, `stub` and `expect` may appear in a test",
1056        "§21.2: a test names a log, an input and an expectation — there is no fixture to build and \
1057         no `setUp` to write.",
1058    ),
1059    e(
1060        "B0706",
1061        Stage::Tests,
1062        "a clause needs something this program does not have",
1063        "The state a test arranges is a fold over the program's own event stream, so a program \
1064         with no `merge_clients` → `decide` → `durable(fold(…))` has nothing for `given` and \
1065         `when` to mean.",
1066    ),
1067    e(
1068        "B0707",
1069        Stage::Tests,
1070        "an atom is performed by more than one definition, so a stub cannot answer from the call",
1071        "The performers are named. A stub is a value for an effect atom; where two definitions \
1072         perform the same atom with different result types, one value cannot serve both.",
1073    ),
1074];
1075
1076#[cfg(test)]
1077mod tests {
1078    use super::*;
1079
1080    #[test]
1081    fn the_index_is_sorted_and_has_no_duplicates() {
1082        let codes: Vec<&str> = INDEX.iter().map(|e| e.code).collect();
1083        let mut sorted = codes.clone();
1084        sorted.sort_unstable();
1085        sorted.dedup();
1086        assert_eq!(codes, sorted, "the index must be sorted and unique");
1087    }
1088
1089    #[test]
1090    fn every_entry_says_something() {
1091        for entry in INDEX {
1092            assert!(
1093                entry.code.len() == 5 && entry.code.starts_with('B'),
1094                "{entry:?}"
1095            );
1096            assert!(!entry.title.is_empty(), "{entry:?}");
1097            // Long enough that "see the source" cannot pass for an explanation.
1098            assert!(entry.explain.len() > 40, "{entry:?}");
1099        }
1100    }
1101
1102    #[test]
1103    fn a_code_can_be_looked_up_either_way() {
1104        assert_eq!(lookup("B0341").map(|e| e.stage), Some(Stage::Types));
1105        assert_eq!(lookup("b0341").map(|e| e.code), Some("B0341"));
1106        assert_eq!(lookup("B9999"), None);
1107    }
1108
1109    #[test]
1110    fn every_code_belongs_to_the_stage_its_number_names() {
1111        for entry in INDEX {
1112            let band = &entry.code[1..3];
1113            let expected: &[Stage] = match band {
1114                "01" => &[Stage::Syntax],
1115                "02" => &[Stage::Macros],
1116                "03" => &[Stage::Types],
1117                // B040x is placement, B041x is the security check that runs with it.
1118                "04" => &[Stage::Placement, Stage::Security],
1119                "05" => &[Stage::Signals],
1120                "06" => &[Stage::Modules],
1121                "07" => &[Stage::Tests],
1122                other => panic!("unknown band B{other}xx in {entry:?}"),
1123            };
1124            assert!(expected.contains(&entry.stage), "{entry:?}");
1125        }
1126    }
1127}