Skip to main content

gruel_util/
error.rs

1//! Error types and compiler diagnostics.
2//!
3//! Provides the error infrastructure used throughout the compilation pipeline.
4//! Errors carry source location information for diagnostic rendering.
5//!
6//! # Diagnostic System
7//!
8//! Errors and warnings can include rich diagnostic information:
9//! - **Labels**: Secondary spans pointing to related code locations
10//! - **Notes**: Informational context about the error/warning
11//! - **Helps**: Actionable suggestions for fixing the issue
12
13use crate::span::Span;
14use rustc_hash::FxHashSet as HashSet;
15use std::borrow::Cow;
16use std::fmt;
17use thiserror::Error;
18
19// ============================================================================
20// Error Codes
21// ============================================================================
22//
23// Every error kind has a unique, stable error code for searchability.
24// Codes are assigned by category and must never change once assigned.
25// See issue gruel-0c9y for the design rationale.
26
27/// A unique error code for each error type.
28///
29/// Error codes are formatted as `E` followed by a 4-digit zero-padded number
30/// (e.g., `E0001`, `E0042`). They are assigned by category:
31///
32/// - **E0001-E0099**: Lexer errors (tokenization)
33/// - **E0100-E0199**: Parser errors (syntax)
34/// - **E0200-E0399**: Semantic errors (types, names, scopes)
35/// - **E0400-E0499**: Struct/enum errors
36/// - **E0500-E0599**: Control flow errors
37/// - **E0600-E0699**: Match errors
38/// - **E0700-E0799**: Intrinsic errors
39/// - **E0800-E0899**: Literal/operator errors
40/// - **E0900-E0999**: Array errors
41/// - **E1000-E1099**: Linker/target errors
42/// - **E1100-E1199**: Preview feature errors
43/// - **E9000-E9999**: Internal compiler errors
44///
45/// Once assigned, error codes must never change to maintain stability for
46/// documentation, search engines, and user bookmarks.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub struct ErrorCode(pub u16);
49
50impl ErrorCode {
51    // ========================================================================
52    // Lexer errors (E0001-E0099)
53    // ========================================================================
54    pub const UNEXPECTED_CHARACTER: Self = Self(1);
55    pub const INVALID_INTEGER: Self = Self(2);
56    pub const INVALID_STRING_ESCAPE: Self = Self(3);
57    pub const UNTERMINATED_STRING: Self = Self(4);
58    pub const INVALID_FLOAT: Self = Self(5);
59    pub const EMPTY_CHAR_LIT: Self = Self(6);
60    pub const UNTERMINATED_CHAR_LIT: Self = Self(7);
61    pub const MULTI_CHAR_LIT: Self = Self(8);
62    pub const INVALID_CHAR_ESCAPE: Self = Self(9);
63    pub const INVALID_UNICODE_ESCAPE: Self = Self(10);
64
65    // ========================================================================
66    // Parser errors (E0100-E0199)
67    // ========================================================================
68    pub const UNEXPECTED_TOKEN: Self = Self(100);
69    pub const UNEXPECTED_EOF: Self = Self(101);
70    pub const PARSE_ERROR: Self = Self(102);
71
72    // ========================================================================
73    // Semantic errors (E0200-E0399)
74    // ========================================================================
75    pub const NO_MAIN_FUNCTION: Self = Self(200);
76    pub const UNDEFINED_VARIABLE: Self = Self(201);
77    pub const UNDEFINED_FUNCTION: Self = Self(202);
78    pub const ASSIGN_TO_IMMUTABLE: Self = Self(203);
79    pub const UNKNOWN_TYPE: Self = Self(204);
80    pub const USE_AFTER_MOVE: Self = Self(205);
81    pub const TYPE_MISMATCH: Self = Self(206);
82    pub const WRONG_ARGUMENT_COUNT: Self = Self(207);
83
84    // ========================================================================
85    // Struct/enum errors (E0400-E0499)
86    // ========================================================================
87    pub const MISSING_FIELDS: Self = Self(400);
88    pub const UNKNOWN_FIELD: Self = Self(401);
89    pub const DUPLICATE_FIELD: Self = Self(402);
90    pub const COPY_STRUCT_NON_COPY_FIELD: Self = Self(403);
91    pub const RESERVED_TYPE_NAME: Self = Self(404);
92    pub const DUPLICATE_TYPE_DEFINITION: Self = Self(405);
93    pub const LINEAR_VALUE_NOT_CONSUMED: Self = Self(406);
94    pub const LINEAR_STRUCT_COPY: Self = Self(407);
95    pub const DUPLICATE_METHOD: Self = Self(410);
96    pub const DERIVE_DIRECT_FIELD_ACCESS: Self = Self(440);
97    pub const DERIVE_NOT_A_DERIVE: Self = Self(441);
98    pub const UNKNOWN_DIRECTIVE: Self = Self(442);
99    pub const UNDEFINED_METHOD: Self = Self(411);
100    pub const PRIVATE_FIELD: Self = Self(443);
101    pub const UNDEFINED_ASSOC_FN: Self = Self(412);
102    pub const METHOD_CALL_ON_NON_STRUCT: Self = Self(413);
103    pub const METHOD_CALLED_AS_ASSOC_FN: Self = Self(414);
104    pub const ASSOC_FN_CALLED_AS_METHOD: Self = Self(415);
105    pub const DUPLICATE_DESTRUCTOR: Self = Self(416);
106    pub const DESTRUCTOR_UNKNOWN_TYPE: Self = Self(417);
107    pub const DUPLICATE_CONSTANT: Self = Self(418);
108    pub const CONST_EXPR_NOT_SUPPORTED: Self = Self(434);
109    pub const DUPLICATE_VARIANT: Self = Self(419);
110    pub const UNKNOWN_VARIANT: Self = Self(420);
111    pub const UNKNOWN_ENUM_TYPE: Self = Self(421);
112    pub const FIELD_WRONG_ORDER: Self = Self(422);
113    pub const FIELD_ACCESS_ON_NON_STRUCT: Self = Self(423);
114    pub const INVALID_ASSIGNMENT_TARGET: Self = Self(424);
115    pub const INOUT_NON_LVALUE: Self = Self(425);
116    pub const INOUT_EXCLUSIVE_ACCESS: Self = Self(426);
117    pub const BORROW_NON_LVALUE: Self = Self(427);
118    pub const MUTATE_BORROWED_VALUE: Self = Self(428);
119    pub const MOVE_OUT_OF_BORROW: Self = Self(429);
120    pub const BORROW_INOUT_CONFLICT: Self = Self(430);
121    pub const INOUT_KEYWORD_MISSING: Self = Self(431);
122    pub const BORROW_KEYWORD_MISSING: Self = Self(432);
123    pub const EMPTY_STRUCT: Self = Self(433);
124    pub const REFERENCE_ESCAPES_FUNCTION: Self = Self(434);
125
126    // ========================================================================
127    // Control flow errors (E0500-E0599)
128    // ========================================================================
129    pub const BREAK_OUTSIDE_LOOP: Self = Self(500);
130    pub const CONTINUE_OUTSIDE_LOOP: Self = Self(501);
131    pub const INTRINSIC_REQUIRES_CHECKED: Self = Self(502);
132    pub const UNCHECKED_CALL_REQUIRES_CHECKED: Self = Self(503);
133    /// ADR-0088: `@mark(unchecked) fn __drop` rejected.
134    pub const UNCHECKED_DESTRUCTOR: Self = Self(504);
135    /// ADR-0088: extern fn lacks `@mark(unchecked)`.
136    pub const EXTERN_FN_MISSING_UNCHECKED: Self = Self(505);
137    /// ADR-0088: interface method `is_unchecked` mismatch.
138    pub const INTERFACE_METHOD_UNCHECKED_MISMATCH: Self = Self(506);
139
140    // ========================================================================
141    // Match errors (E0600-E0699)
142    // ========================================================================
143    pub const NON_EXHAUSTIVE_MATCH: Self = Self(600);
144    pub const EMPTY_MATCH: Self = Self(601);
145    pub const INVALID_MATCH_TYPE: Self = Self(602);
146
147    // ========================================================================
148    // Intrinsic errors (E0700-E0799)
149    // ========================================================================
150    pub const UNKNOWN_INTRINSIC: Self = Self(700);
151    pub const INTRINSIC_WRONG_ARG_COUNT: Self = Self(701);
152    pub const INTRINSIC_TYPE_MISMATCH: Self = Self(702);
153    pub const IMPORT_REQUIRES_STRING_LITERAL: Self = Self(703);
154    pub const MODULE_NOT_FOUND: Self = Self(704);
155    pub const STD_LIB_NOT_FOUND: Self = Self(705);
156    pub const PRIVATE_MEMBER_ACCESS: Self = Self(706);
157    pub const UNKNOWN_MODULE_MEMBER: Self = Self(707);
158
159    // ========================================================================
160    // Literal/operator errors (E0800-E0899)
161    // ========================================================================
162    pub const LITERAL_OUT_OF_RANGE: Self = Self(800);
163    pub const CANNOT_NEGATE_UNSIGNED: Self = Self(801);
164    pub const CHAINED_COMPARISON: Self = Self(802);
165
166    // ========================================================================
167    // Array errors (E0900-E0999)
168    // ========================================================================
169    pub const INDEX_ON_NON_ARRAY: Self = Self(900);
170    pub const ARRAY_LENGTH_MISMATCH: Self = Self(901);
171    pub const INDEX_OUT_OF_BOUNDS: Self = Self(902);
172    pub const TYPE_ANNOTATION_REQUIRED: Self = Self(903);
173    pub const MOVE_OUT_OF_INDEX: Self = Self(904);
174
175    // ========================================================================
176    // Linker/target errors (E1000-E1099)
177    // ========================================================================
178    pub const LINK_ERROR: Self = Self(1000);
179    pub const UNSUPPORTED_TARGET: Self = Self(1001);
180
181    // ========================================================================
182    // Preview feature errors (E1100-E1199)
183    // ========================================================================
184    pub const PREVIEW_FEATURE_REQUIRED: Self = Self(1100);
185
186    // ========================================================================
187    // C FFI errors (ADR-0085) (E1500-E1599)
188    // ========================================================================
189    /// ADR-0085: any C-FFI-related diagnostic without a dedicated code.
190    pub const C_FFI: Self = Self(1500);
191
192    // ========================================================================
193    // Interface conformance errors (ADR-0056) (E1400-E1499)
194    // ========================================================================
195    pub const INTERFACE_METHOD_MISSING: Self = Self(1400);
196    pub const INTERFACE_METHOD_SIGNATURE_MISMATCH: Self = Self(1401);
197
198    // ========================================================================
199    // Pattern errors (E1300-E1399)
200    // ========================================================================
201    /// Refutable pattern used in let binding (ADR-0049).
202    pub const REFUTABLE_PATTERN_IN_LET: Self = Self(1300);
203
204    // ========================================================================
205    // Comptime errors (E1200-E1299)
206    // ========================================================================
207    pub const COMPTIME_EVALUATION_FAILED: Self = Self(1200);
208    pub const COMPTIME_ARG_NOT_CONST: Self = Self(1201);
209    pub const COMPTIME_USER_ERROR: Self = Self(1202);
210
211    // ========================================================================
212    // Lang items (E1300-E1399) - ADR-0079
213    // ========================================================================
214    pub const INVALID_LANG_ITEM: Self = Self(1300);
215
216    // ========================================================================
217    // Internal compiler errors (E9000-E9999)
218    // ========================================================================
219    pub const INTERNAL_ERROR: Self = Self(9000);
220    pub const INTERNAL_CODEGEN_ERROR: Self = Self(9001);
221}
222
223impl fmt::Display for ErrorCode {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        write!(f, "E{:04}", self.0)
226    }
227}
228
229// ============================================================================
230// Boxed Error Payloads
231// ============================================================================
232//
233// # Boxing Policy
234//
235// Large error variants are boxed to reduce the size of ErrorKind.
236// This keeps Result<T, CompileError> smaller on the stack.
237// Errors are cold paths, so the extra indirection is acceptable.
238//
239// ## When to Box
240//
241// Box error payloads when the variant data is **≥ 72 bytes** (3 or more Strings).
242//
243// Basic sizes on 64-bit systems:
244// - String: 24 bytes
245// - Vec<T>: 24 bytes
246// - Box<T>: 8 bytes (pointer)
247// - Cow<'static, str>: 24 bytes
248//
249// Examples:
250// - 1 String: 24 bytes → inline
251// - 2 Strings: 48 bytes → inline
252// - 3 Strings: 72 bytes → **box**
253// - String + Vec<String>: 48 bytes → inline (unless Vec typically large)
254//
255// ## Pattern
256//
257// Use a dedicated struct for boxed payloads:
258//
259// ```rust
260// #[derive(Debug, Clone, PartialEq, Eq)]
261// pub struct LargeErrorPayload {
262//     pub field1: String,
263//     pub field2: String,
264//     pub field3: String,
265// }
266//
267// #[error("message")]
268// LargeError(Box<LargeErrorPayload>),
269// ```
270//
271// ## Current Status
272//
273// As of 2026-05-05 (post-ADR-0079):
274// - ErrorKind size: 56 bytes
275// - Boxed variants: 3 (MissingFields, IntrinsicTypeMismatch,
276//   FieldWrongOrder). The previous `CopyStructNonCopyField` was
277//   retired when the field-Copy check moved into the prelude
278//   `derive Copy` body via `comptime if` + `@compile_error`.
279// - All boxed variants contain 3+ Strings or String + Vec
280// - Policy is consistently applied
281
282/// Payload for `ErrorKind::MissingFields`.
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct MissingFieldsError {
285    pub struct_name: String,
286    pub missing_fields: Vec<String>,
287}
288
289/// Payload for `ErrorKind::CloneStructNonCopyField` (ADR-0065).
290#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct CloneStructNonCopyFieldError {
292    pub struct_name: String,
293    pub field_name: String,
294    pub field_type: String,
295}
296
297/// Payload for `ErrorKind::PostureMismatch` (ADR-0080).
298///
299/// Reports that the declared posture (`copy` / unmarked / `linear`) of a
300/// type is inconsistent with one of its members' posture. `host_kind` is
301/// `"struct"` or `"enum"`; `host_name` is the type name (or
302/// `"<anonymous>"` for an anonymous literal). `member_kind` is `"field"`
303/// for structs or `"variant"`/`"variant field"` for enums. `member_name`
304/// names the offending member; `member_type` names its type;
305/// `member_posture` is the offending posture (`"affine"` / `"linear"`).
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct PostureMismatchError {
308    pub host_kind: &'static str,
309    pub host_name: String,
310    pub declared_posture: &'static str,
311    pub member_kind: &'static str,
312    pub member_name: String,
313    pub member_type: String,
314    pub member_posture: &'static str,
315}
316
317/// Payload for `ErrorKind::IntrinsicTypeMismatch`.
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct IntrinsicTypeMismatchError {
320    pub name: String,
321    pub expected: String,
322    pub found: String,
323}
324
325/// Payload for `ErrorKind::FieldWrongOrder`.
326#[derive(Debug, Clone, PartialEq, Eq)]
327pub struct FieldWrongOrderError {
328    pub struct_name: String,
329    pub expected_field: String,
330    pub found_field: String,
331}
332
333/// Payload for `ErrorKind::InterfaceMethodUncheckedMismatch` (ADR-0088).
334///
335/// Three strings plus two bools push the inline variant over the
336/// 64-byte `ErrorKind` budget, so the body is boxed.
337#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct InterfaceMethodUncheckedMismatchError {
339    pub type_name: String,
340    pub interface_name: String,
341    pub method_name: String,
342    pub expected_unchecked: bool,
343    pub actual_unchecked: bool,
344}
345
346// ============================================================================
347// Preview Features
348// ============================================================================
349
350/// A preview feature that can be enabled with `--preview`.
351///
352/// Preview features are in-progress language additions that:
353/// - May change or be removed before stabilization
354/// - Require explicit opt-in via `--preview <feature>`
355/// - Allow incremental implementation to be merged to main
356///
357/// See ADR-0005 for the full design.
358///
359/// When all preview features are stabilized, this enum may be empty.
360/// New preview features are added here as development begins.
361#[derive(
362    Debug,
363    Clone,
364    Copy,
365    PartialEq,
366    Eq,
367    Hash,
368    strum::Display,
369    strum::EnumString,
370    strum::EnumIter,
371    strum::IntoStaticStr,
372)]
373#[non_exhaustive]
374#[strum(serialize_all = "snake_case")]
375pub enum PreviewFeature {
376    /// Testing infrastructure feature - permanently unstable.
377    /// Used to verify the preview feature gating mechanism works.
378    TestInfra,
379}
380
381/// Boxed payload for [`ErrorKind::InterfaceMethodMissing`] (ADR-0056).
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub struct InterfaceMethodMissingData {
384    pub type_name: String,
385    pub interface_name: String,
386    pub method_name: String,
387    pub expected_signature: String,
388}
389
390/// Boxed payload for [`ErrorKind::InterfaceMethodSignatureMismatch`] (ADR-0056).
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct InterfaceMethodSignatureMismatchData {
393    pub type_name: String,
394    pub interface_name: String,
395    pub method_name: String,
396    pub expected_signature: String,
397    pub found_signature: String,
398}
399
400impl PreviewFeature {
401    /// Get the CLI name for this feature (used with `--preview`).
402    pub fn name(&self) -> &'static str {
403        (*self).into()
404    }
405
406    /// Get the ADR number documenting this feature.
407    pub fn adr(&self) -> &'static str {
408        match *self {
409            PreviewFeature::TestInfra => "ADR-0005",
410        }
411    }
412
413    /// Get all available preview features.
414    pub fn all() -> Vec<PreviewFeature> {
415        use strum::IntoEnumIterator;
416        PreviewFeature::iter().collect()
417    }
418
419    /// Get a comma-separated list of all feature names (for help text).
420    pub fn all_names() -> String {
421        let names: Vec<&'static str> = Self::all().iter().map(|f| f.name()).collect();
422        if names.is_empty() {
423            "(none)".to_string()
424        } else {
425            names.join(", ")
426        }
427    }
428}
429
430/// A set of enabled preview features.
431pub type PreviewFeatures = HashSet<PreviewFeature>;
432
433// ============================================================================
434// Diagnostic Types
435// ============================================================================
436
437/// A secondary label pointing to related code.
438///
439/// Labels appear as additional annotations in the source snippet,
440/// helping users understand the relationship between different parts of code.
441#[derive(Debug, Clone)]
442pub struct Label {
443    /// The message explaining this location's relevance.
444    pub message: String,
445    /// The source location to highlight.
446    pub span: Span,
447}
448
449impl Label {
450    /// Create a new label with a message and span.
451    pub fn new(message: impl Into<String>, span: Span) -> Self {
452        Self {
453            message: message.into(),
454            span,
455        }
456    }
457}
458
459/// An informational note providing context.
460///
461/// Notes appear as footer messages and explain why something happened
462/// or provide additional context about the diagnostic.
463#[derive(Debug, Clone)]
464pub struct Note(pub String);
465
466impl Note {
467    /// Create a new note.
468    pub fn new(message: impl Into<String>) -> Self {
469        Self(message.into())
470    }
471}
472
473impl std::fmt::Display for Note {
474    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475        write!(f, "{}", self.0)
476    }
477}
478
479/// An actionable help suggestion.
480///
481/// Helps appear as footer messages and suggest specific actions
482/// the user can take to resolve the issue.
483#[derive(Debug, Clone)]
484pub struct Help(pub String);
485
486impl Help {
487    /// Create a new help suggestion.
488    pub fn new(message: impl Into<String>) -> Self {
489        Self(message.into())
490    }
491}
492
493impl std::fmt::Display for Help {
494    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495        write!(f, "{}", self.0)
496    }
497}
498
499/// How confident we are that a suggested fix is correct.
500///
501/// This follows rustc's conventions for suggestion applicability levels.
502/// IDEs and tools can use this to decide whether to auto-apply suggestions.
503#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
504pub enum Applicability {
505    /// The suggestion is definitely correct and can be safely auto-applied.
506    ///
507    /// Use this when the fix is guaranteed to compile and preserve semantics.
508    MachineApplicable,
509
510    /// The suggestion might be correct but should be reviewed by a human.
511    ///
512    /// Use this when the fix will likely work but may change behavior in
513    /// edge cases, or when there are multiple equally valid options.
514    MaybeIncorrect,
515
516    /// The suggestion contains placeholders that the user must fill in.
517    ///
518    /// Use this when the fix shows the general shape but needs specific
519    /// values like variable names or types.
520    HasPlaceholders,
521
522    /// The suggestion is just a hint and may not even compile.
523    ///
524    /// Use this for illustrative suggestions that show concepts rather
525    /// than working code.
526    #[default]
527    Unspecified,
528}
529
530impl std::fmt::Display for Applicability {
531    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532        match self {
533            Applicability::MachineApplicable => write!(f, "MachineApplicable"),
534            Applicability::MaybeIncorrect => write!(f, "MaybeIncorrect"),
535            Applicability::HasPlaceholders => write!(f, "HasPlaceholders"),
536            Applicability::Unspecified => write!(f, "Unspecified"),
537        }
538    }
539}
540
541/// A suggested code fix that can be applied to resolve a diagnostic.
542///
543/// Suggestions provide machine-readable fix information that IDEs and
544/// tools can use to offer quick-fix actions.
545#[derive(Debug, Clone)]
546pub struct Suggestion {
547    /// Human-readable description of what the suggestion does.
548    pub message: String,
549    /// The span of code to replace.
550    pub span: Span,
551    /// The replacement text.
552    pub replacement: String,
553    /// How confident we are that this fix is correct.
554    pub applicability: Applicability,
555}
556
557impl Suggestion {
558    /// Create a new suggestion with unspecified applicability.
559    pub fn new(message: impl Into<String>, span: Span, replacement: impl Into<String>) -> Self {
560        Self {
561            message: message.into(),
562            span,
563            replacement: replacement.into(),
564            applicability: Applicability::Unspecified,
565        }
566    }
567
568    /// Create a suggestion that is safe to auto-apply.
569    pub fn machine_applicable(
570        message: impl Into<String>,
571        span: Span,
572        replacement: impl Into<String>,
573    ) -> Self {
574        Self {
575            message: message.into(),
576            span,
577            replacement: replacement.into(),
578            applicability: Applicability::MachineApplicable,
579        }
580    }
581
582    /// Create a suggestion that may need human review.
583    pub fn maybe_incorrect(
584        message: impl Into<String>,
585        span: Span,
586        replacement: impl Into<String>,
587    ) -> Self {
588        Self {
589            message: message.into(),
590            span,
591            replacement: replacement.into(),
592            applicability: Applicability::MaybeIncorrect,
593        }
594    }
595
596    /// Create a suggestion with placeholders.
597    pub fn with_placeholders(
598        message: impl Into<String>,
599        span: Span,
600        replacement: impl Into<String>,
601    ) -> Self {
602        Self {
603            message: message.into(),
604            span,
605            replacement: replacement.into(),
606            applicability: Applicability::HasPlaceholders,
607        }
608    }
609
610    /// Set the applicability of this suggestion.
611    pub fn with_applicability(mut self, applicability: Applicability) -> Self {
612        self.applicability = applicability;
613        self
614    }
615}
616
617/// Rich diagnostic information for errors and warnings.
618///
619/// This struct collects all supplementary information that can be
620/// attached to a diagnostic message.
621#[derive(Debug, Clone, Default)]
622pub struct Diagnostic {
623    /// Secondary labels pointing to related code locations.
624    pub labels: Vec<Label>,
625    /// Informational notes providing context.
626    pub notes: Vec<Note>,
627    /// Actionable help suggestions.
628    pub helps: Vec<Help>,
629    /// Code suggestions that can be applied to fix the issue.
630    pub suggestions: Vec<Suggestion>,
631}
632
633impl Diagnostic {
634    /// Create an empty diagnostic.
635    pub fn new() -> Self {
636        Self::default()
637    }
638
639    /// Check if this diagnostic has any content.
640    pub fn is_empty(&self) -> bool {
641        self.labels.is_empty()
642            && self.notes.is_empty()
643            && self.helps.is_empty()
644            && self.suggestions.is_empty()
645    }
646}
647
648// ============================================================================
649// Generic Diagnostic Wrapper
650// ============================================================================
651
652/// A compilation diagnostic (error or warning) with optional source location.
653///
654/// This is a generic wrapper that holds a diagnostic kind along with optional
655/// source location and rich diagnostic information (labels, notes, helps).
656///
657/// Use the type aliases [`CompileError`] and [`CompileWarning`] for the
658/// specific error and warning types.
659///
660/// Diagnostics can include rich information using the builder methods:
661/// ```ignore
662/// CompileError::new(ErrorKind::TypeMismatch { ... }, span)
663///     .with_label("expected because of this", other_span)
664///     .with_note("types must match exactly")
665///     .with_help("consider adding a type conversion")
666/// ```
667#[derive(Debug, Clone)]
668#[must_use = "compiler diagnostics should not be ignored"]
669pub struct DiagnosticWrapper<K> {
670    /// The specific kind of diagnostic.
671    pub kind: K,
672    span: Option<Span>,
673    diagnostic: Box<Diagnostic>,
674}
675
676impl<K> DiagnosticWrapper<K> {
677    /// Create a new diagnostic with the given kind and span.
678    #[inline]
679    pub fn new(kind: K, span: Span) -> Self {
680        Self {
681            kind,
682            span: Some(span),
683            diagnostic: Box::new(Diagnostic::new()),
684        }
685    }
686
687    /// Create a diagnostic without a source location.
688    ///
689    /// Use this for diagnostics that don't correspond to a specific source
690    /// location, such as "no main function found" or linker errors.
691    #[inline]
692    pub fn without_span(kind: K) -> Self {
693        Self {
694            kind,
695            span: None,
696            diagnostic: Box::new(Diagnostic::new()),
697        }
698    }
699
700    /// Returns true if this diagnostic has source location information.
701    #[inline]
702    pub fn has_span(&self) -> bool {
703        self.span.is_some()
704    }
705
706    /// Get the span, if present.
707    #[inline]
708    pub fn span(&self) -> Option<Span> {
709        self.span
710    }
711
712    /// Get the diagnostic information.
713    #[inline]
714    pub fn diagnostic(&self) -> &Diagnostic {
715        &self.diagnostic
716    }
717
718    /// Add a secondary label pointing to related code.
719    ///
720    /// Labels appear as additional annotations in the source snippet.
721    #[inline]
722    pub fn with_label(mut self, message: impl Into<String>, span: Span) -> Self {
723        self.diagnostic.labels.push(Label::new(message, span));
724        self
725    }
726
727    /// Add an informational note.
728    ///
729    /// Notes appear as footer messages providing context.
730    #[inline]
731    pub fn with_note(mut self, message: impl Into<String>) -> Self {
732        self.diagnostic.notes.push(Note::new(message));
733        self
734    }
735
736    /// Add a help suggestion.
737    ///
738    /// Helps appear as footer messages with actionable advice.
739    #[inline]
740    pub fn with_help(mut self, message: impl Into<String>) -> Self {
741        self.diagnostic.helps.push(Help::new(message));
742        self
743    }
744
745    /// Add a code suggestion that can be applied to fix the issue.
746    ///
747    /// Suggestions provide machine-readable fix information for IDEs and tools.
748    #[inline]
749    pub fn with_suggestion(mut self, suggestion: Suggestion) -> Self {
750        self.diagnostic.suggestions.push(suggestion);
751        self
752    }
753}
754
755impl<K: fmt::Display> fmt::Display for DiagnosticWrapper<K> {
756    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
757        write!(f, "{}", self.kind)
758    }
759}
760
761impl<K: fmt::Display + fmt::Debug> std::error::Error for DiagnosticWrapper<K> {}
762
763// ============================================================================
764// Compile Errors
765// ============================================================================
766
767/// A compilation error with optional source location information.
768///
769/// Some errors (like `NoMainFunction` or `LinkError`) don't have a meaningful
770/// source location. Use `has_span()` to check before rendering location info.
771///
772/// Errors can include rich diagnostic information using the builder methods:
773/// ```ignore
774/// CompileError::new(ErrorKind::TypeMismatch { ... }, span)
775///     .with_label("expected because of this", other_span)
776///     .with_note("types must match exactly")
777///     .with_help("consider adding a type conversion")
778/// ```
779pub type CompileError = DiagnosticWrapper<ErrorKind>;
780
781// Helper functions for complex error formatting in thiserror attributes
782
783fn format_argument_count(expected: usize, found: usize) -> String {
784    if expected == 1 {
785        format!("expected {} argument, found {}", expected, found)
786    } else {
787        format!("expected {} arguments, found {}", expected, found)
788    }
789}
790
791fn format_missing_witnesses(missing: &[String]) -> String {
792    if missing.is_empty() {
793        return String::new();
794    }
795    let list = missing
796        .iter()
797        .map(|w| format!("`{}`", w))
798        .collect::<Vec<_>>()
799        .join(", ");
800    if missing.len() == 1 {
801        format!(": pattern {} not covered", list)
802    } else {
803        format!(": patterns {} not covered", list)
804    }
805}
806
807fn format_missing_fields(err: &MissingFieldsError) -> String {
808    if err.missing_fields.len() == 1 {
809        format!(
810            "missing field '{}' in struct '{}'",
811            err.missing_fields[0], err.struct_name
812        )
813    } else {
814        let fields = err
815            .missing_fields
816            .iter()
817            .map(|f| format!("'{}'", f))
818            .collect::<Vec<_>>()
819            .join(", ");
820        format!("missing fields {} in struct '{}'", fields, err.struct_name)
821    }
822}
823
824fn format_intrinsic_arg_count(name: &str, expected: usize, found: usize) -> String {
825    if expected == 1 {
826        format!(
827            "intrinsic '@{}' expects {} argument, found {}",
828            name, expected, found
829        )
830    } else {
831        format!(
832            "intrinsic '@{}' expects {} arguments, found {}",
833            name, expected, found
834        )
835    }
836}
837
838fn format_array_length_mismatch(expected: u64, found: u64) -> String {
839    if expected == 1 {
840        format!(
841            "expected array of {} element, found {} elements",
842            expected, found
843        )
844    } else {
845        format!(
846            "expected array of {} elements, found {} elements",
847            expected, found
848        )
849    }
850}
851
852/// The kind of compilation error.
853#[derive(Debug, Clone, PartialEq, Eq, Error)]
854pub enum ErrorKind {
855    // Lexer errors
856    #[error("unexpected character: {0}")]
857    UnexpectedCharacter(char),
858    #[error("invalid integer literal")]
859    InvalidInteger,
860    #[error("invalid floating-point literal")]
861    InvalidFloat,
862    #[error("invalid escape sequence: \\{0}")]
863    InvalidStringEscape(char),
864    #[error("unterminated string literal")]
865    UnterminatedString,
866    /// ADR-0071: empty char literal (`''`).
867    #[error("empty char literal")]
868    EmptyCharLit,
869    /// ADR-0071: char literal not closed before end of line or end of file.
870    #[error("unterminated char literal")]
871    UnterminatedCharLit,
872    /// ADR-0071: char literal contains more than one Unicode scalar.
873    #[error("char literal must contain exactly one Unicode scalar value")]
874    MultiCharLit,
875    /// ADR-0071: invalid escape sequence inside a char literal.
876    #[error("invalid char escape sequence")]
877    InvalidCharEscape,
878    /// ADR-0071: `\u{...}` malformed or value not a valid Unicode scalar.
879    #[error("invalid unicode escape: not a valid Unicode scalar value")]
880    InvalidUnicodeEscape,
881
882    // Parser errors
883    #[error("expected {expected}, found {found}")]
884    UnexpectedToken {
885        expected: Cow<'static, str>,
886        found: Cow<'static, str>,
887    },
888    #[error("unexpected end of file, expected {expected}")]
889    UnexpectedEof { expected: Cow<'static, str> },
890    /// A custom parse error with a specific message.
891    ///
892    /// Used for parser-generated errors that don't fit the "expected X, found Y" pattern.
893    #[error("{0}")]
894    ParseError(String),
895
896    /// ADR-0085: any C-FFI-related diagnostic that doesn't have its own
897    /// specific variant. Holds a free-form message; specific shapes
898    /// (`LinkExternDuplicateImport`, `FfiTypeNotAllowed`, etc.) get
899    /// dedicated variants as the surface grows.
900    #[error("{0}")]
901    CFfi(String),
902
903    // Semantic errors
904    #[error("no main function found")]
905    NoMainFunction,
906    #[error("undefined variable '{0}'")]
907    UndefinedVariable(String),
908    #[error("undefined function '{0}'")]
909    UndefinedFunction(String),
910    #[error("cannot assign to immutable variable '{0}'")]
911    AssignToImmutable(String),
912    #[error("unknown type '{0}'")]
913    UnknownType(String),
914    /// Use of a value after it has been moved.
915    #[error("use of moved value '{0}'")]
916    UseAfterMove(String),
917    /// Attempt to move a non-copy field out of a struct (banned by ADR-0036).
918    #[error("cannot move field `{field}` out of `{type_name}`")]
919    CannotMoveField { type_name: String, field: String },
920    #[error("type mismatch: expected {expected}, found {found}")]
921    TypeMismatch { expected: String, found: String },
922    #[error("{}", format_argument_count(*.expected, *.found))]
923    WrongArgumentCount { expected: usize, found: usize },
924
925    // Struct errors
926    #[error("{}", format_missing_fields(.0))]
927    MissingFields(Box<MissingFieldsError>),
928    #[error("unknown field '{field_name}' in struct '{struct_name}'")]
929    UnknownField {
930        struct_name: String,
931        field_name: String,
932    },
933    /// ADR-0072: access to a private field of a synthetic builtin struct
934    /// from outside that builtin's own methods.
935    #[error("field '{field_name}' of '{struct_name}' is private")]
936    PrivateField {
937        struct_name: String,
938        field_name: String,
939    },
940    #[error("duplicate field '{field_name}' in struct '{struct_name}'")]
941    DuplicateField {
942        struct_name: String,
943        field_name: String,
944    },
945    /// Missing field in struct destructuring pattern
946    #[error("missing field `{field}` in destructuring of `{struct_name}`")]
947    MissingFieldInDestructure { struct_name: String, field: String },
948    /// Anonymous struct with no fields is not allowed
949    #[error("empty struct is not allowed")]
950    EmptyStruct,
951    /// Anonymous enum with no variants is not allowed
952    #[error("anonymous enum must have at least one variant")]
953    EmptyAnonEnum,
954    /// User-defined type collides with a built-in type name
955    #[error("cannot define type `{type_name}`: name is reserved for built-in type")]
956    ReservedTypeName { type_name: String },
957    /// Duplicate type definition
958    #[error("duplicate type definition: `{type_name}` is already defined")]
959    DuplicateTypeDefinition { type_name: String },
960    /// Linear value was not consumed before going out of scope
961    #[error("linear value '{0}' must be consumed but was dropped")]
962    LinearValueNotConsumed(String),
963    /// Linear struct cannot derive Copy
964    #[error("linear struct '{0}' cannot be marked `@derive(Copy)`")]
965    LinearStructCopy(String),
966    /// Linear struct cannot derive Clone (ADR-0065)
967    #[error("linear struct '{0}' cannot be marked `@derive(Clone)`")]
968    LinearStructClone(String),
969    /// @derive(Clone) v1 limitation: every field must be Copy.
970    #[error("@derive(Clone) on struct '{struct_name}' requires every field to be Copy in v1; field '{field_name}' has type '{field_type}' which is not Copy. Hand-write `fn clone(self: Ref(Self)) -> Self` instead.", struct_name = .0.struct_name, field_name = .0.field_name, field_type = .0.field_type)]
971    CloneStructNonCopyField(Box<CloneStructNonCopyFieldError>),
972    /// Declared posture is inconsistent with members' postures (ADR-0080).
973    #[error(
974        "{declared_posture} {host_kind} '{host_name}' contains {member_posture} {member_kind} '{member_name}' of type '{member_type}'",
975        declared_posture = .0.declared_posture,
976        host_kind = .0.host_kind,
977        host_name = .0.host_name,
978        member_posture = .0.member_posture,
979        member_kind = .0.member_kind,
980        member_name = .0.member_name,
981        member_type = .0.member_type,
982    )]
983    PostureMismatch(Box<PostureMismatchError>),
984    /// A directive name that is not in the recognized set (ADR-0075).
985    /// `note` carries either a near-match suggestion or a retirement
986    /// pointer (for `@handle` and `@copy`).
987    #[error("unknown directive `@{name}`{}", note.as_deref().map(|n| format!("; {n}")).unwrap_or_default())]
988    UnknownDirective { name: String, note: Option<String> },
989    /// `@mark(...)` carries a marker name not in `BUILTIN_MARKERS` (ADR-0083).
990    #[error("unknown marker `{name}`{}", note.as_deref().map(|n| format!("; {n}")).unwrap_or_default())]
991    UnknownMarker { name: String, note: Option<String> },
992    /// `@mark(...)` carries a marker that is not applicable to the host
993    /// item kind (ADR-0083).
994    #[error("marker `{marker}` is not applicable to {item_kind}")]
995    MarkerNotApplicable {
996        marker: String,
997        item_kind: &'static str,
998    },
999    /// More than one thread-safety marker on the same type (ADR-0084).
1000    /// At most one of `unsend`, `checked_send`, `checked_sync` is allowed.
1001    #[error(
1002        "conflicting thread-safety markers on type `{type_name}`: at most one of `unsend`, `checked_send`, `checked_sync` may be applied"
1003    )]
1004    ConflictingThreadSafetyMarkers { type_name: String },
1005    /// `@spawn(fn, arg)` argument has type that is not at least `Send`
1006    /// on the trichotomy (ADR-0084).
1007    #[error(
1008        "@spawn argument of type `{arg_type}` is not Send (classified as Unsend); the spawned thread cannot take ownership across thread boundaries"
1009    )]
1010    SpawnArgNotSend { arg_type: String },
1011    /// `@spawn(fn, _)` return type is not at least `Send` (ADR-0084).
1012    #[error(
1013        "@spawn function `{fn_name}` returns `{ret_type}`, which is not Send (classified as Unsend); the join site cannot transfer the result back across the thread boundary"
1014    )]
1015    SpawnReturnNotSend { fn_name: String, ret_type: String },
1016    /// `@spawn(fn, arg)` argument is a `Ref(T)` or `MutRef(T)` —
1017    /// references are scope-bound and cannot outlive the caller's frame.
1018    #[error(
1019        "@spawn argument of type `{arg_type}` is a reference; the spawned thread outlives the caller's scope so references cannot be passed across — move ownership instead"
1020    )]
1021    SpawnArgIsRef { arg_type: String },
1022    /// `@spawn(fn, arg)` argument is a Linear type — linearity is a
1023    /// per-thread property today.
1024    #[error(
1025        "@spawn argument of type `{arg_type}` is Linear; linear values cannot be transferred across threads in the v1 spawn surface"
1026    )]
1027    SpawnArgIsLinear { arg_type: String },
1028    /// `@spawn(fn, _)` function has wrong arity. v1 only supports
1029    /// single-argument workers; tuple-wrap multiple values on the
1030    /// caller's side.
1031    #[error("@spawn function `{fn_name}` must take exactly one parameter; found {arity}")]
1032    SpawnFunctionWrongArity { fn_name: String, arity: usize },
1033    /// `@spawn(fn, _)` function name does not resolve to a top-level
1034    /// function. Methods, anonymous functions, and comptime-bound
1035    /// values are not supported in v1.
1036    #[error("@spawn first argument must be a top-level function name; `{name}` did not resolve")]
1037    SpawnFunctionNotFound { name: String },
1038    /// Duplicate method definition in impl blocks for the same type
1039    #[error("duplicate method '{method_name}' for type '{type_name}'")]
1040    DuplicateMethod {
1041        type_name: String,
1042        method_name: String,
1043    },
1044    /// A `derive` body uses direct field projection (`self.field`) instead
1045    /// of `@field(self, "name")`. The host type's structure is not known at
1046    /// derive-definition time (ADR-0058), so direct projection is rejected.
1047    #[error(
1048        "direct field access on `self` is not allowed in `derive {derive_name}`; use `@field(self, \"...\")` because the host type is not known at derive-definition time"
1049    )]
1050    DeriveDirectFieldAccess {
1051        derive_name: String,
1052        method_name: String,
1053    },
1054    /// `@derive(N)` references a name that is not a `derive` item.
1055    #[error("expected a `derive` item, found {found} `{name}`")]
1056    DeriveNotADerive {
1057        /// The name in the `@derive(...)` directive.
1058        name: String,
1059        /// What `name` actually resolved to (e.g., "struct", "enum", "function", or "unknown name").
1060        found: String,
1061    },
1062    /// Method not found on a type
1063    #[error("no method named '{method_name}' found for type '{type_name}'")]
1064    UndefinedMethod {
1065        type_name: String,
1066        method_name: String,
1067    },
1068    /// Associated function not found on a type
1069    #[error("no associated function named '{function_name}' found for type '{type_name}'")]
1070    UndefinedAssocFn {
1071        type_name: String,
1072        function_name: String,
1073    },
1074    /// Method call on non-struct type
1075    #[error("no method named '{method_name}' on type '{found}'")]
1076    MethodCallOnNonStruct { found: String, method_name: String },
1077    /// Calling a method (with self) as an associated function
1078    #[error(
1079        "'{type_name}::{method_name}' is a method, not an associated function; use receiver.{method_name}() syntax"
1080    )]
1081    MethodCalledAsAssocFn {
1082        type_name: String,
1083        method_name: String,
1084    },
1085    /// Calling an associated function (without self) as a method
1086    #[error(
1087        "'{function_name}' is an associated function, not a method; use {type_name}::{function_name}() syntax"
1088    )]
1089    AssocFnCalledAsMethod {
1090        type_name: String,
1091        function_name: String,
1092    },
1093
1094    // Destructor errors
1095    /// Duplicate destructor for the same type
1096    #[error("duplicate destructor for type '{type_name}'")]
1097    DuplicateDestructor { type_name: String },
1098    /// Destructor for unknown type
1099    #[error("unknown type '{type_name}' in destructor")]
1100    DestructorUnknownType { type_name: String },
1101    /// Inline `fn __drop(self)` is invalid on this type (wrong signature, `@derive(Copy)`,
1102    /// linear, etc). ADR-0053.
1103    #[error("invalid `fn __drop` on type '{type_name}': {reason}")]
1104    InvalidInlineDrop { type_name: String, reason: String },
1105
1106    // Constant errors
1107    /// Duplicate constant declaration
1108    #[error("duplicate {kind} '{name}'")]
1109    DuplicateConstant { name: String, kind: String },
1110    /// Expression not supported in const context
1111    #[error("{expr_kind} is not supported in const context")]
1112    ConstExprNotSupported { expr_kind: String },
1113
1114    // Enum errors
1115    #[error("duplicate variant '{variant_name}' in enum '{enum_name}'")]
1116    DuplicateVariant {
1117        enum_name: String,
1118        variant_name: String,
1119    },
1120    #[error("unknown variant '{variant_name}' in enum '{enum_name}'")]
1121    UnknownVariant {
1122        enum_name: String,
1123        variant_name: String,
1124    },
1125    #[error("unknown enum type '{0}'")]
1126    UnknownEnumType(String),
1127    #[error("struct '{struct_name}' fields must be initialized in declaration order: expected '{expected_field}', found '{found_field}'", struct_name = .0.struct_name, expected_field = .0.expected_field, found_field = .0.found_field)]
1128    FieldWrongOrder(Box<FieldWrongOrderError>),
1129    #[error("field access on non-struct type '{found}'")]
1130    FieldAccessOnNonStruct { found: String },
1131    #[error("invalid assignment target")]
1132    InvalidAssignmentTarget,
1133    /// Inout argument is not an lvalue (variable, field, or array element)
1134    #[error("inout argument must be an lvalue (variable, field, or array element)")]
1135    InoutNonLvalue,
1136    /// Same variable passed to multiple inout parameters in a single call
1137    #[error("cannot pass same variable '{variable}' to multiple inout parameters")]
1138    InoutExclusiveAccess { variable: String },
1139    /// Borrow argument is not an lvalue (variable, field, or array element)
1140    #[error("borrow argument must be a variable, field, or array element")]
1141    BorrowNonLvalue,
1142    /// Cannot mutate a borrowed value
1143    #[error("cannot mutate borrowed value '{variable}'")]
1144    MutateBorrowedValue { variable: String },
1145    /// Cannot move out of a borrowed value
1146    #[error("cannot move out of borrowed value '{variable}'")]
1147    MoveOutOfBorrow { variable: String },
1148    /// Same variable passed to both borrow and inout parameters (law of exclusivity)
1149    #[error("cannot borrow '{variable}' while it is mutably borrowed (inout)")]
1150    BorrowInoutConflict { variable: String },
1151    /// Argument to inout parameter is missing `inout` keyword at call site
1152    #[error("argument to inout parameter must use 'inout' keyword")]
1153    InoutKeywordMissing,
1154    /// Argument to borrow parameter is missing `borrow` keyword at call site
1155    #[error("argument to borrow parameter must use 'borrow' keyword")]
1156    BorrowKeywordMissing,
1157    /// Reference (`Ref(T)` / `MutRef(T)`) escapes the function in which it
1158    /// was constructed (ADR-0062).
1159    #[error("reference type `{type_name}` cannot escape the function it was constructed in")]
1160    ReferenceEscapesFunction { type_name: String },
1161
1162    // Control flow errors
1163    #[error("'break' outside of loop")]
1164    BreakOutsideLoop,
1165    #[error(
1166        "'break' in for-in loop over array with non-Copy element type '{element_type}' would leak un-iterated elements"
1167    )]
1168    BreakInConsumingForLoop { element_type: String },
1169    #[error("'continue' outside of loop")]
1170    ContinueOutsideLoop,
1171
1172    // Checked block errors
1173    #[error("intrinsic '@{0}' can only be used inside a `checked` block")]
1174    IntrinsicRequiresChecked(String),
1175    #[error("call to unchecked function '{0}' can only be used inside a `checked` block")]
1176    UncheckedCallRequiresChecked(String),
1177    /// ADR-0088: drop glue runs implicitly at scope exit; there is no
1178    /// caller `checked { }` to gate it, so `@mark(unchecked) fn __drop`
1179    /// is forbidden.
1180    #[error(
1181        "`@mark(unchecked)` is not allowed on `fn __drop`; drop glue runs implicitly at scope exit and cannot be gated by `checked {{ }}`"
1182    )]
1183    UncheckedDestructor,
1184    /// ADR-0088: an FFI import inside `link_extern` /
1185    /// `static_link_extern` must carry `@mark(unchecked)`.
1186    #[error(
1187        "extern fn `{fn_name}` in `link_extern(\"{library}\")` must be declared `@mark(unchecked) fn …`; FFI imports are unverified from the Gruel side"
1188    )]
1189    ExternFnMissingUnchecked { fn_name: String, library: String },
1190    /// ADR-0088: implementor's `is_unchecked` flag on a method does
1191    /// not match the interface signature's. Conformance is strict —
1192    /// a checked interface method may not be satisfied by an
1193    /// `@mark(unchecked)` impl, and vice versa.
1194    #[error(
1195        "method `{method_name}` on type `{type_name}` does not conform to interface `{interface_name}`: interface declares it as {} but implementor declares it as {}",
1196        if .0.expected_unchecked { "`@mark(unchecked)`" } else { "checked" },
1197        if .0.actual_unchecked { "`@mark(unchecked)`" } else { "checked" },
1198        method_name = .0.method_name,
1199        type_name = .0.type_name,
1200        interface_name = .0.interface_name,
1201    )]
1202    InterfaceMethodUncheckedMismatch(Box<InterfaceMethodUncheckedMismatchError>),
1203
1204    // Match errors
1205    //
1206    // `missing` is a short, comma-separated list of uncovered patterns
1207    // (variant names, bool cases, or `_` when no finite witness
1208    // applies). Empty when the old call-sites haven't been updated yet
1209    // — the base message still makes sense in that case.
1210    #[error("match is not exhaustive{}", format_missing_witnesses(.missing))]
1211    NonExhaustiveMatch { missing: Vec<String> },
1212    #[error("match expression has no arms")]
1213    EmptyMatch,
1214    #[error("cannot match on type '{0}', expected integer, bool, or enum")]
1215    InvalidMatchType(String),
1216
1217    // Intrinsic errors
1218    #[error("unknown intrinsic '@{0}'")]
1219    UnknownIntrinsic(String),
1220    #[error("{}", format_intrinsic_arg_count(name, *.expected, *.found))]
1221    IntrinsicWrongArgCount {
1222        name: String,
1223        expected: usize,
1224        found: usize,
1225    },
1226    #[error("intrinsic '@{name}' expects {expected}, found {found}", name = .0.name, expected = .0.expected, found = .0.found)]
1227    IntrinsicTypeMismatch(Box<IntrinsicTypeMismatchError>),
1228
1229    // Module errors
1230    #[error("@import requires a string literal argument")]
1231    ImportRequiresStringLiteral,
1232    #[error("cannot find module '{path}'")]
1233    ModuleNotFound {
1234        path: String,
1235        /// Candidates that were tried (for error message)
1236        candidates: Vec<String>,
1237    },
1238    #[error("standard library not found")]
1239    StdLibNotFound,
1240    #[error("{item_kind} `{name}` is private")]
1241    PrivateMemberAccess { item_kind: String, name: String },
1242    #[error("module `{module_name}` has no member `{member_name}`")]
1243    UnknownModuleMember {
1244        module_name: String,
1245        member_name: String,
1246    },
1247
1248    // Literal errors
1249    #[error("literal value {value} is out of range for type '{ty}'")]
1250    LiteralOutOfRange { value: u64, ty: String },
1251
1252    // Operator errors
1253    #[error("cannot apply unary operator `-` to type '{0}'")]
1254    CannotNegateUnsigned(String),
1255    #[error("comparison operators cannot be chained")]
1256    ChainedComparison,
1257
1258    // Array errors
1259    #[error("cannot index into non-array type '{found}'")]
1260    IndexOnNonArray { found: String },
1261    #[error("{}", format_array_length_mismatch(*.expected, *.found))]
1262    ArrayLengthMismatch { expected: u64, found: u64 },
1263    #[error("index out of bounds: the length is {length} but the index is {index}")]
1264    IndexOutOfBounds { index: i64, length: u64 },
1265    #[error("type annotation required for empty array")]
1266    TypeAnnotationRequired,
1267    /// Cannot move non-Copy element out of array index position
1268    #[error("cannot move out of indexed position: element type '{element_type}' is not Copy")]
1269    MoveOutOfIndex { element_type: String },
1270
1271    // Linker errors
1272    #[error("link error: {0}")]
1273    LinkError(String),
1274
1275    // Target errors
1276    #[error("unsupported target: {0}")]
1277    UnsupportedTarget(String),
1278
1279    // Preview feature errors
1280    #[error("{what} requires preview feature `{}`", .feature.name())]
1281    PreviewFeatureRequired {
1282        feature: PreviewFeature,
1283        what: String,
1284    },
1285
1286    // Interface conformance errors (ADR-0056)
1287    /// Type does not provide a method required by an interface.
1288    #[error(
1289        "type `{}` does not conform to interface `{}`: missing method `{}`",
1290        .0.type_name,
1291        .0.interface_name,
1292        .0.method_name
1293    )]
1294    InterfaceMethodMissing(Box<InterfaceMethodMissingData>),
1295    /// Type provides a method but with a signature that does not match the
1296    /// interface requirement.
1297    #[error(
1298        "type `{}` does not conform to interface `{}`: method `{}` has the wrong signature",
1299        .0.type_name,
1300        .0.interface_name,
1301        .0.method_name
1302    )]
1303    InterfaceMethodSignatureMismatch(Box<InterfaceMethodSignatureMismatchData>),
1304
1305    // Pattern errors (ADR-0049)
1306    #[error("refutable pattern in let binding: matches only a subset of possible values")]
1307    RefutablePatternInLet,
1308
1309    // Comptime errors
1310    #[error("comptime evaluation failed: {reason}")]
1311    ComptimeEvaluationFailed { reason: String },
1312
1313    #[error("comptime parameter requires a compile-time known value")]
1314    ComptimeArgNotConst { param_name: String },
1315
1316    #[error("{0}")]
1317    ComptimeUserError(String),
1318
1319    /// `@lang(...)` problem (ADR-0079): unknown lang-item name, wrong
1320    /// argument shape, duplicate binding, or use outside the prelude.
1321    #[error("invalid `@lang` directive: {reason}")]
1322    InvalidLangItem { reason: String },
1323
1324    // Internal compiler errors (bugs in the compiler itself)
1325    #[error("internal compiler error: {0}")]
1326    InternalError(String),
1327
1328    // Codegen internal errors (compiler bugs)
1329    #[error("internal codegen error: {0}")]
1330    InternalCodegenError(String),
1331}
1332
1333impl ErrorKind {
1334    /// Get the error code for this error kind.
1335    ///
1336    /// Every error kind has a unique, stable error code that can be used
1337    /// for documentation lookup and searchability.
1338    pub fn code(&self) -> ErrorCode {
1339        match self {
1340            // Lexer errors (E0001-E0099)
1341            ErrorKind::UnexpectedCharacter(_) => ErrorCode::UNEXPECTED_CHARACTER,
1342            ErrorKind::InvalidInteger => ErrorCode::INVALID_INTEGER,
1343            ErrorKind::InvalidFloat => ErrorCode::INVALID_FLOAT,
1344            ErrorKind::InvalidStringEscape(_) => ErrorCode::INVALID_STRING_ESCAPE,
1345            ErrorKind::UnterminatedString => ErrorCode::UNTERMINATED_STRING,
1346            ErrorKind::EmptyCharLit => ErrorCode::EMPTY_CHAR_LIT,
1347            ErrorKind::UnterminatedCharLit => ErrorCode::UNTERMINATED_CHAR_LIT,
1348            ErrorKind::MultiCharLit => ErrorCode::MULTI_CHAR_LIT,
1349            ErrorKind::InvalidCharEscape => ErrorCode::INVALID_CHAR_ESCAPE,
1350            ErrorKind::InvalidUnicodeEscape => ErrorCode::INVALID_UNICODE_ESCAPE,
1351
1352            // Parser errors (E0100-E0199)
1353            ErrorKind::UnexpectedToken { .. } => ErrorCode::UNEXPECTED_TOKEN,
1354            ErrorKind::UnexpectedEof { .. } => ErrorCode::UNEXPECTED_EOF,
1355            ErrorKind::ParseError(_) => ErrorCode::PARSE_ERROR,
1356
1357            // Semantic errors (E0200-E0399)
1358            ErrorKind::NoMainFunction => ErrorCode::NO_MAIN_FUNCTION,
1359            ErrorKind::UndefinedVariable(_) => ErrorCode::UNDEFINED_VARIABLE,
1360            ErrorKind::UndefinedFunction(_) => ErrorCode::UNDEFINED_FUNCTION,
1361            ErrorKind::AssignToImmutable(_) => ErrorCode::ASSIGN_TO_IMMUTABLE,
1362            ErrorKind::UnknownType(_) => ErrorCode::UNKNOWN_TYPE,
1363            ErrorKind::UseAfterMove(_) => ErrorCode::USE_AFTER_MOVE,
1364            ErrorKind::CannotMoveField { .. } => ErrorCode::USE_AFTER_MOVE,
1365            ErrorKind::TypeMismatch { .. } => ErrorCode::TYPE_MISMATCH,
1366            ErrorKind::WrongArgumentCount { .. } => ErrorCode::WRONG_ARGUMENT_COUNT,
1367
1368            // Struct/enum errors (E0400-E0499)
1369            ErrorKind::MissingFields(_) => ErrorCode::MISSING_FIELDS,
1370            ErrorKind::MissingFieldInDestructure { .. } => ErrorCode::MISSING_FIELDS,
1371            ErrorKind::UnknownField { .. } => ErrorCode::UNKNOWN_FIELD,
1372            ErrorKind::PrivateField { .. } => ErrorCode::PRIVATE_FIELD,
1373            ErrorKind::DuplicateField { .. } => ErrorCode::DUPLICATE_FIELD,
1374            ErrorKind::EmptyStruct => ErrorCode::EMPTY_STRUCT,
1375            ErrorKind::EmptyAnonEnum => ErrorCode::EMPTY_STRUCT, // reuse code
1376            ErrorKind::ReservedTypeName { .. } => ErrorCode::RESERVED_TYPE_NAME,
1377            ErrorKind::DuplicateTypeDefinition { .. } => ErrorCode::DUPLICATE_TYPE_DEFINITION,
1378            ErrorKind::LinearValueNotConsumed(_) => ErrorCode::LINEAR_VALUE_NOT_CONSUMED,
1379            ErrorKind::LinearStructCopy(_) => ErrorCode::LINEAR_STRUCT_COPY,
1380            ErrorKind::LinearStructClone(_) => ErrorCode::LINEAR_STRUCT_COPY,
1381            ErrorKind::CloneStructNonCopyField { .. } => ErrorCode::COPY_STRUCT_NON_COPY_FIELD,
1382            ErrorKind::PostureMismatch { .. } => ErrorCode::COPY_STRUCT_NON_COPY_FIELD,
1383            ErrorKind::UnknownDirective { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1384            ErrorKind::UnknownMarker { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1385            ErrorKind::MarkerNotApplicable { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1386            ErrorKind::ConflictingThreadSafetyMarkers { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1387            ErrorKind::SpawnArgNotSend { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1388            ErrorKind::SpawnReturnNotSend { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1389            ErrorKind::SpawnArgIsRef { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1390            ErrorKind::SpawnArgIsLinear { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1391            ErrorKind::SpawnFunctionWrongArity { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1392            ErrorKind::SpawnFunctionNotFound { .. } => ErrorCode::UNKNOWN_DIRECTIVE,
1393            ErrorKind::DuplicateMethod { .. } => ErrorCode::DUPLICATE_METHOD,
1394            ErrorKind::DeriveDirectFieldAccess { .. } => ErrorCode::DERIVE_DIRECT_FIELD_ACCESS,
1395            ErrorKind::DeriveNotADerive { .. } => ErrorCode::DERIVE_NOT_A_DERIVE,
1396            ErrorKind::UndefinedMethod { .. } => ErrorCode::UNDEFINED_METHOD,
1397            ErrorKind::UndefinedAssocFn { .. } => ErrorCode::UNDEFINED_ASSOC_FN,
1398            ErrorKind::MethodCallOnNonStruct { .. } => ErrorCode::METHOD_CALL_ON_NON_STRUCT,
1399            ErrorKind::MethodCalledAsAssocFn { .. } => ErrorCode::METHOD_CALLED_AS_ASSOC_FN,
1400            ErrorKind::AssocFnCalledAsMethod { .. } => ErrorCode::ASSOC_FN_CALLED_AS_METHOD,
1401            ErrorKind::DuplicateDestructor { .. } => ErrorCode::DUPLICATE_DESTRUCTOR,
1402            ErrorKind::DestructorUnknownType { .. } => ErrorCode::DESTRUCTOR_UNKNOWN_TYPE,
1403            ErrorKind::InvalidInlineDrop { .. } => ErrorCode::DUPLICATE_DESTRUCTOR,
1404            ErrorKind::DuplicateConstant { .. } => ErrorCode::DUPLICATE_CONSTANT,
1405            ErrorKind::ConstExprNotSupported { .. } => ErrorCode::CONST_EXPR_NOT_SUPPORTED,
1406            ErrorKind::DuplicateVariant { .. } => ErrorCode::DUPLICATE_VARIANT,
1407            ErrorKind::UnknownVariant { .. } => ErrorCode::UNKNOWN_VARIANT,
1408            ErrorKind::UnknownEnumType(_) => ErrorCode::UNKNOWN_ENUM_TYPE,
1409            ErrorKind::FieldWrongOrder(_) => ErrorCode::FIELD_WRONG_ORDER,
1410            ErrorKind::FieldAccessOnNonStruct { .. } => ErrorCode::FIELD_ACCESS_ON_NON_STRUCT,
1411            ErrorKind::InvalidAssignmentTarget => ErrorCode::INVALID_ASSIGNMENT_TARGET,
1412            ErrorKind::InoutNonLvalue => ErrorCode::INOUT_NON_LVALUE,
1413            ErrorKind::InoutExclusiveAccess { .. } => ErrorCode::INOUT_EXCLUSIVE_ACCESS,
1414            ErrorKind::BorrowNonLvalue => ErrorCode::BORROW_NON_LVALUE,
1415            ErrorKind::MutateBorrowedValue { .. } => ErrorCode::MUTATE_BORROWED_VALUE,
1416            ErrorKind::MoveOutOfBorrow { .. } => ErrorCode::MOVE_OUT_OF_BORROW,
1417            ErrorKind::BorrowInoutConflict { .. } => ErrorCode::BORROW_INOUT_CONFLICT,
1418            ErrorKind::InoutKeywordMissing => ErrorCode::INOUT_KEYWORD_MISSING,
1419            ErrorKind::BorrowKeywordMissing => ErrorCode::BORROW_KEYWORD_MISSING,
1420            ErrorKind::ReferenceEscapesFunction { .. } => ErrorCode::REFERENCE_ESCAPES_FUNCTION,
1421
1422            // Control flow errors (E0500-E0599)
1423            ErrorKind::BreakOutsideLoop => ErrorCode::BREAK_OUTSIDE_LOOP,
1424            ErrorKind::BreakInConsumingForLoop { .. } => ErrorCode::BREAK_OUTSIDE_LOOP,
1425            ErrorKind::ContinueOutsideLoop => ErrorCode::CONTINUE_OUTSIDE_LOOP,
1426            ErrorKind::IntrinsicRequiresChecked(_) => ErrorCode::INTRINSIC_REQUIRES_CHECKED,
1427            ErrorKind::UncheckedCallRequiresChecked(_) => {
1428                ErrorCode::UNCHECKED_CALL_REQUIRES_CHECKED
1429            }
1430            ErrorKind::UncheckedDestructor => ErrorCode::UNCHECKED_DESTRUCTOR,
1431            ErrorKind::ExternFnMissingUnchecked { .. } => ErrorCode::EXTERN_FN_MISSING_UNCHECKED,
1432            ErrorKind::InterfaceMethodUncheckedMismatch(_) => {
1433                ErrorCode::INTERFACE_METHOD_UNCHECKED_MISMATCH
1434            }
1435
1436            // Match errors (E0600-E0699)
1437            ErrorKind::NonExhaustiveMatch { .. } => ErrorCode::NON_EXHAUSTIVE_MATCH,
1438            ErrorKind::EmptyMatch => ErrorCode::EMPTY_MATCH,
1439            ErrorKind::InvalidMatchType(_) => ErrorCode::INVALID_MATCH_TYPE,
1440
1441            // Intrinsic errors (E0700-E0799)
1442            ErrorKind::UnknownIntrinsic(_) => ErrorCode::UNKNOWN_INTRINSIC,
1443            ErrorKind::IntrinsicWrongArgCount { .. } => ErrorCode::INTRINSIC_WRONG_ARG_COUNT,
1444            ErrorKind::IntrinsicTypeMismatch(_) => ErrorCode::INTRINSIC_TYPE_MISMATCH,
1445            ErrorKind::ImportRequiresStringLiteral => ErrorCode::IMPORT_REQUIRES_STRING_LITERAL,
1446            ErrorKind::ModuleNotFound { .. } => ErrorCode::MODULE_NOT_FOUND,
1447            ErrorKind::StdLibNotFound => ErrorCode::STD_LIB_NOT_FOUND,
1448            ErrorKind::PrivateMemberAccess { .. } => ErrorCode::PRIVATE_MEMBER_ACCESS,
1449            ErrorKind::UnknownModuleMember { .. } => ErrorCode::UNKNOWN_MODULE_MEMBER,
1450
1451            // Literal/operator errors (E0800-E0899)
1452            ErrorKind::LiteralOutOfRange { .. } => ErrorCode::LITERAL_OUT_OF_RANGE,
1453            ErrorKind::CannotNegateUnsigned(_) => ErrorCode::CANNOT_NEGATE_UNSIGNED,
1454            ErrorKind::ChainedComparison => ErrorCode::CHAINED_COMPARISON,
1455
1456            // Array errors (E0900-E0999)
1457            ErrorKind::IndexOnNonArray { .. } => ErrorCode::INDEX_ON_NON_ARRAY,
1458            ErrorKind::ArrayLengthMismatch { .. } => ErrorCode::ARRAY_LENGTH_MISMATCH,
1459            ErrorKind::IndexOutOfBounds { .. } => ErrorCode::INDEX_OUT_OF_BOUNDS,
1460            ErrorKind::TypeAnnotationRequired => ErrorCode::TYPE_ANNOTATION_REQUIRED,
1461            ErrorKind::MoveOutOfIndex { .. } => ErrorCode::MOVE_OUT_OF_INDEX,
1462
1463            // Linker/target errors (E1000-E1099)
1464            ErrorKind::LinkError(_) => ErrorCode::LINK_ERROR,
1465            ErrorKind::UnsupportedTarget(_) => ErrorCode::UNSUPPORTED_TARGET,
1466
1467            // Preview feature errors (E1100-E1199)
1468            ErrorKind::PreviewFeatureRequired { .. } => ErrorCode::PREVIEW_FEATURE_REQUIRED,
1469            ErrorKind::InterfaceMethodMissing { .. } => ErrorCode::INTERFACE_METHOD_MISSING,
1470            ErrorKind::InterfaceMethodSignatureMismatch { .. } => {
1471                ErrorCode::INTERFACE_METHOD_SIGNATURE_MISMATCH
1472            }
1473
1474            // Pattern errors (E1300-E1399)
1475            ErrorKind::RefutablePatternInLet => ErrorCode::REFUTABLE_PATTERN_IN_LET,
1476
1477            // Comptime errors (E1200-E1299)
1478            ErrorKind::ComptimeEvaluationFailed { .. } => ErrorCode::COMPTIME_EVALUATION_FAILED,
1479            ErrorKind::ComptimeArgNotConst { .. } => ErrorCode::COMPTIME_ARG_NOT_CONST,
1480            ErrorKind::ComptimeUserError(_) => ErrorCode::COMPTIME_USER_ERROR,
1481            ErrorKind::InvalidLangItem { .. } => ErrorCode::INVALID_LANG_ITEM,
1482
1483            // C FFI errors (E1500-E1599) — ADR-0085.
1484            ErrorKind::CFfi(_) => ErrorCode::C_FFI,
1485
1486            // Internal compiler errors (E9000-E9999)
1487            ErrorKind::InternalError(_) => ErrorCode::INTERNAL_ERROR,
1488            ErrorKind::InternalCodegenError(_) => ErrorCode::INTERNAL_CODEGEN_ERROR,
1489        }
1490    }
1491}
1492
1493impl CompileError {
1494    /// Create an error at a specific position (zero-length span).
1495    #[inline]
1496    pub fn at(kind: ErrorKind, pos: u32) -> Self {
1497        Self {
1498            kind,
1499            span: Some(Span::point(pos)),
1500            diagnostic: Box::new(Diagnostic::new()),
1501        }
1502    }
1503
1504    /// Build a `UseAfterMove` error at `use_span` with a "value moved here"
1505    /// label pointing at `moved_span`.
1506    pub fn use_after_move(name: impl Into<String>, use_span: Span, moved_span: Span) -> Self {
1507        Self::new(ErrorKind::UseAfterMove(name.into()), use_span)
1508            .with_label("value moved here", moved_span)
1509    }
1510
1511    /// Build a `TypeMismatch` error.
1512    pub fn type_mismatch(
1513        expected: impl Into<String>,
1514        found: impl Into<String>,
1515        span: Span,
1516    ) -> Self {
1517        Self::new(
1518            ErrorKind::TypeMismatch {
1519                expected: expected.into(),
1520                found: found.into(),
1521            },
1522            span,
1523        )
1524    }
1525}
1526
1527/// Result type for compilation operations.
1528pub type CompileResult<T> = Result<T, CompileError>;
1529
1530// ============================================================================
1531// Multiple Error Collection
1532// ============================================================================
1533
1534/// A collection of compilation errors.
1535///
1536/// This type supports collecting multiple errors during compilation to provide
1537/// users with more comprehensive diagnostics. Instead of stopping at the first
1538/// error, the compiler can continue and report multiple issues at once.
1539///
1540/// # Usage
1541///
1542/// Use `CompileErrors` when a compilation phase can detect multiple independent
1543/// errors. For example, semantic analysis can report multiple type errors in
1544/// different functions.
1545///
1546/// ```ignore
1547/// let mut errors = CompileErrors::new();
1548/// errors.push(CompileError::new(ErrorKind::TypeMismatch { ... }, span1));
1549/// errors.push(CompileError::new(ErrorKind::UndefinedVariable("x".into()), span2));
1550///
1551/// if !errors.is_empty() {
1552///     return Err(errors);
1553/// }
1554/// ```
1555///
1556/// # Error Semantics
1557///
1558/// - An empty `CompileErrors` represents no errors (not a failure)
1559/// - A non-empty `CompileErrors` represents one or more compilation failures
1560/// - When converted to a single `CompileError`, the first error is used
1561#[derive(Debug, Clone)]
1562pub struct CompileErrors {
1563    errors: Vec<CompileError>,
1564}
1565
1566impl CompileErrors {
1567    /// Create a new empty error collection.
1568    pub fn new() -> Self {
1569        Self { errors: Vec::new() }
1570    }
1571
1572    /// Create an error collection from a single error.
1573    pub fn from_error(error: CompileError) -> Self {
1574        Self {
1575            errors: vec![error],
1576        }
1577    }
1578
1579    /// Add an error to the collection.
1580    pub fn push(&mut self, error: CompileError) {
1581        self.errors.push(error);
1582    }
1583
1584    /// Extend this collection with errors from another collection.
1585    pub fn extend(&mut self, other: CompileErrors) {
1586        self.errors.extend(other.errors);
1587    }
1588
1589    /// Returns true if there are no errors.
1590    pub fn is_empty(&self) -> bool {
1591        self.errors.is_empty()
1592    }
1593
1594    /// Returns the number of errors.
1595    pub fn len(&self) -> usize {
1596        self.errors.len()
1597    }
1598
1599    /// Get the first error, if any.
1600    pub fn first(&self) -> Option<&CompileError> {
1601        self.errors.first()
1602    }
1603
1604    /// Iterate over all errors.
1605    pub fn iter(&self) -> impl Iterator<Item = &CompileError> {
1606        self.errors.iter()
1607    }
1608
1609    /// Get all errors as a slice.
1610    pub fn as_slice(&self) -> &[CompileError] {
1611        &self.errors
1612    }
1613
1614    /// Check if the collection contains errors and return as a result.
1615    ///
1616    /// Returns `Ok(())` if empty, or `Err(self)` if there are errors.
1617    pub fn into_result(self) -> Result<(), CompileErrors> {
1618        if self.is_empty() { Ok(()) } else { Err(self) }
1619    }
1620
1621    /// Fail with these errors if non-empty, otherwise return the value.
1622    ///
1623    /// This is useful for combining error checking with a result:
1624    /// ```ignore
1625    /// let output = SemaOutput { ... };
1626    /// errors.into_result_with(output)
1627    /// ```
1628    pub fn into_result_with<T>(self, value: T) -> Result<T, CompileErrors> {
1629        if self.is_empty() {
1630            Ok(value)
1631        } else {
1632            Err(self)
1633        }
1634    }
1635}
1636
1637impl Default for CompileErrors {
1638    fn default() -> Self {
1639        Self::new()
1640    }
1641}
1642
1643impl IntoIterator for CompileErrors {
1644    type Item = CompileError;
1645    type IntoIter = std::vec::IntoIter<CompileError>;
1646
1647    fn into_iter(self) -> Self::IntoIter {
1648        self.errors.into_iter()
1649    }
1650}
1651
1652impl From<CompileError> for CompileErrors {
1653    fn from(error: CompileError) -> Self {
1654        Self::from_error(error)
1655    }
1656}
1657
1658impl From<Vec<CompileError>> for CompileErrors {
1659    fn from(errors: Vec<CompileError>) -> Self {
1660        Self { errors }
1661    }
1662}
1663
1664impl From<CompileErrors> for CompileError {
1665    /// Convert a collection to a single error.
1666    ///
1667    /// Uses the first error in the collection. If the collection is empty,
1668    /// returns an internal error (this indicates a compiler bug).
1669    fn from(errors: CompileErrors) -> Self {
1670        debug_assert!(
1671            !errors.is_empty(),
1672            "converting empty CompileErrors to CompileError"
1673        );
1674        errors.errors.into_iter().next().unwrap_or_else(|| {
1675            CompileError::without_span(ErrorKind::InternalError(
1676                "empty error collection converted to single error".into(),
1677            ))
1678        })
1679    }
1680}
1681
1682impl fmt::Display for CompileErrors {
1683    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1684        match self.errors.len() {
1685            0 => write!(f, "no errors"),
1686            1 => write!(f, "{}", self.errors[0]),
1687            n => write!(
1688                f,
1689                "{} (and {} more error{})",
1690                self.errors[0],
1691                n - 1,
1692                if n == 2 { "" } else { "s" }
1693            ),
1694        }
1695    }
1696}
1697
1698impl std::error::Error for CompileErrors {}
1699
1700/// Result type for operations that can produce multiple errors.
1701pub type MultiErrorResult<T> = Result<T, CompileErrors>;
1702
1703// ============================================================================
1704// Error Helper Traits
1705// ============================================================================
1706
1707/// Extension trait for converting `Option<T>` to `CompileResult<T>`.
1708///
1709/// This trait simplifies the common pattern of converting lookup failures
1710/// (returning `None`) into compilation errors with source spans.
1711///
1712/// # Example
1713/// ```ignore
1714/// use gruel_util::{OptionExt, ErrorKind};
1715///
1716/// let result = ctx.locals.get(name)
1717///     .ok_or_compile_error(ErrorKind::UndefinedVariable(name_str.to_string()), span)?;
1718/// ```
1719pub trait OptionExt<T> {
1720    /// Convert `None` to a `CompileError` with the given kind and span.
1721    fn ok_or_compile_error(self, kind: ErrorKind, span: Span) -> CompileResult<T>;
1722}
1723
1724impl<T> OptionExt<T> for Option<T> {
1725    #[inline]
1726    fn ok_or_compile_error(self, kind: ErrorKind, span: Span) -> CompileResult<T> {
1727        self.ok_or_else(|| CompileError::new(kind, span))
1728    }
1729}
1730
1731/// The kind of compilation warning.
1732#[derive(Debug, Clone, PartialEq, Eq, Error)]
1733pub enum WarningKind {
1734    /// A variable was declared but never used.
1735    #[error("unused variable '{0}'")]
1736    UnusedVariable(String),
1737    /// A function was declared but never called.
1738    #[error("unused function '{0}'")]
1739    UnusedFunction(String),
1740    /// Code that will never be executed.
1741    #[error("unreachable code")]
1742    UnreachableCode,
1743    /// A pattern that will never be matched because a previous pattern already covers it.
1744    #[error("unreachable pattern '{0}'")]
1745    UnreachablePattern(String),
1746    /// A comptime-evaluated `@dbg` call was present during compilation.
1747    #[error("comptime debug statement present — remove before release")]
1748    ComptimeDbgPresent(String),
1749}
1750
1751/// A compilation warning with optional source location information.
1752///
1753/// Warnings don't stop compilation but indicate potential issues in the code.
1754///
1755/// Warnings can include rich diagnostic information using the builder methods:
1756/// ```ignore
1757/// CompileWarning::new(WarningKind::UnusedVariable("x".into()), span)
1758///     .with_help("if this is intentional, prefix it with an underscore: `_x`")
1759/// ```
1760pub type CompileWarning = DiagnosticWrapper<WarningKind>;
1761
1762impl WarningKind {
1763    /// Returns the variable name if this is an UnusedVariable warning.
1764    pub fn unused_variable_name(&self) -> Option<&str> {
1765        match self {
1766            WarningKind::UnusedVariable(name) => Some(name),
1767            _ => None,
1768        }
1769    }
1770
1771    /// Format the warning message with an optional line number.
1772    ///
1773    /// When `line_number` is Some, the line number is appended to the message
1774    /// for warnings that have a name (like unused variables). This helps
1775    /// disambiguate when multiple variables share the same name.
1776    pub fn format_with_line(&self, line_number: Option<usize>) -> String {
1777        match (self, line_number) {
1778            (WarningKind::UnusedVariable(name), Some(line)) => {
1779                format!("unused variable '{}' (line {})", name, line)
1780            }
1781            (WarningKind::UnusedFunction(name), Some(line)) => {
1782                format!("unused function '{}' (line {})", name, line)
1783            }
1784            _ => self.to_string(),
1785        }
1786    }
1787}
1788
1789#[cfg(test)]
1790mod tests {
1791    use super::*;
1792
1793    #[test]
1794    fn test_error_with_span() {
1795        let span = Span::new(10, 20);
1796        let error = CompileError::new(ErrorKind::InvalidInteger, span);
1797
1798        assert!(error.has_span());
1799        assert_eq!(error.span(), Some(span));
1800        assert_eq!(error.to_string(), "invalid integer literal");
1801    }
1802
1803    #[test]
1804    fn test_error_without_span() {
1805        let error = CompileError::without_span(ErrorKind::NoMainFunction);
1806
1807        assert!(!error.has_span());
1808        assert_eq!(error.span(), None);
1809        assert_eq!(error.to_string(), "no main function found");
1810    }
1811
1812    #[test]
1813    fn test_error_at_position() {
1814        let error = CompileError::at(ErrorKind::InvalidInteger, 42);
1815
1816        assert!(error.has_span());
1817        assert_eq!(error.span(), Some(Span::point(42)));
1818    }
1819
1820    #[test]
1821    fn test_error_messages() {
1822        let cases: Vec<(ErrorKind, &str)> = vec![
1823            (
1824                ErrorKind::UnexpectedCharacter('@'),
1825                "unexpected character: @",
1826            ),
1827            (
1828                ErrorKind::UnexpectedToken {
1829                    expected: Cow::Borrowed("identifier"),
1830                    found: Cow::Borrowed("'+'"),
1831                },
1832                "expected identifier, found '+'",
1833            ),
1834            (
1835                ErrorKind::UnexpectedEof {
1836                    expected: Cow::Borrowed("'}'"),
1837                },
1838                "unexpected end of file, expected '}'",
1839            ),
1840            (
1841                ErrorKind::ParseError("custom parse error".into()),
1842                "custom parse error",
1843            ),
1844            (
1845                ErrorKind::UndefinedVariable("foo".into()),
1846                "undefined variable 'foo'",
1847            ),
1848            (
1849                ErrorKind::UndefinedFunction("bar".into()),
1850                "undefined function 'bar'",
1851            ),
1852            (
1853                ErrorKind::AssignToImmutable("x".into()),
1854                "cannot assign to immutable variable 'x'",
1855            ),
1856            (ErrorKind::UnknownType("Foo".into()), "unknown type 'Foo'"),
1857            (
1858                ErrorKind::TypeMismatch {
1859                    expected: "i32".into(),
1860                    found: "bool".into(),
1861                },
1862                "type mismatch: expected i32, found bool",
1863            ),
1864            (
1865                ErrorKind::WrongArgumentCount {
1866                    expected: 1,
1867                    found: 3,
1868                },
1869                "expected 1 argument, found 3",
1870            ),
1871            (
1872                ErrorKind::WrongArgumentCount {
1873                    expected: 2,
1874                    found: 0,
1875                },
1876                "expected 2 arguments, found 0",
1877            ),
1878            (
1879                ErrorKind::LinkError("undefined symbol".into()),
1880                "link error: undefined symbol",
1881            ),
1882        ];
1883        for (kind, expected) in cases {
1884            let error = CompileError::without_span(kind);
1885            assert_eq!(error.to_string(), expected);
1886        }
1887    }
1888
1889    #[test]
1890    fn test_error_kind_equality() {
1891        assert_eq!(ErrorKind::InvalidInteger, ErrorKind::InvalidInteger);
1892        assert_eq!(ErrorKind::NoMainFunction, ErrorKind::NoMainFunction);
1893        assert_ne!(ErrorKind::InvalidInteger, ErrorKind::NoMainFunction);
1894    }
1895
1896    #[test]
1897    fn test_error_implements_std_error() {
1898        fn assert_error<T: std::error::Error>() {}
1899        assert_error::<CompileError>();
1900    }
1901
1902    // ========================================================================
1903    // Diagnostic tests
1904    // ========================================================================
1905
1906    #[test]
1907    fn test_diagnostic_empty_by_default() {
1908        let diag = Diagnostic::new();
1909        assert!(diag.is_empty());
1910        assert!(diag.labels.is_empty());
1911        assert!(diag.notes.is_empty());
1912        assert!(diag.helps.is_empty());
1913        assert!(diag.suggestions.is_empty());
1914    }
1915
1916    #[test]
1917    fn test_diagnostic_not_empty() {
1918        // With label
1919        let mut diag = Diagnostic::new();
1920        diag.labels.push(Label::new("test", Span::new(0, 10)));
1921        assert!(!diag.is_empty());
1922
1923        // With note
1924        let mut diag = Diagnostic::new();
1925        diag.notes.push(Note::new("test note"));
1926        assert!(!diag.is_empty());
1927
1928        // With help
1929        let mut diag = Diagnostic::new();
1930        diag.helps.push(Help::new("test help"));
1931        assert!(!diag.is_empty());
1932
1933        // With suggestion
1934        let mut diag = Diagnostic::new();
1935        diag.suggestions
1936            .push(Suggestion::new("try this", Span::new(0, 10), "replacement"));
1937        assert!(!diag.is_empty());
1938    }
1939
1940    #[test]
1941    fn test_label_creation() {
1942        let span = Span::new(10, 20);
1943        let label = Label::new("expected type here", span);
1944        assert_eq!(label.message, "expected type here");
1945        assert_eq!(label.span, span);
1946    }
1947
1948    #[test]
1949    fn test_note_display() {
1950        let note = Note::new("types must match exactly");
1951        assert_eq!(note.to_string(), "types must match exactly");
1952    }
1953
1954    #[test]
1955    fn test_help_display() {
1956        let help = Help::new("consider adding a type annotation");
1957        assert_eq!(help.to_string(), "consider adding a type annotation");
1958    }
1959
1960    #[test]
1961    fn test_suggestion_creation() {
1962        let span = Span::new(10, 20);
1963        let suggestion = Suggestion::new("try this fix", span, "new_code");
1964        assert_eq!(suggestion.message, "try this fix");
1965        assert_eq!(suggestion.span, span);
1966        assert_eq!(suggestion.replacement, "new_code");
1967        assert_eq!(suggestion.applicability, Applicability::Unspecified);
1968    }
1969
1970    #[test]
1971    fn test_suggestion_machine_applicable() {
1972        let span = Span::new(0, 5);
1973        let suggestion = Suggestion::machine_applicable("rename variable", span, "new_name");
1974        assert_eq!(suggestion.applicability, Applicability::MachineApplicable);
1975    }
1976
1977    #[test]
1978    fn test_suggestion_maybe_incorrect() {
1979        let span = Span::new(0, 5);
1980        let suggestion = Suggestion::maybe_incorrect("try adding mut", span, "mut x");
1981        assert_eq!(suggestion.applicability, Applicability::MaybeIncorrect);
1982    }
1983
1984    #[test]
1985    fn test_suggestion_with_placeholders() {
1986        let span = Span::new(0, 5);
1987        let suggestion = Suggestion::with_placeholders("add type annotation", span, ": <type>");
1988        assert_eq!(suggestion.applicability, Applicability::HasPlaceholders);
1989    }
1990
1991    #[test]
1992    fn test_suggestion_with_applicability() {
1993        let span = Span::new(0, 5);
1994        let suggestion = Suggestion::new("fix", span, "new_code")
1995            .with_applicability(Applicability::MachineApplicable);
1996        assert_eq!(suggestion.applicability, Applicability::MachineApplicable);
1997    }
1998
1999    #[test]
2000    fn test_applicability_display() {
2001        assert_eq!(
2002            Applicability::MachineApplicable.to_string(),
2003            "MachineApplicable"
2004        );
2005        assert_eq!(Applicability::MaybeIncorrect.to_string(), "MaybeIncorrect");
2006        assert_eq!(
2007            Applicability::HasPlaceholders.to_string(),
2008            "HasPlaceholders"
2009        );
2010        assert_eq!(Applicability::Unspecified.to_string(), "Unspecified");
2011    }
2012
2013    #[test]
2014    fn test_applicability_default() {
2015        assert_eq!(Applicability::default(), Applicability::Unspecified);
2016    }
2017
2018    #[test]
2019    fn test_error_with_suggestion() {
2020        let span = Span::new(10, 20);
2021        let error =
2022            CompileError::new(ErrorKind::AssignToImmutable("x".to_string()), span).with_suggestion(
2023                Suggestion::machine_applicable("add mut", Span::new(4, 5), "mut x"),
2024            );
2025
2026        let diag = error.diagnostic();
2027        assert_eq!(diag.suggestions.len(), 1);
2028        assert_eq!(diag.suggestions[0].message, "add mut");
2029        assert_eq!(diag.suggestions[0].replacement, "mut x");
2030        assert_eq!(
2031            diag.suggestions[0].applicability,
2032            Applicability::MachineApplicable
2033        );
2034    }
2035
2036    #[test]
2037    fn test_error_with_label() {
2038        let span = Span::new(10, 20);
2039        let label_span = Span::new(0, 5);
2040        let error = CompileError::new(
2041            ErrorKind::TypeMismatch {
2042                expected: "i32".to_string(),
2043                found: "bool".to_string(),
2044            },
2045            span,
2046        )
2047        .with_label("expected because of this", label_span);
2048
2049        let diag = error.diagnostic();
2050        assert_eq!(diag.labels.len(), 1);
2051        assert_eq!(diag.labels[0].message, "expected because of this");
2052        assert_eq!(diag.labels[0].span, label_span);
2053    }
2054
2055    #[test]
2056    fn test_error_with_note() {
2057        let span = Span::new(10, 20);
2058        let error = CompileError::new(
2059            ErrorKind::TypeMismatch {
2060                expected: "i32".to_string(),
2061                found: "bool".to_string(),
2062            },
2063            span,
2064        )
2065        .with_note("if and else branches must have compatible types");
2066
2067        let diag = error.diagnostic();
2068        assert_eq!(diag.notes.len(), 1);
2069        assert_eq!(
2070            diag.notes[0].to_string(),
2071            "if and else branches must have compatible types"
2072        );
2073    }
2074
2075    #[test]
2076    fn test_error_with_help() {
2077        let span = Span::new(10, 20);
2078        let error = CompileError::new(ErrorKind::AssignToImmutable("x".to_string()), span)
2079            .with_help("consider making `x` mutable: `let mut x`");
2080
2081        let diag = error.diagnostic();
2082        assert_eq!(diag.helps.len(), 1);
2083        assert_eq!(
2084            diag.helps[0].to_string(),
2085            "consider making `x` mutable: `let mut x`"
2086        );
2087    }
2088
2089    #[test]
2090    fn test_error_with_multiple_diagnostics() {
2091        let span = Span::new(10, 20);
2092        let label_span = Span::new(0, 5);
2093        let error = CompileError::new(
2094            ErrorKind::TypeMismatch {
2095                expected: "i32".to_string(),
2096                found: "bool".to_string(),
2097            },
2098            span,
2099        )
2100        .with_label("then branch is here", label_span)
2101        .with_note("if and else branches must have compatible types")
2102        .with_help("consider using a type conversion");
2103
2104        let diag = error.diagnostic();
2105        assert_eq!(diag.labels.len(), 1);
2106        assert_eq!(diag.notes.len(), 1);
2107        assert_eq!(diag.helps.len(), 1);
2108    }
2109
2110    #[test]
2111    fn test_error_diagnostic_empty_by_default() {
2112        let span = Span::new(10, 20);
2113        let error = CompileError::new(ErrorKind::InvalidInteger, span);
2114        assert!(error.diagnostic().is_empty());
2115    }
2116
2117    #[test]
2118    fn test_warning_with_help() {
2119        let span = Span::new(10, 20);
2120        let warning = CompileWarning::new(WarningKind::UnusedVariable("foo".to_string()), span)
2121            .with_help("if this is intentional, prefix it with an underscore: `_foo`");
2122
2123        let diag = warning.diagnostic();
2124        assert_eq!(diag.helps.len(), 1);
2125        assert_eq!(
2126            diag.helps[0].to_string(),
2127            "if this is intentional, prefix it with an underscore: `_foo`"
2128        );
2129    }
2130
2131    #[test]
2132    fn test_warning_with_label_and_note() {
2133        let span = Span::new(20, 25);
2134        let diverging_span = Span::new(10, 18);
2135        let warning = CompileWarning::new(WarningKind::UnreachableCode, span)
2136            .with_label(
2137                "any code following this expression is unreachable",
2138                diverging_span,
2139            )
2140            .with_note("this warning occurs because the preceding expression diverges");
2141
2142        let diag = warning.diagnostic();
2143        assert_eq!(diag.labels.len(), 1);
2144        assert_eq!(diag.labels[0].span, diverging_span);
2145        assert_eq!(diag.notes.len(), 1);
2146    }
2147
2148    #[test]
2149    fn test_warning_diagnostic_empty_by_default() {
2150        let span = Span::new(10, 20);
2151        let warning = CompileWarning::new(WarningKind::UnreachableCode, span);
2152        assert!(warning.diagnostic().is_empty());
2153    }
2154
2155    // ========================================================================
2156    // Preview feature tests
2157    // ========================================================================
2158
2159    #[test]
2160    fn test_preview_feature_test_infra() {
2161        let feature: PreviewFeature = "test_infra".parse().unwrap();
2162        assert_eq!(feature, PreviewFeature::TestInfra);
2163        assert_eq!(feature.name(), "test_infra");
2164        assert_eq!(feature.adr(), "ADR-0005");
2165    }
2166
2167    #[test]
2168    fn test_preview_feature_from_str_unknown() {
2169        assert!("unknown".parse::<PreviewFeature>().is_err());
2170        assert!("".parse::<PreviewFeature>().is_err());
2171    }
2172
2173    #[test]
2174    fn test_preview_feature_all_contains_test_infra() {
2175        assert!(PreviewFeature::all().contains(&PreviewFeature::TestInfra));
2176    }
2177
2178    #[test]
2179    fn test_preview_feature_all_names() {
2180        let names = PreviewFeature::all_names();
2181        // Order follows the enum declaration order via strum::EnumIter.
2182        assert_eq!(names, "test_infra");
2183    }
2184
2185    // ========================================================================
2186    // OptionExt trait tests
2187    // ========================================================================
2188
2189    #[test]
2190    fn test_option_ext_some() {
2191        let span = Span::new(10, 20);
2192        let result: CompileResult<i32> =
2193            Some(42).ok_or_compile_error(ErrorKind::InvalidInteger, span);
2194        assert!(result.is_ok());
2195        assert_eq!(result.unwrap(), 42);
2196    }
2197
2198    #[test]
2199    fn test_option_ext_none() {
2200        let span = Span::new(10, 20);
2201        let result: CompileResult<i32> = None.ok_or_compile_error(ErrorKind::InvalidInteger, span);
2202        assert!(result.is_err());
2203        let error = result.unwrap_err();
2204        assert_eq!(error.span(), Some(span));
2205        assert!(matches!(error.kind, ErrorKind::InvalidInteger));
2206    }
2207
2208    #[test]
2209    fn test_option_ext_with_complex_error() {
2210        let span = Span::new(5, 15);
2211        let result: CompileResult<String> =
2212            None.ok_or_compile_error(ErrorKind::UndefinedVariable("foo".to_string()), span);
2213        assert!(result.is_err());
2214        let error = result.unwrap_err();
2215        assert_eq!(error.to_string(), "undefined variable 'foo'");
2216    }
2217
2218    // ========================================================================
2219    // CompileErrors tests
2220    // ========================================================================
2221
2222    #[test]
2223    fn test_compile_errors_new_is_empty() {
2224        let errors = CompileErrors::new();
2225        assert!(errors.is_empty());
2226        assert_eq!(errors.len(), 0);
2227    }
2228
2229    #[test]
2230    fn test_compile_errors_from_error() {
2231        let error = CompileError::without_span(ErrorKind::InvalidInteger);
2232        let errors = CompileErrors::from_error(error);
2233        assert!(!errors.is_empty());
2234        assert_eq!(errors.len(), 1);
2235    }
2236
2237    #[test]
2238    fn test_compile_errors_push() {
2239        let mut errors = CompileErrors::new();
2240        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2241        errors.push(CompileError::without_span(ErrorKind::NoMainFunction));
2242        assert_eq!(errors.len(), 2);
2243    }
2244
2245    #[test]
2246    fn test_compile_errors_extend() {
2247        let mut errors1 = CompileErrors::new();
2248        errors1.push(CompileError::without_span(ErrorKind::InvalidInteger));
2249
2250        let mut errors2 = CompileErrors::new();
2251        errors2.push(CompileError::without_span(ErrorKind::NoMainFunction));
2252        errors2.push(CompileError::without_span(ErrorKind::BreakOutsideLoop));
2253
2254        errors1.extend(errors2);
2255        assert_eq!(errors1.len(), 3);
2256    }
2257
2258    #[test]
2259    fn test_compile_errors_first() {
2260        let mut errors = CompileErrors::new();
2261        assert!(errors.first().is_none());
2262
2263        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2264        errors.push(CompileError::without_span(ErrorKind::NoMainFunction));
2265
2266        let first = errors.first().unwrap();
2267        assert!(matches!(first.kind, ErrorKind::InvalidInteger));
2268    }
2269
2270    #[test]
2271    fn test_compile_errors_iter() {
2272        let mut errors = CompileErrors::new();
2273        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2274        errors.push(CompileError::without_span(ErrorKind::NoMainFunction));
2275
2276        let kinds: Vec<_> = errors.iter().map(|e| &e.kind).collect();
2277        assert_eq!(kinds.len(), 2);
2278    }
2279
2280    #[test]
2281    fn test_compile_errors_into_result_empty() {
2282        let errors = CompileErrors::new();
2283        assert!(errors.into_result().is_ok());
2284    }
2285
2286    #[test]
2287    fn test_compile_errors_into_result_non_empty() {
2288        let mut errors = CompileErrors::new();
2289        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2290        assert!(errors.into_result().is_err());
2291    }
2292
2293    #[test]
2294    fn test_compile_errors_into_result_with() {
2295        let errors = CompileErrors::new();
2296        let result = errors.into_result_with(42);
2297        assert_eq!(result.unwrap(), 42);
2298
2299        let mut errors = CompileErrors::new();
2300        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2301        let result = errors.into_result_with(42);
2302        assert!(result.is_err());
2303    }
2304
2305    #[test]
2306    fn test_compile_errors_from_single_error() {
2307        let error = CompileError::without_span(ErrorKind::InvalidInteger);
2308        let errors: CompileErrors = error.into();
2309        assert_eq!(errors.len(), 1);
2310    }
2311
2312    #[test]
2313    fn test_compile_errors_to_single_error() {
2314        let mut errors = CompileErrors::new();
2315        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2316        errors.push(CompileError::without_span(ErrorKind::NoMainFunction));
2317
2318        let error: CompileError = errors.into();
2319        // Should get the first error
2320        assert!(matches!(error.kind, ErrorKind::InvalidInteger));
2321    }
2322
2323    /// Test that empty CompileErrors conversion doesn't panic in release builds.
2324    /// In debug builds, this triggers a debug_assert panic (as expected).
2325    /// This test verifies the graceful fallback behavior in release mode.
2326    #[test]
2327    #[cfg_attr(debug_assertions, ignore)]
2328    fn test_empty_compile_errors_to_single_error() {
2329        // Converting an empty CompileErrors should not panic in release;
2330        // instead it should return an InternalError.
2331        let empty = CompileErrors::new();
2332        let error: CompileError = empty.into();
2333
2334        // Should get an InternalError with a descriptive message
2335        match &error.kind {
2336            ErrorKind::InternalError(msg) => {
2337                assert!(msg.contains("empty error collection"));
2338            }
2339            other => panic!("expected InternalError, got {:?}", other),
2340        }
2341    }
2342
2343    #[test]
2344    fn test_compile_errors_display_empty() {
2345        let errors = CompileErrors::new();
2346        assert_eq!(errors.to_string(), "no errors");
2347    }
2348
2349    #[test]
2350    fn test_compile_errors_display_single() {
2351        let errors =
2352            CompileErrors::from_error(CompileError::without_span(ErrorKind::InvalidInteger));
2353        assert_eq!(errors.to_string(), "invalid integer literal");
2354    }
2355
2356    #[test]
2357    fn test_compile_errors_display_multiple() {
2358        let mut errors = CompileErrors::new();
2359        errors.push(CompileError::without_span(ErrorKind::InvalidInteger));
2360        errors.push(CompileError::without_span(ErrorKind::NoMainFunction));
2361        assert_eq!(
2362            errors.to_string(),
2363            "invalid integer literal (and 1 more error)"
2364        );
2365
2366        errors.push(CompileError::without_span(ErrorKind::BreakOutsideLoop));
2367        assert_eq!(
2368            errors.to_string(),
2369            "invalid integer literal (and 2 more errors)"
2370        );
2371    }
2372
2373    // ========================================================================
2374    // Error code tests
2375    // ========================================================================
2376
2377    #[test]
2378    fn test_error_code_display() {
2379        assert_eq!(ErrorCode::TYPE_MISMATCH.to_string(), "E0206");
2380        assert_eq!(ErrorCode::UNDEFINED_VARIABLE.to_string(), "E0201");
2381        assert_eq!(ErrorCode::INTERNAL_ERROR.to_string(), "E9000");
2382        assert_eq!(ErrorCode(1).to_string(), "E0001");
2383        assert_eq!(ErrorCode(42).to_string(), "E0042");
2384        assert_eq!(ErrorCode(1234).to_string(), "E1234");
2385    }
2386
2387    #[test]
2388    fn test_error_kind_codes() {
2389        let cases: Vec<(ErrorKind, ErrorCode)> = vec![
2390            // Lexer
2391            (
2392                ErrorKind::UnexpectedCharacter('@'),
2393                ErrorCode::UNEXPECTED_CHARACTER,
2394            ),
2395            (ErrorKind::InvalidInteger, ErrorCode::INVALID_INTEGER),
2396            (ErrorKind::InvalidFloat, ErrorCode::INVALID_FLOAT),
2397            (
2398                ErrorKind::InvalidStringEscape('n'),
2399                ErrorCode::INVALID_STRING_ESCAPE,
2400            ),
2401            (
2402                ErrorKind::UnterminatedString,
2403                ErrorCode::UNTERMINATED_STRING,
2404            ),
2405            // Parser
2406            (
2407                ErrorKind::UnexpectedToken {
2408                    expected: "identifier".into(),
2409                    found: "+".into(),
2410                },
2411                ErrorCode::UNEXPECTED_TOKEN,
2412            ),
2413            (
2414                ErrorKind::UnexpectedEof {
2415                    expected: "}".into(),
2416                },
2417                ErrorCode::UNEXPECTED_EOF,
2418            ),
2419            (
2420                ErrorKind::ParseError("custom error".into()),
2421                ErrorCode::PARSE_ERROR,
2422            ),
2423            // Semantic
2424            (ErrorKind::NoMainFunction, ErrorCode::NO_MAIN_FUNCTION),
2425            (
2426                ErrorKind::UndefinedVariable("x".into()),
2427                ErrorCode::UNDEFINED_VARIABLE,
2428            ),
2429            (
2430                ErrorKind::UndefinedFunction("foo".into()),
2431                ErrorCode::UNDEFINED_FUNCTION,
2432            ),
2433            (
2434                ErrorKind::TypeMismatch {
2435                    expected: "i32".into(),
2436                    found: "bool".into(),
2437                },
2438                ErrorCode::TYPE_MISMATCH,
2439            ),
2440            // Control flow
2441            (ErrorKind::BreakOutsideLoop, ErrorCode::BREAK_OUTSIDE_LOOP),
2442            (
2443                ErrorKind::ContinueOutsideLoop,
2444                ErrorCode::CONTINUE_OUTSIDE_LOOP,
2445            ),
2446            // Internal
2447            (
2448                ErrorKind::InternalError("bug".into()),
2449                ErrorCode::INTERNAL_ERROR,
2450            ),
2451            (
2452                ErrorKind::InternalCodegenError("codegen bug".into()),
2453                ErrorCode::INTERNAL_CODEGEN_ERROR,
2454            ),
2455        ];
2456        for (kind, expected_code) in cases {
2457            assert_eq!(kind.code(), expected_code, "wrong code for: {kind}");
2458        }
2459    }
2460
2461    #[test]
2462    fn test_error_code_equality() {
2463        assert_eq!(ErrorCode::TYPE_MISMATCH, ErrorCode(206));
2464        assert_ne!(ErrorCode::TYPE_MISMATCH, ErrorCode::UNDEFINED_VARIABLE);
2465    }
2466
2467    #[test]
2468    fn test_error_code_hash() {
2469        use rustc_hash::FxHashSet as HashSet;
2470        let mut set = HashSet::default();
2471        set.insert(ErrorCode::TYPE_MISMATCH);
2472        set.insert(ErrorCode::UNDEFINED_VARIABLE);
2473        assert_eq!(set.len(), 2);
2474        assert!(set.contains(&ErrorCode::TYPE_MISMATCH));
2475    }
2476
2477    // ========================================================================
2478    // ErrorKind size and boxing policy tests
2479    // ========================================================================
2480
2481    #[test]
2482    fn test_error_kind_size() {
2483        // Measure the size of ErrorKind to understand current memory usage
2484        let size = std::mem::size_of::<ErrorKind>();
2485
2486        // Enforce the size limit to prevent regression.
2487        // Target: ≤ 64 bytes (enum tag + largest inline variant)
2488        //
2489        // Current: 56 bytes (as of 2026-01-11)
2490        // - Enum discriminant: 8 bytes
2491        // - Largest inline variant: 48 bytes (2 Strings or 2 Cows)
2492        //
2493        // If this fails, check which variants are > 48 bytes and box them.
2494        assert!(
2495            size <= 64,
2496            "ErrorKind is {} bytes, exceeds 64-byte limit. \
2497             Consider boxing large variants (≥ 72 bytes / 3+ Strings). \
2498             See the boxing policy documentation above ErrorKind.",
2499            size
2500        );
2501    }
2502
2503    #[test]
2504    fn test_error_kind_variant_sizes() {
2505        use std::mem::size_of;
2506
2507        // Measure individual variant data sizes to identify which ones should be boxed
2508        println!("String: {} bytes", size_of::<String>());
2509        println!("Vec<String>: {} bytes", size_of::<Vec<String>>());
2510        println!(
2511            "Cow<'static, str>: {} bytes",
2512            size_of::<Cow<'static, str>>()
2513        );
2514
2515        // Inline variants (currently unboxed)
2516        println!("TypeMismatch data: {} bytes", size_of::<(String, String)>());
2517        println!("UnknownField data: {} bytes", size_of::<(String, String)>());
2518        println!(
2519            "DuplicateField data: {} bytes",
2520            size_of::<(String, String)>()
2521        );
2522        println!(
2523            "ModuleNotFound data: {} bytes",
2524            size_of::<(String, Vec<String>)>()
2525        );
2526
2527        // Boxed variants (currently boxed)
2528        println!(
2529            "MissingFieldsError: {} bytes",
2530            size_of::<MissingFieldsError>()
2531        );
2532        println!(
2533            "IntrinsicTypeMismatchError: {} bytes",
2534            size_of::<IntrinsicTypeMismatchError>()
2535        );
2536        println!(
2537            "FieldWrongOrderError: {} bytes",
2538            size_of::<FieldWrongOrderError>()
2539        );
2540    }
2541}