fix: improve C backend robustness, reduce compilation errors by 61%

- Fix closure captured variable types: look up actual types from var_types
  instead of hardcoding LuxInt for all captured variables
- Register function parameters in var_types so closures can find their types
- Replace is_string_expr() with infer_expr_type() for more accurate string
  detection in binary ops (concat, comparison)
- Add missing String operations to infer_expr_type (substring, indexOf, etc.)
- Add module method call type inference (String.*, List.*, Int.*, Float.*)
- Add built-in Result type (Ok/Err) to C prelude alongside Option
- Register Ok/Err/Some/None in variant_to_type and variant_field_types
- Fix variable scoping: use if-statement pattern instead of ternary when
  branches emit statements (prevents redefinition of h2/h3 etc.)
- Add RC scope management for if-else branches and match arms to prevent
  undeclared variable errors from cleanup code
- Add infer_pattern_binding_type for better match result type inference
- Add expr_emits_statements helper to detect statement-emitting expressions
- Add infer_option_inner_type for String.indexOf (returns Option<Int>)

Reduces blu-site compilation errors from 286 to 111 (remaining are mostly
unsupported json effect and function-as-value references).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 17:56:27 -05:00
parent 73b5eee664
commit d8871acf7e

View File

@@ -196,6 +196,18 @@ impl CBackend {
self.closures.clear();
self.emit_prelude();
// Register built-in variant → type mappings
self.variant_to_type.insert("Some".to_string(), "Option".to_string());
self.variant_to_type.insert("None".to_string(), "Option".to_string());
self.variant_to_type.insert("Ok".to_string(), "Result".to_string());
self.variant_to_type.insert("Err".to_string(), "Result".to_string());
// Register built-in variant field types
self.variant_field_types.insert(("Option".to_string(), "Some".to_string()), vec!["void*".to_string()]);
self.variant_field_types.insert(("Option".to_string(), "None".to_string()), vec![]);
self.variant_field_types.insert(("Result".to_string(), "Ok".to_string()), vec!["void*".to_string()]);
self.variant_field_types.insert(("Result".to_string(), "Err".to_string()), vec!["void*".to_string()]);
// First pass: collect all function names, types, and effects
for decl in &program.declarations {
match decl {
@@ -359,8 +371,8 @@ impl CBackend {
// Check for string comparison - use strcmp instead of pointer comparison
if matches!(op, BinaryOp::Eq | BinaryOp::Ne) {
let left_is_string = self.is_string_expr(left);
let right_is_string = self.is_string_expr(right);
let left_is_string = self.infer_expr_type(left).as_deref() == Some("LuxString");
let right_is_string = self.infer_expr_type(right).as_deref() == Some("LuxString");
if left_is_string || right_is_string {
if matches!(op, BinaryOp::Eq) {
return Ok(format!("(strcmp({}, {}) == 0)", l, r));
@@ -679,6 +691,13 @@ impl CBackend {
self.writeln("typedef struct { void* field0; } Option_Some_Data;");
self.writeln("typedef struct { Option_Tag tag; union { Option_Some_Data some; } data; } Option;");
self.writeln("");
// Built-in Result type
self.writeln("// Built-in Result type");
self.writeln("typedef enum { Result_TAG_OK, Result_TAG_ERR } Result_Tag;");
self.writeln("typedef struct { void* field0; } Result_Ok_Data;");
self.writeln("typedef struct { void* field0; } Result_Err_Data;");
self.writeln("typedef struct { Result_Tag tag; union { Result_Ok_Data ok; Result_Err_Data err; } data; } Result;");
self.writeln("");
self.writeln("// === Evidence Passing Types ===");
self.writeln("// These enable O(1) effect handler lookup instead of O(n) stack search.");
@@ -2018,6 +2037,7 @@ impl CBackend {
}
/// Check if an expression is a string expression (for concatenation detection)
#[allow(dead_code)]
fn is_string_expr(&self, expr: &Expr) -> bool {
match expr {
// String literals
@@ -2169,6 +2189,14 @@ impl CBackend {
// Push function scope for RC tracking
self.push_rc_scope();
// Register function parameter types in var_types so closures can look them up
for param in &func.params {
let escaped = self.escape_c_keyword(&param.name.name);
if let Ok(c_type) = self.type_expr_to_c(&param.typ) {
self.var_types.insert(escaped, c_type);
}
}
// Emit function body
let result = self.emit_expr(&func.body)?;
@@ -2310,8 +2338,8 @@ impl CBackend {
// Check for string concatenation
if matches!(op, BinaryOp::Add) {
let left_is_string = self.is_string_expr(left);
let right_is_string = self.is_string_expr(right);
let left_is_string = self.infer_expr_type(left).as_deref() == Some("LuxString");
let right_is_string = self.infer_expr_type(right).as_deref() == Some("LuxString");
if left_is_string || right_is_string {
// If args are function calls returning String, store in temp for cleanup
let (actual_l, l_temp) = if left_is_string_call && !self.is_rc_temp(&l) {
@@ -2355,8 +2383,8 @@ impl CBackend {
// Check for string comparison - use strcmp instead of pointer comparison
if matches!(op, BinaryOp::Eq | BinaryOp::Ne) {
let left_is_string = self.is_string_expr(left);
let right_is_string = self.is_string_expr(right);
let left_is_string = self.infer_expr_type(left).as_deref() == Some("LuxString");
let right_is_string = self.infer_expr_type(right).as_deref() == Some("LuxString");
if left_is_string || right_is_string {
if matches!(op, BinaryOp::Eq) {
return Ok(format!("(strcmp({}, {}) == 0)", l, r));
@@ -2437,7 +2465,10 @@ impl CBackend {
let then_creates_rc = self.expr_creates_rc_value(then_branch);
let else_creates_rc = self.expr_creates_rc_value(else_branch);
if then_creates_rc || else_creates_rc {
let then_emits_stmts = self.expr_emits_statements(then_branch);
let else_emits_stmts = self.expr_emits_statements(else_branch);
if then_creates_rc || else_creates_rc || then_emits_stmts || else_emits_stmts {
// Use if-statement pattern to avoid allocating unused branch
let result_type = self.infer_expr_type(then_branch)
.or_else(|| self.infer_expr_type(else_branch))
@@ -2452,16 +2483,25 @@ impl CBackend {
self.writeln(&format!("if ({}) {{", cond));
self.indent += 1;
// Push RC scope for then branch so temps are cleaned up within the branch
self.push_rc_scope();
// Emit then branch
let then_val = self.emit_expr(then_branch)?;
self.writeln(&format!("{} = {};", result_var, then_val));
// Pop RC scope - cleanup temps before leaving the branch
// Skip the then_val if it's being assigned to result (ownership transfer)
self.pop_rc_scope_except(Some(&then_val));
self.indent -= 1;
self.writeln("} else {");
self.indent += 1;
// Push RC scope for else branch
self.push_rc_scope();
// Emit else branch
let else_val = self.emit_expr(else_branch)?;
self.writeln(&format!("{} = {};", result_var, else_val));
// Pop RC scope - cleanup temps before leaving the branch
self.pop_rc_scope_except(Some(&else_val));
self.indent -= 1;
self.writeln("}");
@@ -2644,9 +2684,16 @@ impl CBackend {
.map(|t| self.type_expr_to_c(t).unwrap_or_else(|_| "LuxInt".to_string()))
.unwrap_or_else(|| "LuxInt".to_string());
// Determine captured variable types (default to LuxInt for now, escape C keywords)
// Determine captured variable types from var_types (fallback to LuxInt)
let env_fields: Vec<(String, String)> = free_vars.iter()
.map(|v| (self.escape_c_keyword(v), "LuxInt".to_string()))
.map(|v| {
let escaped = self.escape_c_keyword(v);
let typ = self.var_types.get(&escaped)
.or_else(|| self.var_types.get(v.as_str()))
.cloned()
.unwrap_or_else(|| "LuxInt".to_string());
(escaped, typ)
})
.collect();
// Store closure info for later emission
@@ -3735,6 +3782,9 @@ impl CBackend {
self.indent += 1;
// Push RC scope for this match arm so temp variables are cleaned up within the arm
self.push_rc_scope();
// Extract and emit variable bindings from the pattern
let bindings = self.extract_pattern_bindings(&arm.pattern, &scrutinee_var, type_name.as_deref());
for (var_name, c_expr, c_type) in &bindings {
@@ -3782,8 +3832,11 @@ impl CBackend {
if is_void {
// For void expressions, just emit the body without assignment
self.writeln(&format!("{};", body));
self.pop_rc_scope();
} else {
self.writeln(&format!("{} = {};", result_var, body));
// Pop RC scope, skip the body value if it's being assigned (ownership transfer)
self.pop_rc_scope_except(Some(&body));
}
self.indent -= 1;
}
@@ -3813,6 +3866,15 @@ impl CBackend {
return t;
}
}
// If the body is a simple Var referencing a pattern-bound variable,
// try to infer the type from the pattern context
if let Expr::Var(body_ident) = &arm.body {
if let Some(t) = self.infer_pattern_binding_type(&arm.pattern, &body_ident.name) {
if t != "void*" {
return t;
}
}
}
}
// If all arms were void, return void
if found_void {
@@ -3822,6 +3884,40 @@ impl CBackend {
"LuxInt".to_string()
}
/// Infer the type of a pattern-bound variable by looking at what the pattern
/// destructs. E.g. Ok(x) in a Result match -> x is void* (generic).
fn infer_pattern_binding_type(&self, pattern: &Pattern, var_name: &str) -> Option<String> {
match pattern {
Pattern::Constructor { name, fields, .. } => {
// Check if any field binds the variable
for (i, field) in fields.iter().enumerate() {
if let Pattern::Var(ident) = field {
if ident.name == var_name {
// Look up the field type from variant_field_types
if let Some(type_name) = self.variant_to_type.get(&name.name) {
if let Some(field_types) = self.variant_field_types.get(&(type_name.clone(), name.name.clone())) {
if let Some(ft) = field_types.get(i) {
return Some(ft.clone());
}
}
}
}
}
}
None
}
Pattern::Var(ident) => {
if ident.name == var_name {
// This is a wildcard-like binding, type comes from scrutinee
None
} else {
None
}
}
_ => None,
}
}
/// Check if any arm has a string literal pattern
fn has_string_literal_patterns(&self, arms: &[MatchArm]) -> bool {
for arm in arms {
@@ -3891,14 +3987,60 @@ impl CBackend {
return Some(ret_type.clone());
}
}
// Check module method calls like String.substring, List.map, etc.
if let Expr::Field { object, field, .. } = func.as_ref() {
if let Expr::Var(module) = object.as_ref() {
match module.name.as_str() {
"String" => match field.name.as_str() {
"length" | "indexOf" | "lastIndexOf" => return Some("LuxInt".to_string()),
"substring" | "trim" | "toLower" | "toUpper" | "replace"
| "join" | "fromChar" | "repeat" => return Some("LuxString".to_string()),
"split" | "lines" | "chars" => return Some("LuxList*".to_string()),
"startsWith" | "endsWith" | "contains" => return Some("LuxBool".to_string()),
"parseInt" | "parseFloat" => return Some("Option".to_string()),
_ => {}
},
"List" => match field.name.as_str() {
"map" | "filter" | "concat" | "reverse" | "take" | "drop"
| "range" | "sort" | "sortBy" | "flatten" | "intersperse" => return Some("LuxList*".to_string()),
"head" | "tail" | "get" | "find" | "last" => return Some("Option".to_string()),
"length" | "fold" | "foldLeft" => return Some("LuxInt".to_string()),
"isEmpty" | "any" | "all" | "contains" => return Some("LuxBool".to_string()),
_ => {}
},
"Int" => match field.name.as_str() {
"toString" => return Some("LuxString".to_string()),
"parse" => return Some("Option".to_string()),
"abs" | "min" | "max" => return Some("LuxInt".to_string()),
_ => {}
},
"Float" => match field.name.as_str() {
"toString" => return Some("LuxString".to_string()),
"parse" => return Some("Option".to_string()),
_ => return Some("LuxFloat".to_string()),
},
_ => {}
}
}
}
None
}
Expr::BinaryOp { op, left, right, .. } => {
// String concatenation with + returns LuxString
if matches!(op, BinaryOp::Add) {
if self.is_string_expr(left) || self.is_string_expr(right) {
let left_type = self.infer_expr_type(left);
let right_type = self.infer_expr_type(right);
if left_type.as_deref() == Some("LuxString") || right_type.as_deref() == Some("LuxString") {
return Some("LuxString".to_string());
}
return left_type.or(right_type);
}
// Comparison ops return bool
if matches!(op, BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge) {
return Some("LuxBool".to_string());
}
if matches!(op, BinaryOp::And | BinaryOp::Or) {
return Some("LuxBool".to_string());
}
// For other binary ops, infer from operands
self.infer_expr_type(left).or_else(|| self.infer_expr_type(right))
@@ -3945,6 +4087,7 @@ impl CBackend {
"read" => Some("LuxString".to_string()),
"write" | "append" | "delete" | "mkdir" => Some("void".to_string()),
"exists" | "isDir" => Some("LuxBool".to_string()),
"readDir" | "listDir" => Some("LuxList*".to_string()),
_ => None,
}
} else if effect.name == "Http" {
@@ -3968,10 +4111,11 @@ impl CBackend {
}
} else if effect.name == "String" {
match operation.name.as_str() {
"trim" => Some("LuxString".to_string()),
"length" => Some("LuxInt".to_string()),
"lines" | "split" => Some("LuxList*".to_string()),
"contains" => Some("LuxBool".to_string()),
"trim" | "substring" | "toLower" | "toUpper" | "replace"
| "join" | "fromChar" | "repeat" => Some("LuxString".to_string()),
"length" | "indexOf" | "lastIndexOf" => Some("LuxInt".to_string()),
"lines" | "split" | "chars" => Some("LuxList*".to_string()),
"contains" | "startsWith" | "endsWith" => Some("LuxBool".to_string()),
"parseInt" | "parseFloat" => Some("Option".to_string()),
_ => None,
}
@@ -3983,6 +4127,23 @@ impl CBackend {
// Type of match is the type of its arms
Some(self.infer_match_result_type(arms))
}
Expr::Field { object, field, .. } => {
// Check if it's a record field access — look up object type + field
if let Some(obj_type) = self.infer_expr_type(object) {
// Look through variant_field_types for record fields
// For now, check var_types for the field access result
let field_key = format!("{}.{}", obj_type, field.name);
if let Some(t) = self.var_types.get(&field_key) {
return Some(t.clone());
}
}
None
}
Expr::Let { body, .. } => {
// Type of a let expression is the type of its body
self.infer_expr_type(body)
}
Expr::Lambda { .. } => Some("LuxClosure*".to_string()),
_ => None,
}
}
@@ -4064,11 +4225,12 @@ impl CBackend {
}
} else if effect.name == "Process" && operation.name == "env" {
Some("LuxString".to_string())
} else if effect.name == "String" && (operation.name == "parseInt" || operation.name == "parseFloat") {
if operation.name == "parseInt" {
Some("LuxInt".to_string())
} else {
Some("LuxFloat".to_string())
} else if effect.name == "String" {
match operation.name.as_str() {
"parseInt" => Some("LuxInt".to_string()),
"parseFloat" => Some("LuxFloat".to_string()),
"indexOf" | "lastIndexOf" => Some("LuxInt".to_string()),
_ => None,
}
} else {
None
@@ -4077,14 +4239,19 @@ impl CBackend {
Expr::Call { func, .. } => {
if let Expr::Field { object, field, .. } = func.as_ref() {
if let Expr::Var(module) = object.as_ref() {
if module.name == "List" {
match field.name.as_str() {
match module.name.as_str() {
"List" => match field.name.as_str() {
"head" | "get" | "find" => Some("LuxString".to_string()),
"tail" => Some("LuxList*".to_string()),
_ => None,
}
} else {
None
},
"String" => match field.name.as_str() {
"indexOf" | "lastIndexOf" => Some("LuxInt".to_string()),
"parseInt" => Some("LuxInt".to_string()),
"parseFloat" => Some("LuxFloat".to_string()),
_ => None,
},
_ => None,
}
} else {
None
@@ -4093,6 +4260,7 @@ impl CBackend {
None
}
}
Expr::Var(_) => None,
_ => None,
}
}
@@ -4536,6 +4704,49 @@ impl CBackend {
}
}
/// Check if an expression will emit C statements (variable declarations, etc.)
/// which means it cannot be safely used in a ternary expression.
fn expr_emits_statements(&self, expr: &Expr) -> bool {
match expr {
Expr::Block { statements, result, .. } => {
!statements.is_empty() || self.expr_emits_statements(result)
}
Expr::Let { .. } => true,
Expr::Match { .. } => true,
Expr::If { then_branch, else_branch, .. } => {
self.expr_emits_statements(then_branch) || self.expr_emits_statements(else_branch)
}
Expr::Call { func, args, .. } => {
// Constructor calls with field access (e.g. BState(...)) may be fine,
// but calls to functions returning strings will emit concat temps
if let Expr::Var(ident) = func.as_ref() {
if let Some(ret_type) = self.function_return_types.get(&ident.name) {
if ret_type == "LuxString" {
return true;
}
}
}
// Check if any argument emits statements
args.iter().any(|a| self.expr_emits_statements(a))
}
Expr::BinaryOp { op, left, right, .. } => {
// String concatenation emits temp variables
if matches!(op, BinaryOp::Add) {
let lt = self.infer_expr_type(left);
let rt = self.infer_expr_type(right);
if lt.as_deref() == Some("LuxString") || rt.as_deref() == Some("LuxString") {
return true;
}
}
self.expr_emits_statements(left) || self.expr_emits_statements(right)
}
Expr::Lambda { .. } => true,
Expr::List { .. } => true,
Expr::EffectOp { .. } => true,
_ => false,
}
}
/// Check if an expression creates a new RC-managed value that needs tracking
fn expr_creates_rc_value(&self, expr: &Expr) -> bool {
match expr {