Skip to main content

graphcal_compiler/registry/
error.rs

1use std::sync::Arc;
2
3use miette::{Diagnostic, NamedSource, SourceSpan};
4use thiserror::Error;
5
6use crate::syntax::names::{
7    DeclName, DimName, FieldName, FnName, IndexName, IndexVariantName, ScopedName, StructTypeName,
8    UnitName, UnitRef,
9};
10
11/// Rich diagnostic error types for graphcal evaluation.
12#[derive(Debug, Clone, Error, Diagnostic)]
13pub enum GraphcalError {
14    #[error("duplicate name `{name}`")]
15    #[diagnostic(code(graphcal::N001), help("each name must be unique within a file"))]
16    DuplicateName {
17        name: String,
18        #[source_code]
19        src: NamedSource<Arc<String>>,
20        #[label("duplicate definition here")]
21        duplicate: SourceSpan,
22        #[label("first defined here")]
23        first: SourceSpan,
24    },
25
26    #[error("{kind} `{name}` shadows a built-in name")]
27    #[diagnostic(
28        code(graphcal::N009),
29        help("choose a different name; built-in dimensions, types, and units cannot be redefined")
30    )]
31    BuiltinNameShadowed {
32        kind: &'static str,
33        name: String,
34        #[source_code]
35        src: NamedSource<Arc<String>>,
36        #[label("shadows a built-in name")]
37        span: SourceSpan,
38    },
39
40    #[error("conflicting definitions of unit `{name}` reach this file through includes")]
41    #[diagnostic(
42        code(graphcal::N010),
43        help(
44            "included modules share this file's unit scope, and two of them define `{name}` with different dimensions or scales, so references would be ambiguous — rename one of the definitions"
45        )
46    )]
47    ConflictingImportedUnit {
48        name: UnitRef,
49        #[source_code]
50        src: NamedSource<Arc<String>>,
51        #[label("import brings in a conflicting `{name}`")]
52        span: SourceSpan,
53    },
54
55    #[error("property `{property}` is not valid in {context}")]
56    #[diagnostic(code(graphcal::N011), help("{valid}"))]
57    InvalidPlotProperty {
58        property: String,
59        context: &'static str,
60        /// Preformatted help listing the valid property set for `context`.
61        valid: String,
62        #[source_code]
63        src: NamedSource<Arc<String>>,
64        #[label("not a {context} property")]
65        span: SourceSpan,
66    },
67
68    #[error("property `{property}` expects {expected}")]
69    #[diagnostic(code(graphcal::D015))]
70    PlotPropertyTypeMismatch {
71        property: &'static str,
72        expected: &'static str,
73        found: String,
74        #[source_code]
75        src: NamedSource<Arc<String>>,
76        #[label("this is {found}")]
77        span: SourceSpan,
78    },
79
80    #[error(
81        "property `{property}` must be dimensionless, but this value has dimension {dimension}"
82    )]
83    #[diagnostic(
84        code(graphcal::D016),
85        help(
86            "plot properties are raw rendering quantities (pixels, ratios); write a plain number instead of a dimensioned value"
87        )
88    )]
89    PlotPropertyDimensioned {
90        property: &'static str,
91        dimension: String,
92        #[source_code]
93        src: NamedSource<Arc<String>>,
94        #[label("dimensioned value")]
95        span: SourceSpan,
96    },
97
98    #[error("cannot `import` plot `{name}`")]
99    #[diagnostic(
100        code(graphcal::M021),
101        help(
102            "plots are runtime sinks evaluated against an instance; request them through an include brace list instead: `include path(...).{{ {name} }}`"
103        )
104    )]
105    ImportPlotItem {
106        name: String,
107        #[source_code]
108        src: NamedSource<Arc<String>>,
109        #[label("plots cannot travel through `import`")]
110        span: SourceSpan,
111    },
112
113    #[error("attribute `hidden` does not apply to include item `{name}`")]
114    #[diagnostic(
115        code(graphcal::A018),
116        help("`#[hidden]` on an include item is only valid when the item names a plot")
117    )]
118    HiddenIncludeItemNotAPlot {
119        name: String,
120        #[source_code]
121        src: NamedSource<Arc<String>>,
122        #[label("not a plot item")]
123        span: SourceSpan,
124    },
125
126    #[error("{owner_kind} `{owner}` references unknown plot `{name}`")]
127    #[diagnostic(
128        code(graphcal::N012),
129        help("`plots:` entries must name `plot` declarations visible in this file")
130    )]
131    UnknownPlotReference {
132        owner_kind: &'static str,
133        owner: ScopedName,
134        name: ScopedName,
135        #[source_code]
136        src: NamedSource<Arc<String>>,
137        #[label("no plot with this name")]
138        span: SourceSpan,
139    },
140
141    #[error("`{name}` is a {actual_kind}, not a plot")]
142    #[diagnostic(
143        code(graphcal::N013),
144        help("{owner_kind}s compose `plot` declarations; they cannot nest other {actual_kind}s")
145    )]
146    CompositionReferencesNonPlot {
147        owner_kind: &'static str,
148        actual_kind: &'static str,
149        name: ScopedName,
150        #[source_code]
151        src: NamedSource<Arc<String>>,
152        #[label("this names a {actual_kind}")]
153        span: SourceSpan,
154    },
155
156    #[error("{owner_kind} `{owner}` lists plot `{name}` more than once")]
157    #[diagnostic(
158        code(graphcal::N014),
159        help("each plot may appear at most once in a `plots:` list")
160    )]
161    DuplicatePlotReference {
162        owner_kind: &'static str,
163        owner: ScopedName,
164        name: ScopedName,
165        #[source_code]
166        src: NamedSource<Arc<String>>,
167        #[label("duplicate entry")]
168        span: SourceSpan,
169    },
170
171    #[error("unknown graph reference `@{name}`")]
172    #[diagnostic(
173        code(graphcal::N002),
174        help("graph references must point to a `param` or `node`")
175    )]
176    UnknownGraphRef {
177        name: ScopedName,
178        #[source_code]
179        src: NamedSource<Arc<String>>,
180        #[label("not found")]
181        span: SourceSpan,
182    },
183
184    #[error("unknown constant `{name}`")]
185    #[diagnostic(
186        code(graphcal::N003),
187        help("constant references must point to a `const` or built-in constant (PI, E)")
188    )]
189    UnknownConstRef {
190        name: ScopedName,
191        #[source_code]
192        src: NamedSource<Arc<String>>,
193        #[label("not found")]
194        span: SourceSpan,
195    },
196
197    #[error("unknown function `{name}`")]
198    #[diagnostic(
199        code(graphcal::N004),
200        help("check function name and ensure it is defined")
201    )]
202    UnknownFunction {
203        name: String,
204        #[source_code]
205        src: NamedSource<Arc<String>>,
206        #[label("unknown function")]
207        span: SourceSpan,
208    },
209
210    #[error("graph reference `@{name}` not allowed in const expression")]
211    #[diagnostic(
212        code(graphcal::N005),
213        help(
214            "const expressions are evaluated at compile time and cannot reference params or nodes"
215        )
216    )]
217    GraphRefInConst {
218        name: ScopedName,
219        #[source_code]
220        src: NamedSource<Arc<String>>,
221        #[label("@ reference not allowed here")]
222        span: SourceSpan,
223    },
224
225    #[error("graph reference `@{name}` not allowed in const unit scale")]
226    #[diagnostic(
227        code(graphcal::D017),
228        help(
229            "`const unit` scales are compile-time constants; use plain `unit` for runtime-dependent units"
230        )
231    )]
232    GraphRefInConstUnit {
233        name: ScopedName,
234        #[source_code]
235        src: NamedSource<Arc<String>>,
236        #[label("@ reference not allowed in a const unit")]
237        span: SourceSpan,
238    },
239
240    #[error("non-const unit `{name}` not allowed in const expression")]
241    #[diagnostic(
242        code(graphcal::D018),
243        help(
244            "`const node` bodies and `const unit` definitions can only use prelude units, `base unit`, or `const unit` declarations; use `node` or plain `unit` for runtime-unit calculations"
245        )
246    )]
247    NonConstUnitInConst {
248        name: UnitRef,
249        #[source_code]
250        src: NamedSource<Arc<String>>,
251        #[label("unit is not const")]
252        span: SourceSpan,
253    },
254
255    #[error("graph reference `@{name}` not allowed in function body")]
256    #[diagnostic(code(graphcal::F001))]
257    GraphRefInFn {
258        name: ScopedName,
259        #[source_code]
260        src: NamedSource<Arc<String>>,
261        #[label("@ reference not allowed here")]
262        span: SourceSpan,
263        #[help]
264        help: String,
265    },
266
267    #[error("recursive function `{name}` detected")]
268    #[diagnostic(
269        code(graphcal::F002),
270        help("graphcal does not support recursive functions")
271    )]
272    RecursiveFunction {
273        name: FnName,
274        #[source_code]
275        src: NamedSource<Arc<String>>,
276        #[label("involved in recursion")]
277        span: SourceSpan,
278    },
279
280    #[error("function `{name}` expects {expected} argument(s), got {got}")]
281    #[diagnostic(code(graphcal::N006))]
282    WrongArity {
283        name: FnName,
284        expected: usize,
285        got: usize,
286        #[source_code]
287        src: NamedSource<Arc<String>>,
288        #[label("wrong number of arguments")]
289        span: SourceSpan,
290    },
291
292    #[error("function `{name}` expects {expected} generic argument(s), got {got}")]
293    #[diagnostic(
294        code(graphcal::N007),
295        help("provide all generic parameters or none (to infer)")
296    )]
297    WrongGenericArity {
298        name: FnName,
299        expected: usize,
300        got: usize,
301        #[source_code]
302        src: NamedSource<Arc<String>>,
303        #[label("wrong number of generic arguments")]
304        span: SourceSpan,
305    },
306
307    #[error(
308        "generic argument type mismatch for parameter `{param}` of function `{name}`: expected {expected} constraint, got {found}"
309    )]
310    #[diagnostic(code(graphcal::N008))]
311    GenericArgMismatch {
312        name: FnName,
313        param: String,
314        expected: String,
315        found: String,
316        #[source_code]
317        src: NamedSource<Arc<String>>,
318        #[label("this generic argument")]
319        span: SourceSpan,
320    },
321
322    #[error("cyclic dependency involving `{name}`")]
323    #[diagnostic(
324        code(graphcal::G001),
325        help("declarations cannot form dependency cycles")
326    )]
327    CyclicDependency {
328        name: String,
329        #[source_code]
330        src: NamedSource<Arc<String>>,
331        #[label("involved in cycle")]
332        span: SourceSpan,
333    },
334
335    #[error("{message}")]
336    #[diagnostic(code(graphcal::E001))]
337    EvalError {
338        message: String,
339        #[source_code]
340        src: NamedSource<Arc<String>>,
341        #[label("error here")]
342        span: SourceSpan,
343    },
344
345    /// An internal invariant violation that should never be reached if earlier
346    /// compiler phases (parsing, resolution, `dim_check`) are correct.
347    #[error("internal error: {message}")]
348    #[diagnostic(
349        code(graphcal::X001),
350        help("this is a compiler bug — please report it")
351    )]
352    InternalError {
353        message: String,
354        #[source_code]
355        src: NamedSource<Arc<String>>,
356        #[label("unexpected state here")]
357        span: SourceSpan,
358    },
359
360    #[error("dimension exponent overflow")]
361    #[diagnostic(
362        code(graphcal::D010),
363        help("dimension exponents are stored as `i32`; reduce the magnitude of the exponent")
364    )]
365    DimensionOverflow {
366        #[source_code]
367        src: NamedSource<Arc<String>>,
368        #[label("overflow here")]
369        span: SourceSpan,
370    },
371
372    #[error("dimension mismatch: expected {expected}, found {found}")]
373    #[diagnostic(code(graphcal::D001))]
374    DimensionMismatch {
375        expected: String,
376        found: String,
377        #[source_code]
378        src: NamedSource<Arc<String>>,
379        #[label("has dimension {found}")]
380        span: SourceSpan,
381        #[help]
382        help: String,
383    },
384
385    #[error("mismatched index axes in {context}: {lhs} vs {rhs}")]
386    #[diagnostic(
387        code(graphcal::D011),
388        help(
389            "element-wise operands must be indexed by the same axes in the same order; a scalar operand broadcasts to every key"
390        )
391    )]
392    IndexedShapeMismatch {
393        context: String,
394        lhs: String,
395        rhs: String,
396        #[source_code]
397        src: NamedSource<Arc<String>>,
398        #[label("has type {rhs}")]
399        span: SourceSpan,
400    },
401
402    #[error("type annotation mismatch: declared {declared}, inferred {inferred}")]
403    #[diagnostic(
404        code(graphcal::D002),
405        help("the declared type must match the inferred dimension of the expression")
406    )]
407    DimensionMismatchInAnnotation {
408        declared: String,
409        inferred: String,
410        #[source_code]
411        src: NamedSource<Arc<String>>,
412        #[label("declared as {declared}")]
413        span: SourceSpan,
414    },
415
416    #[error("unknown unit `{name}`")]
417    #[diagnostic(
418        code(graphcal::D003),
419        help(
420            "a bare unit name must be declared in this file, selectively imported, or part of the prelude; units of a module imported with an alias are referenced as `alias.unit`"
421        )
422    )]
423    UnknownUnit {
424        name: UnitRef,
425        #[source_code]
426        src: NamedSource<Arc<String>>,
427        #[label("unknown unit")]
428        span: SourceSpan,
429    },
430
431    #[error("unknown dimension `{name}`")]
432    #[diagnostic(
433        code(graphcal::D004),
434        help("dimension must be declared or part of the prelude")
435    )]
436    UnknownDimension {
437        name: DimName,
438        #[source_code]
439        src: NamedSource<Arc<String>>,
440        #[label("unknown dimension")]
441        span: SourceSpan,
442    },
443
444    #[error("cyclic dimension dependency involving `{name}`")]
445    #[diagnostic(
446        code(graphcal::D008),
447        help("derived dimensions cannot form dependency cycles")
448    )]
449    CyclicDimension {
450        name: DimName,
451        #[source_code]
452        src: NamedSource<Arc<String>>,
453        #[label("involved in cycle")]
454        span: SourceSpan,
455    },
456
457    #[error("cyclic unit dependency involving `{name}`")]
458    #[diagnostic(code(graphcal::D009), help("units cannot form dependency cycles"))]
459    CyclicUnit {
460        name: UnitName,
461        #[source_code]
462        src: NamedSource<Arc<String>>,
463        #[label("involved in cycle")]
464        span: SourceSpan,
465    },
466
467    #[error("exponent in power must be a numeric literal for dimensional analysis")]
468    #[diagnostic(
469        code(graphcal::D005),
470        help("use a literal exponent like `x ^ 2.0` so dimensions can be checked at compile time")
471    )]
472    NonLiteralExponent {
473        #[source_code]
474        src: NamedSource<Arc<String>>,
475        #[label("non-literal exponent")]
476        span: SourceSpan,
477    },
478
479    #[error("conversion target dimension {target} does not match expression dimension {expr_dim}")]
480    #[diagnostic(
481        code(graphcal::D006),
482        help("the `->` conversion operator can only change units within the same dimension")
483    )]
484    ConversionDimensionMismatch {
485        target: String,
486        expr_dim: String,
487        #[source_code]
488        src: NamedSource<Arc<String>>,
489        #[label("target unit has different dimension")]
490        span: SourceSpan,
491    },
492
493    #[error("`->` cannot be applied to an expression that already has a display target")]
494    #[diagnostic(
495        code(graphcal::D012),
496        help(
497            "an expression carries at most one `->` target; remove the inner conversion — only the outermost target takes effect"
498        )
499    )]
500    NestedConversion {
501        #[source_code]
502        src: NamedSource<Arc<String>>,
503        #[label("the operand of this conversion is itself a conversion")]
504        span: SourceSpan,
505    },
506
507    #[error("`->` has no effect in this position")]
508    #[diagnostic(
509        code(graphcal::D013),
510        help(
511            "a conversion only affects how a declaration's final value is displayed; move it to the top level of the declaration (or a selected `if`/`match` branch, constructor field, map entry, for-comprehension body, or scan/unfold init), or remove it"
512        )
513    )]
514    IneffectiveConversion {
515        #[source_code]
516        src: NamedSource<Arc<String>>,
517        #[label("this conversion's display target is discarded")]
518        span: SourceSpan,
519    },
520
521    #[error("user-defined units on dimension `{dim}` are not supported")]
522    #[diagnostic(
523        code(graphcal::D014),
524        help(
525            "common units of this dimension (e.g. \u{b0}C, \u{b0}F for Temperature) are affine scales with an offset; a purely multiplicative `unit` definition would display silently wrong values. Keep values in the base unit, or model the offset explicitly in your expressions"
526        )
527    )]
528    AffineProneUnitDefinition {
529        dim: String,
530        #[source_code]
531        src: NamedSource<Arc<String>>,
532        #[label("unit defined on an affine-prone dimension")]
533        span: SourceSpan,
534    },
535
536    #[error("unknown struct type `{name}`")]
537    #[diagnostic(
538        code(graphcal::S002),
539        help("struct types must be declared with `type` before use")
540    )]
541    UnknownStructType {
542        name: String,
543        #[source_code]
544        src: NamedSource<Arc<String>>,
545        #[label("not found")]
546        span: SourceSpan,
547    },
548
549    #[error("unknown field `{field_name}` on struct `{type_name}`")]
550    #[diagnostic(code(graphcal::S003))]
551    UnknownField {
552        type_name: StructTypeName,
553        field_name: FieldName,
554        #[source_code]
555        src: NamedSource<Arc<String>>,
556        #[label("no such field")]
557        span: SourceSpan,
558    },
559
560    #[error("missing field(s) {missing:?} in construction of `{type_name}`")]
561    #[diagnostic(
562        code(graphcal::S004),
563        help("all fields are required when constructing a struct")
564    )]
565    MissingFields {
566        type_name: StructTypeName,
567        missing: Vec<FieldName>,
568        #[source_code]
569        src: NamedSource<Arc<String>>,
570        #[label("incomplete construction")]
571        span: SourceSpan,
572    },
573
574    #[error("extra field(s) {extra:?} in construction of `{type_name}`")]
575    #[diagnostic(
576        code(graphcal::S005),
577        help("only fields declared in the struct type are allowed")
578    )]
579    ExtraFields {
580        type_name: StructTypeName,
581        extra: Vec<FieldName>,
582        #[source_code]
583        src: NamedSource<Arc<String>>,
584        #[label("unexpected fields")]
585        span: SourceSpan,
586    },
587
588    #[error("field `{field_name}` of `{type_name}`: expected dimension {expected}, found {found}")]
589    #[diagnostic(code(graphcal::S006))]
590    FieldDimensionMismatch {
591        type_name: StructTypeName,
592        field_name: FieldName,
593        expected: String,
594        found: String,
595        #[source_code]
596        src: NamedSource<Arc<String>>,
597        #[label("has dimension {found}")]
598        span: SourceSpan,
599    },
600
601    #[error("cannot access field of non-struct value `{name}`")]
602    #[diagnostic(
603        code(graphcal::S007),
604        help("field access `.field` is only valid on struct values")
605    )]
606    NotAStruct {
607        name: String,
608        #[source_code]
609        src: NamedSource<Arc<String>>,
610        #[label("not a struct")]
611        span: SourceSpan,
612    },
613
614    #[error("unknown local variable `{name}`")]
615    #[diagnostic(
616        code(graphcal::S008),
617        help(
618            "local variables are introduced by `for`, `scan`, `unfold`, `match`, or function parameters"
619        )
620    )]
621    UnknownLocalRef {
622        name: String,
623        #[source_code]
624        src: NamedSource<Arc<String>>,
625        #[label("not found")]
626        span: SourceSpan,
627    },
628
629    #[error("unknown index `{name}`")]
630    #[diagnostic(
631        code(graphcal::I001),
632        help(
633            "declare with `index Name = {{ Variant1, Variant2, ... }};` or `index Name = linspace(start, end, step: step);`"
634        )
635    )]
636    UnknownIndex {
637        name: IndexName,
638        #[source_code]
639        src: NamedSource<Arc<String>>,
640        #[label("unknown index")]
641        span: SourceSpan,
642    },
643
644    #[error("unknown variant `{variant_name}` in index `{index_name}`")]
645    #[diagnostic(code(graphcal::I002))]
646    UnknownVariant {
647        index_name: IndexName,
648        variant_name: IndexVariantName,
649        #[source_code]
650        src: NamedSource<Arc<String>>,
651        #[label("not a variant of `{index_name}`")]
652        span: SourceSpan,
653    },
654
655    #[error("missing variant(s) {missing:?} in map literal for index `{index_name}`")]
656    #[diagnostic(
657        code(graphcal::I003),
658        help("map literals must cover all variants of the index")
659    )]
660    MissingVariants {
661        index_name: IndexName,
662        missing: Vec<IndexVariantName>,
663        #[source_code]
664        src: NamedSource<Arc<String>>,
665        #[label("incomplete map literal")]
666        span: SourceSpan,
667    },
668
669    #[error("extra variant(s) {extra:?} in map literal for index `{index_name}`")]
670    #[diagnostic(
671        code(graphcal::I004),
672        help("only variants declared in the index are allowed")
673    )]
674    ExtraVariants {
675        index_name: IndexName,
676        extra: Vec<IndexVariantName>,
677        #[source_code]
678        src: NamedSource<Arc<String>>,
679        #[label("unexpected variants")]
680        span: SourceSpan,
681    },
682
683    #[error("index mismatch: expected `{expected}`, found `{found}`")]
684    #[diagnostic(code(graphcal::I005))]
685    IndexMismatch {
686        expected: IndexName,
687        found: IndexName,
688        #[source_code]
689        src: NamedSource<Arc<String>>,
690        #[label("wrong index")]
691        span: SourceSpan,
692    },
693
694    #[error("file not found: {path}")]
695    #[diagnostic(code(graphcal::M000), help("check that the file path is correct"))]
696    FileNotFound { path: String },
697
698    #[error("circular import detected: {cycle}")]
699    #[diagnostic(
700        code(graphcal::M001),
701        help("files cannot import each other in a cycle")
702    )]
703    CircularImport { cycle: String },
704
705    #[error("imported file not found: {path}")]
706    #[diagnostic(code(graphcal::M002))]
707    ImportFileNotFound {
708        path: String,
709        #[source_code]
710        src: NamedSource<Arc<String>>,
711        #[label("referenced here")]
712        span: SourceSpan,
713    },
714
715    #[error("name `{name}` not found in imported file `{file_path}`")]
716    #[diagnostic(
717        code(graphcal::M003),
718        help("check that the name is declared in the imported file")
719    )]
720    ImportNameNotFound {
721        name: String,
722        file_path: String,
723        #[source_code]
724        src: NamedSource<Arc<String>>,
725        #[label("not found in imported file")]
726        span: SourceSpan,
727    },
728
729    #[error("filename `{stem}` is not a valid module name")]
730    #[diagnostic(
731        code(graphcal::M004),
732        help(
733            "module names must be lower_snake_case identifiers; use `as alias` to specify an explicit name"
734        )
735    )]
736    InvalidModuleName {
737        stem: String,
738        #[source_code]
739        src: NamedSource<Arc<String>>,
740        #[label("imported here")]
741        span: SourceSpan,
742    },
743
744    #[error("duplicate module name `{name}`")]
745    #[diagnostic(code(graphcal::M005))]
746    DuplicateModuleName {
747        name: String,
748        #[source_code]
749        src: NamedSource<Arc<String>>,
750        #[label("duplicate module import")]
751        span: SourceSpan,
752        #[label("first imported here")]
753        first: SourceSpan,
754    },
755
756    #[error("unknown module `{name}`")]
757    #[diagnostic(
758        code(graphcal::M006),
759        help("check that an `import` declaration imports this module")
760    )]
761    UnknownModule {
762        name: String,
763        #[source_code]
764        src: NamedSource<Arc<String>>,
765        #[label("unknown module")]
766        span: SourceSpan,
767    },
768
769    #[error("name `{name}` not found in module `{module}`")]
770    #[diagnostic(
771        code(graphcal::M007),
772        help("check that the name is declared in the imported file")
773    )]
774    QualifiedNameNotFound {
775        module: String,
776        name: String,
777        #[source_code]
778        src: NamedSource<Arc<String>>,
779        #[label("not found in module")]
780        span: SourceSpan,
781    },
782
783    #[error(
784        "range index `{name}`: start, end, and step must have the same dimension (found {start_dim}, {end_dim}, {step_dim})"
785    )]
786    #[diagnostic(
787        code(graphcal::I006),
788        help("all three values in range(start, end, step: step) must share the same dimension")
789    )]
790    RangeIndexDimensionMismatch {
791        name: IndexName,
792        start_dim: String,
793        end_dim: String,
794        step_dim: String,
795        #[source_code]
796        src: NamedSource<Arc<String>>,
797        #[label("dimension mismatch")]
798        span: SourceSpan,
799    },
800
801    #[error("range index `{name}`: {message}")]
802    #[diagnostic(code(graphcal::I007))]
803    RangeIndexInvalid {
804        name: IndexName,
805        message: String,
806        #[source_code]
807        src: NamedSource<Arc<String>>,
808        #[label("invalid range")]
809        span: SourceSpan,
810    },
811
812    #[error("cannot reference assert `{name}` with `@`")]
813    #[diagnostic(
814        code(graphcal::A003),
815        help("assert declarations are post-evaluation checks and cannot be referenced with `@`")
816    )]
817    GraphRefToAssert {
818        name: DeclName,
819        #[source_code]
820        src: NamedSource<Arc<String>>,
821        #[label("`@{name}` is an assert, not a param or node")]
822        span: SourceSpan,
823    },
824
825    #[error("assert body must evaluate to Bool, got {found}")]
826    #[diagnostic(
827        code(graphcal::A004),
828        help("assert declarations must have a body that evaluates to Bool")
829    )]
830    AssertBodyNotBool {
831        found: String,
832        #[source_code]
833        src: NamedSource<Arc<String>>,
834        #[label("expected Bool, found {found}")]
835        span: SourceSpan,
836    },
837
838    #[error("assumed assertion `{name}` failed")]
839    #[diagnostic(code(graphcal::A002))]
840    AssumedAssertionFailed {
841        name: DeclName,
842        #[source_code]
843        src: NamedSource<Arc<String>>,
844        #[label("this assertion failed")]
845        span: SourceSpan,
846        #[help]
847        help: String,
848    },
849
850    #[error("unknown assert `{name}` in #[assumes(...)]")]
851    #[diagnostic(
852        code(graphcal::A005),
853        help("`#[assumes(...)]` arguments must reference `assert` declarations")
854    )]
855    UnknownAssertInAssumes {
856        name: String,
857        #[source_code]
858        src: NamedSource<Arc<String>>,
859        #[label("not an assert declaration")]
860        span: SourceSpan,
861    },
862
863    #[error("`#[assumes(...)]` is not valid on `{kind}` declarations")]
864    #[diagnostic(
865        code(graphcal::A006),
866        help("`#[assumes(...)]` is only valid on `node` and `param` declarations")
867    )]
868    InvalidAssumesTarget {
869        kind: String,
870        #[source_code]
871        src: NamedSource<Arc<String>>,
872        #[label("not a node or param")]
873        span: SourceSpan,
874    },
875
876    #[error("attribute `hidden` does not apply to `{kind}` declarations")]
877    #[diagnostic(
878        code(graphcal::A017),
879        help(
880            "`#[hidden]` suppresses a plot's standalone output; it is only valid on `plot` declarations"
881        )
882    )]
883    InvalidHiddenTarget {
884        kind: String,
885        #[source_code]
886        src: NamedSource<Arc<String>>,
887        #[label("not a plot")]
888        span: SourceSpan,
889    },
890
891    #[error("unknown attribute `{name}`")]
892    #[diagnostic(
893        code(graphcal::A007),
894        help(
895            "recognized attributes are `#[assumes(...)]`, `#[expected_fail]`, `#[hidden]`, and `#[lazy]`"
896        )
897    )]
898    UnknownAttribute {
899        name: String,
900        #[source_code]
901        src: NamedSource<Arc<String>>,
902        #[label("unknown attribute")]
903        span: SourceSpan,
904    },
905
906    #[error("`#[expected_fail]` is not valid on `{kind}` declarations")]
907    #[diagnostic(
908        code(graphcal::A008),
909        help("`#[expected_fail]` is only valid on `assert` declarations")
910    )]
911    InvalidExpectedFailTarget {
912        kind: String,
913        #[source_code]
914        src: NamedSource<Arc<String>>,
915        #[label("not an assert")]
916        span: SourceSpan,
917    },
918
919    #[error(
920        "invalid argument in `#[expected_fail(...)]`: expected `Index.Variant`, `module.Index.Variant`, `#N` (range axes), or grouped variants"
921    )]
922    #[diagnostic(code(graphcal::A009))]
923    ExpectedFailInvalidArg {
924        #[source_code]
925        src: NamedSource<Arc<String>>,
926        #[label("invalid argument")]
927        span: SourceSpan,
928    },
929
930    #[error("`#[expected_fail(...)]` on non-indexed assertion")]
931    #[diagnostic(
932        code(graphcal::A010),
933        help("use `#[expected_fail]` without arguments for non-indexed assertions")
934    )]
935    ExpectedFailNotIndexed {
936        #[source_code]
937        src: NamedSource<Arc<String>>,
938        #[label("this assertion is not indexed")]
939        span: SourceSpan,
940    },
941
942    #[error("`#[expected_fail]` without arguments on indexed assertion")]
943    #[diagnostic(
944        code(graphcal::A011),
945        help(
946            "use `#[expected_fail(Index.Variant, ...)]` (qualified `module.Index.Variant` also works) to specify which variants are expected to fail; for Nat range axes use `#[expected_fail(#N, ...)]`"
947        )
948    )]
949    ExpectedFailAllOnIndexed {
950        #[source_code]
951        src: NamedSource<Arc<String>>,
952        #[label("this assertion is indexed")]
953        span: SourceSpan,
954    },
955
956    #[error("duplicate key in `#[expected_fail(...)]`")]
957    #[diagnostic(code(graphcal::A012), help("each expected-fail key must be unique"))]
958    ExpectedFailDuplicateKey {
959        #[source_code]
960        src: NamedSource<Arc<String>>,
961        #[label("duplicate expected-fail key")]
962        span: SourceSpan,
963    },
964
965    #[error("`#[expected_fail(...)]` key has the wrong index shape")]
966    #[diagnostic(
967        code(graphcal::A013),
968        help(
969            "single-index assertions require `Index.Variant` keys; multi-index assertions require full tuple keys in assertion axis order"
970        )
971    )]
972    ExpectedFailKeyShapeMismatch {
973        expected: usize,
974        found: usize,
975        #[source_code]
976        src: NamedSource<Arc<String>>,
977        #[label("expected {expected} index axis/axes, found {found}")]
978        span: SourceSpan,
979    },
980
981    #[error("`#[expected_fail(...)]` key does not belong to the assertion index")]
982    #[diagnostic(
983        code(graphcal::A014),
984        help("expected-fail keys must use the assertion's indexes in axis order")
985    )]
986    ExpectedFailKeyIndexMismatch {
987        expected: String,
988        found: String,
989        #[source_code]
990        src: NamedSource<Arc<String>>,
991        #[label("expected index `{expected}`, found `{found}`")]
992        span: SourceSpan,
993    },
994
995    #[error("`#[expected_fail(...)]` range step `#{step}` is out of bounds")]
996    #[diagnostic(
997        code(graphcal::A016),
998        help(
999            "range steps in expected-fail keys must satisfy `0 <= N < size` for a `range(size)` axis"
1000        )
1001    )]
1002    ExpectedFailRangeStepOutOfBounds {
1003        step: u64,
1004        size: u64,
1005        #[source_code]
1006        src: NamedSource<Arc<String>>,
1007        #[label("step #{step} on an axis of size {size}")]
1008        span: SourceSpan,
1009    },
1010
1011    #[error("negative tolerance in tolerance assertion")]
1012    #[diagnostic(
1013        code(graphcal::A015),
1014        help(
1015            "the tolerance in `~= expected +/- tolerance` must be non-negative; use `0` for exact-match semantics"
1016        )
1017    )]
1018    NegativeTolerance {
1019        found: String,
1020        #[source_code]
1021        src: NamedSource<Arc<String>>,
1022        #[label("tolerance is {found}")]
1023        span: SourceSpan,
1024    },
1025
1026    #[error("import path `{path}` resolves outside the project root")]
1027    #[diagnostic(
1028        code(graphcal::M008),
1029        help(
1030            "imports must reference files within the project directory tree; place a `graphcal.toml` in an ancestor directory to widen the project root"
1031        )
1032    )]
1033    ImportOutsideRoot {
1034        path: String,
1035        #[source_code]
1036        src: NamedSource<Arc<String>>,
1037        #[label("resolves outside project root")]
1038        span: SourceSpan,
1039    },
1040
1041    #[error("cannot override `{name}`: it is a {actual_kind}, not a param")]
1042    #[diagnostic(
1043        code(graphcal::O001),
1044        help("only `param` declarations can be overridden with --set")
1045    )]
1046    OverrideNotAParam {
1047        name: DeclName,
1048        actual_kind: crate::registry::resolve_types::DeclCategory,
1049    },
1050
1051    #[error("unknown parameter `{name}` in --set override")]
1052    #[diagnostic(
1053        code(graphcal::O002),
1054        help("the name must match a `param` declared in the file")
1055    )]
1056    OverrideUnknownParam { name: DeclName },
1057
1058    #[error("required param `{name}` has no value")]
1059    #[diagnostic(
1060        code(graphcal::O003),
1061        help(
1062            "provide a value via `--set '{name}=<value>'`, `--input`, or a parameterized import binding"
1063        )
1064    )]
1065    RequiredParamNotProvided {
1066        name: String,
1067        #[source_code]
1068        src: NamedSource<Arc<String>>,
1069        #[label("declared here without a default value")]
1070        span: SourceSpan,
1071    },
1072
1073    #[error("unknown param `{name}` in import binding for `{file_path}`")]
1074    #[diagnostic(
1075        code(graphcal::M009),
1076        help("param bindings must reference `param` declarations in the imported file")
1077    )]
1078    UnknownParamBinding {
1079        name: String,
1080        file_path: String,
1081        #[source_code]
1082        src: NamedSource<Arc<String>>,
1083        #[label("not a param in the imported file")]
1084        span: SourceSpan,
1085    },
1086
1087    #[error("binding target `{name}` is a {actual_kind}, not a param")]
1088    #[diagnostic(
1089        code(graphcal::M010),
1090        help("only `param` declarations can be overridden in import bindings")
1091    )]
1092    BindingNotAParam {
1093        name: String,
1094        actual_kind: String,
1095        #[source_code]
1096        src: NamedSource<Arc<String>>,
1097        #[label("targets a {actual_kind}, not a param")]
1098        span: SourceSpan,
1099    },
1100
1101    #[error("instantiated import requires an alias or selective names")]
1102    #[diagnostic(
1103        code(graphcal::M011),
1104        help("use `import \"path\"(...) as alias;` or `import \"path\"(...) {{ name1, name2 }};`")
1105    )]
1106    InstantiatedImportNeedsNamespace {
1107        #[source_code]
1108        src: NamedSource<Arc<String>>,
1109        #[label("instantiated import without alias or selective names")]
1110        span: SourceSpan,
1111    },
1112
1113    #[error("bare module import requires a graphcal.toml with [package].name")]
1114    #[diagnostic(
1115        code(graphcal::M012),
1116        help(
1117            "create a graphcal.toml file in the project root with:\n\n[package]\nname = \"your_package_name\""
1118        )
1119    )]
1120    BareImportWithoutManifest {
1121        path: String,
1122        #[source_code]
1123        src: NamedSource<Arc<String>>,
1124        #[label("bare module path requires a manifest")]
1125        span: SourceSpan,
1126    },
1127
1128    #[error("module path starts with `{path_first}` but package name is `{package_name}`")]
1129    #[diagnostic(
1130        code(graphcal::M013),
1131        help("module paths must start with the package name from graphcal.toml")
1132    )]
1133    PackageNameMismatch {
1134        path_first: String,
1135        package_name: String,
1136        #[source_code]
1137        src: NamedSource<Arc<String>>,
1138        #[label("should start with `{package_name}`")]
1139        span: SourceSpan,
1140    },
1141
1142    #[error("standard library modules are not yet implemented")]
1143    #[diagnostic(
1144        code(graphcal::M014),
1145        help(
1146            "the graphcal standard library (graphcal/math, etc.) will be available in a future release"
1147        )
1148    )]
1149    StdlibNotImplemented {
1150        path: String,
1151        #[source_code]
1152        src: NamedSource<Arc<String>>,
1153        #[label("stdlib not yet available")]
1154        span: SourceSpan,
1155    },
1156
1157    #[error("failed to parse graphcal.toml: {message}")]
1158    #[diagnostic(code(graphcal::M015))]
1159    ManifestError { message: String },
1160
1161    #[error("cross-file import `{path}` from a file outside any package")]
1162    #[diagnostic(
1163        code(graphcal::M017),
1164        help(
1165            "a Graphcal file is either part of a real package (lives at `<source_dir>/<package>.gcl` or under `<source_dir>/<package>/`) or a standalone virtual-package script. Standalone files may only reference their own top-level decls (via `import <file_stem>.{{...}};`) or their own inline DAGs. To pull symbols from a sibling file, add a `graphcal.toml` and place this file inside the package's namespace directory."
1166        )
1167    )]
1168    CrossFileImportInVirtualPackage {
1169        path: String,
1170        #[source_code]
1171        src: NamedSource<Arc<String>>,
1172        #[label("not reachable from a virtual-package file")]
1173        span: SourceSpan,
1174    },
1175
1176    #[error("binding target `{name}` is an index, not a param")]
1177    #[diagnostic(
1178        code(graphcal::M016),
1179        help("index bindings must use another index name as the value, e.g., `{name} = MyIndex`")
1180    )]
1181    BindingTargetsIndex {
1182        name: String,
1183        #[source_code]
1184        src: NamedSource<Arc<String>>,
1185        #[label("targets an index, not a param")]
1186        span: SourceSpan,
1187    },
1188
1189    #[error("index binding `{dep_index} = {value}`: `{value}` is not a known index")]
1190    #[diagnostic(
1191        code(graphcal::M017),
1192        help(
1193            "the right-hand side of an index binding must be a `cat` or `range` index declared in the importing file or its transitive imports"
1194        )
1195    )]
1196    IndexBindingNotAnIndex {
1197        dep_index: String,
1198        value: String,
1199        #[source_code]
1200        src: NamedSource<Arc<String>>,
1201        #[label("not a known index")]
1202        span: SourceSpan,
1203    },
1204
1205    #[error("index kind mismatch: `{dep_index}` is {dep_kind} but `{bound_index}` is {bound_kind}")]
1206    #[diagnostic(
1207        code(graphcal::M018),
1208        help(
1209            "named indexes (`cat`) can only be bound to named indexes; range indexes can only be bound to range indexes"
1210        )
1211    )]
1212    IndexKindMismatch {
1213        dep_index: String,
1214        dep_kind: String,
1215        bound_index: String,
1216        bound_kind: String,
1217        #[source_code]
1218        src: NamedSource<Arc<String>>,
1219        #[label("kind mismatch")]
1220        span: SourceSpan,
1221    },
1222
1223    #[error(
1224        "index dimension mismatch: `{dep_index}` requires dimension {expected_dim} but `{bound_index}` has dimension {found_dim}"
1225    )]
1226    #[diagnostic(
1227        code(graphcal::I009),
1228        help("range index bindings must have matching dimensions")
1229    )]
1230    IndexBindingDimensionMismatch {
1231        dep_index: String,
1232        expected_dim: String,
1233        bound_index: String,
1234        found_dim: String,
1235        #[source_code]
1236        src: NamedSource<Arc<String>>,
1237        #[label("dimension mismatch")]
1238        span: SourceSpan,
1239    },
1240
1241    /// A required index was not bound via parameterized import.
1242    ///
1243    /// Required indexes (`index Foo;`, `index Foo: Time;`) must be bound when the
1244    /// file is imported. They cannot be evaluated standalone.
1245    #[error("required index `{name}` must be bound via parameterized include")]
1246    #[diagnostic(
1247        code(graphcal::I010),
1248        help("use `include \"./file.gcl\"({name}: SomeIndex)` to bind this index")
1249    )]
1250    RequiredIndexNotBound {
1251        name: String,
1252        #[source_code]
1253        src: NamedSource<Arc<String>>,
1254        #[label("required index declared here")]
1255        span: SourceSpan,
1256    },
1257
1258    #[error("cannot import runtime item `{name}`; use `include` for runtime nodes and params")]
1259    #[diagnostic(
1260        code(graphcal::M020),
1261        help(
1262            "`import` only allows compile-time items (const, dimension, unit, type, index, dag); use `include` for runtime nodes and params"
1263        )
1264    )]
1265    ImportRuntimeItem {
1266        name: String,
1267        #[source_code]
1268        src: NamedSource<Arc<String>>,
1269        #[label("runtime item cannot be imported")]
1270        span: SourceSpan,
1271    },
1272
1273    // --- Domain constraint errors ---
1274    #[error("unknown timezone `{timezone}`")]
1275    #[diagnostic(
1276        code(graphcal::D007),
1277        help(
1278            "use a valid IANA timezone name like \"UTC\", \"America/New_York\", or \"Asia/Tokyo\""
1279        )
1280    )]
1281    InvalidTimezone {
1282        timezone: String,
1283        #[source_code]
1284        src: NamedSource<Arc<String>>,
1285        #[label("not a recognized IANA timezone")]
1286        span: SourceSpan,
1287    },
1288
1289    #[error("domain violation: `{name}` value {value} is {violation}")]
1290    #[diagnostic(
1291        code(graphcal::C001),
1292        help("the value must satisfy the domain constraints declared on the type")
1293    )]
1294    DomainViolation {
1295        name: String,
1296        value: String,
1297        violation: String,
1298        #[source_code]
1299        src: NamedSource<Arc<String>>,
1300        #[label("value out of declared domain")]
1301        span: SourceSpan,
1302    },
1303
1304    #[error(
1305        "domain bound dimension mismatch on `{name}`: type has dimension {type_dim}, but {bound_name} bound has dimension {bound_dim}"
1306    )]
1307    #[diagnostic(
1308        code(graphcal::C002),
1309        help("domain bounds must have the same dimension as the constrained type")
1310    )]
1311    DomainDimensionMismatch {
1312        name: String,
1313        type_dim: String,
1314        bound_name: String,
1315        bound_dim: String,
1316        #[source_code]
1317        src: NamedSource<Arc<String>>,
1318        #[label("dimension mismatch in domain bound")]
1319        span: SourceSpan,
1320    },
1321
1322    #[error("domain constraint on `{name}`: min ({min}) exceeds max ({max})")]
1323    #[diagnostic(
1324        code(graphcal::C003),
1325        help("the min bound must be less than or equal to the max bound")
1326    )]
1327    DomainMinExceedsMax {
1328        name: String,
1329        min: String,
1330        max: String,
1331        #[source_code]
1332        src: NamedSource<Arc<String>>,
1333        #[label("min > max")]
1334        span: SourceSpan,
1335    },
1336
1337    #[error("domain constraints are not valid on `{type_kind}` types")]
1338    #[diagnostic(
1339        code(graphcal::C004),
1340        help(
1341            "domain constraints (min/max) are only valid on scalar, Dimensionless, and Int types"
1342        )
1343    )]
1344    InvalidDomainTarget {
1345        type_kind: String,
1346        #[source_code]
1347        src: NamedSource<Arc<String>>,
1348        #[label("constraints not valid here")]
1349        span: SourceSpan,
1350    },
1351
1352    #[error(
1353        "domain bound on Int `{name}` must be unitless: {bound_name} bound has type {bound_type}"
1354    )]
1355    #[diagnostic(
1356        code(graphcal::C005),
1357        help("Int values are unitless; their domain bounds must be Int or dimensionless")
1358    )]
1359    IntDomainBoundNotUnitless {
1360        name: String,
1361        bound_name: String,
1362        bound_type: String,
1363        #[source_code]
1364        src: NamedSource<Arc<String>>,
1365        #[label("Int bound is not unitless")]
1366        span: SourceSpan,
1367    },
1368
1369    #[error("domain constraints are not supported on generic type arguments")]
1370    #[diagnostic(
1371        code(graphcal::C006),
1372        help(
1373            "put the constraint on the field in the struct definition, not on the generic type argument"
1374        )
1375    )]
1376    GenericTypeArgDomainConstraint {
1377        #[source_code]
1378        src: NamedSource<Arc<String>>,
1379        #[label("constraint not allowed here")]
1380        span: SourceSpan,
1381    },
1382
1383    // --- Visibility errors ---
1384    /// Attempting to import a private (non-`pub`) item from another file.
1385    #[error("cannot import private item `{name}` from `{file_path}`")]
1386    #[diagnostic(
1387        code(graphcal::V001),
1388        help("add `pub` to the declaration in the source file to make it importable")
1389    )]
1390    ImportPrivateItem {
1391        name: String,
1392        file_path: String,
1393        #[source_code]
1394        src: NamedSource<Arc<String>>,
1395        #[label("not visible — item is private")]
1396        span: SourceSpan,
1397    },
1398
1399    /// A required `index`, `type`, or `dim` is not marked `pub(bind)`.
1400    ///
1401    /// `param` is excluded: required `param` is implicitly bindable (A5);
1402    /// it never carries a visibility annotation.
1403    #[error("required {kind} `{name}` must be declared `pub(bind)`")]
1404    #[diagnostic(
1405        code(graphcal::V002),
1406        help(
1407            "required indexes, types, and dimensions form the bindable interface — add `pub(bind)` before the declaration"
1408        )
1409    )]
1410    RequiredItemMustBeBindable {
1411        kind: String,
1412        name: String,
1413        #[source_code]
1414        src: NamedSource<Arc<String>>,
1415        #[label("required item must be `pub(bind)`")]
1416        span: SourceSpan,
1417    },
1418
1419    /// A visible declaration references a private type-system item in
1420    /// its written signature (A9 case 1).
1421    ///
1422    /// The `pub_kind` string is the declaration kind (e.g. `"param"`,
1423    /// `"pub node"`, `"pub type"`). `param` is always visible (A5 §4.0)
1424    /// and never carries an explicit annotation.
1425    #[error(
1426        "`{pub_kind}` `{pub_name}` references private {ref_kind} `{ref_name}` in its signature"
1427    )]
1428    #[diagnostic(
1429        code(graphcal::V003),
1430        help(
1431            "add `pub` to `{ref_name}` so it is visible across the include boundary, or stop exposing `{pub_name}`"
1432        )
1433    )]
1434    PrivateInPublic {
1435        pub_kind: String,
1436        pub_name: String,
1437        ref_kind: String,
1438        ref_name: String,
1439        #[source_code]
1440        src: NamedSource<Arc<String>>,
1441        #[label("references private `{ref_name}`")]
1442        ref_span: SourceSpan,
1443        #[label("visible declaration is here")]
1444        pub_span: SourceSpan,
1445    },
1446
1447    /// A `pub(bind)` index with concrete variants has its variants used
1448    /// in a non-bindable body (`node` / `const`) or a public sink
1449    /// declaration in the defining file.
1450    ///
1451    /// Per axiom A10(c) / A10(b), a bindable index's variant literals
1452    /// must not appear in bodies that cannot themselves be re-bound by
1453    /// importers (the defining library must abstract over the index).
1454    #[error(
1455        "variant literal `{index}.{variant}` of `pub(bind) index` cannot be used in the defining file"
1456    )]
1457    #[diagnostic(
1458        code(graphcal::V004),
1459        help(
1460            "pub(bind) indexes may be overridden by importers; use `param` declarations for variant-specific values, or abstract over the index via `for p : I {{ … }}`"
1461        )
1462    )]
1463    PubIndexVariantLiteral {
1464        index: String,
1465        variant: String,
1466        #[source_code]
1467        src: NamedSource<Arc<String>>,
1468        #[label("variant literal of pub(bind) index")]
1469        span: SourceSpan,
1470    },
1471
1472    /// An include overrides a bindable symbol `s`, but some kept
1473    /// declaration's body or default mentions a name nominally tied to
1474    /// `s` and was not itself re-bound by the same include statement
1475    /// (A8).
1476    ///
1477    /// Nominally-tied mentions today are: variant literals `s.v` for
1478    /// an overridden `index`, and constructors / field accesses of `s`
1479    /// for an overridden `type`. `dim` and `param` overrides are
1480    /// vacuous for A8 — their substitution is total — so they never
1481    /// trigger this error.
1482    #[error(
1483        "include overrides {overridden_kind} `{overridden}` but does not re-bind `{orphan_decl}`, whose default mentions `{detail}`"
1484    )]
1485    #[diagnostic(
1486        code(graphcal::V005),
1487        help(
1488            "add a binding for `{orphan_decl}` to this include, or keep `{overridden}` bound to its default"
1489        )
1490    )]
1491    IncludeMustReconcileOverride {
1492        overridden: String,
1493        overridden_kind: String,
1494        orphan_decl: String,
1495        detail: String,
1496        #[source_code]
1497        src: NamedSource<Arc<String>>,
1498        #[label("include is missing a binding for `{orphan_decl}`")]
1499        span: SourceSpan,
1500    },
1501
1502    /// A `pub include` / `pub import` (or selective `{ pub items }`)
1503    /// re-exports a declaration whose effective (post-substitution)
1504    /// signature mentions a symbol that is `V = private` at the
1505    /// importing site — A9 case 2 / visibility composition.
1506    ///
1507    /// Concretely: an include binding renames a bindable symbol `s`
1508    /// in the dep to a name that is private at the importer, and the
1509    /// re-exported surface of the include carries that name into the
1510    /// importer's public API. Downstream consumers of the importer
1511    /// would see a signature referring to a symbol they cannot name.
1512    #[error(
1513        "re-exported {reexport_kind} `{reexport_name}`'s signature references private {leaked_kind} `{leaked_name}`"
1514    )]
1515    #[diagnostic(
1516        code(graphcal::V006),
1517        help(
1518            "make `{leaked_name}` `pub` at the importing file, or drop the `pub` / `pub(..)` re-export marker on this include / import"
1519        )
1520    )]
1521    GenericsLeakage {
1522        reexport_kind: String,
1523        reexport_name: String,
1524        leaked_kind: String,
1525        leaked_name: String,
1526        #[source_code]
1527        src: NamedSource<Arc<String>>,
1528        #[label("leaks private `{leaked_name}` across the include boundary")]
1529        span: SourceSpan,
1530    },
1531
1532    #[error("unknown dag `{name}`")]
1533    #[diagnostic(
1534        code(graphcal::G002),
1535        help("the inline call references a dag that is not declared in this file")
1536    )]
1537    UnknownDag {
1538        name: String,
1539        #[source_code]
1540        src: NamedSource<Arc<String>>,
1541        #[label("unknown dag")]
1542        span: SourceSpan,
1543    },
1544
1545    #[error("unknown param `{name}` in inline dag call to `{dag_name}`")]
1546    #[diagnostic(
1547        code(graphcal::G003),
1548        help("the binding name must match a `param` declared in the called dag")
1549    )]
1550    UnknownInlineDagParam {
1551        name: String,
1552        dag_name: String,
1553        #[source_code]
1554        src: NamedSource<Arc<String>>,
1555        #[label("not a param in `{dag_name}`")]
1556        span: SourceSpan,
1557    },
1558
1559    #[error("missing required binding(s) {missing:?} in inline dag call to `{dag_name}`")]
1560    #[diagnostic(
1561        code(graphcal::G004),
1562        help("every `param` declared in the dag must be bound at each inline call site")
1563    )]
1564    MissingInlineDagBindings {
1565        missing: Vec<String>,
1566        dag_name: String,
1567        #[source_code]
1568        src: NamedSource<Arc<String>>,
1569        #[label("missing binding(s)")]
1570        span: SourceSpan,
1571    },
1572
1573    #[error("unknown output `{name}` in inline dag call to `{dag_name}`")]
1574    #[diagnostic(
1575        code(graphcal::G005),
1576        help("the projection after `).` must name a `node` declared in the called dag")
1577    )]
1578    UnknownInlineDagOutput {
1579        name: String,
1580        dag_name: String,
1581        #[source_code]
1582        src: NamedSource<Arc<String>>,
1583        #[label("not a node in `{dag_name}`")]
1584        span: SourceSpan,
1585    },
1586
1587    #[error("inline dag call binding `{param_name}`: expected {expected}, found {found}")]
1588    #[diagnostic(
1589        code(graphcal::G006),
1590        help("the binding expression must have the same type as the dag's param declaration")
1591    )]
1592    InlineDagArgDimensionMismatch {
1593        param_name: String,
1594        expected: String,
1595        found: String,
1596        #[source_code]
1597        src: NamedSource<Arc<String>>,
1598        #[label("type mismatch")]
1599        span: SourceSpan,
1600    },
1601}
1602
1603impl GraphcalError {
1604    /// Return the `NamedSource` embedded in this error, if any.
1605    ///
1606    /// Most variants carry a `#[source_code]` field naming the file and its
1607    /// full source text. Exposing it as a typed accessor lets diagnostic
1608    /// emitters pair the error's offsets with the exact source they index
1609    /// into — instead of inferring (name, source) from external context,
1610    /// which can silently desynchronize when an imported file is the origin.
1611    ///
1612    /// Returns `None` for the handful of variants that represent errors
1613    /// without a source location: file-system errors before parsing
1614    /// ([`Self::FileNotFound`], [`Self::CircularImport`],
1615    /// [`Self::ManifestError`]) and CLI override errors
1616    /// ([`Self::OverrideNotAParam`], [`Self::OverrideUnknownParam`]).
1617    #[must_use]
1618    #[expect(
1619        clippy::too_many_lines,
1620        reason = "exhaustive variant list; one arm per error variant"
1621    )]
1622    pub const fn named_source(&self) -> Option<&NamedSource<Arc<String>>> {
1623        let src = match self {
1624            Self::FileNotFound { .. }
1625            | Self::CircularImport { .. }
1626            | Self::ManifestError { .. }
1627            | Self::OverrideNotAParam { .. }
1628            | Self::OverrideUnknownParam { .. } => return None,
1629            Self::DuplicateName { src, .. }
1630            | Self::BuiltinNameShadowed { src, .. }
1631            | Self::ConflictingImportedUnit { src, .. }
1632            | Self::InvalidPlotProperty { src, .. }
1633            | Self::PlotPropertyTypeMismatch { src, .. }
1634            | Self::PlotPropertyDimensioned { src, .. }
1635            | Self::UnknownPlotReference { src, .. }
1636            | Self::CompositionReferencesNonPlot { src, .. }
1637            | Self::DuplicatePlotReference { src, .. }
1638            | Self::ImportPlotItem { src, .. }
1639            | Self::HiddenIncludeItemNotAPlot { src, .. }
1640            | Self::UnknownGraphRef { src, .. }
1641            | Self::UnknownConstRef { src, .. }
1642            | Self::UnknownFunction { src, .. }
1643            | Self::GraphRefInConst { src, .. }
1644            | Self::GraphRefInConstUnit { src, .. }
1645            | Self::NonConstUnitInConst { src, .. }
1646            | Self::GraphRefInFn { src, .. }
1647            | Self::RecursiveFunction { src, .. }
1648            | Self::WrongArity { src, .. }
1649            | Self::WrongGenericArity { src, .. }
1650            | Self::GenericArgMismatch { src, .. }
1651            | Self::CyclicDependency { src, .. }
1652            | Self::EvalError { src, .. }
1653            | Self::InternalError { src, .. }
1654            | Self::DimensionOverflow { src, .. }
1655            | Self::DimensionMismatch { src, .. }
1656            | Self::IndexedShapeMismatch { src, .. }
1657            | Self::DimensionMismatchInAnnotation { src, .. }
1658            | Self::UnknownUnit { src, .. }
1659            | Self::UnknownDimension { src, .. }
1660            | Self::CyclicDimension { src, .. }
1661            | Self::CyclicUnit { src, .. }
1662            | Self::NonLiteralExponent { src, .. }
1663            | Self::ConversionDimensionMismatch { src, .. }
1664            | Self::NestedConversion { src, .. }
1665            | Self::IneffectiveConversion { src, .. }
1666            | Self::AffineProneUnitDefinition { src, .. }
1667            | Self::UnknownStructType { src, .. }
1668            | Self::UnknownField { src, .. }
1669            | Self::MissingFields { src, .. }
1670            | Self::ExtraFields { src, .. }
1671            | Self::FieldDimensionMismatch { src, .. }
1672            | Self::NotAStruct { src, .. }
1673            | Self::UnknownLocalRef { src, .. }
1674            | Self::UnknownIndex { src, .. }
1675            | Self::UnknownVariant { src, .. }
1676            | Self::MissingVariants { src, .. }
1677            | Self::ExtraVariants { src, .. }
1678            | Self::IndexMismatch { src, .. }
1679            | Self::ImportFileNotFound { src, .. }
1680            | Self::ImportNameNotFound { src, .. }
1681            | Self::InvalidModuleName { src, .. }
1682            | Self::DuplicateModuleName { src, .. }
1683            | Self::UnknownModule { src, .. }
1684            | Self::QualifiedNameNotFound { src, .. }
1685            | Self::RangeIndexDimensionMismatch { src, .. }
1686            | Self::RangeIndexInvalid { src, .. }
1687            | Self::GraphRefToAssert { src, .. }
1688            | Self::AssertBodyNotBool { src, .. }
1689            | Self::AssumedAssertionFailed { src, .. }
1690            | Self::UnknownAssertInAssumes { src, .. }
1691            | Self::InvalidAssumesTarget { src, .. }
1692            | Self::InvalidHiddenTarget { src, .. }
1693            | Self::UnknownAttribute { src, .. }
1694            | Self::InvalidExpectedFailTarget { src, .. }
1695            | Self::ExpectedFailInvalidArg { src, .. }
1696            | Self::ExpectedFailNotIndexed { src, .. }
1697            | Self::ExpectedFailAllOnIndexed { src, .. }
1698            | Self::ExpectedFailDuplicateKey { src, .. }
1699            | Self::ExpectedFailKeyShapeMismatch { src, .. }
1700            | Self::ExpectedFailKeyIndexMismatch { src, .. }
1701            | Self::ExpectedFailRangeStepOutOfBounds { src, .. }
1702            | Self::NegativeTolerance { src, .. }
1703            | Self::ImportOutsideRoot { src, .. }
1704            | Self::RequiredParamNotProvided { src, .. }
1705            | Self::UnknownParamBinding { src, .. }
1706            | Self::BindingNotAParam { src, .. }
1707            | Self::InstantiatedImportNeedsNamespace { src, .. }
1708            | Self::BareImportWithoutManifest { src, .. }
1709            | Self::PackageNameMismatch { src, .. }
1710            | Self::StdlibNotImplemented { src, .. }
1711            | Self::CrossFileImportInVirtualPackage { src, .. }
1712            | Self::BindingTargetsIndex { src, .. }
1713            | Self::IndexBindingNotAnIndex { src, .. }
1714            | Self::IndexKindMismatch { src, .. }
1715            | Self::IndexBindingDimensionMismatch { src, .. }
1716            | Self::RequiredIndexNotBound { src, .. }
1717            | Self::ImportRuntimeItem { src, .. }
1718            | Self::InvalidTimezone { src, .. }
1719            | Self::DomainViolation { src, .. }
1720            | Self::DomainDimensionMismatch { src, .. }
1721            | Self::DomainMinExceedsMax { src, .. }
1722            | Self::InvalidDomainTarget { src, .. }
1723            | Self::IntDomainBoundNotUnitless { src, .. }
1724            | Self::GenericTypeArgDomainConstraint { src, .. }
1725            | Self::ImportPrivateItem { src, .. }
1726            | Self::RequiredItemMustBeBindable { src, .. }
1727            | Self::PrivateInPublic { src, .. }
1728            | Self::PubIndexVariantLiteral { src, .. }
1729            | Self::IncludeMustReconcileOverride { src, .. }
1730            | Self::GenericsLeakage { src, .. }
1731            | Self::UnknownDag { src, .. }
1732            | Self::UnknownInlineDagParam { src, .. }
1733            | Self::MissingInlineDagBindings { src, .. }
1734            | Self::UnknownInlineDagOutput { src, .. }
1735            | Self::InlineDagArgDimensionMismatch { src, .. } => src,
1736        };
1737        Some(src)
1738    }
1739}