Skip to main content

bock_errors/
catalog.rs

1//! Catalog of diagnostic codes emitted by the compiler.
2//!
3//! Source crates emit diagnostics with inline numeric codes (e.g. `E1001`).
4//! This catalog is the single queryable registry of those codes together
5//! with their human-facing metadata — used by editor extensions, the
6//! vocabulary emitter, and online documentation.
7//!
8//! The catalog is deliberately separate from emission sites: keeping it
9//! centralized means tools can render it without depending on the internal
10//! structure of every compiler pass. When a new code is introduced at an
11//! emission site, add an entry here too.
12
13use crate::Severity;
14
15/// Metadata for a single diagnostic code.
16pub struct DiagnosticCodeInfo {
17    /// Zero-padded code string (e.g. `"E1001"`).
18    pub code: &'static str,
19    /// Severity classification.
20    pub severity: Severity,
21    /// Short single-sentence summary shown by editors.
22    pub summary: &'static str,
23    /// Longer description (optional; markdown permitted).
24    pub description: &'static str,
25    /// Spec section references (e.g. `&["§6.1", "§17.4"]`).
26    pub spec_refs: &'static [&'static str],
27}
28
29/// The full catalog of diagnostic codes.
30///
31/// Codes are grouped by the *domain* of the violated rule (which usually,
32/// but not always, matches the crate of origin):
33/// - `1xxx` — lexer (`bock-lexer`) and name resolution (`bock-air`)
34/// - `2xxx` — parser (`bock-parser`)
35/// - `4xxx` — type checker (`bock-types/checker`)
36/// - `5xxx` — ownership (`bock-types/ownership`)
37/// - `6xxx` — effects (`bock-types/effects`; E6005 is emitted by the
38///   `bock-air` resolver and E6006 by the checker, but both report
39///   effect-system rules so they live in this family)
40/// - `7xxx` — capabilities (`bock-types/capabilities`)
41/// - `8xxx` — context-annotation system (`bock-air`): `@context`/`@capability`
42///   interpretation (`context.rs`), context validation (`validate_context.rs`),
43///   and capability verification / composition / PII flow
44///   (`verify_capabilities.rs`, `compose_context.rs`)
45#[must_use]
46pub fn diagnostic_catalog() -> Vec<DiagnosticCodeInfo> {
47    vec![
48        // ── Lexer (1xxx) ────────────────────────────────────────────────
49        DiagnosticCodeInfo {
50            code: "E1001",
51            severity: Severity::Error,
52            summary: "Unexpected character in source.",
53            description: "The lexer encountered a character it does not recognize as part of any valid token. (Lexer-only: the name-resolution pass reports an undefined name as `E1009`, an unfound module as `E1010`, and an unfound symbol as `E1011`.)",
54            spec_refs: &["§1"],
55        },
56        DiagnosticCodeInfo {
57            code: "E1002",
58            severity: Severity::Error,
59            summary: "Unterminated string literal.",
60            description: "A string literal was opened but never closed before end of input.",
61            spec_refs: &["§1.3"],
62        },
63        DiagnosticCodeInfo {
64            code: "E1003",
65            severity: Severity::Error,
66            summary: "Invalid escape sequence in string.",
67            description: "An escape sequence in a string literal is not one of the recognized forms.",
68            spec_refs: &["§1.3"],
69        },
70        DiagnosticCodeInfo {
71            code: "E1004",
72            severity: Severity::Error,
73            summary: "Invalid character literal.",
74            description: "A character literal is empty, contains more than one character, or has an invalid escape.",
75            spec_refs: &["§1.3"],
76        },
77        DiagnosticCodeInfo {
78            code: "E1005",
79            severity: Severity::Error,
80            summary: "Invalid digit for numeric literal.",
81            description: "A digit was found that is outside the range of the declared numeric base (e.g. `8` in an octal literal or a non-hex digit after `0x`).",
82            spec_refs: &["§1.3"],
83        },
84        DiagnosticCodeInfo {
85            code: "E1006",
86            severity: Severity::Error,
87            summary: "Unterminated block comment.",
88            description: "A `/* ... */` block comment was opened but never closed before end of input.",
89            spec_refs: &["§1.2"],
90        },
91        // ── Name resolution (also 1xxx) ─────────────────────────────────
92        // The name-resolution pass (`bock-air/resolve.rs`) owns its own slots
93        // in the `1xxx` range; it does NOT share codes with the lexer. The
94        // former E1001/E1005/E1006 lexer↔resolver overlaps were split: the
95        // resolver's undefined-name → E1009, module-not-found → E1010, and
96        // symbol-not-found → E1011 (Q-error-code-renumbering).
97        DiagnosticCodeInfo {
98            code: "W1001",
99            severity: Severity::Warning,
100            summary: "Unused import.",
101            description: "An import was declared but never referenced. Uses of an imported effect name in `with`, `handling`, and `impl … for` positions count as references (Q-w1001-effect-import-false-positive).",
102            spec_refs: &["§10"],
103        },
104        DiagnosticCodeInfo {
105            code: "E1007",
106            severity: Severity::Error,
107            summary: "Symbol is not visible.",
108            description: "The referenced symbol exists but is private; declare it `public` to export it.",
109            spec_refs: &["§10"],
110        },
111        DiagnosticCodeInfo {
112            code: "E1008",
113            severity: Severity::Error,
114            summary: "Circular module dependency.",
115            description: "The `use` import graph contains a cycle, so the modules cannot be compiled in any dependency order. The message names every module in the cycle in order and points at one offending `use` edge. Fix by removing one of the `use` edges in the cycle, or by extracting the shared items into a third module that both can import.",
116            spec_refs: &["§10"],
117        },
118        DiagnosticCodeInfo {
119            code: "E1009",
120            severity: Severity::Error,
121            summary: "Undefined name.",
122            description: "An identifier reference could not be resolved to any binding in scope. Most often a name is not imported or is misspelled; check the `use` declarations. (A bare effect operation such as `log` called outside a `with` clause or `handling` block is reported separately as `E6005`, not this code.) Emitted by the name-resolution pass.",
123            spec_refs: &["§10"],
124        },
125        DiagnosticCodeInfo {
126            code: "E1010",
127            severity: Severity::Error,
128            summary: "Imported module not found.",
129            description: "A `use` path named a module the registry could not locate. Check the import path and that the target file declares `module <path>`. Emitted by the name-resolution pass.",
130            spec_refs: &["§10"],
131        },
132        DiagnosticCodeInfo {
133            code: "E1011",
134            severity: Severity::Error,
135            summary: "Imported symbol not found in module.",
136            description: "A `use` path names a module that exists but does not export the requested symbol. Emitted by the name-resolution pass.",
137            spec_refs: &["§10"],
138        },
139        // ── Parser (2xxx) ───────────────────────────────────────────────
140        DiagnosticCodeInfo {
141            code: "E2000",
142            severity: Severity::Error,
143            summary: "Parse error at top level.",
144            description: "The parser encountered unexpected input while reading a top-level item.",
145            spec_refs: &["§11"],
146        },
147        DiagnosticCodeInfo {
148            code: "E2001",
149            severity: Severity::Error,
150            summary: "Unexpected token.",
151            description: "The next token did not match any alternative at the current grammar position.",
152            spec_refs: &["§11"],
153        },
154        DiagnosticCodeInfo {
155            code: "E2002",
156            severity: Severity::Error,
157            summary: "Missing expected token.",
158            description: "The parser required a specific token here and found something else.",
159            spec_refs: &["§11"],
160        },
161        DiagnosticCodeInfo {
162            code: "E2010",
163            severity: Severity::Error,
164            summary: "Invalid declaration.",
165            description: "A top-level declaration has malformed structure.",
166            spec_refs: &["§4"],
167        },
168        DiagnosticCodeInfo {
169            code: "E2020",
170            severity: Severity::Error,
171            summary: "Invalid expression.",
172            description: "An expression could not be parsed due to malformed structure.",
173            spec_refs: &["§5"],
174        },
175        DiagnosticCodeInfo {
176            code: "E2021",
177            severity: Severity::Error,
178            summary: "Invalid pattern.",
179            description: "A pattern in a `match` or `let` binding could not be parsed.",
180            spec_refs: &["§7"],
181        },
182        DiagnosticCodeInfo {
183            code: "E2022",
184            severity: Severity::Error,
185            summary: "Invalid type expression.",
186            description: "A type annotation could not be parsed as a valid type expression.",
187            spec_refs: &["§2"],
188        },
189        DiagnosticCodeInfo {
190            code: "E2030",
191            severity: Severity::Error,
192            summary: "Parentheses required (lambda parameters / `if` condition).",
193            description: "A construct that requires parentheses was written without them. Lambda parameters must be parenthesized; single-identifier forms (`x => …`) are not accepted. An `if` condition must be parenthesized (`if (cond) { … }`); the diagnostic names the offending token and a note gives the wrapped form. (A missing function name after `fn` is reported separately as `E2073`.)",
194            spec_refs: &["§5"],
195        },
196        DiagnosticCodeInfo {
197            code: "E2031",
198            severity: Severity::Error,
199            summary: "Invalid lambda / function parameter.",
200            description: "A parameter position expected a name and found something else — e.g. a missing name after `mut`, or a non-identifier where a parameter name was required.",
201            spec_refs: &["§5"],
202        },
203        DiagnosticCodeInfo {
204            code: "E2040",
205            severity: Severity::Error,
206            summary: "Invalid generic parameter list.",
207            description: "A generic parameter list is malformed.",
208            spec_refs: &["§4.5"],
209        },
210        DiagnosticCodeInfo {
211            code: "E2050",
212            severity: Severity::Error,
213            summary: "Invalid use declaration.",
214            description: "A `use` import is malformed.",
215            spec_refs: &["§10"],
216        },
217        DiagnosticCodeInfo {
218            code: "E2060",
219            severity: Severity::Error,
220            summary: "Invalid attribute / annotation.",
221            description: "An `@annotation` or `#attribute` could not be parsed.",
222            spec_refs: &["§4.7"],
223        },
224        DiagnosticCodeInfo {
225            code: "E2061",
226            severity: Severity::Error,
227            summary: "Expected constant name.",
228            description: "A `const` declaration was missing its name: a constant name was expected and a different token was found.",
229            spec_refs: &["§4"],
230        },
231        DiagnosticCodeInfo {
232            code: "E2070",
233            severity: Severity::Error,
234            summary: "Invalid match arm.",
235            description: "A match arm is malformed; each arm is `pattern => expression`.",
236            spec_refs: &["§7"],
237        },
238        DiagnosticCodeInfo {
239            code: "E2071",
240            severity: Severity::Error,
241            summary: "Expected associated type name.",
242            description: "An associated-type position inside a `trait`/`impl` expected a type name (`type Name`) and found a different token.",
243            spec_refs: &["§4.4"],
244        },
245        DiagnosticCodeInfo {
246            code: "E2072",
247            severity: Severity::Error,
248            summary: "Expected method name.",
249            description: "A method declaration inside a `trait`/`impl`/`class` expected a method name after `fn` and found a different token.",
250            spec_refs: &["§4.4"],
251        },
252        DiagnosticCodeInfo {
253            code: "E2073",
254            severity: Severity::Error,
255            summary: "Expected function name.",
256            description: "A top-level (or nested) function declaration expected a function name after `fn` and found a different token.",
257            spec_refs: &["§4"],
258        },
259        DiagnosticCodeInfo {
260            code: "E2090",
261            severity: Severity::Error,
262            summary: "Invalid effect declaration.",
263            description: "An `effect` declaration or `with` clause is malformed.",
264            spec_refs: &["§8"],
265        },
266        DiagnosticCodeInfo {
267            code: "E2091",
268            severity: Severity::Error,
269            summary: "Expected effect operation name.",
270            description: "An effect operation declaration inside an `effect` block expected an operation name after `fn` and found a different token.",
271            spec_refs: &["§8"],
272        },
273        DiagnosticCodeInfo {
274            code: "E2092",
275            severity: Severity::Error,
276            summary: "Tuple positional indexing is not available in v1.",
277            description: "`t.0` / `t.1` positional tuple indexing is not a v1 form. Destructure with `let (a, b) = t` to bind tuple elements instead.",
278            spec_refs: &["§5"],
279        },
280        // ── Type checker (4xxx) ────────────────────────────────────────
281        DiagnosticCodeInfo {
282            code: "E4001",
283            severity: Severity::Error,
284            summary: "Type mismatch.",
285            description: "Expected and actual types do not unify. The message reads `expected `T`, found `U``: `T` is the type the surrounding context requires and `U` is the type the expression actually has, both in surface Bock syntax. When a direct conversion to the expected type exists, a note suggests it (`.to_float()`, `.to_int()`, `.to_string()`, or `Int.try_from`/`Float.try_from` for parsing a String).",
286            spec_refs: &["§2"],
287        },
288        DiagnosticCodeInfo {
289            code: "E4002",
290            severity: Severity::Error,
291            summary: "Undefined variable.",
292            description: "A name referenced in an expression has no binding in scope.",
293            spec_refs: &["§2"],
294        },
295        DiagnosticCodeInfo {
296            code: "E4003",
297            severity: Severity::Error,
298            summary: "Arity mismatch in call.",
299            description: "The number of arguments does not match the callee's parameter count.",
300            spec_refs: &["§5"],
301        },
302        DiagnosticCodeInfo {
303            code: "E4004",
304            severity: Severity::Error,
305            summary: "Value is not callable.",
306            description: "An expression of non-function type was used in a call position.",
307            spec_refs: &["§5"],
308        },
309        DiagnosticCodeInfo {
310            code: "E4005",
311            severity: Severity::Error,
312            summary: "`where` clause predicate failed.",
313            description: "A refined-type predicate could not be satisfied.",
314            spec_refs: &["§2"],
315        },
316        DiagnosticCodeInfo {
317            code: "E4010",
318            severity: Severity::Error,
319            summary: "Overlapping trait implementations.",
320            description: "Two `impl` blocks apply to the same type and violate coherence.",
321            spec_refs: &["§4.4"],
322        },
323        DiagnosticCodeInfo {
324            code: "E4011",
325            severity: Severity::Error,
326            summary: "Cannot implement a core trait for a primitive type.",
327            description: "Core traits (`Equatable`, `Comparable`, `Displayable`, `Hashable`) have sealed, compiler-provided conformances for primitive types. User code may not add its own `impl` of a core trait for a primitive (an orphan-rule violation). Wrap the primitive in a newtype and implement the trait for that instead.",
328            spec_refs: &["§18.5"],
329        },
330        DiagnosticCodeInfo {
331            code: "E4012",
332            severity: Severity::Error,
333            summary: "Conversion could not be resolved.",
334            description: "A `.into()` call (or `from`/`try_from`) could not be resolved: no `From`/`Into`/`TryFrom` impl relates the source and target types. For `.into()` the target comes from the expected type, so the call site must have a reachable annotation (a `let y: U =`, an `fn -> U` return position, or a typed argument).",
335            spec_refs: &["§18.4"],
336        },
337        DiagnosticCodeInfo {
338            code: "E4013",
339            severity: Severity::Error,
340            summary: "No such method on a concrete type.",
341            description: "A method that does not exist on the receiver's concrete type was called. The receiver resolved to a fully-known type (a primitive, a built-in collection, an `Optional`/`Result`, or a user record/class/enum in scope) and the method is in none of that type's method sets (intrinsic, canonical-trait, inherent/trait impl, or inherited trait default). When a near-miss name exists a \"did you mean `…`?\" suggestion is offered. Not raised for unresolved inference variables or §4.9 sketch-mode receivers.",
342            spec_refs: &["§18.3"],
343        },
344        DiagnosticCodeInfo {
345            code: "E4014",
346            severity: Severity::Error,
347            summary: "Bare module-qualified import.",
348            description: "A `use` declaration named a module path with neither a brace-list nor a wildcard (a bare `use core.error`). Per §12.2 this is not a v1 import form; module-qualified access is deferred to v1.x. Import the names you need with the braced form (`use core.error.{ Error }`) or the discouraged wildcard (`use core.error.*`).",
349            spec_refs: &["§12.2"],
350        },
351        DiagnosticCodeInfo {
352            code: "E4015",
353            severity: Severity::Error,
354            summary: "Operand or bound instantiation is not `Equatable`.",
355            description: "An `==`/`!=` operand (or a type instantiating an `Equatable` bound) does not conform to `Equatable` (DQ29). Records and enums conform STRUCTURALLY iff every field / variant payload type conforms (recursively); `List[T]`/`Set[T]`/`Optional[T]` iff `T`, `Map[K, V]` iff `K` and `V`, `Result[T, E]` iff `T` and `E`, tuples iff all components; generic user types decide per instantiation. A non-Equatable leaf (e.g. an `Fn` field) poisons the type, and the message names the offending field path and type. Classes are excluded from the structural default and need an explicit `impl Equatable`. An explicit impl always wins over the structural rules. Fix: implement `Equatable` for the type, or remove the comparison.",
356            spec_refs: &["§18.5"],
357        },
358        // ── Ownership (5xxx) ───────────────────────────────────────────
359        DiagnosticCodeInfo {
360            code: "E5001",
361            severity: Severity::Error,
362            summary: "Use after move.",
363            description: "A value was used after it had been moved into another binding or call.",
364            spec_refs: &["§3"],
365        },
366        DiagnosticCodeInfo {
367            code: "E5002",
368            severity: Severity::Error,
369            summary: "Mutable borrow of non-mut binding.",
370            description: "The callee takes a `mut` borrow but the binding was not declared `mut`.",
371            spec_refs: &["§3"],
372        },
373        DiagnosticCodeInfo {
374            code: "E5003",
375            severity: Severity::Error,
376            summary: "Value moved inside loop.",
377            description: "The loop body moves a value captured from outside, which would move it more than once.",
378            spec_refs: &["§3"],
379        },
380        DiagnosticCodeInfo {
381            code: "E5004",
382            severity: Severity::Error,
383            summary: "In-place `List` mutator requires a `mut` receiver.",
384            description: "An in-place `List` mutator (`push`/`append`, DQ18; `pop`/`remove_at`/`insert`/`reverse` and indexed `set`, DQ30) was called on a receiver that is not a mutable lvalue. These methods mutate the list in place, so the receiver must be a `mut` binding. Fix: declare the list with `let mut`, take a `mut` parameter, or use a value-returning combinator (`+` / `concat`).",
385            spec_refs: &["§3"],
386        },
387        // ── Effects (6xxx) ─────────────────────────────────────────────
388        DiagnosticCodeInfo {
389            code: "E6001",
390            severity: Severity::Error,
391            summary: "Undeclared effect.",
392            description: "A function uses an algebraic effect that is not in its declared `with` clause.",
393            spec_refs: &["§8"],
394        },
395        DiagnosticCodeInfo {
396            code: "W6002",
397            severity: Severity::Warning,
398            summary: "Public function has undeclared effect (development mode).",
399            description: "A public function uses an effect that is not in its declared `with` clause. Promotes to error in production strictness.",
400            spec_refs: &["§8"],
401        },
402        DiagnosticCodeInfo {
403            code: "E6003",
404            severity: Severity::Error,
405            summary: "Propagated effect not declared.",
406            description: "A called function has an effect that the caller does not declare or handle.",
407            spec_refs: &["§8"],
408        },
409        DiagnosticCodeInfo {
410            code: "W6004",
411            severity: Severity::Warning,
412            summary: "Public function propagates undeclared effect (development mode).",
413            description: "A public function calls a function whose effects escape its declared clause.",
414            spec_refs: &["§8"],
415        },
416        DiagnosticCodeInfo {
417            code: "E6005",
418            severity: Severity::Error,
419            summary: "Effect operation called without its effect declared or handled.",
420            description: "An effect operation (e.g. `log(...)` of `effect Log`) was called, but the operation's effect is neither declared by the enclosing function (`with <Effect>`) nor handled in an enclosing scope (a `handling (<Effect> with <handler>) { ... }` block or a module-level `handle <Effect> with <handler>`). Fix by declaring the effect on the function or installing a handler. Emitted by the name-resolution pass: effect operations are only in scope in those contexts.",
421            spec_refs: &["§8", "§10.3", "§10.4"],
422        },
423        DiagnosticCodeInfo {
424            code: "E6006",
425            severity: Severity::Error,
426            summary: "Lambda-handler form is reserved until v1.x.",
427            description: "The lambda-based handler surface `Effect.handler(op: (args) => ...)` is reserved for v1.x and is not a v1 form. v1 supports exactly one handler form: declare a record, `impl <Effect> for <Record>`, and install it with `handle <Effect> with <record>` (module level) or `handling (<Effect> with <record>) { ... }` (block level).",
428            spec_refs: &["§10.4"],
429        },
430        // ── Capabilities (7xxx) ────────────────────────────────────────
431        DiagnosticCodeInfo {
432            code: "E7001",
433            severity: Severity::Error,
434            summary: "Missing capability.",
435            description: "A function requires a capability that is not granted by the caller.",
436            spec_refs: &["§9"],
437        },
438        DiagnosticCodeInfo {
439            code: "W7002",
440            severity: Severity::Warning,
441            summary: "Public function requires uninstalled capability (development mode).",
442            description: "A public function's required capability is not present in the caller's capability set.",
443            spec_refs: &["§9"],
444        },
445        DiagnosticCodeInfo {
446            code: "E7003",
447            severity: Severity::Error,
448            summary: "Propagated capability not declared.",
449            description: "A callee requires a capability the caller has not declared.",
450            spec_refs: &["§9"],
451        },
452        DiagnosticCodeInfo {
453            code: "W7004",
454            severity: Severity::Warning,
455            summary: "Public function propagates uninstalled capability.",
456            description: "A public function calls into code requiring capabilities it has not declared.",
457            spec_refs: &["§9"],
458        },
459        // ── Context-annotation system (8xxx) ───────────────────────────
460        // @context / @capability interpretation (bock-air/context.rs).
461        DiagnosticCodeInfo {
462            code: "E8001",
463            severity: Severity::Error,
464            summary: "Unknown capability in `@capability`.",
465            description: "A `@capability` annotation named a capability the compiler does not recognize.",
466            spec_refs: &["§9"],
467        },
468        DiagnosticCodeInfo {
469            code: "E8002",
470            severity: Severity::Error,
471            summary: "Expected capability name in `@capability`.",
472            description: "A `@capability` argument was not a capability name (e.g. `Capability.Network` or `Network`).",
473            spec_refs: &["§9"],
474        },
475        DiagnosticCodeInfo {
476            code: "E8003",
477            severity: Severity::Error,
478            summary: "Expected duration or byte size in `@performance`.",
479            description: "A `@performance` budget argument was not a duration (e.g. `100.ms`) or a byte size (e.g. `50.mb`).",
480            spec_refs: &["§17"],
481        },
482        DiagnosticCodeInfo {
483            code: "E8004",
484            severity: Severity::Error,
485            summary: "`@invariant` requires an expression argument.",
486            description: "An `@invariant` annotation was written without the boolean expression it constrains.",
487            spec_refs: &["§17"],
488        },
489        DiagnosticCodeInfo {
490            code: "E8005",
491            severity: Severity::Error,
492            summary: "Invalid `@security` argument.",
493            description: "A `@security` annotation expected a string `level` or a boolean `pii` flag, or was given neither.",
494            spec_refs: &["§17"],
495        },
496        DiagnosticCodeInfo {
497            code: "E8006",
498            severity: Severity::Error,
499            summary: "Expected string argument in `@domain`.",
500            description: "A `@domain` annotation argument was not a string literal.",
501            spec_refs: &["§17"],
502        },
503        DiagnosticCodeInfo {
504            code: "E8010",
505            severity: Severity::Error,
506            summary: "`@invariant` expression must be boolean-typed.",
507            description: "An `@invariant` expression must be a comparison, logical, or call expression that yields a boolean.",
508            spec_refs: &["§17"],
509        },
510        // Context validation (bock-air/validate_context.rs).
511        DiagnosticCodeInfo {
512            code: "E8011",
513            severity: Severity::Error,
514            summary: "Child security level less restrictive than parent.",
515            description: "An item's `@security` level is less restrictive than the level it inherits from an enclosing context, which would weaken the parent's guarantee.",
516            spec_refs: &["§17"],
517        },
518        DiagnosticCodeInfo {
519            code: "W8011",
520            severity: Severity::Warning,
521            summary: "Child declares `pii=false` but parent declares `pii=true`.",
522            description: "PII status is inherited: a child cannot drop a parent's `pii=true` to `pii=false`. The narrower flag is ignored and a warning is issued.",
523            spec_refs: &["§17"],
524        },
525        DiagnosticCodeInfo {
526            code: "E8013",
527            severity: Severity::Error,
528            summary: "Public item is missing context annotations (production mode).",
529            description: "In production (strict) mode every public item (`fn`/`class`/`trait`/`record`/`enum`) must carry a `@context` annotation. The standard-mode form of this rule is the warning `W8013`.",
530            spec_refs: &["§17"],
531        },
532        DiagnosticCodeInfo {
533            code: "W8013",
534            severity: Severity::Warning,
535            summary: "Public item is missing context annotations (standard mode).",
536            description: "In standard mode a public item (`fn`/`class`/`trait`/`record`/`enum`) without a `@context` annotation is flagged as a recommendation, once per item. Promotes to the error `E8013` in production (strict) mode; suppressed entirely in lax mode and for embedded-stdlib modules.",
537            spec_refs: &["§17"],
538        },
539        DiagnosticCodeInfo {
540            code: "W8015",
541            severity: Severity::Warning,
542            summary: "Unknown security level in `@security`.",
543            description: "A `@security(level: …)` string was not one of the known security levels; the annotation is kept but flagged.",
544            spec_refs: &["§17"],
545        },
546        DiagnosticCodeInfo {
547            code: "E8016",
548            severity: Severity::Error,
549            summary: "`@performance` budget must be positive.",
550            description: "A `@performance` budget (`max_latency` or `max_memory`) was zero or negative; budgets must be positive values.",
551            spec_refs: &["§17"],
552        },
553        // Capability verification, composition, and PII flow
554        // (bock-air/verify_capabilities.rs, compose_context.rs).
555        DiagnosticCodeInfo {
556            code: "E8020",
557            severity: Severity::Error,
558            summary: "Effect operation has no handler in scope.",
559            description: "A called effect operation has no handler installed in any enclosing scope and the effect is not declared, so it can never be discharged.",
560            spec_refs: &["§8", "§10"],
561        },
562        DiagnosticCodeInfo {
563            code: "W8020",
564            severity: Severity::Warning,
565            summary: "Effect declared-but-unused.",
566            description: "An effect declared in a function's `with` clause is never used in its body (capability verification). Drop the unused effect from the `with` clause. (A PII-tainted signature without a security context is reported separately as `W8023`.)",
567            spec_refs: &["§8"],
568        },
569        DiagnosticCodeInfo {
570            code: "E8021",
571            severity: Severity::Error,
572            summary: "Callee requires an undeclared capability.",
573            description: "A called function requires a capability that is not declared in the current scope, so the requirement cannot be satisfied.",
574            spec_refs: &["§9"],
575        },
576        DiagnosticCodeInfo {
577            code: "W8021",
578            severity: Severity::Warning,
579            summary: "Importing a PII-returning function into a module without a PII security context.",
580            description: "A `use` imports a function that returns PII-tainted types into a module whose `@security` context does not acknowledge PII.",
581            spec_refs: &["§17"],
582        },
583        DiagnosticCodeInfo {
584            code: "W8022",
585            severity: Severity::Warning,
586            summary: "PII-tainted type passed to a logging/output function.",
587            description: "A PII-tainted type flows into a logging or output sink, which is a potential data leak. One warning is emitted per call site.",
588            spec_refs: &["§17"],
589        },
590        DiagnosticCodeInfo {
591            code: "W8023",
592            severity: Severity::Warning,
593            summary: "PII-tainted signature without a security context.",
594            description: "A function has PII-tainted types in its signature but its module lacks a `@security` context acknowledging PII (e.g. `@security(level: \"confidential\")` or `@security(pii: true)`). Emitted by context composition.",
595            spec_refs: &["§17"],
596        },
597        DiagnosticCodeInfo {
598            code: "E8023",
599            severity: Severity::Error,
600            summary: "Public declaration is missing `@context` (production mode).",
601            description: "In production mode a public function, class, trait, or type must carry a `@context` annotation; capability verification rejects the bare declaration.",
602            spec_refs: &["§17"],
603        },
604    ]
605}
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    #[test]
612    fn catalog_non_empty() {
613        assert!(!diagnostic_catalog().is_empty());
614    }
615
616    #[test]
617    fn codes_have_prefix() {
618        for info in diagnostic_catalog() {
619            let first = info.code.chars().next().unwrap();
620            assert!(
621                matches!(first, 'E' | 'W'),
622                "code {:?} must start with E or W",
623                info.code
624            );
625        }
626    }
627
628    // ── Emission ↔ catalog correspondence (Q-error-catalog-completeness) ───────
629
630    /// Parse a catalog `code` string into `(prefix, number)`.
631    ///
632    /// Every catalog code must be a plain `E`/`W` followed by digits — no
633    /// disambiguation suffixes like `"E1005 (module)"`, which neither parse
634    /// nor round-trip through [`crate::DiagnosticCode`]'s `Display`.
635    fn parse_code(code: &str) -> Option<(char, u16)> {
636        let mut chars = code.chars();
637        let prefix = chars.next()?;
638        if !matches!(prefix, 'E' | 'W') {
639            return None;
640        }
641        let digits: String = chars.collect();
642        let number: u16 = digits.parse().ok()?;
643        Some((prefix, number))
644    }
645
646    #[test]
647    fn every_catalog_code_parses_and_round_trips() {
648        for info in diagnostic_catalog() {
649            let (prefix, number) = parse_code(info.code).unwrap_or_else(|| {
650                panic!(
651                    "catalog code {:?} is not a parseable `E`/`W` + digits code \
652                     (no suffixes like `(module)` — split or fold collisions instead)",
653                    info.code
654                )
655            });
656            // Severity prefix must agree with the declared severity.
657            let expected_prefix = match info.severity {
658                Severity::Error => 'E',
659                Severity::Warning => 'W',
660                // Info/Hint diagnostics are not catalogued today; guard anyway.
661                Severity::Info | Severity::Hint => prefix,
662            };
663            assert_eq!(
664                prefix, expected_prefix,
665                "code {} prefix disagrees with its severity {:?}",
666                info.code, info.severity
667            );
668            // Display must reproduce the catalog string exactly.
669            let rendered = crate::DiagnosticCode { prefix, number }.to_string();
670            assert_eq!(
671                rendered, info.code,
672                "catalog code {:?} does not round-trip through DiagnosticCode \
673                 Display (got {rendered:?})",
674                info.code
675            );
676        }
677    }
678
679    #[test]
680    fn catalog_has_no_duplicate_codes() {
681        let mut seen = std::collections::BTreeSet::new();
682        for info in diagnostic_catalog() {
683            assert!(
684                seen.insert(info.code),
685                "duplicate catalog entry for code {:?}",
686                info.code
687            );
688        }
689    }
690
691    /// Scan a Rust source string for `DiagnosticCode { prefix: 'X', number: N }`
692    /// constructions, returning the set of rendered code strings (e.g. `E8003`).
693    ///
694    /// Only "real" emitted codes — `number >= 1000` — are collected; the small
695    /// numbers (`1`, `42`, `99`, `204`, …) are exclusively diagnostic
696    /// *test fixtures* that construct a `DiagnosticCode` directly and are not
697    /// part of the emitted vocabulary.
698    fn scan_emitted_codes(src: &str) -> std::collections::BTreeSet<String> {
699        // Strip whitespace between tokens so the pattern can be matched without
700        // caring about line breaks / indentation in the multi-line struct
701        // literal. We only need a flattened, whitespace-collapsed view.
702        let flat: String = src.split_whitespace().collect::<Vec<_>>().join(" ");
703        let mut out = std::collections::BTreeSet::new();
704        let needle = "DiagnosticCode { prefix: '";
705        let mut rest = flat.as_str();
706        while let Some(pos) = rest.find(needle) {
707            rest = &rest[pos + needle.len()..];
708            // Next char is the prefix; then `' , number: <digits>`.
709            let mut it = rest.chars();
710            let Some(prefix) = it.next() else { break };
711            if !matches!(prefix, 'E' | 'W') {
712                continue;
713            }
714            // Locate `number:` after the prefix.
715            let Some(num_pos) = rest.find("number:") else {
716                break;
717            };
718            let after = rest[num_pos + "number:".len()..].trim_start();
719            let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
720            if let Ok(number) = digits.parse::<u16>() {
721                if number >= 1000 {
722                    out.insert(format!("{prefix}{number:04}"));
723                }
724            }
725        }
726        out
727    }
728
729    #[test]
730    fn scan_emitted_codes_basic() {
731        let src = r#"
732            self.diag.error(
733                DiagnosticCode { prefix: 'E', number: 8003 },
734                "x", span,
735            );
736            let c = DiagnosticCode {
737                prefix: 'W',
738                number: 1001,
739            };
740            // a test fixture, must be ignored:
741            DiagnosticCode { prefix: 'E', number: 204 }
742        "#;
743        let codes = scan_emitted_codes(src);
744        assert!(codes.contains("E8003"), "got {codes:?}");
745        assert!(codes.contains("W1001"), "got {codes:?}");
746        assert!(
747            !codes.contains("E0204"),
748            "test fixtures (<1000) must be skipped: {codes:?}"
749        );
750    }
751
752    /// Every diagnostic code actually emitted by a compiler crate must have a
753    /// catalog entry. This is the standing guard for
754    /// Q-error-catalog-completeness: the catalog is the single registry, and
755    /// an unregistered emitted code is a defect (an editor/agent cannot look it
756    /// up). The scan walks every `compiler/crates/*/src/**/*.rs` for
757    /// `DiagnosticCode { prefix, number >= 1000 }` constructions.
758    ///
759    /// If this fails, add the missing code to `diagnostic_catalog()` (do NOT
760    /// renumber an existing emission — that is a design decision).
761    #[test]
762    fn every_emitted_code_is_registered() {
763        let crates_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
764            .parent()
765            .expect("bock-errors lives under compiler/crates/");
766
767        let registered: std::collections::BTreeSet<String> = diagnostic_catalog()
768            .iter()
769            .map(|i| i.code.to_string())
770            .collect();
771
772        let mut emitted = std::collections::BTreeSet::new();
773        let mut files_scanned = 0usize;
774        let mut stack = vec![crates_dir.to_path_buf()];
775        while let Some(dir) = stack.pop() {
776            let Ok(entries) = std::fs::read_dir(&dir) else {
777                continue;
778            };
779            for entry in entries.flatten() {
780                let path = entry.path();
781                if path.is_dir() {
782                    // Only descend into `src/` trees and crate roots; skip
783                    // `target/` and hidden dirs.
784                    let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
785                    if name == "target" || name.starts_with('.') {
786                        continue;
787                    }
788                    stack.push(path);
789                } else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
790                    if let Ok(src) = std::fs::read_to_string(&path) {
791                        files_scanned += 1;
792                        emitted.extend(scan_emitted_codes(&src));
793                    }
794                }
795            }
796        }
797
798        assert!(
799            files_scanned > 50,
800            "expected to scan the whole crates tree, only saw {files_scanned} files \
801             (CARGO_MANIFEST_DIR layout changed?)"
802        );
803        // Sanity floor: the scan must find the well-known codes.
804        for expected in ["E1001", "E2000", "E4001", "E6005", "E8003", "W1001"] {
805            assert!(
806                emitted.contains(expected),
807                "scanner failed to find {expected}; scan is broken"
808            );
809        }
810
811        let unregistered: Vec<&String> = emitted.difference(&registered).collect();
812        assert!(
813            unregistered.is_empty(),
814            "these emitted diagnostic codes are NOT in the catalog \
815             (register them in diagnostic_catalog(); do not renumber): {unregistered:?}"
816        );
817    }
818}