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 "a form is not available in a macro body",
273 "A macro body is pure compile-time computation — bindings, `if`, `for`, `while`, lambdas, \
274 calls and `quote:`. `match`, `try:`, `raise`, `parallel:` and declarations belong to the \
275 program the macro expands to, not to the expander.",
276 ),
277 e(
278 "B0206",
279 Stage::Macros,
280 "unquoting an unbound name",
281 "`$x` inside a template names something that is bound nowhere in this macro — usually a \
282 parameter that was renamed. `$e` evaluates `e` in the macro body's own environment, so \
283 anything the body bound is fair game.",
284 ),
285 e(
286 "B0207",
287 Stage::Macros,
288 "a primitive may not be called while expanding a macro",
289 "Macro expansion is capability-restricted (`docs/02` §2.4): it is pure computation over \
290 the module's own definitions, so that what a compile produces depends on the source and \
291 on nothing else. The primitive named performs an effect — reading the clock, the \
292 environment, the network — and is refused *by name* so that reaching for one is a \
293 diagnostic rather than a spelling mistake.",
294 ),
295 e(
296 "B0208",
297 Stage::Macros,
298 "cannot find a name at compile time",
299 "The macro interpreter's environment is a whitelist: locals, the `def`s of this module \
300 and of the ones it imports, and the pure builtins. There is no name in it for a file, a \
301 socket or a process, which is the sandbox rather than an omission.",
302 ),
303 e(
304 "B0209",
305 Stage::Macros,
306 "a macro body computed the wrong kind of value",
307 "A compile-time computation applied an operation to something it does not apply to — \
308 adding a Str to an Int, indexing past the end of a list, calling a value that is not a \
309 function. Macro bodies are evaluated before the checker runs, so these are caught by \
310 running rather than by typing.",
311 ),
312 e(
313 "B0210",
314 Stage::Macros,
315 "`ui` needs an indented block",
316 "Write `ui:` followed by an indented element.",
317 ),
318 e(
319 "B0211",
320 Stage::Macros,
321 "`ui` block is empty",
322 "A view must produce exactly one root element.",
323 ),
324 e(
325 "B0212",
326 Stage::Macros,
327 "`ui` block has more than one root",
328 "An `Html` value is a single tree. Wrap the elements in one — a `div:` or `main:` block.",
329 ),
330 e(
331 "B0213",
332 Stage::Macros,
333 "the form nests too deep to expand",
334 "The expander walks a form's arguments as deeply as they nest, and stops at the count the \
335 reader stops at. This is not `B0201`: nothing here says a macro failed to terminate — the \
336 two were one counter until they were separated, and a deep expression with no macros in \
337 it reported the wrong one.",
338 ),
339 e(
340 "B0214",
341 Stage::Macros,
342 "macro expansion produced too much",
343 "`B0201` and `B0213` bound how *deep* expansion goes; this bounds how much it makes. A \
344 macro that doubles its output at each of a few levels is shallow, terminates, and is \
345 enormous — eight nestings of a two-line macro is 256 copies of its argument — so the \
346 expander charges what it produces against a budget for the whole module. Reaching it means \
347 a macro is generating far more than any program in this repository does, and the fix is \
348 almost never a bigger budget.",
349 ),
350 e(
351 "B0215",
352 Stage::Macros,
353 "a macro body ran too long",
354 "`B0214` bounds what expansion *produces*; this bounds what it *does*. A macro body is a \
355 Beck program running at compile time, so `while true:` in one is a compiler that does \
356 not finish — the budget is a bound on how long a compile takes, shared by the whole \
357 module because that is what a compile is.",
358 ),
359 e(
360 "B0216",
361 Stage::Macros,
362 "a macro body recursed too deep",
363 "A compile-time call chain — usually a `def` that calls itself with no base case — went \
364 past the front end's nesting ceiling. A fixed count rather than a reading of the stack, \
365 for `adr/0012`'s reason: a diagnostic that depends on the build profile is not a \
366 diagnostic.",
367 ),
368 e(
369 "B0217",
370 Stage::Macros,
371 "that is not an event the client listens for",
372 "A handler is a declarative attribute carrying a command, and the client interprets a \
373 closed set of them — `on_click`, `on_enter`, `on_submit`, `on_input`, `on_change`. Any \
374 other name reaches the browser as an attribute wired to nothing, which is why this is a \
375 refusal rather than a lint: there is no such thing as a custom event here.",
376 ),
377 e(
378 "B0218",
379 Stage::Macros,
380 "that is not an HTML attribute",
381 "A `ui:` element carries its keyword arguments to the page as attributes, and a name HTML \
382 does not have is one the browser ignores — a page that quietly does less than it says. An \
383 attribute of your own is spelled `data_…`, which is HTML's own extension point; `aria_…` \
384 is admitted the same way.",
385 ),
386 e(
387 "B0219",
388 Stage::Macros,
389 "image has no alt text",
390 "A screen reader announces an `img` by its `alt` attribute; without one it announces the \
391 file name, or nothing. `alt=\"\"` is HTML's own spelling for an image that carries no \
392 meaning and is accepted — the check is for the attribute, not for a value, because a \
393 compile-time check knows the shape of the tree and not the text in it. \
394 `a11y_exempt=\"…\"` turns it off on one element, with the reason written down.",
395 ),
396 e(
397 "B0220",
398 Stage::Macros,
399 "button has no accessible name",
400 "A `button` is named by its own text. One with no children and no `aria_label`, \
401 `aria_labelledby` or `title` announces nothing at all, which is the icon-button mistake. \
402 Any child is accepted, because whether an expression renders to empty text is not a \
403 question this stage can answer.",
404 ),
405 e(
406 "B0221",
407 Stage::Macros,
408 "form control has no label",
409 "An `input`, `select` or `textarea` is named by `aria_label`, `aria_labelledby`, `title`, \
410 or a `label(for=…)` pointing at its `id`. A **placeholder is not a label** — it disappears \
411 as soon as somebody types, which is WCAG 3.3.2's commonest real failure. An `id` is \
412 accepted as evidence of a label elsewhere, because a `ui:` block composes out of functions \
413 and this check sees one element at a time.",
414 ),
415 e(
416 "B0222",
417 Stage::Macros,
418 "class is one slip from a utility",
419 "The class vocabulary is **open** — a name this compiler does not know is a name of your \
420 own, and is left alone — so this is a warning rather than a refusal. What it says is that \
421 the name is within one edit of a utility that would have had a rule, which is what \
422 `rounded-ful` is to `rounded-full`. `beck explain style` lists which of a page's classes \
423 are utilities and which are the program's own.",
424 ),
425 e(
426 "B0223",
427 Stage::Macros,
428 "typed macro over a declaration",
429 "A `typed macro` is expanded by the **checker**, and what its body is given is what the \
430 checker inferred an *expression* to be. A declaration has nothing inferred about it — a \
431 model's fields are in its syntax — so a macro that decorates one is an ordinary `macro`, \
432 which receives it before checking runs at all. `docs/02` §2.4 is the division, and \
433 `lib/json.beck`'s `derive_json` is the shape a declaration macro takes.",
434 ),
435 e(
436 "B0224",
437 Stage::Macros,
438 "refused by a macro",
439 "A macro that reads a type and writes the code a value of it goes through meets types it \
440 has no rule for. `refuse(\"…\")` is how it says so: the message is the macro author's, the \
441 position is the call, and the second label is the line in the macro body that decided. \
442 Without it such a macro emits code that fails to check for a reason nobody can trace back \
443 to the macro.",
444 ),
445 e(
447 "B0300",
448 Stage::Types,
449 "not a tier",
450 "`@on(…)` takes `client`, `server`, `data` or `any`.",
451 ),
452 w(
453 "B0301",
454 Stage::Types,
455 "unsupported decorator",
456 "The compiler understands `@on(client|server|data|any)` and `@signal`. A warning rather \
457 than an error, so an unknown decorator does not stop a build — but it does nothing.",
458 ),
459 e(
460 "B0302",
461 Stage::Types,
462 "type is declared twice",
463 "Two declarations in this module share a type name.",
464 ),
465 e(
466 "B0303",
467 Stage::Types,
468 "a top-level parameter needs a type annotation",
469 "Inference is intra-module and boundaries are declared (§3.6): a top-level definition's \
470 parameters are part of its published signature, so they are written rather than guessed.",
471 ),
472 e(
473 "B0304",
474 Stage::Types,
475 "needs a return type",
476 "Same reason as B0303: a top-level definition's result type is part of its contract.",
477 ),
478 e(
479 "B0305",
480 Stage::Types,
481 "neither an effect nor a row",
482 "A `uses` clause names effect atoms and row aliases, and this is neither. `beck doc \
483 reference` lists the atom set; a `row Name = …` declaration in the module is what makes a \
484 name for a bundle of them.",
485 ),
486 e(
487 "B0306",
488 Stage::Types,
489 "is not a rendering mode",
490 "`@render` takes `server` (Mode A: the server renders and the wire carries DOM patches) \
491 or `client` (Mode B: the browser renders and the wire carries the state).",
492 ),
493 e(
494 "B0307",
495 Stage::Types,
496 "unsupported top-level item",
497 "This form is not something a module may contain at top level.",
498 ),
499 e(
500 "B0308",
501 Stage::Types,
502 "expected a type",
503 "A type position holds something that is not a type expression.",
504 ),
505 e(
506 "B0310",
507 Stage::Types,
508 "cannot find type",
509 "No declaration, import or builtin of that name is in scope.",
510 ),
511 e(
512 "B0311",
513 Stage::Types,
514 "wrong number of type arguments",
515 "A mention of a type carries one argument per declared parameter — `union Tree[T]` is \
516 written `Tree[Int]`, never bare `Tree`.",
517 ),
518 e(
519 "B0312",
520 Stage::Types,
521 "a type alias defined in terms of itself, or a name the language reserves",
522 "An alias is transparent, so a self-referential one describes no type — a `union` may be \
523 recursive, an alias may not. The same code covers a definition named after a reserved \
524 form such as `record`, which would compile and never be reachable.",
525 ),
526 e(
527 "B0313",
528 Stage::Types,
529 "a type parameter takes no type arguments",
530 "A type parameter of the definition or declaration being read names an unknown type, so it \
531 has no structure to apply arguments to: `T[Int]` says nothing, whatever `T` turns out to \
532 be at the call site.",
533 ),
534 e(
535 "B0314",
536 Stage::Types,
537 "a type parameter shadows an existing type",
538 "A type parameter is a name the definition or declaration invents, and one that shadowed \
539 an existing type would make its fields or its signature read as though they mentioned \
540 that type.",
541 ),
542 e(
543 "B0315",
544 Stage::Types,
545 "a type parameter is repeated",
546 "The same name appears twice in one type-parameter list, where the second would silently \
547 shadow the first.",
548 ),
549 e(
550 "B0316",
551 Stage::Types,
552 "a declaration cannot bound its type parameter",
553 "A bound says what a body may call, and a `model`, a `union`, a `newtype` and a `type` have \
554 no body. The definitions that take the type apart are where the bound belongs.",
555 ),
556 e(
557 "B0317",
558 Stage::Types,
559 "a declaration shadows a builtin type",
560 "`Int`, `Str`, `Bool`, `Float`, `Unit`, `Html`, `Attr`, `list`, `Map`, `Stream`, `Signal`, \
561 `Envelope`, `secret` and `internal` are the language's own type names, and a `model`, \
562 `union`, `newtype` or `type` may not take one. B0314 has refused the same shadowing for a \
563 type *parameter* since docs/27; a declaration was the production it did not cover, so the \
564 name meant the builtin in one signature and the declaration in the next (docs/63 §63.10).",
565 ),
566 e(
567 "B0320",
568 Stage::Types,
569 "type mismatch",
570 "Unification failed; the message names what was being unified — an argument, a field, a \
571 result, or the two branches of an `if`. The branches of an `if` are reported as two \
572 alternatives rather than as actual-and-expected: typing one as the other's expectation \
573 is what refused SICP exercise 1.43 (docs/27 §27.2).",
574 ),
575 w(
576 "B0330",
577 Stage::Types,
578 "statements after `return` are unreachable",
579 "A `return` ends its block; anything after it never runs.",
580 ),
581 e(
582 "B0331",
583 Stage::Types,
584 "loops are not available in Phase 1",
585 "Everything is an expression and `var` is not yet mutable, so a loop has nothing to \
586 accumulate into. Use `map_list`, `filter_list` or `fold`.",
587 ),
588 e(
589 "B0332",
590 Stage::Types,
591 "a `quote` survived macro expansion",
592 "A template reached the checker, which means it was never instantiated. Quoted forms are \
593 data for a macro, not code.",
594 ),
595 e(
596 "B0333",
597 Stage::Types,
598 "a keyword argument outside a call",
599 "`name=value` is call syntax and has no meaning as an expression on its own.",
600 ),
601 e(
602 "B0334",
603 Stage::Types,
604 "unsupported expression",
605 "This form has no meaning in expression position.",
606 ),
607 e(
608 "B0335",
609 Stage::Types,
610 "has no body",
611 "A bodyless `def` is a declaration, which is what a `.becki` interface file is made of; \
612 an ordinary module has to define what it declares.",
613 ),
614 e(
615 "B0340",
616 Stage::Types,
617 "cannot find name in this scope",
618 "No local, parameter, top-level definition, import or prelude name matches — after \
619 hygiene, which means a name a macro introduced is not visible to code the macro did not \
620 write.",
621 ),
622 e(
623 "B0341",
624 Stage::Types,
625 "match is not exhaustive",
626 "The missing variants are listed. This is deliberate and load-bearing: adding a variant \
627 must break every fold that consumes it, which is what makes a missed migration a compile \
628 error rather than a 3 a.m. page.",
629 ),
630 e(
631 "B0342",
632 Stage::Types,
633 "unsupported pattern",
634 "A `case` arm's pattern is a variant applied to field bindings, or `_`. This form is \
635 neither.",
636 ),
637 e(
638 "B0343",
639 Stage::Types,
640 "not a constructor",
641 "The head of a pattern must be a variant of the union being matched.",
642 ),
643 e(
644 "B0344",
645 Stage::Types,
646 "cannot tell which field this binds or sets",
647 "A pattern or record update names a field the compiler cannot resolve to the type in hand.",
648 ),
649 e(
650 "B0345",
651 Stage::Types,
652 "the tail of a list pattern is a name, not a pattern",
653 "The tail of a list pattern binds the rest of the list, so it takes a name or `_`. \
654 `[a, *[b, c]]` is `[a, b, c]` written twice over.",
655 ),
656 e(
657 "B0346",
658 Stage::Types,
659 "cannot tell which model this record builds",
660 "A record literal's type comes from what is expected of it, and nothing here says which \
661 model it is.",
662 ),
663 e(
664 "B0347",
665 Stage::Types,
666 "expected a field or method name",
667 "The right-hand side of a `.` must be a name.",
668 ),
669 e(
670 "B0348",
671 Stage::Types,
672 "`with` takes named fields",
673 "Functional record update is written `t.with(done=…)`.",
674 ),
675 e(
676 "B0349",
677 Stage::Types,
678 "no such field on this type",
679 "The field named in a `with` is not declared on the model being updated.",
680 ),
681 e(
682 "B0350",
683 Stage::Types,
684 "no field or function for this type",
685 "A `.` reached neither a field of the receiver nor a function that could take it.",
686 ),
687 e(
688 "B0351",
689 Stage::Types,
690 "wrong number of arguments",
691 "A call passes a different number of arguments than the function or constructor takes.",
692 ),
693 e(
694 "B0352",
695 Stage::Types,
696 "not callable",
697 "The callee's type is not a function type.",
698 ),
699 e(
700 "B0353",
701 Stage::Types,
702 "no such variant",
703 "The union being constructed or matched has no variant of that name. The type is named in \
704 the message.",
705 ),
706 e(
707 "B0354",
708 Stage::Types,
709 "cannot construct this type",
710 "The name is a type, but not one with a constructor — an alias or a builtin.",
711 ),
712 e(
713 "B0355",
714 Stage::Types,
715 "this case can never match",
716 "Every value the arm matches is already matched by an arm above it, so it cannot run. A \
717 warning rather than an error: `case _` written after every variant of a union is a habit \
718 rather than a mistake.",
719 ),
720 e(
721 "B0356",
722 Stage::Types,
723 "the alternatives of an or-pattern bind different names",
724 "Every alternative of `a | b` has to bind the same names at the same types, because the \
725 body reads them without knowing which one matched.",
726 ),
727 e(
728 "B0357",
729 Stage::Types,
730 "`|` and `@` are only meaningful in a `case` pattern",
731 "Beck has no bitwise operators. `|` separates the alternatives of an or-pattern, `@` names \
732 the value a pattern takes apart, and neither means anything anywhere else.",
733 ),
734 e(
735 "B0358",
736 Stage::Types,
737 "the left of `@` is a name",
738 "`whole @ Circle(r)` binds `whole` to the value the pattern matched. What stands to the \
739 left of `@` is the name being bound.",
740 ),
741 e(
742 "B0359",
743 Stage::Types,
744 "an identity declaration this compiler cannot derive a deployment from",
745 "`identity = external(issuer=\"https://login.acme.com\")` and `identity = managed()` are \
746 the two forms, and a program declares at most one. An external issuer is an `https` URL — \
747 the key set's only integrity protection is the transport it arrives over — and its host \
748 has to be one an egress rule could name, because §6.5 derives the cluster's rule from it. \
749 `managed()` takes no arguments: it provisions a provider into the object graph, and the \
750 issuer is a Service the deployment names rather than the program.",
751 ),
752 e(
753 "B0360",
754 Stage::Types,
755 "cannot be called inside a fold",
756 "A fold must be replay-pure, and this would make replay non-deterministic. Time is data on \
757 the envelope (`env.at`) and entity ids are minted at the edge: mint the id in the \
758 client's command and read it from the event.",
759 ),
760 e(
761 "B0370",
762 Stage::Types,
763 "performs more than its signature declares",
764 "The undeclared atoms are listed. A `uses` clause is the published bound, and widening it \
765 is a breaking API change — so the compiler will not widen it for you.",
766 ),
767 e(
769 "B0380",
770 Stage::Types,
771 "a trait cannot be declared here",
772 "The name already belongs to a type, the trait is declared twice, or the file is a \
773 `.becki` — a trait does not cross a module boundary, so an interface may not hold one.",
774 ),
775 e(
776 "B0381",
777 Stage::Types,
778 "a trait declaration is wrong",
779 "A trait holds `def` signatures with no bodies, each mentioning `Self` in a parameter so \
780 that a call has something to dispatch on. A method name belongs to one trait only.",
781 ),
782 e(
783 "B0382",
784 Stage::Types,
785 "an impl does not match its trait",
786 "An impl writes bodies and parameter *names*; the types, the return type and the effect \
787 row are the trait's. Every method must be implemented, exactly once, and no others.",
788 ),
789 e(
790 "B0383",
791 Stage::Types,
792 "cannot find the trait or the type this impl names",
793 "Both halves of `impl Trait for Type` have to resolve before the impl can be registered.",
794 ),
795 e(
796 "B0384",
797 Stage::Types,
798 "conflicting implementations",
799 "Coherence: one impl per trait per type constructor, and no blanket impl over a type \
800 parameter — so what a call means never depends on which impls happen to be in scope.",
801 ),
802 e(
803 "B0385",
804 Stage::Types,
805 "orphan impl",
806 "An impl belongs with the trait or with the type. Implementing somebody else's trait for \
807 somebody else's type is what lets two modules supply one and disagree.",
808 ),
809 e(
810 "B0386",
811 Stage::Types,
812 "no implementation can be chosen here",
813 "An implementation comes from a concrete type or from a bound on a type parameter — write \
814 `[T: Trait]` to say the parameter has one. A trait method and a bounded definition are \
815 both called rather than passed: the implementation is supplied at the call site, so a \
816 reference that is never called has nowhere to receive it.",
817 ),
818 e(
819 "B0387",
820 Stage::Types,
821 "the type does not implement the trait",
822 "There is no `impl Trait for Type` in scope for the receiver's type.",
823 ),
824 e(
825 "B0388",
826 Stage::Types,
827 "an imported module implements a trait this program does not import",
828 "The impl is dropped, so its methods cannot be called here — a trait is a name, and a name \
829 is visible where its module is imported directly rather than through somebody else's \
830 import. A warning rather than a refusal, because a module may legitimately publish an \
831 impl for a trait the importer never names; import the trait's module to use it.",
832 ),
833 e(
834 "B0389",
835 Stage::Types,
836 "a block has more statements than the checker will follow",
837 "A block is a chain of `let`s however flat it looks in source, so the checker recurses once \
838 per statement and a long enough body reaches the end of the host stack. The bound is \
839 `beck_diag::depth::MAX_BLOCK` — much larger than the nesting ceiling, because 256 levels \
840 of nesting is pathological and 256 sequential bindings is merely a long function. It is a \
841 fixed count rather than a reading of the stack, so the same file is accepted or refused \
842 identically in a debug and a release build; without it, a long enough body aborted the \
843 process with no span at all.",
844 ),
845 e(
846 "B0390",
847 Stage::Types,
848 "the expression nests too deep to check",
849 "The checker walks an expression and a type as deeply as they nest, and stops at \
850 `beck_diag::depth::MAX_NESTING` levels — the same count the reader stops at, because the \
851 checker can be handed a tree a macro produced rather than one anybody typed. Everything \
852 downstream walks the `Core` this pass built, so it is bounded by the same number.",
853 ),
854 e(
855 "B0391",
856 Stage::Types,
857 "a raised value must have a declared type",
858 "`raise` performs `raises(T)`, and the atom names `T` so that a handler can say what it \
859 catches. A builtin will not do: `raises(Int)` would make every integer failure in a \
860 program the same failure, and a handler could not tell them apart.",
861 ),
862 e(
863 "B0392",
864 Stage::Types,
865 "nothing here can fail, and nothing says what this would catch",
866 "A `try:` reifies one failure into a `Result`, and it takes the error type from the \
867 enclosing signature's `Result[T, E]` where there is one and from the block's own row \
868 where there is not. Neither said anything here. Either the call you meant to make is not \
869 there, or the `try:` is left over from a signature that has stopped failing — which is \
870 the good case, and the diagnostic is how you find out.",
871 ),
872 e(
873 "B0393",
874 Stage::Types,
875 "the block can fail in more than one way, and nothing says which to catch",
876 "A `Result[T, E]` has one error type, and a `try:` catches one — the rest keep travelling, \
877 which is what makes a handler composable. Here nothing named which: give the enclosing \
878 definition a `Result[T, E]` return type, and the handler catches that `E`.",
879 ),
880 e(
881 "B0394",
882 Stage::Types,
883 "the row is declared twice",
884 "Two `row Name = …` declarations with the same name. A row alias is a name for a bundle of \
885 effect atoms, and a second one would make every `uses` clause mentioning it ambiguous.",
886 ),
887 e(
888 "B0395",
889 Stage::Types,
890 "the host of an outbound call has to be written at the call site",
891 "`http_fetch` performs `net.out(host)`, and §6.5 derives the cluster's egress policy from \
892 that atom and nothing else. A host computed at run time is an outbound call the \
893 deployment cannot be told about, so the argument is read where it is written. Compute \
894 the path, the port, the headers and the body; or take a closure, so the caller names its \
895 own host and the row carries the atom out.",
896 ),
897 e(
898 "B0396",
899 Stage::Types,
900 "that is not a host an outbound call can name",
901 "The host becomes a NetworkPolicy peer and a `uses net.out(…)` clause, both of which are \
902 written as bare DNS labels — so a scheme, a port or a path in it is a name neither could \
903 carry. `origin` is refused for a different reason: it is the one outbound atom a client \
904 tier discharges, and a client reaches its own server over the command channel.",
905 ),
906 e(
907 "B0397",
908 Stage::Types,
909 "a parallel scope has fewer than two children",
910 "A `parallel:` scope runs its bindings as children. With one there is nothing to run it \
911 alongside, and with none there is nothing to run — either way the form is claiming a \
912 concurrency it does not have, and an ordinary block says the same thing without the \
913 claim. The tail is everything after the last binding, so a scope written with its work \
914 in the tail has no children either.",
915 ),
916 e(
917 "B0398",
918 Stage::Types,
919 "a child of a parallel scope names another child",
920 "The children of a `parallel:` scope run together, so none of them can see another's \
921 result — a child that could would have to run second, and then it is not a child but a \
922 next line. Move the reader into the scope's tail, which runs after the join with every \
923 child's result in scope, or out into a second scope below this one.",
924 ),
925 e(
926 "B0399",
927 Stage::Types,
928 "a child of a parallel scope performs an effect another child could observe",
929 "The claim a `parallel:` scope makes is that its answer does not depend on the order its \
930 children ran in, and an effect on state the program holds — the log, the document, the \
931 merge point, a file, an external store — breaks it: two children appending to the log in \
932 the other order is a different log. `net.out(host)` is not on that list and is the case \
933 the form exists for. Do the shared-state part in the tail, which runs once, after the \
934 join.",
935 ),
936 e(
938 "B0400",
939 Stage::Placement,
940 "performs effects no single tier can discharge",
941 "Each tier discharges a fixed set (§3.3). A row no tier covers has to be split across \
942 definitions that can each be placed.",
943 ),
944 e(
945 "B0401",
946 Stage::Placement,
947 "placed on a tier that cannot discharge an effect it performs",
948 "The written `@on(…)` and the inferred row disagree. The diagnostic names the atom, the \
949 tier, and the tiers that could discharge it. `ingress` is the merge point and only the \
950 server holds it; `durable` is the data tier's; `dom` is the browser's.",
951 ),
952 e(
953 "B0402",
954 Stage::Placement,
955 "a fold function must be replay-pure",
956 "The function reached by a `fold` performs effects that replay would not reproduce. Both \
957 the fold and the definition are reported.",
958 ),
959 e(
960 "B0403",
961 Stage::Placement,
962 "a program has exactly one merge point",
963 "A second `merge_clients()`. The merge point is where time and nondeterminism enter; two \
964 of them would mean two total orders, and replay would no longer be a function of the log.",
965 ),
966 e(
967 "B0404",
968 Stage::Placement,
969 "cannot be unplaced",
970 "`@on(any)` means every tier, and an atom in the row is not discharged on every tier. The \
971 fix-it names the tiers that can.",
972 ),
973 e(
974 "B0405",
975 Stage::Placement,
976 "only a component can say where it renders",
977 "`@render` was written on something that is not a `Signal[Html]`. A definition is unplaced \
978 code compiled to every tier that needs it (§3.3); rendering is decided per component.",
979 ),
980 e(
981 "B0410",
982 Stage::Security,
983 "runs on the client, so its value must be Sendable",
984 "This value crosses to the browser, and §3.5's claim is that the compiler proves a secret \
985 cannot. The offending field and the path that reaches it are both named — `beck explain \
986 flow <Type>` prints the same walk.",
987 ),
988 e(
989 "B0411",
990 Stage::Security,
991 "durable, so its state must be storable",
992 "The log is the only description of this program's history; a value it cannot read back \
993 is a state replay would not reproduce.",
994 ),
995 e(
996 "B0412",
997 Stage::Security,
998 "requires a capability nothing can discharge",
999 "A `Session` reaches exactly one place in a Beck program: the validator `decide` is given, \
1000 which is the only function handed a `Proposal`. Authority is one chokepoint (§3.5), so a \
1001 capability required outside it has no holder.",
1002 ),
1003 e(
1005 "B0500",
1006 Stage::Signals,
1007 "this program has no merge point",
1008 "A Beck application is a fold over an event stream, and the stream starts at \
1009 `merge_clients()`. Not an error for a library: this code, with B0501 and B0505, is what \
1010 says a module is a domain module rather than an application.",
1011 ),
1012 e(
1013 "B0501",
1014 Stage::Signals,
1015 "this program has no durable state",
1016 "`durable(fold(f, init, s))` is what makes the log a database.",
1017 ),
1018 e(
1019 "B0502",
1020 Stage::Signals,
1021 "`durable` must wrap a `fold`",
1022 "Only a fold has an accumulator to persist.",
1023 ),
1024 e(
1025 "B0504",
1026 Stage::Signals,
1027 "events must come from `decide`",
1028 "The fold has no chokepoint upstream of it. `decide` is the sole consumer of ingress and \
1029 the one place a command becomes an event — §3.5's \"authority is one chokepoint\".",
1030 ),
1031 e(
1032 "B0505",
1033 Stage::Signals,
1034 "no signal is placed on the client",
1035 "`page` is the tier crossing: a `Signal[Html]` the browser subscribes to.",
1036 ),
1037 e(
1038 "B0506",
1039 Stage::Signals,
1040 "not a signal",
1041 "A signal's inputs are other signals. A function is applied *through* a construct — \
1042 `signal_map(s, f)` — rather than named as an input.",
1043 ),
1044 e(
1045 "B0507",
1046 Stage::Signals,
1047 "not a signal construct",
1048 "§3.7's signal vocabulary is `merge_clients`, `filter_map`, `fold`, `durable`, \
1049 `signal_map`, `map2`, `per_session` and `decide`.",
1050 ),
1051 e(
1052 "B0508",
1053 Stage::Signals,
1054 "unsupported signal expression",
1055 "A signal is a node in the dataflow, not a computation. The computation goes in a `def` \
1056 and the signal names it: `summary: Signal[Summary] = signal_map(counts, summarise)`.",
1057 ),
1058 e(
1059 "B0509",
1060 Stage::Signals,
1061 "a signal defined in terms of itself",
1062 "The cycle is printed. A cycle through a `fold` is sound — an accumulator is a value, so \
1063 the recursion has a bottom, which is why `events → todos → events` is legal. One with no \
1064 fold in it has no first value to compute from.",
1065 ),
1066 e(
1067 "B0510",
1068 Stage::Signals,
1069 "two signals are the page, and there is no router yet",
1070 "The slicer will slice both; the runtime serves one document per connection, and choosing \
1071 between them is routing — a Phase 3 client bullet that is not built.",
1072 ),
1073 e(
1074 "B0511",
1075 Stage::Signals,
1076 "a program has one authority chokepoint",
1077 "A second `decide`. §3.5 rests on validation being one place: two of them are two answers \
1078 to \"may this actor do this\", and the log would record whichever ran.",
1079 ),
1080 e(
1081 "B0512",
1082 Stage::Signals,
1083 "the chokepoint does not read a durable fold",
1084 "`decide` threads the accumulator through validation, so what it reads has to be one — \
1085 that is what makes first-writer-wins and ownership decidable (§3.7).",
1086 ),
1087 e(
1088 "B0513",
1089 Stage::Signals,
1090 "a fold that is not durable",
1091 "Its accumulator has nowhere to live across a restart. The log is what survives, and \
1092 `durable` is what says an accumulator is folded from it.",
1093 ),
1094 e(
1095 "B0514",
1096 Stage::Signals,
1097 "renders differently for each actor, so it cannot render on the client",
1098 "The page reads who is asking — `session.actor` or `session.claims` — so it filters, \
1099 scopes or hides by identity. `@render(client)` sends the browser the state rather than \
1100 the page, which would hand every actor what the filter was removing (docs/94 §94.2). \
1101 Reading `session.path` is not this: the browser chose the route and already holds the \
1102 state, so a page that varies by route is eligible (docs/94 §94.3).",
1103 ),
1104 e(
1105 "B0515",
1106 Stage::Signals,
1107 "the chokepoint reads `presence`, which is not in the log",
1108 "Who was connected when an event was recorded is written down nowhere, so a `validate` \
1109 that decided from the roster would decide one thing now and another on replay. Record the \
1110 fact instead: propose a command when a client arrives, and decide from the state that \
1111 fold produces.",
1112 ),
1113 e(
1114 "B0516",
1115 Stage::Signals,
1116 "reads `presence`, so it cannot render on the client",
1117 "`@render(client)` sends the browser the accumulator, and who is connected is in neither \
1118 the accumulator nor the log — it is a fact the server holds about its own sockets \
1119 (docs/48 §48.9).",
1120 ),
1121 e(
1122 "B0517",
1123 Stage::Signals,
1124 "the chokepoint reads `freshness`, which is not in the log",
1125 "How many of a client's commands were in flight when an event was recorded is written \
1126 down nowhere, and on replay nothing is in flight at all — so a `validate` that decided \
1127 from it would accept a command today and refuse it on the way back. Decide from the \
1128 accumulator, which says the same thing now and on replay.",
1129 ),
1130 e(
1131 "B0518",
1132 Stage::Signals,
1133 "reads `freshness`, so it cannot render on the server",
1134 "§3.7's freshness dimension is a client's account of the commands it has proposed and not \
1135 yet had confirmed. A server renders what it has recorded, so its answer is `Confirmed` at \
1136 every position of every log and the page's other branch would be unreachable. This is \
1137 `B0516` from the other side: `@render(client)` is what makes a guess possible, and \
1138 therefore what makes saying so possible (docs/94 §94.5).",
1139 ),
1140 e(
1141 "B0519",
1142 Stage::Signals,
1143 "a fold over the log that is not `durable`",
1144 "The stream this folds is the log's, so its accumulator *is* a function of the log \
1145 whatever the program calls it: every event on it was validated and recorded, and replay \
1146 would reproduce this state whether or not anybody asked. Declining to write it down does \
1147 not make it ephemeral, it makes it a state the log can reconstruct and the process \
1148 cannot. `docs/10` D30 is the rule this enforces — **ephemerality comes from the stream, \
1149 never from the absence of a `durable` wrapper** — and it corrects D1, which named the \
1150 right problem and the wrong mechanism. State that should not survive a restart folds \
1151 gestures (`gestures(step, init)`), which are never recorded; state folded from events is \
1152 `durable(fold(…))`.",
1153 ),
1154 e(
1155 "B0520",
1156 Stage::Signals,
1157 "the chokepoint reads `awareness`, which is not in the log",
1158 "`B0515` for the roster that carries a payload. What each connection was contributing \
1159 when an event was recorded is written down nowhere, so a `validate` that decided from it \
1160 would decide one thing now and another on replay. Record the fact instead: propose a \
1161 command when the thing you are deciding from happens, and decide from the state that \
1162 fold produces.",
1163 ),
1164 e(
1165 "B0521",
1166 Stage::Signals,
1167 "reads `awareness`, so it cannot render on the client",
1168 "`B0516` for the roster that carries a payload. `@render(client)` sends the browser the \
1169 accumulator, and what every *other* connection is contributing is in neither the \
1170 accumulator nor the log — the runtime holds it, one row per socket.",
1171 ),
1172 e(
1173 "B0522",
1174 Stage::Signals,
1175 "reads a `gestures` fold, so it cannot render on the server",
1176 "`B0518` about the other fact only a client holds. A gesture is one client's movement of \
1177 its own interface — not proposed, not validated, not recorded — so it never reaches a \
1178 server, and a page rendered there would show the interface state's initial value at every \
1179 position of every log with every gesture-dependent branch unreachable. `docs/10` D30 \
1180 orders the homes for interface state and puts this one fourth: a page that reaches for a \
1181 client-local fold and cannot render in the browser usually wants markup the platform \
1182 already knows (`<dialog>`, `popover`, `<details name>`) or the route on the `Session`, \
1183 both of which survive a server render and cost nothing.",
1184 ),
1185 e(
1186 "B0523",
1187 Stage::Signals,
1188 "the chokepoint reads a `gestures` fold, which is not in the log",
1189 "`B0515` and `B0520` for D30's client-local interface state, and the clearest case of the \
1190 three. A gesture is never proposed, so no `validate` ever saw one, and never recorded, so \
1191 no replay can reach one — the log holds no trace that it happened. An event whose \
1192 existence depended on whether somebody had a panel open would be unreproducible by \
1193 construction. If interface state should decide an event then it is not interface state: \
1194 propose a command when it changes, and decide from the fold over the events that \
1195 produces, which is D30's fifth home.",
1196 ),
1197 e(
1198 "B0524",
1199 Stage::Signals,
1200 "a variant is both a command and a gesture",
1201 "A handler in the page carries the constructor it builds — `on_click=Open` is the value \
1202 `Open`, serialised — and the client routes on its variant name: a name in the gesture \
1203 union is folded where it was made, and a name in the command union is proposed to the \
1204 server. A name in both would make `on_click` mean whichever decoder ran first, which is \
1205 a page whose buttons do one of two very different things for a reason nobody can read. \
1206 The two unions are different types and the fix is to say so: rename one.",
1207 ),
1208 e(
1210 "B0600",
1211 Stage::Modules,
1212 "has a body, so it is not a signature",
1213 "A `.becki` publishes what a module offers, not how it does it. Regenerate it with \
1214 `beck iface`.",
1215 ),
1216 e(
1217 "B0601",
1218 Stage::Modules,
1219 "defined in more than one module",
1220 "Phase 2 links modules into one namespace and has no qualified reference to tell two \
1221 definitions apart, so a clash is an error rather than a shadowing rule.",
1222 ),
1223 e(
1224 "B0602",
1225 Stage::Modules,
1226 "a module imports itself, directly or through a cycle",
1227 "A module's interface is derived from its body, so a cycle would mean each module needed \
1228 the other's contract before either had one. The cycle is printed.",
1229 ),
1230 e(
1231 "B0603",
1232 Stage::Modules,
1233 "cannot find module",
1234 "The loader looked for `<name>.becki` and `<name>.beck` beside the root module, and for a \
1235 standard-library module of that name, and found neither.",
1236 ),
1237 e(
1238 "B0604",
1239 Stage::Modules,
1240 "has an interface but no implementation",
1241 "An interface is enough to compile against and never enough to run. `beck check` and \
1242 `beck iface` work against a `.becki` with no `.beck` beside it — that is what §3.6's \
1243 separate compilation is — so this is reported where a runnable program is produced, and \
1244 for the root module wherever it is read, because a project whose root is a contract is \
1245 not a program at all.",
1246 ),
1247 e(
1248 "B0605",
1249 Stage::Modules,
1250 "does not match its published interface",
1251 "The checked-in `.becki` and the module compile to different digests. Regenerate it with \
1252 `beck iface`, and review the diff — the difference is an API change.",
1253 ),
1254 e(
1256 "B0700",
1257 Stage::Tests,
1258 "a test block performs effects",
1259 "A test block's own row must be empty: an expectation is a pure question about a state, a \
1260 log and a page. Effects belong to the *subject*, and §21.3 stubs those.",
1261 ),
1262 e(
1263 "B0701",
1264 Stage::Tests,
1265 "a property parameter needs a type",
1266 "The generator is type-directed, so it works from the parameter's declared type. A \
1267 `property` with no types has nothing to generate.",
1268 ),
1269 e(
1270 "B0702",
1271 Stage::Tests,
1272 "not a tier, or not an effect atom",
1273 "`expect place(name) == tier` takes a tier, and a `stub` names an effect atom.",
1274 ),
1275 e(
1276 "B0703",
1277 Stage::Tests,
1278 "not something a stub can stand in for",
1279 "Time, ids and persistence are not stubbed in Beck and there is nothing to write: the \
1280 clock is data on the envelope, ids are minted at the edge, and the durable fold is real \
1281 and in memory.",
1282 ),
1283 e(
1284 "B0704",
1285 Stage::Tests,
1286 "nothing in this program performs this atom",
1287 "The stub would never be reached. The complete list of what a program touches is its \
1288 effect rows, and this atom is not among them.",
1289 ),
1290 e(
1291 "B0705",
1292 Stage::Tests,
1293 "only `given`, `when`, `stub` and `expect` may appear in a test",
1294 "§21.2: a test names a log, an input and an expectation — there is no fixture to build and \
1295 no `setUp` to write.",
1296 ),
1297 e(
1298 "B0706",
1299 Stage::Tests,
1300 "a clause needs something this program does not have",
1301 "The state a test arranges is a fold over the program's own event stream, so a program \
1302 with no `merge_clients` → `decide` → `durable(fold(…))` has nothing for `given` and \
1303 `when` to mean.",
1304 ),
1305 e(
1306 "B0707",
1307 Stage::Tests,
1308 "an atom is performed by more than one definition, so a stub cannot answer from the call",
1309 "The performers are named. A stub is a value for an effect atom; where two definitions \
1310 perform the same atom with different result types, one value cannot serve both.",
1311 ),
1312 e(
1313 "B0708",
1314 Stage::Tests,
1315 "a stub raises what the definition it stands in for cannot",
1316 "A stub stands in for a definition, so it may answer the way that definition may answer — \
1317 failure included, because a `raises(E)` the signature declares is an answer rather than \
1318 an act. Callers were type-checked against the row the signature publishes, so a raise it \
1319 does not declare would unwind through code that provably cannot fail.",
1320 ),
1321];
1322
1323#[cfg(test)]
1324mod tests {
1325 use super::*;
1326
1327 #[test]
1328 fn the_index_is_sorted_and_has_no_duplicates() {
1329 let codes: Vec<&str> = INDEX.iter().map(|e| e.code).collect();
1330 let mut sorted = codes.clone();
1331 sorted.sort_unstable();
1332 sorted.dedup();
1333 assert_eq!(codes, sorted, "the index must be sorted and unique");
1334 }
1335
1336 #[test]
1337 fn every_entry_says_something() {
1338 for entry in INDEX {
1339 assert!(
1340 entry.code.len() == 5 && entry.code.starts_with('B'),
1341 "{entry:?}"
1342 );
1343 assert!(!entry.title.is_empty(), "{entry:?}");
1344 assert!(entry.explain.len() > 40, "{entry:?}");
1346 }
1347 }
1348
1349 #[test]
1350 fn a_code_can_be_looked_up_either_way() {
1351 assert_eq!(lookup("B0341").map(|e| e.stage), Some(Stage::Types));
1352 assert_eq!(lookup("b0341").map(|e| e.code), Some("B0341"));
1353 assert_eq!(lookup("B9999"), None);
1354 }
1355
1356 #[test]
1357 fn every_code_belongs_to_the_stage_its_number_names() {
1358 for entry in INDEX {
1359 let band = &entry.code[1..3];
1360 let expected: &[Stage] = match band {
1361 "01" => &[Stage::Syntax],
1362 "02" => &[Stage::Macros],
1363 "03" => &[Stage::Types],
1364 "04" => &[Stage::Placement, Stage::Security],
1366 "05" => &[Stage::Signals],
1367 "06" => &[Stage::Modules],
1368 "07" => &[Stage::Tests],
1369 other => panic!("unknown band B{other}xx in {entry:?}"),
1370 };
1371 assert!(expected.contains(&entry.stage), "{entry:?}");
1372 }
1373 }
1374}