fix: resolve all stress test bugs

- Record equality: add Record case to values_equal in interpreter
- Invalid escapes: error on unknown escape sequences in lexer
- Unknown effects: validate effect names in check_function with suggestions
- Circular types: add DFS cycle detection in check_type_cycles
- Parser: require | for enum variants, enabling proper type alias syntax

All 265 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 02:45:52 -05:00
parent 07a35f1829
commit c81349d82c
6 changed files with 224 additions and 27 deletions

View File

@@ -532,6 +532,9 @@ impl TypeChecker {
self.collect_declaration(decl);
}
// Check for circular type definitions
self.check_type_cycles(program);
// Second pass: type check all declarations
for decl in &program.declarations {
self.check_declaration(decl);
@@ -544,6 +547,130 @@ impl TypeChecker {
}
}
/// Check for circular type alias definitions
fn check_type_cycles(&mut self, program: &Program) {
use std::collections::HashSet;
// Build a map of type alias dependencies
let mut alias_deps: HashMap<String, Vec<String>> = HashMap::new();
for decl in &program.declarations {
if let Declaration::Type(type_decl) = decl {
if let ast::TypeDef::Alias(type_expr) = &type_decl.definition {
let deps = self.collect_type_references(type_expr);
alias_deps.insert(type_decl.name.name.clone(), deps);
}
}
}
// Check for cycles using DFS
fn has_cycle(
name: &str,
alias_deps: &HashMap<String, Vec<String>>,
visiting: &mut HashSet<String>,
visited: &mut HashSet<String>,
cycle_path: &mut Vec<String>,
) -> bool {
if visiting.contains(name) {
cycle_path.push(name.to_string());
return true;
}
if visited.contains(name) {
return false;
}
visiting.insert(name.to_string());
cycle_path.push(name.to_string());
if let Some(deps) = alias_deps.get(name) {
for dep in deps {
if alias_deps.contains_key(dep) {
if has_cycle(dep, alias_deps, visiting, visited, cycle_path) {
return true;
}
}
}
}
visiting.remove(name);
cycle_path.pop();
visited.insert(name.to_string());
false
}
let mut visited = HashSet::new();
for type_name in alias_deps.keys() {
let mut visiting = HashSet::new();
let mut cycle_path = Vec::new();
if has_cycle(type_name, &alias_deps, &mut visiting, &mut visited, &mut cycle_path) {
// Find the span for this type declaration
let span = program.declarations.iter().find_map(|d| {
if let Declaration::Type(t) = d {
if t.name.name == *type_name {
return Some(t.span);
}
}
None
}).unwrap_or(Span::default());
self.errors.push(TypeError {
message: format!(
"Circular type definition detected: {}",
cycle_path.join(" -> ")
),
span,
});
}
}
}
/// Collect all type names referenced in a type expression
fn collect_type_references(&self, type_expr: &TypeExpr) -> Vec<String> {
let mut refs = Vec::new();
self.collect_type_refs_helper(type_expr, &mut refs);
refs
}
fn collect_type_refs_helper(&self, type_expr: &TypeExpr, refs: &mut Vec<String>) {
match type_expr {
TypeExpr::Named(ident) => {
// Only include user-defined types, not builtins
match ident.name.as_str() {
"Int" | "Float" | "Bool" | "String" | "Char" | "Unit" | "_" => {}
name => refs.push(name.to_string()),
}
}
TypeExpr::App(constructor, args) => {
self.collect_type_refs_helper(constructor, refs);
for arg in args {
self.collect_type_refs_helper(arg, refs);
}
}
TypeExpr::Function { params, return_type, .. } => {
for p in params {
self.collect_type_refs_helper(p, refs);
}
self.collect_type_refs_helper(return_type, refs);
}
TypeExpr::Tuple(elements) => {
for e in elements {
self.collect_type_refs_helper(e, refs);
}
}
TypeExpr::Record(fields) => {
for f in fields {
self.collect_type_refs_helper(&f.typ, refs);
}
}
TypeExpr::Unit => {}
TypeExpr::Versioned { base, .. } => {
self.collect_type_refs_helper(base, refs);
}
}
}
/// Type check a program with module support
pub fn check_program_with_modules(
&mut self,
@@ -790,6 +917,28 @@ impl TypeChecker {
}
fn check_function(&mut self, func: &FunctionDecl) {
// Validate that all declared effects exist
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test"];
for effect in &func.effects {
let is_builtin = builtin_effects.contains(&effect.name.as_str());
let is_defined = self.env.lookup_effect(&effect.name).is_some();
if !is_builtin && !is_defined {
// Find similar effect names for suggestion
let mut available: Vec<&str> = builtin_effects.to_vec();
available.extend(self.env.effects.keys().map(|s| s.as_str()));
let suggestions = find_similar_names(&effect.name, available, 2);
let mut message = format!("Unknown effect: {}", effect.name);
if let Some(hint) = format_did_you_mean(&suggestions) {
message.push_str(&format!(". {}", hint));
}
self.errors.push(TypeError {
message,
span: effect.span,
});
}
}
// Set up the environment with parameters
let mut local_env = self.env.clone();
for param in &func.params {