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 <noreply@anthropic.com>
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -770,7 +770,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lux"
|
name = "lux"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ pub struct CBackend {
|
|||||||
imported_modules: HashSet<String>,
|
imported_modules: HashSet<String>,
|
||||||
/// Variable name renames: Lux name → C variable name (for let binding name mangling)
|
/// Variable name renames: Lux name → C variable name (for let binding name mangling)
|
||||||
var_renames: HashMap<String, String>,
|
var_renames: HashMap<String, String>,
|
||||||
|
/// Inner type for Option-typed variables (variable name -> inner C type, e.g. "LuxInt")
|
||||||
|
var_option_inner_types: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CBackend {
|
impl CBackend {
|
||||||
@@ -177,6 +179,7 @@ impl CBackend {
|
|||||||
module_functions: HashMap::new(),
|
module_functions: HashMap::new(),
|
||||||
imported_modules: HashSet::new(),
|
imported_modules: HashSet::new(),
|
||||||
var_renames: HashMap::new(),
|
var_renames: HashMap::new(),
|
||||||
|
var_option_inner_types: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +282,7 @@ impl CBackend {
|
|||||||
Declaration::Let(let_decl) => {
|
Declaration::Let(let_decl) => {
|
||||||
// Skip run expressions - they're handled in the main wrapper
|
// Skip run expressions - they're handled in the main wrapper
|
||||||
if !matches!(&let_decl.value, Expr::Run { .. }) {
|
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.writeln(&format!("{} {}({}) {{", ret_type, mangled_name, full_params));
|
||||||
self.indent += 1;
|
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;
|
let old_has_evidence = self.has_evidence;
|
||||||
if is_effectful {
|
if is_effectful {
|
||||||
self.has_evidence = true;
|
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_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("static Option lux_option_some(void* value) { return (Option){Option_TAG_SOME, .data.some = {value}}; }");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
self.writeln("// String indexOf / lastIndexOf — return Option<Int>");
|
|
||||||
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("// === Boxing/Unboxing ===");
|
||||||
self.writeln("// All boxed values are RC-managed.");
|
self.writeln("// All boxed values are RC-managed.");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
@@ -2192,6 +2193,20 @@ impl CBackend {
|
|||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
self.writeln("static LuxString lux_unbox_string(void* p) { return (LuxString)p; }");
|
self.writeln("static LuxString lux_unbox_string(void* p) { return (LuxString)p; }");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("// String indexOf / lastIndexOf — return Option<Int>");
|
||||||
|
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("// === Polymorphic Drop Function ===");
|
||||||
self.writeln("// Called when an object's refcount reaches zero.");
|
self.writeln("// Called when an object's refcount reaches zero.");
|
||||||
self.writeln("// Recursively decrefs any owned references before freeing.");
|
self.writeln("// Recursively decrefs any owned references before freeing.");
|
||||||
@@ -2727,7 +2742,11 @@ impl CBackend {
|
|||||||
for param in &func.params {
|
for param in &func.params {
|
||||||
let escaped = self.escape_c_keyword(¶m.name.name);
|
let escaped = self.escape_c_keyword(¶m.name.name);
|
||||||
if let Ok(c_type) = self.type_expr_to_c(¶m.typ) {
|
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
|
// Infer the type from the value expression
|
||||||
let var_type = self.infer_expr_type(value).unwrap_or_else(|| "LuxInt".to_string());
|
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));
|
self.writeln(&format!("{} {} = {};", var_type, var_name, val));
|
||||||
|
|
||||||
// Register the variable rename so nested expressions can find it
|
// Register the variable rename so nested expressions can find it
|
||||||
@@ -3396,6 +3422,13 @@ impl CBackend {
|
|||||||
// Record variable type for future inference
|
// Record variable type for future inference
|
||||||
self.var_types.insert(escaped_name.clone(), typ.clone());
|
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
|
// Handle ownership transfer or RC registration
|
||||||
if let Some(source_name) = source_var {
|
if let Some(source_name) = source_var {
|
||||||
// Ownership transfer: unregister source, register dest
|
// Ownership transfer: unregister source, register dest
|
||||||
@@ -4872,8 +4905,8 @@ impl CBackend {
|
|||||||
if c_type == "void*" {
|
if c_type == "void*" {
|
||||||
// Cast from void* to actual type
|
// Cast from void* to actual type
|
||||||
if Self::is_primitive_c_type(&actual_type) {
|
if Self::is_primitive_c_type(&actual_type) {
|
||||||
// For primitive types stored as void*, cast via intptr_t
|
// For primitive types stored as boxed void*, dereference
|
||||||
self.writeln(&format!("{} {} = ({})(intptr_t)({});", actual_type, var_name, actual_type, c_expr));
|
self.writeln(&format!("{} {} = *({}*)({});", actual_type, var_name, actual_type, c_expr));
|
||||||
} else if !actual_type.ends_with('*') && actual_type != "void" {
|
} else if !actual_type.ends_with('*') && actual_type != "void" {
|
||||||
// Struct types: cast to pointer and dereference
|
// Struct types: cast to pointer and dereference
|
||||||
self.writeln(&format!("{} {} = *({}*)({});", actual_type, var_name, actual_type, c_expr));
|
self.writeln(&format!("{} {} = *({}*)({});", actual_type, var_name, actual_type, c_expr));
|
||||||
@@ -5414,7 +5447,13 @@ impl CBackend {
|
|||||||
None
|
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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5592,9 +5631,25 @@ impl CBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_global_let(&mut self, name: &Ident) -> Result<(), CGenError> {
|
fn emit_global_let(&mut self, let_decl: &crate::ast::LetDecl) -> Result<(), CGenError> {
|
||||||
// Declare global variable without initializer (initialized in main)
|
// Infer type from the value expression (or type annotation)
|
||||||
self.writeln(&format!("static LuxInt {} = 0;", name.name));
|
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("");
|
self.writeln("");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -5697,6 +5752,20 @@ impl CBackend {
|
|||||||
self.type_expr_to_c(type_expr)
|
self.type_expr_to_c(type_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the inner C type from an Option<T> type expression
|
||||||
|
fn option_inner_type_from_type_expr(&self, type_expr: &TypeExpr) -> Option<String> {
|
||||||
|
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<String, CGenError> {
|
fn type_expr_to_c(&self, type_expr: &TypeExpr) -> Result<String, CGenError> {
|
||||||
match type_expr {
|
match type_expr {
|
||||||
TypeExpr::Named(ident) => {
|
TypeExpr::Named(ident) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user