cyrs_diag/codes.rs
1//! Diagnostic code registry (spec 0001 §10.2).
2//!
3//! Codes are **stable**. Once assigned, a code's meaning cannot change.
4//! New checks get new codes; removed checks leave their code retired,
5//! never reused. CI (§17.2 / §17.6) enforces uniqueness.
6//!
7//! Code ranges:
8//!
9//! | Range | Meaning |
10//! | --------------- | ---------------------------------------- |
11//! | `E0001..=E0999` | Syntax (lexer + parser) |
12//! | `E1000..=E1999` | Name resolution |
13//! | `E2000..=E2999` | Semantic — schema-free |
14//! | `E3000..=E3999` | Semantic — schema-aware |
15//! | `E4000..=E4999` | Dialect / compatibility |
16//! | `E5000..=E5999` | Type system |
17//! | `W6000..=W6999` | Style / lint warnings |
18//! | `W7000..=W7999` | Performance warnings |
19//! | `N8000..=N8999` | Informational notes |
20
21use core::fmt;
22
23/// Stable diagnostic code. Rendered as `E0001` / `W6001` / `N8001`.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[allow(non_camel_case_types)]
27pub enum DiagCode {
28 // --- syntax (E0001..=E0999) --------------------------------------
29 /// Generic / unclassified syntax error.
30 ///
31 /// Docs: `docs/errors/E0001.md`
32 E0001 = 1,
33 /// Unexpected token encountered.
34 ///
35 /// Docs: `docs/errors/E0002.md`
36 E0002 = 2,
37 /// Expected `<token>`, found `<token>`.
38 ///
39 /// Docs: `docs/errors/E0003.md`
40 E0003 = 3,
41 /// Unclosed string literal (missing closing quote).
42 ///
43 /// Docs: `docs/errors/E0004.md`
44 E0004 = 4,
45 /// Unclosed block comment (missing `*/`).
46 ///
47 /// Docs: `docs/errors/E0005.md`
48 E0005 = 5,
49 /// Invalid numeric literal (bad digits or suffix).
50 ///
51 /// Docs: `docs/errors/E0006.md`
52 E0006 = 6,
53 /// Expected a statement (clause keyword) but found something else.
54 ///
55 /// Docs: `docs/errors/E0007.md`
56 E0007 = 7,
57 /// Expected `;` or end of input after statement.
58 ///
59 /// Docs: `docs/errors/E0008.md`
60 E0008 = 8,
61 /// Expected `(` to start a node pattern.
62 ///
63 /// Docs: `docs/errors/E0009.md`
64 E0009 = 9,
65 /// Expected a node pattern after a relationship pattern.
66 ///
67 /// Docs: `docs/errors/E0010.md`
68 E0010 = 10,
69 /// Expected `)` to close a node pattern.
70 ///
71 /// Docs: `docs/errors/E0011.md`
72 E0011 = 11,
73 /// Expected `-` at the start of a relationship pattern.
74 ///
75 /// Docs: `docs/errors/E0012.md`
76 E0012 = 12,
77 /// Expected `-` to close a left-arrow relationship pattern.
78 ///
79 /// Docs: `docs/errors/E0013.md`
80 E0013 = 13,
81 /// Expected `-` or `->` to close a relationship pattern.
82 ///
83 /// Docs: `docs/errors/E0014.md`
84 E0014 = 14,
85 /// Expected `]` to close a relationship detail block.
86 ///
87 /// Docs: `docs/errors/E0015.md`
88 E0015 = 15,
89 /// Expected a label name after `:`.
90 ///
91 /// Docs: `docs/errors/E0016.md`
92 E0016 = 16,
93 /// Expected a relationship type name after `:`.
94 ///
95 /// Docs: `docs/errors/E0017.md`
96 E0017 = 17,
97 /// Expected `}` to close a property map.
98 ///
99 /// Docs: `docs/errors/E0018.md`
100 E0018 = 18,
101 /// Expected a property key identifier.
102 ///
103 /// Docs: `docs/errors/E0019.md`
104 E0019 = 19,
105 /// Expected `:` separating a property key from its value.
106 ///
107 /// Docs: `docs/errors/E0020.md`
108 E0020 = 20,
109 /// Expected an expression for a property value.
110 ///
111 /// Docs: `docs/errors/E0021.md`
112 E0021 = 21,
113 /// Expected an identifier.
114 ///
115 /// Docs: `docs/errors/E0022.md`
116 E0022 = 22,
117 /// Expression nesting depth exceeds the parser limit.
118 ///
119 /// Docs: `docs/errors/E0023.md`
120 E0023 = 23,
121 /// Expected an operand after a unary operator.
122 ///
123 /// Docs: `docs/errors/E0024.md`
124 E0024 = 24,
125 /// Expected `NULL` after `IS` (or `IS NOT`).
126 ///
127 /// Docs: `docs/errors/E0025.md`
128 E0025 = 25,
129 /// Expected a right-hand side operand for a binary expression.
130 ///
131 /// Docs: `docs/errors/E0026.md`
132 E0026 = 26,
133 /// Expected an expression inside parentheses.
134 ///
135 /// Docs: `docs/errors/E0027.md`
136 E0027 = 27,
137 /// Expected `)` to close a parenthesised expression.
138 ///
139 /// Docs: `docs/errors/E0028.md`
140 E0028 = 28,
141 /// Expected `WITH` after `STARTS` (i.e. `STARTS WITH`).
142 ///
143 /// Docs: `docs/errors/E0029.md`
144 E0029 = 29,
145 /// Expected `WITH` after `ENDS` (i.e. `ENDS WITH`).
146 ///
147 /// Docs: `docs/errors/E0030.md`
148 E0030 = 30,
149 /// Expected a property key name after `.`.
150 ///
151 /// Docs: `docs/errors/E0031.md`
152 E0031 = 31,
153 /// Expected an index expression inside `[…]`.
154 ///
155 /// Docs: `docs/errors/E0032.md`
156 E0032 = 32,
157 /// Expected `]` to close a subscript / index expression.
158 ///
159 /// Docs: `docs/errors/E0033.md`
160 E0033 = 33,
161 /// Expected `)` to close a function call argument list.
162 ///
163 /// Docs: `docs/errors/E0034.md`
164 E0034 = 34,
165 /// Expected a function call argument expression.
166 ///
167 /// Docs: `docs/errors/E0035.md`
168 E0035 = 35,
169 /// Expected an expression in a `RETURN` item.
170 ///
171 /// Docs: `docs/errors/E0036.md`
172 E0036 = 36,
173 /// Expected an identifier after `AS` (alias).
174 ///
175 /// Docs: `docs/errors/E0037.md`
176 E0037 = 37,
177 /// Expected `BY` after `ORDER` (i.e. `ORDER BY`).
178 ///
179 /// Docs: `docs/errors/E0038.md`
180 E0038 = 38,
181 /// Expected an expression in an `ORDER BY` item.
182 ///
183 /// Docs: `docs/errors/E0039.md`
184 E0039 = 39,
185 /// Expected an expression after `SKIP`.
186 ///
187 /// Docs: `docs/errors/E0040.md`
188 E0040 = 40,
189 /// Expected an expression after `LIMIT`.
190 ///
191 /// Docs: `docs/errors/E0041.md`
192 E0041 = 41,
193 /// Expected `MATCH` after `OPTIONAL`.
194 ///
195 /// Docs: `docs/errors/E0042.md`
196 E0042 = 42,
197 /// Expected an expression after `WHERE`.
198 ///
199 /// Docs: `docs/errors/E0043.md`
200 E0043 = 43,
201 /// Clause keyword encountered that is not yet implemented (deferred construct).
202 ///
203 /// Docs: `docs/errors/E0044.md`
204 E0044 = 44,
205 /// Expected a clause keyword (`MATCH`, `WITH`, `RETURN`, …).
206 ///
207 /// Docs: `docs/errors/E0045.md`
208 E0045 = 45,
209 /// Invalid escape sequence in a string literal.
210 ///
211 /// Docs: `docs/errors/E0046.md`
212 E0046 = 46,
213 /// Expected an expression in a list literal.
214 ///
215 /// Docs: `docs/errors/E0047.md`
216 E0047 = 47,
217 /// Expected `]` to close a list literal.
218 ///
219 /// Docs: `docs/errors/E0048.md`
220 E0048 = 48,
221 /// Expected a key in a map literal.
222 ///
223 /// Docs: `docs/errors/E0049.md`
224 E0049 = 49,
225 /// Expected `:` in a map literal entry.
226 ///
227 /// Docs: `docs/errors/E0050.md`
228 E0050 = 50,
229 /// Expected an expression for a map literal value.
230 ///
231 /// Docs: `docs/errors/E0051.md`
232 E0051 = 51,
233 /// Expected `}` to close a map literal.
234 ///
235 /// Docs: `docs/errors/E0052.md`
236 E0052 = 52,
237 /// Expected an expression after `UNWIND`.
238 ///
239 /// Docs: `docs/errors/E0053.md`
240 E0053 = 53,
241 /// Expected `AS` after an `UNWIND` expression.
242 ///
243 /// Docs: `docs/errors/E0054.md`
244 E0054 = 54,
245 /// Expected a pattern after `CREATE`.
246 ///
247 /// Docs: `docs/errors/E0055.md`
248 E0055 = 55,
249 /// Expected a pattern after `MERGE`.
250 ///
251 /// Docs: `docs/errors/E0056.md`
252 E0056 = 56,
253 /// Expected a SET item (property assignment or label add).
254 ///
255 /// Docs: `docs/errors/E0057.md`
256 E0057 = 57,
257 /// Expected a REMOVE item (property access or label).
258 ///
259 /// Docs: `docs/errors/E0058.md`
260 E0058 = 58,
261 /// Expected an expression after `DELETE`.
262 ///
263 /// Docs: `docs/errors/E0059.md`
264 E0059 = 59,
265 /// Expected `DELETE` after `DETACH`.
266 ///
267 /// Docs: `docs/errors/E0060.md`
268 E0060 = 60,
269 /// Expected `CREATE` or `MATCH` after `ON` in a MERGE action.
270 ///
271 /// Docs: `docs/errors/E0061.md`
272 E0061 = 61,
273 /// Expected `]` to close a variable-length hop quantifier.
274 ///
275 /// Docs: `docs/errors/E0062.md`
276 E0062 = 62,
277 /// Expected `=` after a path binder identifier.
278 ///
279 /// Docs: `docs/errors/E0063.md`
280 E0063 = 63,
281 /// Expected `(` after a list-predicate keyword (cy-8x5).
282 ///
283 /// Emitted by the parser when `ANY / ALL / NONE / SINGLE` is not
284 /// immediately followed by an opening parenthesis at atom position.
285 ///
286 /// Docs: `docs/errors/E0065.md`
287 E0065 = 65,
288 /// Expected `IN` between the list-predicate binder and its source
289 /// expression (cy-8x5).
290 ///
291 /// Docs: `docs/errors/E0066.md`
292 E0066 = 66,
293 /// Expected `)` to close a list-predicate expression (cy-8x5).
294 ///
295 /// Docs: `docs/errors/E0067.md`
296 E0067 = 67,
297
298 // --- name resolution (E1000–E1999) --------------------------------
299 /// Unresolved variable reference (spec §6.2).
300 ///
301 /// Emitted by `cyrs-sema` resolve pass when a name that appears in
302 /// an expression position cannot be found in any enclosing scope.
303 ///
304 /// Docs: `docs/errors/E1001.md`
305 E1001 = 1001,
306 /// Variable shadows an outer binding (spec §6.2).
307 ///
308 /// Emitted as a **warning** when `SemaOptions::warn_shadowing` is set
309 /// and a new binding introduces a name already visible in an outer scope.
310 ///
311 /// Docs: `docs/errors/E1002.md`
312 E1002 = 1002,
313
314 // --- semantic schema-free (E2000–E2999) --------------------------
315 /// Variable used in arithmetic / numeric context but has non-Value kind
316 /// (Node, Relationship, or Path). Schema-free kind-consistency check
317 /// (spec §6.3).
318 ///
319 /// Emitted by `cyrs-sema` kinds pass when a Node/Relationship/Path
320 /// variable appears as an operand of an arithmetic expression.
321 ///
322 /// Docs: `docs/errors/E2007.md`
323 E2007 = 2007,
324 /// Variable of incorrect kind used in pattern position (spec §6.3).
325 ///
326 /// E.g., a `Value` variable where a node-pattern binder is expected, or
327 /// a `Node` variable where a path binder is expected.
328 ///
329 /// Emitted by `cyrs-sema` kinds pass.
330 ///
331 /// Docs: `docs/errors/E2008.md`
332 E2008 = 2008,
333 /// Arithmetic operand has a non-numeric type (spec §7.4).
334 ///
335 /// Emitted when an operand of `+`, `-`, `*`, `/`, `%`, or `^` infers to
336 /// a type that cannot unify with `Num` (e.g., a boolean or a string).
337 ///
338 /// Docs: `docs/errors/E2009.md`
339 E2009 = 2009,
340 /// String-operator operand has a non-string type (spec §7.4).
341 ///
342 /// Emitted when an operand of `CONTAINS`, `STARTS WITH`, or `ENDS WITH`
343 /// infers to a type other than `String` or `Any`.
344 ///
345 /// Docs: `docs/errors/E2010.md`
346 E2010 = 2010,
347 /// Boolean-operator operand has a non-boolean type (spec §7.4).
348 ///
349 /// Emitted when an operand of `AND`, `OR`, `XOR`, or unary `NOT`
350 /// infers to a type other than `Bool` or `Any`.
351 ///
352 /// Docs: `docs/errors/E2011.md`
353 E2011 = 2011,
354 /// `IN` list element type mismatch (spec §7.4).
355 ///
356 /// Emitted when the left operand of `IN` infers to a type that cannot
357 /// unify with the element type of the right-hand list.
358 ///
359 /// Docs: `docs/errors/E2012.md`
360 E2012 = 2012,
361 /// `IN` right-hand operand is not a list (spec §7.4).
362 ///
363 /// Emitted when the right-hand side of `IN` infers to a non-list type
364 /// (e.g., `x IN 42`).
365 ///
366 /// Docs: `docs/errors/E2013.md`
367 E2013 = 2013,
368
369 // --- semantic schema-aware (E3000–E3999) -------------------------
370 /// Unknown node label (spec §7.5).
371 ///
372 /// Emitted by `cyrs-sema` schema-aware pass when a label referenced in
373 /// a node pattern is not declared in the schema.
374 ///
375 /// Docs: `docs/errors/E3001.md`
376 E3001 = 3001,
377 /// Unknown relationship type (spec §7.5).
378 ///
379 /// Emitted by `cyrs-sema` schema-aware pass when a relationship type
380 /// referenced in a pattern is not declared in the schema.
381 ///
382 /// Docs: `docs/errors/E3002.md`
383 E3002 = 3002,
384 /// Unknown property on label or relationship type (spec §7.5).
385 ///
386 /// Emitted when a property key in an inline pattern map is not declared
387 /// on any of the node's labels or the relationship's type.
388 ///
389 /// Docs: `docs/errors/E3003.md`
390 E3003 = 3003,
391 /// Property type mismatch (spec §7.5).
392 ///
393 /// Emitted when a literal value in an inline property map cannot be
394 /// stored in the declared property type (e.g., `String` into an `Int`
395 /// slot).
396 ///
397 /// Docs: `docs/errors/E3004.md`
398 E3004 = 3004,
399 /// Unknown function name (spec §7.5).
400 ///
401 /// Emitted by `cyrs-sema` schema-aware pass when a function call
402 /// references a name not present in the `SchemaProvider`'s catalog.
403 ///
404 /// Docs: `docs/errors/E3006.md`
405 E3006 = 3006,
406 /// Function or procedure arity mismatch (spec §7.5).
407 ///
408 /// Emitted when a function or procedure call provides the wrong number
409 /// of arguments compared to the declared signature.
410 ///
411 /// Docs: `docs/errors/E3007.md`
412 E3007 = 3007,
413 /// Unknown procedure name (spec §7.5).
414 ///
415 /// Emitted by `cyrs-sema` schema-aware pass when a `CALL` clause
416 /// references a procedure not present in the `SchemaProvider`'s catalog.
417 ///
418 /// Docs: `docs/errors/E3008.md`
419 E3008 = 3008,
420 /// Schema-file property type is unresolved (spec 0002 §9).
421 ///
422 /// Emitted by `cypher schema check` when a property or parameter
423 /// declares an opaque type (e.g. `DURATION`, `POINT`) whose v0
424 /// structural meaning is deferred. Load succeeds — the type round-
425 /// trips — but the linter flags it so operators know the type
426 /// participates as an opaque symbolic value only.
427 ///
428 /// Docs: `docs/errors/E3010.md`
429 E3010 = 3010,
430 /// Relationship-type endpoint cycles through the same label on both
431 /// sides (spec 0002 §9).
432 ///
433 /// Emitted by `cypher schema check` when a rel type declares the
434 /// same label in both `start_labels` and `end_labels`. The spec
435 /// permits self-loops but the linter surfaces them so operators
436 /// can confirm they are intentional (e.g. `KNOWS: Person → Person`
437 /// is fine; `REPORTS_TO: Team → Team` may be a modelling slip).
438 ///
439 /// Docs: `docs/errors/E3011.md`
440 E3011 = 3011,
441
442 // --- dialect (E4000–E4999) ----------------------------------------
443 /// Dialect not supported (spec §9.3).
444 ///
445 /// Emitted by `cyrs-sema::dialect::reject_neo4j_current` when the
446 /// caller attempts to use the `Neo4jCurrent` dialect, which is not
447 /// part of v1 (spec §9.3, §19–§20).
448 ///
449 /// Docs: `docs/errors/E4001.md`
450 E4001 = 4001,
451
452 // --- dialect gates (E4010–E4019) assigned by cy-z49 --------------
453 /// `label_negation` — `!` in label expressions; `GqlAligned` only (spec §9.2).
454 ///
455 /// Docs: `docs/errors/E4010.md`
456 E4010 = 4010,
457 /// `label_union` — `A|B` label-union in patterns; allowed in both v1 dialects (spec §9.2).
458 ///
459 /// Docs: `docs/errors/E4011.md`
460 E4011 = 4011,
461 /// `integer_division` — `/` with integer operands; `OpenCypherV9` only (spec §9.2).
462 ///
463 /// Docs: `docs/errors/E4012.md`
464 E4012 = 4012,
465 /// `union_set` — plain `UNION` (set-semantics); allowed in both v1 dialects.
466 ///
467 /// Docs: `docs/errors/E4013.md`
468 E4013 = 4013,
469 /// `call_procedure` — `CALL proc YIELD …`; allowed in both v1 dialects.
470 ///
471 /// Docs: `docs/errors/E4014.md`
472 E4014 = 4014,
473 /// `load_csv` — `LOAD CSV` clause; deferred (spec §9.3).
474 ///
475 /// Docs: `docs/errors/E4015.md`
476 E4015 = 4015,
477 /// `apoc_functions` — APOC-prefixed functions; deferred (spec §9.3).
478 ///
479 /// Docs: `docs/errors/E4016.md`
480 E4016 = 4016,
481 /// `exists_subquery` — `EXISTS { }` subquery; deferred (spec §9.3).
482 ///
483 /// Docs: `docs/errors/E4017.md`
484 E4017 = 4017,
485 /// `cypher_prefix` — `CYPHER n MATCH …` header; deferred (spec §9.3).
486 ///
487 /// Docs: `docs/errors/E4018.md`
488 E4018 = 4018,
489 /// `call_in_transactions` — `CALL { } IN TRANSACTIONS`; deferred (spec §9.3).
490 ///
491 /// Docs: `docs/errors/E4019.md`
492 E4019 = 4019,
493
494 // --- type system (E5000–E5999) ------------------------------------
495 /// Type mismatch in unification — two incompatible concrete types cannot
496 /// be unified (spec §7.2, §7.3).
497 ///
498 /// Produced by `cyrs-sema::unify::TypeMismatch::into_diagnostic`.
499 /// Call sites supply the source range; code is always `E5003`.
500 ///
501 /// Docs: `docs/errors/E5003.md`
502 E5003 = 5003,
503 /// Indexing or slicing applied to a non-list expression (spec §7.4 /
504 /// §19 row "List indexing / slicing").
505 ///
506 /// Emitted by `cyrs-sema::kinds` when `xs[0]` / `xs[i..j]` is applied
507 /// to a target whose inferred type is not a list (and not `Any` / `Unknown`).
508 /// v1 scope is list-only; string indexing is deferred to a follow-up
509 /// bead and is rejected by this code until then.
510 ///
511 /// Docs: `docs/errors/E5010.md`
512 E5010 = 5010,
513 /// List-predicate iterable is not a list (spec §7.4 / §19 row
514 /// "List predicates").
515 ///
516 /// Emitted by `cyrs-sema::kinds` when the source expression of
517 /// `ANY / ALL / NONE / SINGLE (x IN xs [WHERE p])` is structurally
518 /// non-list: a scalar literal, a `Node` / `Relationship` / `Path`
519 /// variable, a map literal, or a pattern predicate. `Value` variables
520 /// and call results are accepted because they may hold a list at
521 /// runtime — schema-aware refinement is the follow-up's job.
522 ///
523 /// Docs: `docs/errors/E5011.md`
524 E5011 = 5011,
525 /// Built-in function argument kind mismatch (spec §7.4 / §10.2).
526 ///
527 /// Emitted by `cyrs-sema::infer` when a call to a registered
528 /// builtin passes an argument whose inferred type is incompatible
529 /// with the parameter's declared `ArgShape`. The canonical case
530 /// is `id(42)` — the `id` builtin's parameter is `GraphEntity`
531 /// (`Node | Relationship | Path`), so a scalar literal is rejected.
532 ///
533 /// Docs: `docs/errors/E5012.md`
534 E5012 = 5012,
535
536 // --- syntax (E0064) continued -------------------------------------
537 /// Unclosed `[` in a list-indexing / slicing expression (spec §4.3).
538 ///
539 /// Emitted by the parser's `index_or_slice_postfix` recovery path
540 /// when no matching `]` follows the inner expression / slice bounds.
541 /// Distinct from E0033 (`SUBSCRIPT_EXPR` legacy path) so tooling can
542 /// tell the two apart; the typed cy-7s6.1 grammar path uses this
543 /// code exclusively.
544 ///
545 /// Docs: `docs/errors/E0064.md`
546 E0064 = 64,
547 /// Expected `IN` in list comprehension (spec §19 row
548 /// "List comprehensions").
549 ///
550 /// Emitted by the parser's list-comprehension production (cy-5gh)
551 /// when the iteration-variable identifier is not followed by the
552 /// `IN` keyword.
553 ///
554 /// Docs: `docs/errors/E0068.md`
555 E0068 = 68,
556 /// Expected `|` or `]` in list comprehension (spec §19 row
557 /// "List comprehensions").
558 ///
559 /// Emitted by the parser's list-comprehension production (cy-5gh)
560 /// when neither a projection pipe `|` nor the closing bracket `]`
561 /// follows the optional `WHERE` predicate / source expression.
562 ///
563 /// Docs: `docs/errors/E0069.md`
564 E0069 = 69,
565 /// Expected `THEN` in a `CASE` arm (spec §19 row "CASE").
566 ///
567 /// Emitted by the parser's CASE production (cy-41u) when a
568 /// `WHEN <value>` clause is not followed by the mandatory `THEN`
569 /// keyword.
570 ///
571 /// Docs: `docs/errors/E0070.md`
572 E0070 = 70,
573 /// Expected `END` to close a `CASE` expression (spec §19 row "CASE").
574 ///
575 /// Emitted by the parser's CASE production (cy-41u) when the
576 /// terminating `END` keyword is missing after the final arm /
577 /// optional `ELSE` branch.
578 ///
579 /// Docs: `docs/errors/E0071.md`
580 E0071 = 71,
581 /// Expected `)` to close an `EXISTS(<pattern>)` pattern predicate
582 /// (spec §6.1 / §19 row "Pattern predicates in expressions").
583 ///
584 /// Emitted by the parser's `exists_pattern_predicate` production
585 /// (cy-lve) when the closing paren of the wrapping
586 /// `EXISTS ( <pattern> )` form is missing. The bare-pattern form
587 /// `WHERE (a)-->(b)` (cy-7lf) does not emit this code — the inner
588 /// `)` belongs to the pattern's node-pattern production.
589 ///
590 /// Docs: `docs/errors/E0072.md`
591 E0072 = 72,
592 /// Expected `}` to close a map projection (spec §6.1 / §19 row
593 /// "Map projection"; cy-01q).
594 ///
595 /// Emitted by the parser's `map_projection_postfix` production
596 /// when the closing `}` of `n { ... }` is missing. Distinct from
597 /// E0052 (map literal `}`) so tools can distinguish the two
598 /// recovery paths.
599 E0078 = 78,
600 /// Expected property name or `*` after `.` in a map projection item
601 /// (cy-01q). Inside `n { ... }`, a `.` opens either `.NAME`
602 /// (property selector) or `.*` (all-properties spread); anything
603 /// else is an error.
604 E0079 = 79,
605 /// Expected `:` in a map projection literal item (cy-01q).
606 /// Distinct from E0050 (map literal `:`).
607 E0080 = 80,
608 /// Expected expression for map projection literal value (cy-01q).
609 E0081 = 81,
610 /// Expected an item in map projection: `.name`, `key: expr`, `.*`,
611 /// or `*` (cy-01q).
612 E0082 = 82,
613
614 // --- warnings (6000..) -------------------------------------------
615 /// Dead WITH — projection with no downstream reader.
616 W6001 = 6001,
617 /// Identifier collides with a reserved keyword; needs backtick quoting.
618 W6002 = 6002,
619 /// Duplicate key in map literal — last write wins.
620 W6003 = 6003,
621 /// Variable bound but never read.
622 W6004 = 6004,
623 /// Redundant OPTIONAL MATCH — no bound variables escape the clause.
624 W6005 = 6005,
625 /// Pattern has no label or type restriction — will scan broadly.
626 W6006 = 6006,
627 /// Inconsistent keyword casing inside one query.
628 W6007 = 6007,
629 /// Unreachable label — schema-file lint (spec 0002 §9).
630 ///
631 /// Emitted by `cypher schema check` when a label is declared but
632 /// not referenced by any relationship type's `start_labels` or
633 /// `end_labels`. Not fatal: isolated labels are permitted — they
634 /// are legitimate for nodes created without relationships — but
635 /// the lint surfaces them so operators catch typos in rel-type
636 /// endpoint lists.
637 ///
638 /// Docs: `docs/errors/W6010.md`
639 W6010 = 6010,
640
641 // --- performance (7000..) ----------------------------------------
642 /// Cartesian product between disconnected MATCH components.
643 W7001 = 7001,
644 /// Expensive function call inside a row-wise filter.
645 W7002 = 7002,
646 /// Variable-length path without an upper bound.
647 W7003 = 7003,
648 /// Property access on an unindexed label in a selective filter.
649 W7004 = 7004,
650
651 // --- notes (8000..) ----------------------------------------------
652 /// Informational — pattern normalised to canonical direction.
653 N8001 = 8001,
654 /// Informational — inferred type of an expression.
655 N8002 = 8002,
656 /// Informational — variable dropped from scope by this projection.
657 N8003 = 8003,
658}
659
660impl DiagCode {
661 /// Render as the stable wire-format string: `E0001`, `W6001`, `N8001`.
662 #[must_use]
663 pub const fn as_str(self) -> &'static str {
664 match self {
665 Self::E0001 => "E0001",
666 Self::E0002 => "E0002",
667 Self::E0003 => "E0003",
668 Self::E0004 => "E0004",
669 Self::E0005 => "E0005",
670 Self::E0006 => "E0006",
671 Self::E0007 => "E0007",
672 Self::E0008 => "E0008",
673 Self::E0009 => "E0009",
674 Self::E0010 => "E0010",
675 Self::E0011 => "E0011",
676 Self::E0012 => "E0012",
677 Self::E0013 => "E0013",
678 Self::E0014 => "E0014",
679 Self::E0015 => "E0015",
680 Self::E0016 => "E0016",
681 Self::E0017 => "E0017",
682 Self::E0018 => "E0018",
683 Self::E0019 => "E0019",
684 Self::E0020 => "E0020",
685 Self::E0021 => "E0021",
686 Self::E0022 => "E0022",
687 Self::E0023 => "E0023",
688 Self::E0024 => "E0024",
689 Self::E0025 => "E0025",
690 Self::E0026 => "E0026",
691 Self::E0027 => "E0027",
692 Self::E0028 => "E0028",
693 Self::E0029 => "E0029",
694 Self::E0030 => "E0030",
695 Self::E0031 => "E0031",
696 Self::E0032 => "E0032",
697 Self::E0033 => "E0033",
698 Self::E0034 => "E0034",
699 Self::E0035 => "E0035",
700 Self::E0036 => "E0036",
701 Self::E0037 => "E0037",
702 Self::E0038 => "E0038",
703 Self::E0039 => "E0039",
704 Self::E0040 => "E0040",
705 Self::E0041 => "E0041",
706 Self::E0042 => "E0042",
707 Self::E0043 => "E0043",
708 Self::E0044 => "E0044",
709 Self::E0045 => "E0045",
710 Self::E0046 => "E0046",
711 Self::E0047 => "E0047",
712 Self::E0048 => "E0048",
713 Self::E0049 => "E0049",
714 Self::E0050 => "E0050",
715 Self::E0051 => "E0051",
716 Self::E0052 => "E0052",
717 Self::E0053 => "E0053",
718 Self::E0054 => "E0054",
719 Self::E0055 => "E0055",
720 Self::E0056 => "E0056",
721 Self::E0057 => "E0057",
722 Self::E0058 => "E0058",
723 Self::E0059 => "E0059",
724 Self::E0060 => "E0060",
725 Self::E0061 => "E0061",
726 Self::E0062 => "E0062",
727 Self::E0063 => "E0063",
728 Self::E0064 => "E0064",
729 Self::E0065 => "E0065",
730 Self::E0066 => "E0066",
731 Self::E0067 => "E0067",
732 Self::E0068 => "E0068",
733 Self::E0069 => "E0069",
734 Self::E0070 => "E0070",
735 Self::E0071 => "E0071",
736 Self::E0072 => "E0072",
737 Self::E0078 => "E0078",
738 Self::E0079 => "E0079",
739 Self::E0080 => "E0080",
740 Self::E0081 => "E0081",
741 Self::E0082 => "E0082",
742 Self::E1001 => "E1001",
743 Self::E1002 => "E1002",
744 Self::E2007 => "E2007",
745 Self::E2008 => "E2008",
746 Self::E2009 => "E2009",
747 Self::E2010 => "E2010",
748 Self::E2011 => "E2011",
749 Self::E2012 => "E2012",
750 Self::E2013 => "E2013",
751 Self::E3001 => "E3001",
752 Self::E3002 => "E3002",
753 Self::E3003 => "E3003",
754 Self::E3004 => "E3004",
755 Self::E3006 => "E3006",
756 Self::E3007 => "E3007",
757 Self::E3008 => "E3008",
758 Self::E3010 => "E3010",
759 Self::E3011 => "E3011",
760 Self::E4001 => "E4001",
761 Self::E4010 => "E4010",
762 Self::E4011 => "E4011",
763 Self::E4012 => "E4012",
764 Self::E4013 => "E4013",
765 Self::E4014 => "E4014",
766 Self::E4015 => "E4015",
767 Self::E4016 => "E4016",
768 Self::E4017 => "E4017",
769 Self::E4018 => "E4018",
770 Self::E4019 => "E4019",
771 Self::E5003 => "E5003",
772 Self::E5010 => "E5010",
773 Self::E5011 => "E5011",
774 Self::E5012 => "E5012",
775 Self::W6001 => "W6001",
776 Self::W6002 => "W6002",
777 Self::W6003 => "W6003",
778 Self::W6004 => "W6004",
779 Self::W6005 => "W6005",
780 Self::W6006 => "W6006",
781 Self::W6007 => "W6007",
782 Self::W6010 => "W6010",
783 Self::W7001 => "W7001",
784 Self::W7002 => "W7002",
785 Self::W7003 => "W7003",
786 Self::W7004 => "W7004",
787 Self::N8001 => "N8001",
788 Self::N8002 => "N8002",
789 Self::N8003 => "N8003",
790 }
791 }
792
793 /// Severity letter derived from the numeric range (spec §10.2).
794 ///
795 /// `0..=5999` → `'E'`, `6000..=7999` → `'W'`, `8000..=8999` → `'N'`.
796 /// Panics if the discriminant falls outside any registered range —
797 /// the [`ALL`](Self::ALL) invariants enforced by `tests/registry.rs`
798 /// make this unreachable at runtime.
799 #[must_use]
800 pub const fn severity_char(self) -> char {
801 match self as u32 {
802 0..=5999 => 'E',
803 6000..=7999 => 'W',
804 8000..=8999 => 'N',
805 _ => panic!("DiagCode discriminant outside any registered range"),
806 }
807 }
808
809 /// Canonical enumeration of every registered diagnostic code, in
810 /// numeric order. This is THE registry used by
811 /// `tests/registry.rs` to enforce spec §10.2 invariants — every
812 /// variant added to [`DiagCode`] must also be appended here.
813 pub const ALL: &'static [DiagCode] = &[
814 Self::E0001,
815 Self::E0002,
816 Self::E0003,
817 Self::E0004,
818 Self::E0005,
819 Self::E0006,
820 Self::E0007,
821 Self::E0008,
822 Self::E0009,
823 Self::E0010,
824 Self::E0011,
825 Self::E0012,
826 Self::E0013,
827 Self::E0014,
828 Self::E0015,
829 Self::E0016,
830 Self::E0017,
831 Self::E0018,
832 Self::E0019,
833 Self::E0020,
834 Self::E0021,
835 Self::E0022,
836 Self::E0023,
837 Self::E0024,
838 Self::E0025,
839 Self::E0026,
840 Self::E0027,
841 Self::E0028,
842 Self::E0029,
843 Self::E0030,
844 Self::E0031,
845 Self::E0032,
846 Self::E0033,
847 Self::E0034,
848 Self::E0035,
849 Self::E0036,
850 Self::E0037,
851 Self::E0038,
852 Self::E0039,
853 Self::E0040,
854 Self::E0041,
855 Self::E0042,
856 Self::E0043,
857 Self::E0044,
858 Self::E0045,
859 Self::E0046,
860 Self::E0047,
861 Self::E0048,
862 Self::E0049,
863 Self::E0050,
864 Self::E0051,
865 Self::E0052,
866 Self::E0053,
867 Self::E0054,
868 Self::E0055,
869 Self::E0056,
870 Self::E0057,
871 Self::E0058,
872 Self::E0059,
873 Self::E0060,
874 Self::E0061,
875 Self::E0062,
876 Self::E0063,
877 Self::E0064,
878 Self::E0065,
879 Self::E0066,
880 Self::E0067,
881 Self::E0068,
882 Self::E0069,
883 Self::E0070,
884 Self::E0071,
885 Self::E0072,
886 Self::E0078,
887 Self::E0079,
888 Self::E0080,
889 Self::E0081,
890 Self::E0082,
891 Self::E1001,
892 Self::E1002,
893 Self::E2007,
894 Self::E2008,
895 Self::E2009,
896 Self::E2010,
897 Self::E2011,
898 Self::E2012,
899 Self::E2013,
900 Self::E3001,
901 Self::E3002,
902 Self::E3003,
903 Self::E3004,
904 Self::E3006,
905 Self::E3007,
906 Self::E3008,
907 Self::E3010,
908 Self::E3011,
909 Self::E4001,
910 Self::E4010,
911 Self::E4011,
912 Self::E4012,
913 Self::E4013,
914 Self::E4014,
915 Self::E4015,
916 Self::E4016,
917 Self::E4017,
918 Self::E4018,
919 Self::E4019,
920 Self::E5003,
921 Self::E5010,
922 Self::E5011,
923 Self::E5012,
924 Self::W6001,
925 Self::W6002,
926 Self::W6003,
927 Self::W6004,
928 Self::W6005,
929 Self::W6006,
930 Self::W6007,
931 Self::W6010,
932 Self::W7001,
933 Self::W7002,
934 Self::W7003,
935 Self::W7004,
936 Self::N8001,
937 Self::N8002,
938 Self::N8003,
939 ];
940
941 /// Recover the typed [`DiagCode`] for a numeric discriminant.
942 ///
943 /// Returns `None` for any value not registered in [`DiagCode::ALL`]
944 /// (including retired codes). Embedders that map cyrs errors to
945 /// their own typed errors should prefer this over matching on raw
946 /// `u16` values from [`cyrs_syntax::SyntaxError::code`] — names
947 /// survive renumbering, magic numbers do not (cy-emb3).
948 ///
949 /// # Examples
950 ///
951 /// ```
952 /// use cyrs_diag::DiagCode;
953 /// assert_eq!(DiagCode::try_from_u16(3), Some(DiagCode::E0003));
954 /// assert_eq!(DiagCode::try_from_u16(6001), Some(DiagCode::W6001));
955 /// assert_eq!(DiagCode::try_from_u16(9999), None);
956 /// ```
957 #[must_use]
958 pub fn try_from_u16(numeric: u16) -> Option<DiagCode> {
959 Self::ALL.iter().copied().find(|&c| (c as u16) == numeric)
960 }
961
962 /// Recover the typed [`DiagCode`] for a numeric discriminant, or
963 /// fall back to [`DiagCode::E0001`] for unknown values.
964 ///
965 /// Convenience for diagnostic-rendering paths that must always
966 /// produce a code (e.g. [`from`](From::from) impl on
967 /// [`cyrs_syntax::SyntaxError`]). Prefer
968 /// [`try_from_u16`](Self::try_from_u16) when the embedder needs to
969 /// distinguish "unknown code" from "registered E0001" (cy-emb3).
970 #[must_use]
971 pub fn from_u16_or_e0001(numeric: u16) -> DiagCode {
972 Self::try_from_u16(numeric).unwrap_or(Self::E0001)
973 }
974}
975
976impl fmt::Display for DiagCode {
977 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
978 f.write_str(self.as_str())
979 }
980}
981
982/// Lift a [`cyrs_syntax::SyntaxError`] to its typed [`DiagCode`].
983///
984/// `cyrs-syntax` carries diagnostic codes as `u16` to avoid a
985/// reverse dependency on `cyrs-diag` (spec §3 crate graph).
986/// Embedders that already depend on `cyrs-diag` for rendering can
987/// recover the typed enum without re-implementing the lookup table:
988///
989/// ```no_run
990/// use cyrs_diag::DiagCode;
991/// use cyrs_syntax::parse;
992///
993/// let parse = parse("MATCH");
994/// // First syntax error from a deliberately broken query.
995/// let err = parse.errors().first().expect("at least one error");
996/// let code: DiagCode = DiagCode::from(err);
997/// // Match on a stable name — survives any future renumbering.
998/// match code {
999/// DiagCode::E0001 | DiagCode::E0007 | DiagCode::E0045 => { /* generic / clause-level */ }
1000/// _ => { /* other syntax codes */ }
1001/// }
1002/// ```
1003///
1004/// Unknown numeric codes fall back to [`DiagCode::E0001`] — see
1005/// [`DiagCode::try_from_u16`] for a fallible alternative (cy-emb3).
1006impl From<&cyrs_syntax::SyntaxError> for DiagCode {
1007 fn from(err: &cyrs_syntax::SyntaxError) -> Self {
1008 Self::from_u16_or_e0001(err.code)
1009 }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014 use super::DiagCode;
1015
1016 /// Every code rendered as a string has a unique textual form — the
1017 /// canonical registry uniqueness check (spec §10.2).
1018 #[test]
1019 fn codes_are_unique_strings() {
1020 let all = [
1021 DiagCode::E0001,
1022 DiagCode::E0002,
1023 DiagCode::E0003,
1024 DiagCode::E0004,
1025 DiagCode::E0005,
1026 DiagCode::E0006,
1027 DiagCode::E0007,
1028 DiagCode::E0008,
1029 DiagCode::E0009,
1030 DiagCode::E0010,
1031 DiagCode::E0011,
1032 DiagCode::E0012,
1033 DiagCode::E0013,
1034 DiagCode::E0014,
1035 DiagCode::E0015,
1036 DiagCode::E0016,
1037 DiagCode::E0017,
1038 DiagCode::E0018,
1039 DiagCode::E0019,
1040 DiagCode::E0020,
1041 DiagCode::E0021,
1042 DiagCode::E0022,
1043 DiagCode::E0023,
1044 DiagCode::E0024,
1045 DiagCode::E0025,
1046 DiagCode::E0026,
1047 DiagCode::E0027,
1048 DiagCode::E0028,
1049 DiagCode::E0029,
1050 DiagCode::E0030,
1051 DiagCode::E0031,
1052 DiagCode::E0032,
1053 DiagCode::E0033,
1054 DiagCode::E0034,
1055 DiagCode::E0035,
1056 DiagCode::E0036,
1057 DiagCode::E0037,
1058 DiagCode::E0038,
1059 DiagCode::E0039,
1060 DiagCode::E0040,
1061 DiagCode::E0041,
1062 DiagCode::E0042,
1063 DiagCode::E0043,
1064 DiagCode::E0044,
1065 DiagCode::E0045,
1066 DiagCode::E0046,
1067 DiagCode::E0047,
1068 DiagCode::E0048,
1069 DiagCode::E0049,
1070 DiagCode::E0050,
1071 DiagCode::E0051,
1072 DiagCode::E0052,
1073 DiagCode::E0053,
1074 DiagCode::E0054,
1075 DiagCode::E0055,
1076 DiagCode::E0056,
1077 DiagCode::E0057,
1078 DiagCode::E0058,
1079 DiagCode::E0059,
1080 DiagCode::E0060,
1081 DiagCode::E0061,
1082 DiagCode::E0062,
1083 DiagCode::E0063,
1084 DiagCode::E0064,
1085 DiagCode::E0065,
1086 DiagCode::E0066,
1087 DiagCode::E0067,
1088 DiagCode::E0068,
1089 DiagCode::E0069,
1090 DiagCode::E0070,
1091 DiagCode::E0071,
1092 DiagCode::E0072,
1093 DiagCode::E0078,
1094 DiagCode::E0079,
1095 DiagCode::E0080,
1096 DiagCode::E0081,
1097 DiagCode::E0082,
1098 DiagCode::E1001,
1099 DiagCode::E1002,
1100 DiagCode::E2007,
1101 DiagCode::E2008,
1102 DiagCode::E2009,
1103 DiagCode::E2010,
1104 DiagCode::E2011,
1105 DiagCode::E2012,
1106 DiagCode::E2013,
1107 DiagCode::E3001,
1108 DiagCode::E3002,
1109 DiagCode::E3003,
1110 DiagCode::E3004,
1111 DiagCode::E3006,
1112 DiagCode::E3007,
1113 DiagCode::E3008,
1114 DiagCode::E3010,
1115 DiagCode::E3011,
1116 DiagCode::E4001,
1117 DiagCode::E4010,
1118 DiagCode::E4011,
1119 DiagCode::E4012,
1120 DiagCode::E4013,
1121 DiagCode::E4014,
1122 DiagCode::E4015,
1123 DiagCode::E4016,
1124 DiagCode::E4017,
1125 DiagCode::E4018,
1126 DiagCode::E4019,
1127 DiagCode::E5003,
1128 DiagCode::E5010,
1129 DiagCode::E5011,
1130 DiagCode::E5012,
1131 DiagCode::W6001,
1132 DiagCode::W6002,
1133 DiagCode::W6003,
1134 DiagCode::W6004,
1135 DiagCode::W6005,
1136 DiagCode::W6006,
1137 DiagCode::W6007,
1138 DiagCode::W6010,
1139 DiagCode::W7001,
1140 DiagCode::W7002,
1141 DiagCode::W7003,
1142 DiagCode::W7004,
1143 DiagCode::N8001,
1144 DiagCode::N8002,
1145 DiagCode::N8003,
1146 ];
1147 let mut strs: Vec<_> = all.iter().map(|c| c.as_str()).collect();
1148 strs.sort_unstable();
1149 strs.dedup();
1150 assert_eq!(strs.len(), all.len(), "duplicate DiagCode string");
1151 }
1152
1153 /// `try_from_u16` recovers the typed variant for a sample of known
1154 /// codes spanning every range (cy-emb3).
1155 #[test]
1156 fn try_from_u16_known_codes() {
1157 // Syntax range.
1158 assert_eq!(DiagCode::try_from_u16(1), Some(DiagCode::E0001));
1159 assert_eq!(DiagCode::try_from_u16(3), Some(DiagCode::E0003));
1160 assert_eq!(DiagCode::try_from_u16(46), Some(DiagCode::E0046));
1161 assert_eq!(DiagCode::try_from_u16(78), Some(DiagCode::E0078));
1162 // Name resolution / sema / dialect / type system.
1163 assert_eq!(DiagCode::try_from_u16(1001), Some(DiagCode::E1001));
1164 assert_eq!(DiagCode::try_from_u16(2009), Some(DiagCode::E2009));
1165 assert_eq!(DiagCode::try_from_u16(3001), Some(DiagCode::E3001));
1166 assert_eq!(DiagCode::try_from_u16(4001), Some(DiagCode::E4001));
1167 assert_eq!(DiagCode::try_from_u16(5003), Some(DiagCode::E5003));
1168 // Warnings and notes.
1169 assert_eq!(DiagCode::try_from_u16(6001), Some(DiagCode::W6001));
1170 assert_eq!(DiagCode::try_from_u16(7001), Some(DiagCode::W7001));
1171 assert_eq!(DiagCode::try_from_u16(8001), Some(DiagCode::N8001));
1172 }
1173
1174 /// Unregistered, retired, and out-of-range numeric codes are
1175 /// rejected by the fallible lookup (cy-emb3).
1176 #[test]
1177 fn try_from_u16_unknown_codes() {
1178 // Gaps inside ranges (E0073..=E0077, E0083+).
1179 assert_eq!(DiagCode::try_from_u16(0), None);
1180 assert_eq!(DiagCode::try_from_u16(73), None);
1181 assert_eq!(DiagCode::try_from_u16(77), None);
1182 assert_eq!(DiagCode::try_from_u16(83), None);
1183 // Unassigned ranges.
1184 assert_eq!(DiagCode::try_from_u16(999), None);
1185 assert_eq!(DiagCode::try_from_u16(9999), None);
1186 }
1187
1188 /// `from_u16_or_e0001` falls back to the generic-syntax code for
1189 /// any unregistered numeric (cy-emb3).
1190 #[test]
1191 fn from_u16_or_e0001_unknown_falls_back() {
1192 assert_eq!(DiagCode::from_u16_or_e0001(3), DiagCode::E0003);
1193 assert_eq!(DiagCode::from_u16_or_e0001(9999), DiagCode::E0001);
1194 assert_eq!(DiagCode::from_u16_or_e0001(0), DiagCode::E0001);
1195 }
1196
1197 /// Round-trip every registered code through [`DiagCode::ALL`]: the
1198 /// fallible lookup must recover the same variant we started from
1199 /// (cy-emb3).
1200 #[test]
1201 fn try_from_u16_round_trips_every_registered_code() {
1202 for &c in DiagCode::ALL {
1203 assert_eq!(
1204 DiagCode::try_from_u16(c as u16),
1205 Some(c),
1206 "round-trip failed for {c}",
1207 );
1208 }
1209 }
1210
1211 /// `From<&SyntaxError>` is the embedder-facing lift: parse a
1212 /// deliberately-broken query and confirm we surface a registered,
1213 /// renderable code rather than a magic number (cy-emb3).
1214 ///
1215 /// Ignored under Miri because this is the only test in cyrs-diag that
1216 /// drives `cyrs_syntax::parse`, which constructs a rowan
1217 /// `SyntaxNode` tree — rowan 0.16's `ThinArc::drop` trips Stacked
1218 /// Borrows. The other rowan-touching crates handle this via
1219 /// `rowan_thinarc_ub: true` in the miri matrix, but cyrs-diag is
1220 /// otherwise rowan-free and remains strict (§17.12). See
1221 /// `.github/workflows/miri.yml` for context.
1222 #[test]
1223 #[cfg_attr(miri, ignore)]
1224 fn from_syntax_error_lifts_to_typed_code() {
1225 // Bare clause keyword with no body — the parser emits at least
1226 // one syntax error. We don't assert the exact code (recovery
1227 // strategy is not part of this bead's contract); we assert the
1228 // lift produces a registered DiagCode whose string form starts
1229 // with 'E', i.e. the fallback never silently swallows the
1230 // numeric.
1231 let parse = cyrs_syntax::parse("MATCH");
1232 let err = parse
1233 .errors()
1234 .first()
1235 .expect("MATCH on its own should produce at least one syntax error");
1236 let code = DiagCode::from(err);
1237 assert!(
1238 DiagCode::ALL.contains(&code),
1239 "lifted code {code} not in registry",
1240 );
1241 // And the round-trip matches the raw u16 when registered.
1242 if let Some(direct) = DiagCode::try_from_u16(err.code) {
1243 assert_eq!(code, direct);
1244 } else {
1245 assert_eq!(code, DiagCode::E0001, "unknown numeric should fall back");
1246 }
1247 }
1248}