From 4e43d3d50dc78bc4aa382b11df007a8162605962 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Thu, 19 Feb 2026 21:10:52 -0500 Subject: [PATCH] fix: C backend String.indexOf/lastIndexOf compilation (issue 8) Three bugs fixed: - Global let bindings always typed as LuxInt; now inferred from value - Option inner type not tracked for function params; added var_option_inner_types map so match extraction uses correct type - indexOf/lastIndexOf stored ints as (void*)(intptr_t) but extraction expected boxed pointers; now uses lux_box_int consistently Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 2 +- src/codegen/c_backend.rs | 113 +++++++++++++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 593a4d3..aea470d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -770,7 +770,7 @@ dependencies = [ [[package]] name = "lux" -version = "0.1.5" +version = "0.1.6" dependencies = [ "lsp-server", "lsp-types", diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index f176665..920b8a8 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -146,6 +146,8 @@ pub struct CBackend { imported_modules: HashSet, /// Variable name renames: Lux name → C variable name (for let binding name mangling) var_renames: HashMap, + /// Inner type for Option-typed variables (variable name -> inner C type, e.g. "LuxInt") + var_option_inner_types: HashMap, } impl CBackend { @@ -177,6 +179,7 @@ impl CBackend { module_functions: HashMap::new(), imported_modules: HashSet::new(), var_renames: HashMap::new(), + var_option_inner_types: HashMap::new(), } } @@ -279,7 +282,7 @@ impl CBackend { Declaration::Let(let_decl) => { // Skip run expressions - they're handled in the main wrapper if !matches!(&let_decl.value, Expr::Run { .. }) { - self.emit_global_let(&let_decl.name)?; + self.emit_global_let(let_decl)?; } } _ => {} @@ -599,6 +602,18 @@ impl CBackend { self.writeln(&format!("{} {}({}) {{", ret_type, mangled_name, full_params)); self.indent += 1; + // Register parameter types so match/option inference can use them + for p in &func.params { + if let Ok(c_type) = self.type_expr_to_c(&p.typ) { + let escaped = self.escape_c_keyword(&p.name.name); + self.var_types.insert(escaped.clone(), c_type); + // Track Option inner types for match pattern extraction + if let Some(inner) = self.option_inner_type_from_type_expr(&p.typ) { + self.var_option_inner_types.insert(escaped, inner); + } + } + } + let old_has_evidence = self.has_evidence; if is_effectful { self.has_evidence = true; @@ -2145,20 +2160,6 @@ impl CBackend { self.writeln("static Option lux_option_none(void) { return (Option){Option_TAG_NONE}; }"); self.writeln("static Option lux_option_some(void* value) { return (Option){Option_TAG_SOME, .data.some = {value}}; }"); self.writeln(""); - self.writeln("// String indexOf / lastIndexOf — return Option"); - self.writeln("static Option lux_string_indexOf(LuxString s, LuxString needle) {"); - self.writeln(" char* pos = strstr(s, needle);"); - self.writeln(" if (pos) return lux_option_some((void*)(intptr_t)(pos - s));"); - self.writeln(" return lux_option_none();"); - self.writeln("}"); - self.writeln("static Option lux_string_lastIndexOf(LuxString s, LuxString needle) {"); - self.writeln(" char* last = NULL;"); - self.writeln(" char* pos = strstr(s, needle);"); - self.writeln(" while (pos) { last = pos; pos = strstr(pos + 1, needle); }"); - self.writeln(" if (last) return lux_option_some((void*)(intptr_t)(last - s));"); - self.writeln(" return lux_option_none();"); - self.writeln("}"); - self.writeln(""); self.writeln("// === Boxing/Unboxing ==="); self.writeln("// All boxed values are RC-managed."); self.writeln(""); @@ -2192,6 +2193,20 @@ impl CBackend { self.writeln("}"); self.writeln("static LuxString lux_unbox_string(void* p) { return (LuxString)p; }"); self.writeln(""); + self.writeln("// String indexOf / lastIndexOf — return Option"); + self.writeln("static Option lux_string_indexOf(LuxString s, LuxString needle) {"); + self.writeln(" char* pos = strstr(s, needle);"); + self.writeln(" if (pos) return lux_option_some(lux_box_int((LuxInt)(pos - s)));"); + self.writeln(" return lux_option_none();"); + self.writeln("}"); + self.writeln("static Option lux_string_lastIndexOf(LuxString s, LuxString needle) {"); + self.writeln(" char* last = NULL;"); + self.writeln(" char* pos = strstr(s, needle);"); + self.writeln(" while (pos) { last = pos; pos = strstr(pos + 1, needle); }"); + self.writeln(" if (last) return lux_option_some(lux_box_int((LuxInt)(last - s)));"); + self.writeln(" return lux_option_none();"); + self.writeln("}"); + self.writeln(""); self.writeln("// === Polymorphic Drop Function ==="); self.writeln("// Called when an object's refcount reaches zero."); self.writeln("// Recursively decrefs any owned references before freeing."); @@ -2727,7 +2742,11 @@ impl CBackend { 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); + self.var_types.insert(escaped.clone(), c_type); + // Track Option inner types for match pattern extraction + if let Some(inner) = self.option_inner_type_from_type_expr(¶m.typ) { + self.var_option_inner_types.insert(escaped, inner); + } } } @@ -3088,6 +3107,13 @@ impl CBackend { // Infer the type from the value expression let var_type = self.infer_expr_type(value).unwrap_or_else(|| "LuxInt".to_string()); + // Track Option inner type for match pattern extraction + if var_type == "Option" { + if let Some(inner) = self.infer_option_inner_type(value) { + self.var_option_inner_types.insert(var_name.clone(), inner); + } + } + self.writeln(&format!("{} {} = {};", var_type, var_name, val)); // Register the variable rename so nested expressions can find it @@ -3396,6 +3422,13 @@ impl CBackend { // Record variable type for future inference self.var_types.insert(escaped_name.clone(), typ.clone()); + // Track Option inner type for match pattern extraction + if typ == "Option" { + if let Some(inner) = self.infer_option_inner_type(value) { + self.var_option_inner_types.insert(escaped_name.clone(), inner); + } + } + // Handle ownership transfer or RC registration if let Some(source_name) = source_var { // Ownership transfer: unregister source, register dest @@ -4872,8 +4905,8 @@ impl CBackend { if c_type == "void*" { // Cast from void* to actual type if Self::is_primitive_c_type(&actual_type) { - // For primitive types stored as void*, cast via intptr_t - self.writeln(&format!("{} {} = ({})(intptr_t)({});", actual_type, var_name, actual_type, c_expr)); + // For primitive types stored as boxed void*, dereference + self.writeln(&format!("{} {} = *({}*)({});", actual_type, var_name, actual_type, c_expr)); } else if !actual_type.ends_with('*') && actual_type != "void" { // Struct types: cast to pointer and dereference self.writeln(&format!("{} {} = *({}*)({});", actual_type, var_name, actual_type, c_expr)); @@ -5414,7 +5447,13 @@ impl CBackend { None } } - Expr::Var(_) => None, + Expr::Var(ident) => { + let escaped = self.escape_c_keyword(&ident.name); + // Check renamed variables first, then original name + let lookup_name = self.var_renames.get(&ident.name).unwrap_or(&escaped); + self.var_option_inner_types.get(lookup_name).cloned() + .or_else(|| self.var_option_inner_types.get(&escaped).cloned()) + } _ => None, } } @@ -5592,9 +5631,25 @@ impl CBackend { } } - fn emit_global_let(&mut self, name: &Ident) -> Result<(), CGenError> { - // Declare global variable without initializer (initialized in main) - self.writeln(&format!("static LuxInt {} = 0;", name.name)); + fn emit_global_let(&mut self, let_decl: &crate::ast::LetDecl) -> Result<(), CGenError> { + // Infer type from the value expression (or type annotation) + let typ = if let Some(ref type_expr) = let_decl.typ { + self.type_expr_to_c(type_expr).unwrap_or_else(|_| "LuxInt".to_string()) + } else { + self.infer_expr_type(&let_decl.value).unwrap_or_else(|| "LuxInt".to_string()) + }; + let default = match typ.as_str() { + "LuxString" => "NULL", + "LuxBool" => "false", + "LuxFloat" => "0.0", + "LuxList*" | "LuxClosure*" => "NULL", + "Option" => "{0}", + "Result" => "{0}", + _ if typ.ends_with('*') => "NULL", + _ => "0", + }; + self.writeln(&format!("static {} {} = {};", typ, let_decl.name.name, default)); + self.var_types.insert(let_decl.name.name.clone(), typ); self.writeln(""); Ok(()) } @@ -5697,6 +5752,20 @@ impl CBackend { self.type_expr_to_c(type_expr) } + /// Extract the inner C type from an Option type expression + fn option_inner_type_from_type_expr(&self, type_expr: &TypeExpr) -> Option { + if let TypeExpr::App(base, args) = type_expr { + if let TypeExpr::Named(name) = base.as_ref() { + if name.name == "Option" { + if let Some(inner) = args.first() { + return self.type_expr_to_c(inner).ok(); + } + } + } + } + None + } + fn type_expr_to_c(&self, type_expr: &TypeExpr) -> Result { match type_expr { TypeExpr::Named(ident) => {