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:
2026-02-13 04:01:49 -05:00
parent 66132779cc
commit d37f0fb096
5 changed files with 703 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ use crate::ast::{
LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span, Statement,
TypeDecl, TypeExpr, UnaryOp, VariantFields,
};
use crate::diagnostics::{Diagnostic, Severity};
use crate::modules::ModuleLoader;
use crate::types::{
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, PropertySet, Type, TypeEnv,
@@ -30,6 +31,72 @@ impl std::fmt::Display for TypeError {
}
}
impl TypeError {
/// Convert to a rich diagnostic for Elm-style error display
pub fn to_diagnostic(&self) -> Diagnostic {
// Categorize the error and extract hints
let (title, hints) = categorize_type_error(&self.message);
Diagnostic {
severity: Severity::Error,
title,
message: self.message.clone(),
span: self.span,
hints,
}
}
}
/// Categorize a type error message to provide better titles and hints
fn categorize_type_error(message: &str) -> (String, Vec<String>) {
let message_lower = message.to_lowercase();
if message_lower.contains("type mismatch") {
(
"Type Mismatch".to_string(),
vec!["Check that the types on both sides of the expression are compatible.".to_string()],
)
} else if message_lower.contains("undefined variable") || message_lower.contains("not found") {
(
"Unknown Name".to_string(),
vec![
"Check the spelling of the name.".to_string(),
"Make sure the variable is defined before use.".to_string(),
],
)
} else if message_lower.contains("cannot unify") {
(
"Type Mismatch".to_string(),
vec!["The types are not compatible. Check your function arguments and return types.".to_string()],
)
} else if message_lower.contains("expected") && message_lower.contains("argument") {
(
"Wrong Number of Arguments".to_string(),
vec!["Check the function signature and provide the correct number of arguments.".to_string()],
)
} else if message_lower.contains("pure") && message_lower.contains("effect") {
(
"Purity Violation".to_string(),
vec![
"Functions marked 'is pure' cannot perform effects.".to_string(),
"Remove the 'is pure' annotation or handle the effects.".to_string(),
],
)
} else if message_lower.contains("effect") && message_lower.contains("unhandled") {
(
"Unhandled Effect".to_string(),
vec!["Use a 'handle' expression to provide an implementation for this effect.".to_string()],
)
} else if message_lower.contains("recursive") {
(
"Invalid Recursion".to_string(),
vec!["Check that recursive calls have proper base cases.".to_string()],
)
} else {
("Type Error".to_string(), vec![])
}
}
/// Type checker
pub struct TypeChecker {
env: TypeEnv,