1#[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 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
73pub struct CodeEntry {
74 pub code: &'static str,
75 pub stage: Stage,
76 pub title: &'static str,
78 pub explain: &'static str,
80 pub warning: bool,
82}
83
84pub fn lookup(code: &str) -> Option<&'static CodeEntry> {
86 INDEX.iter().find(|e| e.code.eq_ignore_ascii_case(code))
87}
88
89pub 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
124pub const INDEX: &[CodeEntry] = &[
128 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 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 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 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 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 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 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 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 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 "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}