gruel_error/
lib.rs

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