feat: add pattern variable binding to C backend

Implements full pattern matching with variable binding for the C backend:

- Extract variable bindings from patterns (Var, Constructor, Tuple, Record)
- Infer C types for bound variables using variant field type tracking
- Handle recursive ADTs with pointer fields and heap allocation
- Dereference pointer bindings automatically for value semantics

Key implementation details:
- variant_to_type: Maps variant names to parent type for tag generation
- variant_field_types: Maps (type, variant) to field types for inference
- Recursive type fields use Type* pointers with malloc/memcpy
- Pattern bindings dereference pointers to maintain value semantics

Examples that now work:
- match opt { Some(x) => x, None => 0 }
- match tree { Leaf(n) => n, Node(l, r) => sum(l) + sum(r) }

Updates documentation to reflect C backend progress.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 04:23:44 -05:00
parent 6ec1f3bdbb
commit d284ee58a8
3 changed files with 316 additions and 22 deletions

View File

@@ -3,9 +3,30 @@
//! Compiles Lux programs to C code that can be compiled with GCC/Clang.
//! Inspired by Koka's approach: effects compile to evidence passing,
//! no garbage collector needed with Perceus-style reference counting.
//!
//! ## Supported Features
//!
//! - **Functions**: Direct function calls with proper name mangling
//! - **Closures**: Lambda expressions with captured environments
//! - Environment structs hold captured variables
//! - Closures are `{void* env, void* fn_ptr}` structs
//! - **ADTs**: Algebraic data types (enums with data)
//! - Tag enums for variant discrimination
//! - Union structs for variant data
//! - Recursive types use pointers with heap allocation
//! - **Pattern Matching**: Full pattern variable binding
//! - Constructor patterns extract variant data
//! - Nested patterns supported
//! - Type inference for bound variables
//!
//! ## Limitations
//!
//! - **Lists**: Not yet implemented (use interpreter)
//! - **Memory**: No automatic deallocation (memory leaks for closures/ADTs)
//! - **Effects**: Only Console.print supported
use crate::ast::*;
use std::collections::HashSet;
use std::collections::{HashSet, HashMap};
use std::fmt::Write;
/// C code generation errors
@@ -53,6 +74,10 @@ pub struct CBackend {
local_vars: HashSet<String>,
/// Functions that return closures
closure_returning_functions: HashSet<String>,
/// Mapping from variant names to their parent type name
variant_to_type: HashMap<String, String>,
/// Mapping from (type_name, variant_name) to field types
variant_field_types: HashMap<(String, String), Vec<String>>,
}
impl CBackend {
@@ -67,6 +92,8 @@ impl CBackend {
closures: Vec::new(),
local_vars: HashSet::new(),
closure_returning_functions: HashSet::new(),
variant_to_type: HashMap::new(),
variant_field_types: HashMap::new(),
}
}
@@ -380,6 +407,36 @@ impl CBackend {
self.writeln("");
}
TypeDef::Enum(variants) => {
// Record variant -> type mapping for pattern matching
for variant in variants {
self.variant_to_type.insert(variant.name.name.clone(), name.clone());
// Also record field types for pattern binding type inference
// Use self-check for recursive types (which become pointers)
let field_types: Vec<String> = match &variant.fields {
VariantFields::Tuple(fields) => {
fields.iter().map(|f| {
self.type_to_c_with_self_check(f, name).unwrap_or_else(|_| "LuxInt".to_string())
}).collect()
}
VariantFields::Record(fields) => {
fields.iter().map(|f| {
self.type_to_c_with_self_check(&f.typ, name).unwrap_or_else(|_| "LuxInt".to_string())
}).collect()
}
VariantFields::Unit => vec![],
};
self.variant_field_types.insert(
(name.clone(), variant.name.name.clone()),
field_types
);
}
// Forward declare the main struct for recursive types
self.writeln(&format!("struct {};", name));
self.writeln(&format!("typedef struct {} {};", name, name));
self.writeln("");
// Emit tag enum
self.writeln(&format!("typedef enum {}_Tag {{", name));
self.indent += 1;
@@ -400,13 +457,13 @@ impl CBackend {
match &variant.fields {
VariantFields::Tuple(fields) => {
for (i, field) in fields.iter().enumerate() {
let c_type = self.type_to_c(field)?;
let c_type = self.type_to_c_with_self_check(field, name)?;
self.writeln(&format!("{} field{};", c_type, i));
}
}
VariantFields::Record(fields) => {
for field in fields {
let c_type = self.type_to_c(&field.typ)?;
let c_type = self.type_to_c_with_self_check(&field.typ, name)?;
self.writeln(&format!("{} {};", c_type, field.name.name));
}
}
@@ -418,8 +475,8 @@ impl CBackend {
}
}
// Emit main union struct
self.writeln(&format!("typedef struct {} {{", name));
// Emit main union struct (typedef already created above)
self.writeln(&format!("struct {} {{", name));
self.indent += 1;
self.writeln(&format!("{}_Tag tag;", name));
self.writeln("union {");
@@ -433,7 +490,7 @@ impl CBackend {
self.indent -= 1;
self.writeln("} data;");
self.indent -= 1;
self.writeln(&format!("}} {};", name));
self.writeln("};");
self.writeln("");
}
TypeDef::Alias(_) => {
@@ -499,7 +556,16 @@ impl CBackend {
match expr {
Expr::Literal(lit) => self.emit_literal(lit),
Expr::Var(ident) => Ok(ident.name.clone()),
Expr::Var(ident) => {
// Check if this is a unit constructor (no-argument variant)
if let Some(type_name) = self.variant_to_type.get(&ident.name) {
// This is a constructor - emit struct literal
let variant_name = &ident.name;
Ok(format!("({}){{{}_TAG_{}}}", type_name, type_name, variant_name.to_uppercase()))
} else {
Ok(ident.name.clone())
}
}
Expr::BinaryOp { op, left, right, .. } => {
let l = self.emit_expr(left)?;
@@ -568,6 +634,49 @@ impl CBackend {
let c_func_name = self.mangle_name(&ident.name);
Ok(format!("{}({})", c_func_name, args_str))
}
Expr::Var(ident) if self.variant_to_type.contains_key(&ident.name) => {
// ADT constructor call - create struct with tag and data
let type_name = self.variant_to_type.get(&ident.name).unwrap().clone();
let variant_name = ident.name.clone();
let variant_lower = variant_name.to_lowercase();
let variant_upper = variant_name.to_uppercase();
// Look up field types to handle pointer fields
let field_types = self.variant_field_types
.get(&(type_name.clone(), variant_name.clone()))
.cloned()
.unwrap_or_default();
// Generate struct initialization
let arg_values: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
let arg_values = arg_values?;
// Build field initializers, handling pointer fields
let field_inits: Vec<String> = arg_values.iter()
.enumerate()
.map(|(i, v)| {
let field_type = field_types.get(i).map(|s| s.as_str()).unwrap_or("LuxInt");
if field_type.ends_with('*') {
// Pointer field - allocate and copy
let base_type = &field_type[..field_type.len()-1];
format!(".field{} = ({}*)memcpy(malloc(sizeof({})), &({}), sizeof({}))",
i, base_type, base_type, v, base_type)
} else {
format!(".field{} = {}", i, v)
}
})
.collect();
if field_inits.is_empty() {
// Unit variant
Ok(format!("({}){{{}_TAG_{}}}", type_name, type_name, variant_upper))
} else {
// Variant with data
Ok(format!("({}){{{}_TAG_{}, .data.{} = {{{}}}}}",
type_name, type_name, variant_upper,
variant_lower, field_inits.join(", ")))
}
}
_ => {
// Indirect call - treat as closure
let closure_expr = self.emit_expr(func)?;
@@ -653,12 +762,18 @@ impl CBackend {
for stmt in statements {
match stmt {
Statement::Let { name, value, .. } => {
// First, infer type from value expression (before emitting)
let inferred_type = self.infer_expr_type(value);
let val = self.emit_expr(value)?;
// Infer type from value: closures return LuxClosure*
let typ = if val.starts_with("_closure_") || self.is_closure_returning_call(value) {
"LuxClosure*"
// Determine final type
let typ = if let Some(t) = inferred_type {
t
} else if val.starts_with("_closure_") || self.is_closure_returning_call(value) {
"LuxClosure*".to_string()
} else {
"LuxInt"
"LuxInt".to_string()
};
self.writeln(&format!("{} {} = {};", typ, name.name, val));
}
@@ -722,12 +837,22 @@ impl CBackend {
fn emit_match(&mut self, expr: &Expr, arms: &[MatchArm]) -> Result<String, CGenError> {
let scrutinee = self.emit_expr(expr)?;
let scrutinee_var = format!("scrutinee_{}", self.fresh_name());
let result_var = format!("match_result_{}", self.fresh_name());
self.writeln(&format!("LuxInt {};", result_var));
// Infer the type name from the first constructor pattern we find
let type_name = self.infer_type_name_from_arms(arms);
// Infer the result type from the first arm body
let result_type = self.infer_expr_type(&arms[0].body).unwrap_or_else(|| "LuxInt".to_string());
// Use the inferred type for scrutinee, fall back to LuxInt for simple patterns
let scrutinee_type = type_name.as_deref().unwrap_or("LuxInt");
self.writeln(&format!("{} {} = {};", scrutinee_type, scrutinee_var, scrutinee));
self.writeln(&format!("{} {};", result_type, result_var));
for (i, arm) in arms.iter().enumerate() {
let condition = self.pattern_to_condition(&arm.pattern, &scrutinee)?;
let condition = self.pattern_to_condition(&arm.pattern, &scrutinee_var, type_name.as_deref())?;
if i == 0 {
self.writeln(&format!("if ({}) {{", condition));
@@ -736,6 +861,20 @@ impl CBackend {
}
self.indent += 1;
// 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 {
// If the type is a pointer, dereference when binding
// This way the variable holds a value, not a pointer
if c_type.ends_with('*') {
let base_type = &c_type[..c_type.len()-1];
self.writeln(&format!("{} {} = *({});", base_type, var_name, c_expr));
} else {
self.writeln(&format!("{} {} = {};", c_type, var_name, c_expr));
}
}
let body = self.emit_expr(&arm.body)?;
self.writeln(&format!("{} = {};", result_var, body));
self.indent -= 1;
@@ -745,18 +884,159 @@ impl CBackend {
Ok(result_var)
}
fn pattern_to_condition(&self, pattern: &Pattern, scrutinee: &str) -> Result<String, CGenError> {
/// Try to infer the type name from match arms by looking at constructor patterns
fn infer_type_name_from_arms(&self, arms: &[MatchArm]) -> Option<String> {
for arm in arms {
if let Pattern::Constructor { name, .. } = &arm.pattern {
// Look up the variant in our mapping
if let Some(type_name) = self.variant_to_type.get(&name.name) {
return Some(type_name.clone());
}
// Fallback for built-in types that might not be in the map
match name.name.as_str() {
"Some" | "None" => return Some("Option".to_string()),
"Ok" | "Err" => return Some("Result".to_string()),
_ => {}
}
}
}
None
}
/// Try to infer the C type of an expression
fn infer_expr_type(&self, expr: &Expr) -> Option<String> {
match expr {
Expr::Literal(lit) => {
match &lit.kind {
LiteralKind::Int(_) => Some("LuxInt".to_string()),
LiteralKind::Float(_) => Some("LuxFloat".to_string()),
LiteralKind::Bool(_) => Some("LuxBool".to_string()),
LiteralKind::String(_) => Some("LuxString".to_string()),
LiteralKind::Char(_) => Some("char".to_string()),
LiteralKind::Unit => Some("void".to_string()),
}
}
Expr::Var(ident) => {
// Check if it's a unit constructor
if let Some(type_name) = self.variant_to_type.get(&ident.name) {
Some(type_name.clone())
} else {
None
}
}
Expr::Call { func, .. } => {
// Check if calling a constructor
if let Expr::Var(ident) = func.as_ref() {
if let Some(type_name) = self.variant_to_type.get(&ident.name) {
return Some(type_name.clone());
}
}
None
}
Expr::Block { result, .. } => {
// Type of block is the type of the result expression
self.infer_expr_type(result)
}
_ => None,
}
}
fn pattern_to_condition(&self, pattern: &Pattern, scrutinee: &str, type_name: Option<&str>) -> Result<String, CGenError> {
match pattern {
Pattern::Wildcard(_) => Ok("1".to_string()),
Pattern::Var(ident) => Ok(format!("(1) /* bind {} = {} */", ident.name, scrutinee)),
Pattern::Var(_) => Ok("1".to_string()), // Var always matches, binding handled separately
Pattern::Literal(lit) => {
let lit_val = self.emit_literal_value(&lit.kind)?;
Ok(format!("{} == {}", scrutinee, lit_val))
}
Pattern::Constructor { name, .. } => {
Ok(format!("{}.tag == TAG_{}", scrutinee, name.name.to_uppercase()))
Pattern::Constructor { name, fields, .. } => {
// Get the type name for proper tag generation
let tn = type_name.unwrap_or("Option"); // Default fallback
let tag_check = format!("{}.tag == {}_TAG_{}", scrutinee, tn, name.name.to_uppercase());
// If there are nested patterns, we need to check those too
if fields.is_empty() {
Ok(tag_check)
} else {
let mut conditions = vec![tag_check];
let variant_lower = name.name.to_lowercase();
for (i, field_pattern) in fields.iter().enumerate() {
// Access the field data
let field_access = format!("{}.data.{}.field{}", scrutinee, variant_lower, i);
let field_condition = self.pattern_to_condition(field_pattern, &field_access, None)?;
if field_condition != "1" {
conditions.push(field_condition);
}
}
Ok(conditions.join(" && "))
}
}
Pattern::Tuple { elements, .. } => {
// For tuples, check each element pattern
let mut conditions = Vec::new();
for (i, elem_pattern) in elements.iter().enumerate() {
let elem_access = format!("{}.field{}", scrutinee, i);
let elem_condition = self.pattern_to_condition(elem_pattern, &elem_access, None)?;
if elem_condition != "1" {
conditions.push(elem_condition);
}
}
if conditions.is_empty() {
Ok("1".to_string())
} else {
Ok(conditions.join(" && "))
}
}
Pattern::Record { .. } => Ok("1".to_string()), // TODO: record pattern matching
}
}
/// Extract variable bindings from a pattern.
/// Returns a list of (var_name, c_expression, c_type) triples.
fn extract_pattern_bindings(&self, pattern: &Pattern, scrutinee: &str, expected_type: Option<&str>) -> Vec<(String, String, String)> {
match pattern {
Pattern::Wildcard(_) => vec![],
Pattern::Var(ident) => {
let typ = expected_type.unwrap_or("LuxInt").to_string();
vec![(ident.name.clone(), scrutinee.to_string(), typ)]
}
Pattern::Literal(_) => vec![],
Pattern::Constructor { name, fields, .. } => {
let mut bindings = Vec::new();
let variant_lower = name.name.to_lowercase();
// Look up field types for this variant
let type_name = self.variant_to_type.get(&name.name);
let field_types = type_name.and_then(|tn| {
self.variant_field_types.get(&(tn.clone(), name.name.clone()))
});
for (i, field_pattern) in fields.iter().enumerate() {
let field_access = format!("{}.data.{}.field{}", scrutinee, variant_lower, i);
let field_type = field_types.and_then(|ft| ft.get(i)).map(|s| s.as_str());
bindings.extend(self.extract_pattern_bindings(field_pattern, &field_access, field_type));
}
bindings
}
Pattern::Tuple { elements, .. } => {
let mut bindings = Vec::new();
for (i, elem_pattern) in elements.iter().enumerate() {
let elem_access = format!("{}.field{}", scrutinee, i);
bindings.extend(self.extract_pattern_bindings(elem_pattern, &elem_access, None));
}
bindings
}
Pattern::Record { fields, .. } => {
let mut bindings = Vec::new();
for (field_name, field_pattern) in fields {
let field_access = format!("{}.{}", scrutinee, field_name.name);
bindings.extend(self.extract_pattern_bindings(field_pattern, &field_access, None));
}
bindings
}
_ => Ok("1".to_string()),
}
}
@@ -824,6 +1104,17 @@ impl CBackend {
self.type_expr_to_c(type_expr)
}
/// Convert type to C, using pointers for self-referential types
fn type_to_c_with_self_check(&self, type_expr: &TypeExpr, parent_type: &str) -> Result<String, CGenError> {
if let TypeExpr::Named(ident) = type_expr {
if ident.name == parent_type {
// Self-referential type - use pointer
return Ok(format!("{}*", parent_type));
}
}
self.type_expr_to_c(type_expr)
}
fn type_expr_to_c(&self, type_expr: &TypeExpr) -> Result<String, CGenError> {
match type_expr {
TypeExpr::Named(ident) => {