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:
@@ -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(¶m.name.name);
|
||||
if let Ok(c_type) = self.type_expr_to_c(¶m.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,11 +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,
|
||||
},
|
||||
"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
|
||||
@@ -4089,10 +4259,8 @@ impl CBackend {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user