Skip to main content

bynk_syntax/
diagnostics.rs

1//! Central registry of diagnostic codes.
2//!
3//! This is the single source of truth for the `bynk.*` codes the compiler can
4//! emit. The reference page `docs/src/reference/diagnostics.md` is generated
5//! from [`render_markdown`], and the test `tests/diagnostics_registry.rs`
6//! asserts that this table matches exactly the set of codes used across the
7//! compiler source — so a new code cannot be introduced without documenting it
8//! here, and a removed code cannot linger in the docs.
9//!
10//! Each entry is a `(code, summary)` pair, optionally tagged with the grammar
11//! production(s) it constrains (`grammar_symbol`). The category shown in the
12//! generated reference is derived from the second dotted segment of the code;
13//! the grammar weave (`docs/grammar-semantics.json`, the
14//! `{{#grammar-semantics}}` directive, and the diagnostics page's Construct
15//! column) is generated from `grammar_symbol`.
16
17/// One documented diagnostic: its stable code and a one-line summary of the
18/// cause. Richer "cause and fix" material for the common diagnostics lives in
19/// the troubleshooting how-to guides.
20pub struct DiagnosticInfo {
21    pub code: &'static str,
22    pub summary: &'static str,
23    /// The grammar production(s) this diagnostic constrains, by `tree-sitter`
24    /// rule name (e.g. `http_handler`). This is the single source of the
25    /// "static semantics" weave: a grammar-reference entry embeds the
26    /// diagnostics for a rule via `{{#grammar-semantics <rule>}}`, generated
27    /// from here. Empty for diagnostics with no single governing construct
28    /// (e.g. `bynk.boundary.structural_mismatch`). Every non-empty name is
29    /// checked against the grammar by `tests/diagnostics_registry.rs`.
30    pub grammar_symbol: &'static [&'static str],
31}
32
33/// Every diagnostic code the compiler emits, sorted by code.
34pub const REGISTRY: &[DiagnosticInfo] = &[
35    d(
36        "bynk.actor.bearer_identity_not_string_constructible",
37        "A `Bearer` actor's identity is not a string-constructible type.",
38    ),
39    d(
40        "bynk.actor.bearer_missing_secret",
41        "A `Bearer` actor does not name its signing secret.",
42    ),
43    d(
44        "bynk.actor.binder_shadows_param",
45        "A `by` actor binder collides with a handler parameter of the same name.",
46    ),
47    d(
48        "bynk.actor.duplicate_sum_scheme",
49        "Two peers in a multi-actor sum share an authentication scheme.",
50    ),
51    d(
52        "bynk.actor.identity_not_sealed",
53        "An actor identity type is not a context-ownable (sealed) value type.",
54    ),
55    d(
56        "bynk.actor.missing_by_on_http",
57        "An HTTP handler lacks the required `by` actor clause.",
58    ),
59    d(
60        "bynk.actor.outside_context",
61        "An `actor` was declared outside a context (e.g. in a commons).",
62    ),
63    d(
64        "bynk.actor.refinement_base_unsupported",
65        "A refinement actor's base is not a `Bearer` actor (no claims to authorise against).",
66    ),
67    d(
68        "bynk.actor.refinement_in_sum",
69        "A refinement actor appears as a member of a multi-actor sum.",
70    ),
71    d(
72        "bynk.actor.refinement_predicate_unsupported",
73        "A refinement actor's `where` predicate is outside the closed claim-predicate set.",
74    ),
75    d(
76        "bynk.actor.scheme_not_admissible",
77        "An actor's scheme is not admissible on this handler's protocol.",
78    ),
79    d(
80        "bynk.actor.signature_identity_unsupported",
81        "A `Signature` actor declared an `identity`, which is not yet supported.",
82    ),
83    d(
84        "bynk.actor.signature_missing_header",
85        "A `Signature` actor does not name its signature header.",
86    ),
87    d(
88        "bynk.actor.signature_missing_secret",
89        "A `Signature` actor does not name its signing secret.",
90    ),
91    d(
92        "bynk.actor.signature_requires_body",
93        "A `Signature` handler does not take a `body` parameter.",
94    ),
95    d(
96        "bynk.actor.signature_tolerance_without_timestamp",
97        "A `Signature` actor set `tolerance` without a `timestamp` header.",
98    ),
99    d(
100        "bynk.actor.sum_requires_binder",
101        "A multi-actor sum `by` clause has no binder to match the resolved actor.",
102    ),
103    d(
104        "bynk.actor.unknown_actor",
105        "A handler's `by` clause names an actor that is not declared.",
106    ),
107    d(
108        "bynk.actor.unknown_scheme",
109        "An actor declares an authentication scheme that is not compiler-known.",
110    ),
111    d(
112        "bynk.actor.unreachable_sum_arm",
113        "A multi-actor sum has an arm unreachable after a catch-all (`None`) peer.",
114    ),
115    dg(
116        "bynk.adapter.consumes_context",
117        "An `adapter` consumed a context; adapter dependencies are adapter-to-adapter.",
118        &["consumes_decl"],
119    ),
120    dg(
121        "bynk.adapter.consumes_requires_selection",
122        "An `adapter` used a whole-unit or aliased `consumes`; adapters must select capabilities with `consumes U { Cap, … }`.",
123        &["consumes_decl"],
124    ),
125    dg(
126        "bynk.adapter.disallowed_item",
127        "An `adapter` declared a `service`, `agent`, or other item it may not contain.",
128        &["adapter_decl"],
129    ),
130    dg(
131        "bynk.adapter.duplicate_binding",
132        "An `adapter` declared more than one `binding` clause.",
133        &["binding_decl"],
134    ),
135    dg(
136        "bynk.adapter.no_binding",
137        "An `adapter` declares an external provider but no `binding` module to supply it.",
138        &["adapter_decl"],
139    ),
140    dg(
141        "bynk.adapter.provider_has_body",
142        "A provider inside an `adapter` has a Bynk body; adapter providers must be external.",
143        &["provider_decl"],
144    ),
145    dg(
146        "bynk.agent.construction_arity",
147        "An agent was constructed with the wrong number of key arguments.",
148        &["agent_decl"],
149    ),
150    dg(
151        "bynk.agent.handler_arity",
152        "An agent handler was called with the wrong number of arguments.",
153        &["agent_decl"],
154    ),
155    dg(
156        "bynk.agent.handler_not_found",
157        "Called a handler the agent does not declare.",
158        &["agent_decl"],
159    ),
160    dg(
161        "bynk.agent.key_mismatch",
162        "An agent key argument has the wrong type.",
163        &["agent_decl"],
164    ),
165    dg(
166        "bynk.agent.outside_context",
167        "An `agent` was declared outside a context.",
168        &["agent_decl"],
169    ),
170    dg(
171        "bynk.agent.return_not_effect",
172        "An agent handler's return type is not an `Effect`.",
173        &["agent_decl"],
174    ),
175    dg(
176        "bynk.agents.bad_state_initialiser",
177        "An agent `store` field initialiser is not a static value of the field's type.",
178        &["store_field"],
179    ),
180    dg(
181        "bynk.agents.non_zeroable_state_field",
182        "An agent `store` field has no initialiser and no implicit zero value.",
183        &["store_field"],
184    ),
185    dg(
186        "bynk.assert.non_bool",
187        "`assert` was given a non-`Bool` expression.",
188        &["assert_expr"],
189    ),
190    dg(
191        "bynk.assert.outside_test",
192        "`assert` was used outside a test case body.",
193        &["assert_expr"],
194    ),
195    d(
196        "bynk.boundary.structural_mismatch",
197        "Data crossing a context boundary did not match the expected shape.",
198    ),
199    dg(
200        "bynk.capability.op_arity",
201        "A capability operation was called with the wrong number of arguments.",
202        &["capability_decl"],
203    ),
204    dg(
205        "bynk.capability.outside_context",
206        "A `capability` was declared outside a context.",
207        &["capability_decl"],
208    ),
209    dg(
210        "bynk.capability.unknown_operation",
211        "Referenced an operation the capability does not declare.",
212        &["capability_decl"],
213    ),
214    d(
215        "bynk.cell.invalid_target",
216        "A `:=` write targets something that is not a `store Cell` field.",
217    ),
218    d(
219        "bynk.cell.self_reference",
220        "A `:=` right-hand side reads the cell being written (a read-modify-write); use `.update`.",
221    ),
222    dg(
223        "bynk.consumes.alias_conflict",
224        "Two `consumes` aliases collide.",
225        &["consumes_decl"],
226    ),
227    dg(
228        "bynk.consumes.capability_name_clash",
229        "Two flattened `consumes U { Cap }` capabilities collide, or one clashes with a local capability.",
230        &["consumes_decl"],
231    ),
232    dg(
233        "bynk.consumes.in_commons",
234        "`consumes` appears in a `commons` (it is only valid in a context).",
235        &["consumes_decl"],
236    ),
237    dg(
238        "bynk.consumes.name_conflict",
239        "A `consumes` name collides with another name in scope.",
240        &["consumes_decl"],
241    ),
242    dg(
243        "bynk.consumes.self_reference",
244        "A context `consumes` itself.",
245        &["consumes_decl"],
246    ),
247    dg(
248        "bynk.consumes.service_arity",
249        "A consumed service was called with the wrong number of arguments.",
250        &["consumes_decl"],
251    ),
252    dg(
253        "bynk.consumes.target_is_commons",
254        "`consumes` targets a `commons` instead of a context.",
255        &["consumes_decl"],
256    ),
257    dg(
258        "bynk.consumes.unknown_context",
259        "`consumes` names a context that does not exist.",
260        &["consumes_decl"],
261    ),
262    dg(
263        "bynk.consumes.unknown_service",
264        "Called a service the consumed context does not declare.",
265        &["consumes_decl"],
266    ),
267    d(
268        "bynk.context.consumes_cycle",
269        "Contexts form a `consumes` dependency cycle.",
270    ),
271    d(
272        "bynk.context.external_construction",
273        "A context-owned type was constructed from outside that context.",
274    ),
275    dg(
276        "bynk.context.external_provider",
277        "A bodiless (external) provider was declared outside an `adapter`.",
278        &["provider_decl"],
279    ),
280    d(
281        "bynk.context.opaque_inspection",
282        "An opaquely-exported type was inspected from outside its context.",
283    ),
284    dg(
285        "bynk.cron.bad_params",
286        "A cron handler declares more than one parameter, or a non-`Int` one.",
287        &["cron_handler"],
288    ),
289    dg(
290        "bynk.cron.duplicate_schedule",
291        "Two cron handlers declare the same schedule.",
292        &["cron_handler"],
293    ),
294    dg(
295        "bynk.cron.invalid_schedule",
296        "A cron expression is not five whitespace-separated fields.",
297        &["cron_handler"],
298    ),
299    dg(
300        "bynk.cron.return_not_effect_result",
301        "A cron handler does not return `Effect[Result[(), E]]`.",
302        &["cron_handler"],
303    ),
304    d(
305        "bynk.duration.literal_overflow",
306        "A `Duration` literal (`<int>.<unit>`) exceeds the representable millisecond range.",
307    ),
308    dg(
309        "bynk.effect.bind_in_pure_context",
310        "An `<-` bind was used in a pure (non-effectful) context.",
311        &["effect_let_stmt"],
312    ),
313    dg(
314        "bynk.effect.bind_on_non_effect",
315        "An `<-` bind was applied to a non-`Effect` value.",
316        &["effect_let_stmt"],
317    ),
318    d(
319        "bynk.effect.capability_in_pure_context",
320        "A capability was used in a pure context.",
321    ),
322    d(
323        "bynk.effect.cross_context_in_pure_context",
324        "A cross-context call was made in a pure context.",
325    ),
326    dg(
327        "bynk.effect.fn_value_in_pure_context",
328        "An effectful function value was called in a pure context; like a capability call, it is legal only where the enclosing body is effectful.",
329        &["call"],
330    ),
331    dg(
332        "bynk.exports.capability_not_provided",
333        "An exported capability has no provider in its context.",
334        &["exports_decl"],
335    ),
336    dg(
337        "bynk.exports.conflicting_visibility",
338        "A type is exported with conflicting visibilities.",
339        &["exports_decl"],
340    ),
341    dg(
342        "bynk.exports.duplicate_export",
343        "The same name is exported more than once.",
344        &["exports_decl"],
345    ),
346    dg(
347        "bynk.exports.duplicate_in_clause",
348        "A name appears twice in one `exports` clause.",
349        &["exports_decl"],
350    ),
351    dg(
352        "bynk.exports.undeclared_capability",
353        "`exports capability` names a capability that is not declared.",
354        &["exports_decl"],
355    ),
356    dg(
357        "bynk.exports.undeclared_type",
358        "`exports` names a type that is not declared.",
359        &["exports_decl"],
360    ),
361    dg(
362        "bynk.generics.no_bounds",
363        "A type parameter carries a bound (`[A: …]`); bounded generics are not in v0.20a.",
364        &["fn_decl"],
365    ),
366    dg(
367        "bynk.generics.no_generic_types",
368        "A `type` declaration carries a type-parameter list; generic type declarations are not in v0.20a (type parameters belong to functions).",
369        &["type_decl"],
370    ),
371    dg(
372        "bynk.generics.type_arg_mismatch",
373        "Inferred or explicit type arguments conflict, have the wrong arity, target a non-generic function, or a type parameter shadows a declared type.",
374        &["call"],
375    ),
376    dg(
377        "bynk.generics.uninferable_type_arg",
378        "A generic function's type parameter could not be inferred from the arguments and was not given explicitly (`name[T](…)`); a bare generic function also cannot be passed as a value in v0.20a.",
379        &["call"],
380    ),
381    dg(
382        "bynk.given.cross_context_unknown_capability",
383        "`given B.Cap` names a capability the consumed context does not export.",
384        &["given_clause"],
385    ),
386    dg(
387        "bynk.given.undeclared_capability",
388        "A handler uses a capability it did not declare with `given`.",
389        &["given_clause"],
390    ),
391    dg(
392        "bynk.given.unknown_capability",
393        "`given` names a capability that does not exist.",
394        &["given_clause"],
395    ),
396    dg(
397        "bynk.given.unused_capability",
398        "A `given` capability is never used (warning).",
399        &["given_clause"],
400    ),
401    dg(
402        "bynk.http.body_on_get_or_delete",
403        "A GET or DELETE handler declares a `body` parameter.",
404        &["http_handler"],
405    ),
406    dg(
407        "bynk.http.duplicate_route",
408        "Two handlers share the same method and route.",
409        &["http_handler"],
410    ),
411    dg(
412        "bynk.http.extra_param",
413        "A handler parameter is neither a path parameter nor `body`.",
414        &["http_handler"],
415    ),
416    dg(
417        "bynk.http.invalid_path",
418        "An HTTP route path is malformed.",
419        &["http_handler"],
420    ),
421    dg(
422        "bynk.http.path_param_not_stringy",
423        "A path parameter's type is not constructible from a string.",
424        &["http_handler"],
425    ),
426    dg(
427        "bynk.http.reserved_prefix",
428        "A route uses the reserved `/_bynk/` prefix.",
429        &["http_handler"],
430    ),
431    dg(
432        "bynk.http.return_not_effect_http_result",
433        "An HTTP handler does not return `Effect[HttpResult[T]]`.",
434        &["http_handler"],
435    ),
436    dg(
437        "bynk.http.unbound_path_param",
438        "A `:name` route segment has no matching handler parameter.",
439        &["http_handler"],
440    ),
441    d(
442        "bynk.index.bad_argument",
443        "An `@indexed` argument is not a `by: <field>` label.",
444    ),
445    d(
446        "bynk.index.missing",
447        "A query filters a map by equality on a field that is not `@indexed` (a perf-hint warning).",
448    ),
449    d(
450        "bynk.index.unkeyable_key",
451        "An `@indexed(by: k)` field is not value-keyable.",
452    ),
453    d(
454        "bynk.index.unknown_key",
455        "An `@indexed(by: k)` field is not a field of the map's value type.",
456    ),
457    d(
458        "bynk.index.unused",
459        "A declared `@indexed(by: k)` is never used by an equality filter (a hygiene warning).",
460    ),
461    dg(
462        "bynk.integration.duplicate_participant",
463        "A context is listed more than once in a `wires` clause.",
464        &["wires_decl"],
465    ),
466    dg(
467        "bynk.integration.duplicate_suite",
468        "Two integration tests share the same suite name.",
469        &["integration_decl"],
470    ),
471    dg(
472        "bynk.integration.mock_in_integration",
473        "`mocks` is not allowed in an integration test.",
474        &["mocks_decl"],
475    ),
476    dg(
477        "bynk.integration.too_few_participants",
478        "An integration test wires fewer than two contexts.",
479        &["wires_decl"],
480    ),
481    dg(
482        "bynk.integration.unknown_participant",
483        "A `wires` clause names something that is not a declared context.",
484        &["wires_decl"],
485    ),
486    dg(
487        "bynk.integration.unwired_dependency",
488        "A participant consumes a context that is not wired into the integration test.",
489        &["integration_decl"],
490    ),
491    d(
492        "bynk.invariant.cross_agent_reference",
493        "An invariant predicate references another agent; invariants are per-agent.",
494    ),
495    d(
496        "bynk.invariant.duplicate_name",
497        "An agent declares two invariants with the same name.",
498    ),
499    d(
500        "bynk.invariant.impure_predicate",
501        "An invariant predicate uses an effectful or test-only construct.",
502    ),
503    d(
504        "bynk.invariant.not_bool",
505        "An invariant predicate does not have type `Bool`.",
506    ),
507    dg(
508        "bynk.lambda.unannotated_param",
509        "A lambda parameter has no type annotation in a position where no function type is expected to infer it from.",
510        &["lambda_expr"],
511    ),
512    dg(
513        "bynk.lex.bad_escape",
514        "An invalid escape sequence in a string literal.",
515        &["string_literal"],
516    ),
517    dg(
518        "bynk.lex.float_literal_overflow",
519        "A float literal does not fit a finite 64-bit float.",
520        &["float_literal"],
521    ),
522    dg(
523        "bynk.lex.integer_overflow",
524        "An integer literal is out of range.",
525        &["number_literal"],
526    ),
527    d(
528        "bynk.lex.unclosed_doc_block",
529        "A documentation block is not closed.",
530    ),
531    d(
532        "bynk.lex.unexpected_character",
533        "An unexpected character in the source.",
534    ),
535    dg(
536        "bynk.lex.unterminated_interpolation",
537        "An interpolation hole `\\(…)` is not closed on its line.",
538        &["string_literal"],
539    ),
540    dg(
541        "bynk.lex.unterminated_string",
542        "A string literal is not terminated.",
543        &["string_literal"],
544    ),
545    d(
546        "bynk.list.deprecated_function",
547        "A `bynk.list` free function (`map`/`filter`/`find`/`any`/`all`) is deprecated in favour of the `List` method form (warning; auto-fixable).",
548    ),
549    dg(
550        "bynk.mock.arity",
551        "`Mock[T]` was given the wrong number of pin arguments.",
552        &["mock_expr"],
553    ),
554    dg(
555        "bynk.mock.duplicate_target",
556        "A `mocks` target is declared more than once.",
557        &["mocks_decl"],
558    ),
559    dg(
560        "bynk.mock.in_commons_test",
561        "`mocks` used in a commons test, where there is no dependency to inject.",
562        &["mocks_decl"],
563    ),
564    dg(
565        "bynk.mock.literal_violates",
566        "A pinned `Mock[T]` value violates the type's refinement.",
567        &["mock_expr"],
568    ),
569    dg(
570        "bynk.mock.needs_pin",
571        "A bare `Mock[T]` cannot generate a value (e.g. a `Matches` string); pin one.",
572        &["mock_expr"],
573    ),
574    dg(
575        "bynk.mock.outside_test",
576        "`Mock[T]` was used outside a test case body.",
577        &["mock_expr"],
578    ),
579    dg(
580        "bynk.mock.pin_not_literal",
581        "A `Mock[T]` pin argument is not a compile-time literal.",
582        &["mock_expr"],
583    ),
584    dg(
585        "bynk.mock.pin_unsupported",
586        "A pin was given for a type kind that does not support pinning.",
587        &["mock_expr"],
588    ),
589    dg(
590        "bynk.mock.signature_mismatch",
591        "A `mocks` implementation's signature does not match the capability.",
592        &["mocks_decl"],
593    ),
594    dg(
595        "bynk.mock.unknown_target",
596        "`mocks` names a capability that is not in scope.",
597        &["mocks_decl"],
598    ),
599    dg(
600        "bynk.mock.unknown_type",
601        "`Mock[T]` names a type that does not resolve.",
602        &["mock_expr"],
603    ),
604    dg(
605        "bynk.mock.unsupported_kind",
606        "`Mock[T]` cannot fabricate a value for this kind of type.",
607        &["mock_expr"],
608    ),
609    d(
610        "bynk.namespace.reserved",
611        "A user unit is named `bynk` or `bynk.*`; the `bynk` root is reserved for the toolchain.",
612    ),
613    dg(
614        "bynk.parse.consumes_after_decls",
615        "`consumes` appears after other declarations.",
616        &["consumes_decl"],
617    ),
618    dg(
619        "bynk.parse.empty_agent",
620        "An `agent` body is empty.",
621        &["agent_decl"],
622    ),
623    dg(
624        "bynk.parse.empty_capability",
625        "A `capability` body is empty.",
626        &["capability_decl"],
627    ),
628    d(
629        "bynk.parse.empty_interpolation",
630        "An interpolation hole `\\(…)` contains no expression.",
631    ),
632    dg(
633        "bynk.parse.empty_match",
634        "A `match` has no arms.",
635        &["match_expr"],
636    ),
637    dg(
638        "bynk.parse.empty_mock_body",
639        "A `mocks` body is empty.",
640        &["mocks_decl"],
641    ),
642    dg(
643        "bynk.parse.empty_service",
644        "A `service` body is empty.",
645        &["service_decl"],
646    ),
647    dg(
648        "bynk.parse.expected_agent_key",
649        "Expected a `key` declaration in an agent.",
650        &["agent_decl"],
651    ),
652    d(
653        "bynk.parse.expected_agent_storage",
654        "An agent declares no storage — it has no `store` fields.",
655    ),
656    dg(
657        "bynk.parse.expected_base_type",
658        "Expected a base type.",
659        &["base_type"],
660    ),
661    dg(
662        "bynk.parse.expected_capability_op",
663        "Expected a capability operation.",
664        &["capability_op"],
665    ),
666    d("bynk.parse.expected_expression", "Expected an expression."),
667    dg(
668        "bynk.parse.expected_handler",
669        "Expected a handler.",
670        &["handler"],
671    ),
672    d("bynk.parse.expected_item", "Expected a declaration."),
673    dg(
674        "bynk.parse.expected_predicate",
675        "Expected a refinement predicate.",
676        &["refinement"],
677    ),
678    dg(
679        "bynk.parse.expected_provider_op",
680        "Expected a provider operation.",
681        &["provider_op"],
682    ),
683    d("bynk.parse.expected_token", "Expected a specific token."),
684    d("bynk.parse.expected_type", "Expected a type."),
685    d(
686        "bynk.parse.expected_unit_header",
687        "Expected a `commons` or `context` header.",
688    ),
689    dg(
690        "bynk.parse.expected_visibility",
691        "Expected a visibility keyword.",
692        &["exports_decl"],
693    ),
694    dg(
695        "bynk.parse.exports_after_decls",
696        "`exports` appears after other declarations.",
697        &["exports_decl"],
698    ),
699    d(
700        "bynk.parse.extra_tokens",
701        "Unexpected tokens after an otherwise complete construct.",
702    ),
703    dg(
704        "bynk.parse.generic_arg_count",
705        "Wrong number of generic type arguments.",
706        &["generic_type_ref"],
707    ),
708    dg(
709        "bynk.parse.handler_in_agent",
710        "A protocol handler (`on GET`/`schedule`/`message`) was declared in an agent.",
711        &["handler"],
712    ),
713    d(
714        "bynk.parse.invariant_after_handler",
715        "An `invariant` was declared after a handler; invariants precede handlers.",
716    ),
717    dg(
718        "bynk.parse.malformed_float_literal",
719        "A float literal is missing a digit on one side of the `.` (`1.`, `.5`).",
720        &["float_literal"],
721    ),
722    dg(
723        "bynk.parse.non_associative",
724        "A non-associative operator was chained (e.g. `a == b == c`).",
725        &["binary_expr"],
726    ),
727    d(
728        "bynk.parse.orphan_doc_block",
729        "A documentation block is not attached to a declaration (warning).",
730    ),
731    dg(
732        "bynk.parse.reserved_keyword",
733        "A reserved keyword was used as an identifier.",
734        &["identifier"],
735    ),
736    dg(
737        "bynk.parse.self_outside_method",
738        "`self` used outside a method or handler.",
739        &["self_expr"],
740    ),
741    d(
742        "bynk.parse.storage_after_phase",
743        "Agent storage (`state` / `store`) is declared after the invariants or handlers.",
744    ),
745    d(
746        "bynk.parse.unexpected_adapter",
747        "An `adapter` appeared where it is not allowed.",
748    ),
749    dg(
750        "bynk.parse.unexpected_context",
751        "A `context` appeared where it is not allowed.",
752        &["context_decl"],
753    ),
754    d("bynk.parse.unexpected_eof", "Unexpected end of input."),
755    dg(
756        "bynk.parse.unexpected_test",
757        "A `test` appeared where it is not allowed.",
758        &["test_decl"],
759    ),
760    d(
761        "bynk.parse.unknown_effect_method",
762        "An unknown method on `Effect`.",
763    ),
764    dg(
765        "bynk.parse.unknown_handler_kind",
766        "An unknown handler form (expected `call`, an HTTP method, `schedule`, or `message`).",
767        &["handler"],
768    ),
769    dg(
770        "bynk.parse.unknown_predicate",
771        "An unknown refinement predicate.",
772        &["predicate_name"],
773    ),
774    dg(
775        "bynk.parse.uses_after_decls",
776        "`uses` appears after other declarations.",
777        &["uses_decl"],
778    ),
779    d(
780        "bynk.project.file_and_directory",
781        "A unit exists as both a file and a directory.",
782    ),
783    d(
784        "bynk.project.inconsistent_commons_name",
785        "A source file's path does not match its declared name.",
786    ),
787    d(
788        "bynk.project.inconsistent_test_path",
789        "A test file's path does not match its target's name.",
790    ),
791    d(
792        "bynk.project.kind_conflict",
793        "A name is declared as both a commons and a context.",
794    ),
795    d(
796        "bynk.project.no_root",
797        "No project root could be determined.",
798    ),
799    d(
800        "bynk.project.no_sources",
801        "The project contains no source files.",
802    ),
803    d(
804        "bynk.project.read_failed",
805        "A source file could not be read.",
806    ),
807    dg(
808        "bynk.provider.dependency_cycle",
809        "Providers form a capability dependency cycle through `given`.",
810        &["provider_decl"],
811    ),
812    dg(
813        "bynk.provider.extra_operation",
814        "A `provides` block implements an operation not in the capability.",
815        &["provider_decl"],
816    ),
817    dg(
818        "bynk.provider.missing_operation",
819        "A `provides` block is missing a capability operation.",
820        &["provider_decl"],
821    ),
822    dg(
823        "bynk.provider.outside_context",
824        "`provides` was declared outside a context.",
825        &["provider_decl"],
826    ),
827    dg(
828        "bynk.provider.signature_mismatch",
829        "A `provides` operation's signature does not match the capability.",
830        &["provider_decl"],
831    ),
832    dg(
833        "bynk.provider.unknown_capability",
834        "`provides` names a capability that does not exist.",
835        &["provider_decl"],
836    ),
837    d(
838        "bynk.query.join_key_mismatch",
839        "A `joinOn`/`leftJoin` left and right key function return different types.",
840    ),
841    dg(
842        "bynk.query.sum_needs_numeric",
843        "A `sum`/`average` key function does not return a numeric type (`Int`, `Float`, or `Duration`).",
844        &[],
845    ),
846    dg(
847        "bynk.queue.bad_params",
848        "An `on message` handler does not take exactly one `message` parameter.",
849        &["queue_handler"],
850    ),
851    dg(
852        "bynk.queue.duplicate_consumer",
853        "Two `on message` handlers consume the same queue.",
854        &["queue_handler"],
855    ),
856    dg(
857        "bynk.queue.invalid_name",
858        "A `from queue(\"…\")` binding has an empty queue name.",
859        &["queue_handler"],
860    ),
861    dg(
862        "bynk.queue.return_not_queue_result",
863        "An `on message` handler does not return `Effect[QueueResult]`.",
864        &["handler"],
865    ),
866    dg(
867        "bynk.record_spread.field_type_mismatch",
868        "A record-spread override has the wrong type for the field.",
869        &["record_spread"],
870    ),
871    dg(
872        "bynk.record_spread.non_record_base",
873        "The base of a record spread is not a record.",
874        &["record_spread"],
875    ),
876    dg(
877        "bynk.record_spread.type_mismatch",
878        "A record spread's base is a different record type.",
879        &["record_spread"],
880    ),
881    dg(
882        "bynk.record_spread.unknown_field",
883        "A record spread overrides a field the record does not have.",
884        &["record_spread"],
885    ),
886    dg(
887        "bynk.refine.literal_violates",
888        "A literal does not satisfy the refined type's predicate.",
889        &["refined_type"],
890    ),
891    dg(
892        "bynk.requires.unpinned_dependency",
893        "An adapter `binding … requires { … }` entry has an unpinned version range.",
894        &["binding_decl"],
895    ),
896    d(
897        "bynk.resolve.ambiguous_variant",
898        "A variant name is ambiguous across several sum types.",
899    ),
900    dg(
901        "bynk.resolve.arity_mismatch",
902        "A function was called with the wrong number of arguments.",
903        &["call"],
904    ),
905    d("bynk.resolve.duplicate_actor", "Two actors share a name."),
906    dg(
907        "bynk.resolve.duplicate_agent",
908        "Two agents share a name.",
909        &["agent_decl"],
910    ),
911    dg(
912        "bynk.resolve.duplicate_capability",
913        "Two capabilities share a name.",
914        &["capability_decl"],
915    ),
916    dg(
917        "bynk.resolve.duplicate_field",
918        "A record declares a field twice.",
919        &["record_type"],
920    ),
921    dg(
922        "bynk.resolve.duplicate_field_init",
923        "A record construction initialises a field twice.",
924        &["record_construction"],
925    ),
926    dg(
927        "bynk.resolve.duplicate_fn",
928        "Two functions share a name.",
929        &["fn_decl"],
930    ),
931    dg(
932        "bynk.resolve.duplicate_method",
933        "Two methods share a name.",
934        &["fn_decl"],
935    ),
936    dg(
937        "bynk.resolve.duplicate_param",
938        "A parameter name is repeated.",
939        &["param"],
940    ),
941    dg(
942        "bynk.resolve.duplicate_provider",
943        "A capability is provided more than once.",
944        &["provider_decl"],
945    ),
946    dg(
947        "bynk.resolve.duplicate_service",
948        "Two services share a name.",
949        &["service_decl"],
950    ),
951    dg(
952        "bynk.resolve.duplicate_type",
953        "Two types share a name.",
954        &["type_decl"],
955    ),
956    dg(
957        "bynk.resolve.duplicate_variant",
958        "A sum type declares a variant twice.",
959        &["sum_type"],
960    ),
961    d(
962        "bynk.resolve.fn_without_call",
963        "A function was referenced without being called.",
964    ),
965    dg(
966        "bynk.resolve.let_shadows_fn",
967        "A `let` binding shadows a function.",
968        &["let_stmt"],
969    ),
970    dg(
971        "bynk.resolve.let_shadows_type",
972        "A `let` binding shadows a type.",
973        &["let_stmt"],
974    ),
975    d(
976        "bynk.resolve.method_unknown_type",
977        "A method is defined on an unknown type.",
978    ),
979    dg(
980        "bynk.resolve.missing_field",
981        "A record construction omits a required field.",
982        &["record_construction"],
983    ),
984    d(
985        "bynk.resolve.name_conflict",
986        "Two declarations share a name.",
987    ),
988    dg(
989        "bynk.resolve.not_a_record_type",
990        "Record syntax was used on a non-record type.",
991        &["record_construction"],
992    ),
993    dg(
994        "bynk.resolve.opaque_record_construction",
995        "An opaque type was constructed with record syntax.",
996        &["record_construction"],
997    ),
998    dg(
999        "bynk.resolve.param_as_function",
1000        "A value (such as a parameter) was called as a function.",
1001        &["call"],
1002    ),
1003    dg(
1004        "bynk.resolve.recursive_record_field",
1005        "A record directly contains a field of its own type.",
1006        &["record_type"],
1007    ),
1008    dg(
1009        "bynk.resolve.self_outside_method",
1010        "`self` referenced outside a method or handler.",
1011        &["self_expr"],
1012    ),
1013    dg(
1014        "bynk.resolve.type_as_function",
1015        "A type name was called as if it were a function.",
1016        &["call"],
1017    ),
1018    d(
1019        "bynk.resolve.type_in_expr",
1020        "A type name was used where a value is expected.",
1021    ),
1022    dg(
1023        "bynk.resolve.unconsumed_context",
1024        "A context's service was called without a `consumes` declaration.",
1025        &["consumes_decl"],
1026    ),
1027    dg(
1028        "bynk.resolve.unknown_field",
1029        "Accessed a field the record does not have.",
1030        &["field_access"],
1031    ),
1032    dg(
1033        "bynk.resolve.unknown_function",
1034        "Called a function that does not exist.",
1035        &["call"],
1036    ),
1037    d(
1038        "bynk.resolve.unknown_name",
1039        "Referenced a name that is not in scope.",
1040    ),
1041    dg(
1042        "bynk.resolve.unknown_static_member",
1043        "Referenced an unknown static member (e.g. `T.x`).",
1044        &["field_access"],
1045    ),
1046    d(
1047        "bynk.resolve.unknown_type",
1048        "Referenced a type that does not exist.",
1049    ),
1050    dg(
1051        "bynk.send.in_pure_context",
1052        "A `~>` send was used in a pure (non-effectful) context.",
1053        &["effect_send_stmt"],
1054    ),
1055    dg(
1056        "bynk.send.non_effect",
1057        "A `~>` send was applied to a non-`Effect` value.",
1058        &["effect_send_stmt"],
1059    ),
1060    dg(
1061        "bynk.send.requires_unit",
1062        "A `~>` send targets an operation whose reply is not `Effect[()]`.",
1063        &["effect_send_stmt"],
1064    ),
1065    dg(
1066        "bynk.service.missing_from",
1067        "A `from`-less service has a handler other than `on call`.",
1068        &["service_decl"],
1069    ),
1070    dg(
1071        "bynk.service.mixed_protocols",
1072        "A service mixes handler forms that do not match its `from <protocol>`.",
1073        &["service_decl"],
1074    ),
1075    dg(
1076        "bynk.service.outside_context",
1077        "A `service` was declared outside a context.",
1078        &["service_decl"],
1079    ),
1080    dg(
1081        "bynk.service.return_not_effect",
1082        "A service handler's return type is not an `Effect`.",
1083        &["service_decl"],
1084    ),
1085    dg(
1086        "bynk.service.unknown_protocol",
1087        "A `from <protocol>` names an unknown protocol (e.g. a transport like Kafka).",
1088        &["service_decl"],
1089    ),
1090    d(
1091        "bynk.store.annotation_kind_mismatch",
1092        "A storage annotation is used on a kind it does not apply to (e.g. `@ttl` on a `Map`).",
1093    ),
1094    d(
1095        "bynk.store.annotation_unsupported",
1096        "A known storage annotation (`@ttl`/`@retain`/`@indexed`/`@bounded`) is used before the slice that supports it.",
1097    ),
1098    d(
1099        "bynk.store.cache_needs_clock",
1100        "A handler performs a `Cache` operation (TTL expiry reads the clock) without declaring `given Clock`.",
1101    ),
1102    d(
1103        "bynk.store.cache_ttl_required",
1104        "A `Cache` field is missing its required `@ttl(<duration>)` annotation (a keyed store with no expiry is a `Map`).",
1105    ),
1106    d(
1107        "bynk.store.kind_arity",
1108        "A storage kind was applied to the wrong number of type arguments (e.g. `Cell[A, B]`).",
1109    ),
1110    d(
1111        "bynk.store.kind_unsupported",
1112        "A known storage kind (`Queue`) is used before the slice that supports it.",
1113    ),
1114    d(
1115        "bynk.store.log_needs_clock",
1116        "A handler calls `Log.append` (which stamps the current time) without declaring `given Clock`.",
1117    ),
1118    d(
1119        "bynk.store.unknown_annotation",
1120        "A `store` field carries an annotation outside the closed `@indexed`/`@ttl`/`@retain`/`@bounded` set.",
1121    ),
1122    d(
1123        "bynk.store.unknown_kind",
1124        "A `store` field's type is not a known storage kind.",
1125    ),
1126    d(
1127        "bynk.store.unknown_op",
1128        "A storage-`Map`/`Set` operation is not a recognised entry/membership method.",
1129    ),
1130    dg(
1131        "bynk.target.vendor_conflict",
1132        "One deployment unit's in-process closure uses platform-native capabilities from two mutually-exclusive platforms.",
1133        &["consumes_decl"],
1134    ),
1135    dg(
1136        "bynk.target.vendor_required",
1137        "A deployment unit uses a platform-native capability but the build selects another `--platform`.",
1138        &["consumes_decl"],
1139    ),
1140    dg(
1141        "bynk.test.duplicate_case_name",
1142        "Two test cases share a description.",
1143        &["test_case"],
1144    ),
1145    dg(
1146        "bynk.test.unknown_target",
1147        "A `test` block targets a unit that does not exist.",
1148        &["test_decl"],
1149    ),
1150    d(
1151        "bynk.types.ambiguous_constructor",
1152        "`Ok`/`Err` is ambiguous between `Result` and `HttpResult`; qualify it.",
1153    ),
1154    dg(
1155        "bynk.types.argument_mismatch",
1156        "A function argument has the wrong type.",
1157        &["call"],
1158    ),
1159    dg(
1160        "bynk.types.call_arity",
1161        "A function value was applied with the wrong number of arguments.",
1162        &["call"],
1163    ),
1164    dg(
1165        "bynk.types.cannot_infer_option_type_param",
1166        "The value type of `None` could not be inferred.",
1167        &["none_expr"],
1168    ),
1169    d(
1170        "bynk.types.cannot_infer_result_type_params",
1171        "The type parameters of a `Result` could not be inferred.",
1172    ),
1173    d(
1174        "bynk.types.constructor_arity",
1175        "A variant constructor got the wrong number of arguments.",
1176    ),
1177    d(
1178        "bynk.types.constructor_base_mismatch",
1179        "A `.of` constructor was given an argument of the wrong base type.",
1180    ),
1181    dg(
1182        "bynk.types.duplicate_variant_arm",
1183        "A `match` has two arms for the same variant.",
1184        &["match_arm"],
1185    ),
1186    dg(
1187        "bynk.types.empty_refinement",
1188        "A refinement admits no values (contradictory predicates).",
1189        &["refinement"],
1190    ),
1191    dg(
1192        "bynk.types.err_value_mismatch",
1193        "An `Err` payload has the wrong type.",
1194        &["err_expr"],
1195    ),
1196    dg(
1197        "bynk.types.field_access_on_non_record",
1198        "Field access on a value that is not a record.",
1199        &["field_access"],
1200    ),
1201    dg(
1202        "bynk.types.field_refinement_not_base",
1203        "An inline field refinement requires a base or refined type.",
1204        &["record_field"],
1205    ),
1206    dg(
1207        "bynk.types.field_value_mismatch",
1208        "A record field was given a value of the wrong type.",
1209        &["record_construction"],
1210    ),
1211    dg(
1212        "bynk.types.function_at_boundary",
1213        "A function type appeared in a serialisable or boundary position (a record field, sum payload, service/agent handler signature, capability operation signature, agent state field, or agent key); functions cannot serialise or cross a boundary.",
1214        &["function_type_ref"],
1215    ),
1216    dg(
1217        "bynk.types.if_branch_mismatch",
1218        "The branches of an `if` have different types.",
1219        &["if_expr"],
1220    ),
1221    dg(
1222        "bynk.types.if_non_bool_cond",
1223        "An `if` condition is not a `Bool`.",
1224        &["if_expr"],
1225    ),
1226    d(
1227        "bynk.types.interpolation_non_scalar",
1228        "An interpolation hole holds a value with no string form.",
1229    ),
1230    dg(
1231        "bynk.types.invalid_regex",
1232        "A `Matches` predicate contains an invalid regular expression.",
1233        &["refinement"],
1234    ),
1235    dg(
1236        "bynk.types.inverted_range",
1237        "An `InRange` predicate has its bounds inverted.",
1238        &["refinement"],
1239    ),
1240    dg(
1241        "bynk.types.is_base_mismatch",
1242        "An `is` refinement check is applied to a value of the wrong base type.",
1243        &["is_expr"],
1244    ),
1245    dg(
1246        "bynk.types.is_non_sum",
1247        "`is` was applied to a value that is not a sum type.",
1248        &["is_expr"],
1249    ),
1250    dg(
1251        "bynk.types.is_unknown_variant",
1252        "`is` names a variant the type does not have.",
1253        &["is_expr"],
1254    ),
1255    dg(
1256        "bynk.types.json_uncodable",
1257        "A `Json.encode`/`Json.decode` target type cannot pass through the typed JSON codec (functions, effects, error builtins).",
1258        &["method_call"],
1259    ),
1260    dg(
1261        "bynk.types.key_not_orderable",
1262        "A `sortBy`/`min`/`max` key function does not return an orderable type (`Int`, `Float`, `String`, `Duration`, or `Instant`).",
1263        &[],
1264    ),
1265    dg(
1266        "bynk.types.lambda_mismatch",
1267        "A lambda's parameter count, parameter annotations, or body type do not match the expected function type.",
1268        &["lambda_expr"],
1269    ),
1270    dg(
1271        "bynk.types.let_annotation_mismatch",
1272        "A `let` value does not match its type annotation.",
1273        &["let_stmt"],
1274    ),
1275    dg(
1276        "bynk.types.list_element_mismatch",
1277        "A list-literal element has a different type from the list's element type.",
1278        &["list_literal"],
1279    ),
1280    dg(
1281        "bynk.types.match_arm_mismatch",
1282        "A `match` arm has a different type from the others.",
1283        &["match_arm"],
1284    ),
1285    dg(
1286        "bynk.types.match_non_sum_discriminant",
1287        "`match` was applied to a value that is not a sum type.",
1288        &["match_expr"],
1289    ),
1290    dg(
1291        "bynk.types.method_arity",
1292        "A method was called with the wrong number of arguments.",
1293        &["method_call"],
1294    ),
1295    dg(
1296        "bynk.types.method_not_found",
1297        "Called a method the type does not have.",
1298        &["method_call"],
1299    ),
1300    dg(
1301        "bynk.types.method_on_non_named_type",
1302        "A method was called on a built-in type that has no methods.",
1303        &["method_call"],
1304    ),
1305    dg(
1306        "bynk.types.mixed_pattern_bindings",
1307        "A pattern mixes named and positional bindings.",
1308        &["variant_pattern"],
1309    ),
1310    dg(
1311        "bynk.types.negative_length",
1312        "A length predicate was given a negative value.",
1313        &["refinement"],
1314    ),
1315    dg(
1316        "bynk.types.no_numeric_coercion",
1317        "`Int` and `Float` were mixed without an explicit conversion — in an operation or in refinement bounds.",
1318        &["binary_expr", "refinement"],
1319    ),
1320    dg(
1321        "bynk.types.non_exhaustive_match",
1322        "A `match` does not cover every variant.",
1323        &["match_expr"],
1324    ),
1325    dg(
1326        "bynk.types.ok_value_mismatch",
1327        "An `Ok` payload has the wrong type.",
1328        &["ok_expr"],
1329    ),
1330    dg(
1331        "bynk.types.opaque_raw_outside",
1332        "`.raw` on an opaque type was used outside its defining commons.",
1333        &["field_access"],
1334    ),
1335    dg(
1336        "bynk.types.opaque_record_construction",
1337        "An opaque type was constructed with record syntax.",
1338        &["record_construction"],
1339    ),
1340    dg(
1341        "bynk.types.opaque_unsafe_outside",
1342        "`.unsafe` on an opaque type was used outside its defining context.",
1343        &["field_access"],
1344    ),
1345    dg(
1346        "bynk.types.pattern_arity",
1347        "A pattern binds the wrong number of payload fields.",
1348        &["variant_pattern"],
1349    ),
1350    dg(
1351        "bynk.types.pattern_type_mismatch",
1352        "A pattern's type does not match the matched value.",
1353        &["variant_pattern"],
1354    ),
1355    dg(
1356        "bynk.types.predicate_base_mismatch",
1357        "A predicate does not apply to the type's base (e.g. a string predicate on an `Int`).",
1358        &["refinement"],
1359    ),
1360    d(
1361        "bynk.types.query_at_boundary",
1362        "A `Query` type appears in a storable or boundary-crossing position — a query is built and executed in place, never persisted or sent (ADR 0115).",
1363    ),
1364    dg(
1365        "bynk.types.question_error_mismatch",
1366        "`?` propagates an error type incompatible with the function's.",
1367        &["question_expr"],
1368    ),
1369    dg(
1370        "bynk.types.question_on_non_result",
1371        "`?` was applied to a non-`Result` value.",
1372        &["question_expr"],
1373    ),
1374    dg(
1375        "bynk.types.question_outside_result",
1376        "`?` used in a function that does not return a `Result`.",
1377        &["question_expr"],
1378    ),
1379    d(
1380        "bynk.types.return_mismatch",
1381        "A returned value does not match the declared return type.",
1382    ),
1383    dg(
1384        "bynk.types.some_value_mismatch",
1385        "A `Some` payload has the wrong type.",
1386        &["some_expr"],
1387    ),
1388    d(
1389        "bynk.types.type_mismatch",
1390        "Two types that were required to match did not.",
1391    ),
1392    dg(
1393        "bynk.types.uninferable_element_type",
1394        "An empty `[]` (or `List.empty()` / `Map.empty()`) has no expected type to infer its element type from.",
1395        &["list_literal"],
1396    ),
1397    dg(
1398        "bynk.types.unkeyable_distinct",
1399        "A `distinct`/`distinctBy` element or key is not value-keyable (`String`, `Int`, or a refined/opaque type over them).",
1400        &[],
1401    ),
1402    dg(
1403        "bynk.types.unkeyable_map_key",
1404        "A `Map` key type is not value-keyable (`String`, `Int`, or a refined/opaque type over them).",
1405        &["generic_type_ref"],
1406    ),
1407    dg(
1408        "bynk.types.unknown_field",
1409        "Referenced a field the record type does not declare.",
1410        &["field_access"],
1411    ),
1412    dg(
1413        "bynk.types.unknown_pattern_field",
1414        "A pattern names a field the variant does not have.",
1415        &["variant_pattern"],
1416    ),
1417    dg(
1418        "bynk.types.unknown_static_member",
1419        "Referenced an unknown static member on a type.",
1420        &["field_access"],
1421    ),
1422    dg(
1423        "bynk.types.unknown_variant_in_pattern",
1424        "A pattern names a variant the sum type does not have.",
1425        &["variant_pattern"],
1426    ),
1427    dg(
1428        "bynk.types.unreachable_arm",
1429        "A `match` arm is unreachable.",
1430        &["match_arm"],
1431    ),
1432    d(
1433        "bynk.types.variant_arity",
1434        "A variant constructor got the wrong number of payload values.",
1435    ),
1436    d(
1437        "bynk.types.variant_missing_payload",
1438        "A variant requiring a payload was used without one.",
1439    ),
1440    d(
1441        "bynk.types.variant_payload_mismatch",
1442        "A variant payload has the wrong type.",
1443    ),
1444    dg(
1445        "bynk.uses.name_conflict",
1446        "A `uses` name collides with another name.",
1447        &["uses_decl"],
1448    ),
1449    dg(
1450        "bynk.uses.self_reference",
1451        "A commons `uses` itself.",
1452        &["uses_decl"],
1453    ),
1454    dg(
1455        "bynk.uses.target_is_context",
1456        "`uses` targets a context instead of a commons.",
1457        &["uses_decl"],
1458    ),
1459    dg(
1460        "bynk.uses.unknown_commons",
1461        "`uses` names a commons that does not exist.",
1462        &["uses_decl"],
1463    ),
1464];
1465
1466/// A diagnostic with no single governing grammar construct.
1467const fn d(code: &'static str, summary: &'static str) -> DiagnosticInfo {
1468    DiagnosticInfo {
1469        code,
1470        summary,
1471        grammar_symbol: &[],
1472    }
1473}
1474
1475/// A diagnostic that constrains one or more grammar productions.
1476const fn dg(
1477    code: &'static str,
1478    summary: &'static str,
1479    grammar_symbol: &'static [&'static str],
1480) -> DiagnosticInfo {
1481    DiagnosticInfo {
1482        code,
1483        summary,
1484        grammar_symbol,
1485    }
1486}
1487
1488/// The category segment of a code (the part between the first two dots), e.g.
1489/// `"types"` for `"bynk.types.type_mismatch"`.
1490pub fn category(code: &str) -> &str {
1491    code.split('.').nth(1).unwrap_or("")
1492}
1493
1494/// A human-readable heading for a category segment.
1495fn category_title(cat: &str) -> &'static str {
1496    match cat {
1497        "agent" | "agents" => "Agents",
1498        "assert" => "Assertions",
1499        "boundary" => "Boundaries",
1500        "capability" => "Capabilities",
1501        "consumes" => "Consumes",
1502        "context" => "Contexts",
1503        "cron" => "Cron",
1504        "effect" => "Effects",
1505        "exports" => "Exports",
1506        "given" => "Given capabilities",
1507        "http" => "HTTP",
1508        "lex" => "Lexer",
1509        "mock" => "Mock and mocks",
1510        "parse" => "Parser",
1511        "project" => "Project",
1512        "provider" => "Providers",
1513        "queue" => "Queue",
1514        "record_spread" => "Record spread",
1515        "refine" => "Refinement",
1516        "resolve" => "Resolution",
1517        "service" => "Services",
1518        "test" => "Tests",
1519        "types" => "Type checking",
1520        "uses" => "Uses",
1521        _ => "Other",
1522    }
1523}
1524
1525/// Render the diagnostic index as a Markdown reference page, grouped by
1526/// category. This is the generator behind `docs/src/reference/diagnostics.md`.
1527pub fn render_markdown() -> String {
1528    use std::collections::BTreeMap;
1529
1530    // Group codes by their category title, preserving sorted code order.
1531    let mut by_category: BTreeMap<&str, Vec<&DiagnosticInfo>> = BTreeMap::new();
1532    for info in REGISTRY {
1533        by_category
1534            .entry(category_title(category(info.code)))
1535            .or_default()
1536            .push(info);
1537    }
1538
1539    let mut out = String::new();
1540    out.push_str("# Diagnostic index\n\n");
1541    out.push_str(
1542        "<!-- GENERATED FILE — do not edit by hand.\n     \
1543         Source: bynkc/src/diagnostics.rs (`render_markdown`).\n     \
1544         Regenerate with: BYNK_BLESS=1 cargo test -p bynkc --test diagnostics_registry -->\n\n",
1545    );
1546    out.push_str(
1547        "Every diagnostic code the compiler can emit, with a one-line summary of \
1548         the cause, grouped by category. For step-by-step cause-and-fix guidance \
1549         on the most common ones, see the [troubleshooting guides](../troubleshooting/index.md).\n\n",
1550    );
1551    out.push_str(&format!(
1552        "There are **{}** codes in total.\n",
1553        REGISTRY.len()
1554    ));
1555
1556    for (title, infos) in &by_category {
1557        out.push_str(&format!("\n## {title}\n\n"));
1558        out.push_str("| Code | Summary | Construct |\n|---|---|---|\n");
1559        for info in infos {
1560            // The construct column deep-links each governing production to its
1561            // entry in the annotated grammar reference; generated from
1562            // `grammar_symbol` (each value is an embeddable rule, so the
1563            // `#rule-<raw>` anchor resolves — enforced in diagnostics_registry).
1564            let construct = info
1565                .grammar_symbol
1566                .iter()
1567                .map(|sym| format!("[`{sym}`](grammar.md#rule-{sym})"))
1568                .collect::<Vec<_>>()
1569                .join(", ");
1570            out.push_str(&format!(
1571                "| `{}` | {} | {} |\n",
1572                info.code, info.summary, construct
1573            ));
1574        }
1575    }
1576
1577    out
1578}
1579
1580/// Invert the registry into a `{ "<rule>": [ { code, summary }, … ], … }` map,
1581/// serialised as pretty JSON with sorted keys and sorted codes. Only rules with
1582/// at least one diagnostic appear. This is the generator behind
1583/// `docs/grammar-semantics.json`, which the `{{#grammar-semantics <rule>}}`
1584/// preprocessor directive consumes.
1585pub fn render_grammar_semantics_json() -> String {
1586    use std::collections::BTreeMap;
1587
1588    // REGISTRY is sorted by code, so each rule's vector comes out code-sorted;
1589    // the BTreeMap gives sorted rule names.
1590    let mut by_symbol: BTreeMap<&str, Vec<&DiagnosticInfo>> = BTreeMap::new();
1591    for info in REGISTRY {
1592        for sym in info.grammar_symbol {
1593            by_symbol.entry(sym).or_default().push(info);
1594        }
1595    }
1596
1597    let mut map = serde_json::Map::new();
1598    map.insert(
1599        "_generated".to_string(),
1600        serde_json::Value::String(
1601            "Generated from the grammar_symbol field of bynkc/src/diagnostics.rs. \
1602             Do not edit by hand. Regenerate with: BYNK_BLESS=1 cargo test -p \
1603             bynkc --test diagnostics_registry"
1604                .to_string(),
1605        ),
1606    );
1607    for (sym, infos) in by_symbol {
1608        let arr: Vec<serde_json::Value> = infos
1609            .iter()
1610            .map(|info| serde_json::json!({ "code": info.code, "summary": info.summary }))
1611            .collect();
1612        map.insert(sym.to_string(), serde_json::Value::Array(arr));
1613    }
1614
1615    let mut s =
1616        serde_json::to_string_pretty(&serde_json::Value::Object(map)).expect("serialise semantics");
1617    s.push('\n');
1618    s
1619}