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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user