Error Handling Specification

This document defines the comprehensive error handling system for the Phaser compiler, including error types, diagnostic formatting, recovery strategies, and user experience guidelines.

This is a compiler implementation document. For language design and user-facing features, see the docs directory.

Overview

Phaser’s error handling system is designed around the PhaserResult<T> type and provides:

  • Comprehensive error information with source locations
  • Actionable error messages with suggested fixes
  • Graceful error recovery for continued compilation
  • Rich diagnostic output for development tools

Core Error Types

Result Type

pub type PhaserResult<T> = Result<T, PhaserError>;
 
#[derive(Debug, Clone, PartialEq)]
pub struct PhaserError {
    pub kind: ErrorKind,
    pub span: Span,
    pub message: String,
    pub suggestions: Vec<Suggestion>,
    pub related: Vec<RelatedError>,
    pub severity: Severity,
}
 
#[derive(Debug, Clone, PartialEq)]
pub enum Severity {
    Error,
    Warning,
    Info,
    Hint,
}
 
#[derive(Debug, Clone, PartialEq)]
pub struct Suggestion {
    pub message: String,
    pub replacement: Option<TextReplacement>,
    pub applicability: Applicability,
}
 
#[derive(Debug, Clone, PartialEq)]
pub enum Applicability {
    Automatic,      // Can be applied automatically
    Suggested,      // Likely correct, user should review
    Speculative,    // Might be correct, user must verify
}
 
#[derive(Debug, Clone, PartialEq)]
pub struct TextReplacement {
    pub span: Span,
    pub text: String,
}
 
#[derive(Debug, Clone, PartialEq)]
pub struct RelatedError {
    pub span: Span,
    pub message: String,
    pub severity: Severity,
}

Error Categories

#[derive(Debug, Clone, PartialEq)]
pub enum ErrorKind {
    // Lexical errors
    Lexical(LexicalError),
    
    // Syntax errors
    Syntax(SyntaxError),
    
    // Semantic errors
    Semantic(SemanticError),
    
    // Type errors
    Type(TypeError),
    
    // Compile-time evaluation errors
    Comptime(ComptimeError),
    
    // Code generation errors
    Codegen(CodegenError),
    
    // I/O and system errors
    Io(IoError),
    
    // Internal compiler errors
    Internal(InternalError),
}

Phase-Specific Errors

Lexical Errors

#[derive(Debug, Clone, PartialEq)]
pub enum LexicalError {
    UnexpectedCharacter {
        character: char,
        expected: Option<String>,
    },
    UnterminatedString {
        quote_type: QuoteType,
        start_span: Span,
    },
    UnterminatedBlockComment {
        start_span: Span,
    },
    InvalidEscapeSequence {
        sequence: String,
        valid_sequences: Vec<String>,
    },
    InvalidNumericLiteral {
        literal: String,
        reason: NumericLiteralError,
    },
    InvalidUnicodeEscape {
        escape: String,
        reason: UnicodeEscapeError,
    },
    IntegerOverflow {
        literal: String,
        max_value: String,
    },
}
 
#[derive(Debug, Clone, PartialEq)]
pub enum QuoteType {
    Double,  // "
    Single,  // '
}
 
#[derive(Debug, Clone, PartialEq)]
pub enum NumericLiteralError {
    InvalidDigit { digit: char, base: u8 },
    EmptyExponent,
    InvalidSuffix { suffix: String },
    MissingDigits,
}

Syntax Errors

#[derive(Debug, Clone, PartialEq)]
pub enum SyntaxError {
    UnexpectedToken {
        found: TokenType,
        expected: Vec<TokenType>,
    },
    MissingToken {
        expected: TokenType,
        context: String,
    },
    ExtraToken {
        token: TokenType,
    },
    InvalidExpression {
        context: String,
    },
    MismatchedDelimiter {
        opening: Delimiter,
        closing: Delimiter,
        opening_span: Span,
    },
    IncompleteExpression {
        expression_type: String,
    },
    InvalidPattern {
        pattern_context: String,
    },
    InvalidItemInContext {
        item_type: String,
        context: String,
    },
}

Semantic Errors

#[derive(Debug, Clone, PartialEq)]
pub enum SemanticError {
    UndefinedVariable {
        name: String,
        similar_names: Vec<String>,
    },
    UndefinedFunction {
        name: String,
        similar_names: Vec<String>,
    },
    UndefinedType {
        name: String,
        similar_names: Vec<String>,
    },
    DuplicateDefinition {
        name: String,
        first_definition: Span,
        definition_type: String,
    },
    InvalidVisibility {
        item_type: String,
        visibility: String,
        reason: String,
    },
    CircularDependency {
        cycle: Vec<String>,
    },
    InvalidMutability {
        operation: String,
        reason: String,
    },
    UnreachableCode {
        reason: String,
    },
    MissingReturn {
        function_name: String,
        return_type: String,
    },
    InvalidBreakContinue {
        keyword: String,
        context: String,
    },
}

Type Errors

#[derive(Debug, Clone, PartialEq)]
pub enum TypeError {
    TypeMismatch {
        expected: String,
        found: String,
        context: String,
    },
    CannotInferType {
        expression: String,
        suggestions: Vec<String>,
    },
    InvalidOperation {
        operation: String,
        left_type: String,
        right_type: Option<String>,
    },
    FieldNotFound {
        field_name: String,
        type_name: String,
        available_fields: Vec<String>,
    },
    MethodNotFound {
        method_name: String,
        type_name: String,
        available_methods: Vec<String>,
    },
    ArityMismatch {
        expected: usize,
        found: usize,
        function_name: String,
    },
    InvalidCast {
        from_type: String,
        to_type: String,
        reason: String,
    },
    TraitNotImplemented {
        trait_name: String,
        type_name: String,
        missing_methods: Vec<String>,
    },
    LifetimeError {
        kind: LifetimeErrorKind,
    },
}
 
#[derive(Debug, Clone, PartialEq)]
pub enum LifetimeErrorKind {
    BorrowedValueDoesNotLiveEnough {
        borrowed_span: Span,
        dropped_span: Span,
    },
    CannotBorrowAsMutable {
        reason: String,
    },
    UseAfterMove {
        moved_span: Span,
        used_span: Span,
    },
}

Compile-time Errors

#[derive(Debug, Clone, PartialEq)]
pub enum ComptimeError {
    InfiniteLoop {
        iteration_limit: usize,
    },
    StackOverflow {
        call_depth: usize,
        function_name: String,
    },
    InvalidComptimeOperation {
        operation: String,
        reason: String,
    },
    ComptimeValueNotAvailable {
        expression: String,
        reason: String,
    },
    ResourceLimitExceeded {
        resource: String,
        limit: String,
        used: String,
    },
    SideEffectInComptimeContext {
        operation: String,
    },
    NonDeterministicBehavior {
        operation: String,
        reason: String,
    },
}

Error Construction and Conversion

Error Builder

pub struct ErrorBuilder {
    kind: ErrorKind,
    span: Span,
    message: String,
    suggestions: Vec<Suggestion>,
    related: Vec<RelatedError>,
    severity: Severity,
}
 
impl ErrorBuilder {
    pub fn new(kind: ErrorKind, span: Span) -> Self {
        Self {
            kind,
            span,
            message: String::new(),
            suggestions: Vec::new(),
            related: Vec::new(),
            severity: Severity::Error,
        }
    }
    
    pub fn message<S: Into<String>>(mut self, message: S) -> Self {
        self.message = message.into();
        self
    }
    
    pub fn suggestion<S: Into<String>>(mut self, message: S) -> Self {
        self.suggestions.push(Suggestion {
            message: message.into(),
            replacement: None,
            applicability: Applicability::Suggested,
        });
        self
    }
    
    pub fn suggestion_with_replacement<S: Into<String>>(
        mut self,
        message: S,
        span: Span,
        replacement: S,
        applicability: Applicability,
    ) -> Self {
        self.suggestions.push(Suggestion {
            message: message.into(),
            replacement: Some(TextReplacement {
                span,
                text: replacement.into(),
            }),
            applicability,
        });
        self
    }
    
    pub fn related<S: Into<String>>(mut self, span: Span, message: S) -> Self {
        self.related.push(RelatedError {
            span,
            message: message.into(),
            severity: Severity::Info,
        });
        self
    }
    
    pub fn severity(mut self, severity: Severity) -> Self {
        self.severity = severity;
        self
    }
    
    pub fn build(self) -> PhaserError {
        PhaserError {
            kind: self.kind,
            span: self.span,
            message: self.message,
            suggestions: self.suggestions,
            related: self.related,
            severity: self.severity,
        }
    }
}

Error Conversion Traits

impl From<LexicalError> for PhaserError {
    fn from(error: LexicalError) -> Self {
        // Convert lexical error to PhaserError with appropriate message and suggestions
    }
}
 
impl From<SyntaxError> for PhaserError {
    fn from(error: SyntaxError) -> Self {
        // Convert syntax error to PhaserError with appropriate message and suggestions
    }
}
 
// Additional From implementations for each error type...

Diagnostic Formatting

Diagnostic Output

pub struct DiagnosticFormatter {
    source_map: SourceMap,
    color_config: ColorConfig,
}
 
impl DiagnosticFormatter {
    pub fn format_error(&self, error: &PhaserError) -> String {
        let mut output = String::new();
        
        // Error header with severity and location
        output.push_str(&self.format_header(error));
        
        // Source code snippet with highlighting
        output.push_str(&self.format_source_snippet(error));
        
        // Error message
        output.push_str(&self.format_message(error));
        
        // Suggestions
        for suggestion in &error.suggestions {
            output.push_str(&self.format_suggestion(suggestion));
        }
        
        // Related errors
        for related in &error.related {
            output.push_str(&self.format_related(related));
        }
        
        output
    }
    
    fn format_source_snippet(&self, error: &PhaserError) -> String {
        // Format source code with line numbers, highlighting, and carets
        // Example output:
        //   --> src/main.ph:5:12
        //    |
        //  5 |     let x = unknown_variable;
        //    |             ^^^^^^^^^^^^^^^^ undefined variable
        //    |
        //    = help: did you mean `known_variable`?
    }
}

Example Error Output

error[E0425]: cannot find value `unknow_variable` in this scope
  --> src/main.ph:5:13
   |
 5 |     let x = unknow_variable;
   |             ^^^^^^^^^^^^^^^ not found in this scope
   |
help: a local variable with a similar name exists
   |
 5 |     let x = unknown_variable;
   |             ~~~~~~~~~~~~~~~~

error[E0308]: mismatched types
  --> src/main.ph:8:18
   |
 8 |     let result = add_numbers("hello", 42);
   |                  ^^^^^^^^^^^ expected `i32`, found `&str`
   |
note: function defined here
  --> src/main.ph:2:1
   |
 2 | fn add_numbers(a: i32, b: i32) -> i32 {
   | ^^^^^^^^^^^^^ ------  ------ expected `i32`
   |               |
   |               expected `i32`

Error Recovery Strategies

Parser Recovery

pub trait ErrorRecovery {
    fn recover_from_error(&mut self, error: SyntaxError) -> PhaserResult<()>;
    fn synchronize_to_statement(&mut self);
    fn synchronize_to_item(&mut self);
    fn skip_to_delimiter(&mut self, delimiter: Delimiter);
}
 
impl ErrorRecovery for Parser {
    fn recover_from_error(&mut self, error: SyntaxError) -> PhaserResult<()> {
        match error {
            SyntaxError::UnexpectedToken { .. } => {
                self.synchronize_to_statement();
            }
            SyntaxError::MismatchedDelimiter { .. } => {
                self.skip_to_delimiter(Delimiter::RightBrace);
            }
            _ => {
                // Default recovery strategy
                self.advance_token();
            }
        }
        Ok(())
    }
}

Semantic Analysis Recovery

  • Continue analysis after type errors when possible
  • Use error types (!) to prevent cascading errors
  • Maintain symbol table consistency during recovery
  • Provide partial results for IDE integration

Testing Requirements

Error Testing Framework

#[cfg(test)]
mod error_tests {
    use super::*;
    
    #[test]
    fn test_undefined_variable_error() {
        let source = "let x = unknown_var;";
        let result = compile_source(source);
        
        assert!(result.is_err());
        let error = result.unwrap_err();
        
        assert_matches!(error.kind, ErrorKind::Semantic(SemanticError::UndefinedVariable { .. }));
        assert_eq!(error.span.start.line, 1);
        assert_eq!(error.span.start.column, 9);
        assert!(!error.suggestions.is_empty());
    }
    
    #[test]
    fn test_error_recovery() {
        let source = r#"
            fn main() {
                let x = unknown_var; // Error here
                let y = 42;          // Should still parse
            }
        "#;
        
        let result = parse_with_recovery(source);
        assert!(result.has_errors());
        assert!(result.ast.is_some()); // Should have partial AST
    }
}

Error Message Quality Tests

  • Verify error messages are actionable and clear
  • Test suggestion quality and applicability
  • Ensure consistent formatting across error types
  • Validate source location accuracy
  • Test error recovery effectiveness

Integration with Development Tools

Language Server Protocol

pub fn error_to_diagnostic(error: &PhaserError) -> lsp_types::Diagnostic {
    lsp_types::Diagnostic {
        range: span_to_range(&error.span),
        severity: Some(severity_to_lsp(error.severity)),
        code: Some(lsp_types::NumberOrString::String(error_code(&error.kind))),
        message: error.message.clone(),
        related_information: Some(
            error.related.iter()
                .map(|r| related_to_lsp(r))
                .collect()
        ),
        ..Default::default()
    }
}

IDE Integration

  • Provide quick fixes for common errors
  • Support error highlighting and tooltips
  • Enable incremental error checking
  • Offer refactoring suggestions based on errors

Performance Considerations

  • Lazy error message formatting
  • Efficient source location tracking
  • Minimal allocation during error construction
  • Fast error recovery for interactive use
  • Batch error reporting for better performance