Add Elm-style error diagnostics
Implement beautiful, informative error messages inspired by Elm: - Rich diagnostic rendering with source code snippets - Colored output with proper underlines showing error locations - Categorized error titles (Type Mismatch, Unknown Name, etc.) - Contextual hints and suggestions for common errors - Support for type errors, runtime errors, and parse errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
85
src/main.rs
85
src/main.rs
@@ -1,6 +1,7 @@
|
||||
//! Lux - A functional programming language with first-class effects
|
||||
|
||||
mod ast;
|
||||
mod diagnostics;
|
||||
mod interpreter;
|
||||
mod lexer;
|
||||
mod modules;
|
||||
@@ -9,6 +10,7 @@ mod schema;
|
||||
mod typechecker;
|
||||
mod types;
|
||||
|
||||
use diagnostics::render;
|
||||
use interpreter::Interpreter;
|
||||
use parser::Parser;
|
||||
use std::io::{self, Write};
|
||||
@@ -90,7 +92,8 @@ fn run_file(path: &str) {
|
||||
let mut checker = TypeChecker::new();
|
||||
if let Err(errors) = checker.check_program_with_modules(&program, &loader) {
|
||||
for error in errors {
|
||||
eprintln!("Type error: {}", error);
|
||||
let diagnostic = error.to_diagnostic();
|
||||
eprint!("{}", render(&diagnostic, &source, Some(path)));
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -103,7 +106,8 @@ fn run_file(path: &str) {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Runtime error: {}", e);
|
||||
let diagnostic = e.to_diagnostic();
|
||||
eprint!("{}", render(&diagnostic, &source, Some(path)));
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -850,4 +854,81 @@ c")"#;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("pure but has effects"));
|
||||
}
|
||||
|
||||
// Diagnostic rendering tests
|
||||
mod diagnostic_tests {
|
||||
use crate::diagnostics::{render_diagnostic_plain, Diagnostic, Severity};
|
||||
use crate::ast::Span;
|
||||
use crate::typechecker::TypeError;
|
||||
use crate::interpreter::RuntimeError;
|
||||
|
||||
#[test]
|
||||
fn test_type_error_to_diagnostic() {
|
||||
let error = TypeError {
|
||||
message: "Type mismatch: expected Int, got String".to_string(),
|
||||
span: Span { start: 10, end: 20 },
|
||||
};
|
||||
let diag = error.to_diagnostic();
|
||||
|
||||
assert_eq!(diag.severity, Severity::Error);
|
||||
assert_eq!(diag.title, "Type Mismatch");
|
||||
assert!(diag.hints.len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_error_to_diagnostic() {
|
||||
let error = RuntimeError {
|
||||
message: "Division by zero".to_string(),
|
||||
span: Some(Span { start: 5, end: 10 }),
|
||||
};
|
||||
let diag = error.to_diagnostic();
|
||||
|
||||
assert_eq!(diag.severity, Severity::Error);
|
||||
assert_eq!(diag.title, "Division by Zero");
|
||||
assert!(diag.hints.len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diagnostic_render_with_real_code() {
|
||||
let source = "fn add(a: Int, b: Int): Int = a + b\nlet result = add(1, \"two\")";
|
||||
let diag = Diagnostic {
|
||||
severity: Severity::Error,
|
||||
title: "Type Mismatch".to_string(),
|
||||
message: "Expected Int but got String".to_string(),
|
||||
span: Span { start: 56, end: 61 },
|
||||
hints: vec!["The second argument should be an Int.".to_string()],
|
||||
};
|
||||
|
||||
let output = render_diagnostic_plain(&diag, source, Some("example.lux"));
|
||||
|
||||
assert!(output.contains("ERROR"));
|
||||
assert!(output.contains("example.lux"));
|
||||
assert!(output.contains("Type Mismatch"));
|
||||
assert!(output.contains("\"two\""));
|
||||
assert!(output.contains("Hint:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_undefined_variable_categorization() {
|
||||
let error = TypeError {
|
||||
message: "Undefined variable: foobar".to_string(),
|
||||
span: Span { start: 0, end: 6 },
|
||||
};
|
||||
let diag = error.to_diagnostic();
|
||||
|
||||
assert_eq!(diag.title, "Unknown Name");
|
||||
assert!(diag.hints.iter().any(|h| h.contains("spelling")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_purity_violation_categorization() {
|
||||
let error = TypeError {
|
||||
message: "Function 'foo' is declared as pure but has effects: {Console}".to_string(),
|
||||
span: Span { start: 0, end: 10 },
|
||||
};
|
||||
let diag = error.to_diagnostic();
|
||||
|
||||
assert_eq!(diag.title, "Purity Violation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user