feat(selfhost): full tuple type support with .0/.1 field access

Parse (T, U) types and (a, b) expressions, lower to Tuple_* C structs,
and emit common tuple typedefs so buxc2 matches bootstrap tuple codegen.
This commit is contained in:
2026-07-15 16:04:01 +03:00
parent 61ac06ab5f
commit 9efba57b4c
6 changed files with 191 additions and 8 deletions
+2
View File
@@ -52,6 +52,8 @@ struct TypeExpr {
funcParams: *TypeExprList, // for tekFunc
funcRet: *TypeExpr, // for tekFunc
funcParamCount: int, // for tekFunc
tupleElems: *TypeExprList, // for tekTuple
tupleCount: int, // for tekTuple
}
// ---------------------------------------------------------------------------
+6
View File
@@ -1435,6 +1435,12 @@ func CBackend_Generate(mod: *HirModule) -> String {
// Fat function-pointer typedefs (BuxFn_*) — before forward decls
CBE_EmitFatFuncTypedefs(cbe, mod);
// Common tuple struct types
StringBuilder_Append(&cbe.sb, "/* Tuple types */\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
// Env structs for capturing closures (no static instance — heap per value)
var ei2: int = 0;
while ei2 < mod.funcCount {
+40 -1
View File
@@ -74,7 +74,7 @@ func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
if te.kind == tekTuple { return tyNamed; /* Tuple_T_U is a C struct */ }
if te.kind == tekFunc { return tyFunc; }
return Lcx_ResolveTypeKindFromName(te.typeName);
@@ -1202,6 +1202,45 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
// Tuple expression (a, b, ...) → struct init Tuple_int_int { ._0 = a, ._1 = b }
if kind == ekTuple {
var tname: String = "Tuple";
var ti: int = 0;
while ti < expr.callArgCount {
tname = String_Concat(tname, "_int");
ti = ti + 1;
}
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
tname = expr.refType.typeName;
}
n.kind = hStructInit;
n.strValue = tname;
n.typeKind = tyNamed;
n.typeName = tname;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
var tcur: *ExprList = expr.callArgs;
var tidx: int = 0;
while tcur != null as *ExprList {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock;
fNode.strValue = String_Concat("_", String_FromInt(tidx as int64));
fNode.child1 = Lcx_LowerExpr(ctx, tcur.expr);
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
tcur = tcur.next;
tidx = tidx + 1;
}
n.child1 = firstField;
return n;
}
// Closure: fat function pointer (multi-instance via heap env + maker)
if kind == ekClosure {
let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr);
+95 -4
View File
@@ -231,6 +231,57 @@ func parserParseType(p: *Parser) -> *TypeExpr {
return te;
}
// (T, U, ...) tuple type
if kindTok == tkLParen {
discard parserAdvance(p);
var elems: *TypeExprList = null as *TypeExprList;
var elemsTail: *TypeExprList = null as *TypeExprList;
var count: int = 0;
var typeName: String = "Tuple";
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let elemTe: *TypeExpr = parserParseType(p);
let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
node.te = elemTe;
node.next = null as *TypeExprList;
if elems == null as *TypeExprList {
elems = node;
} else {
elemsTail.next = node;
}
elemsTail = node;
count = count + 1;
// Build mangled name: Tuple_int_int
var part: String = "int";
if elemTe != null as *TypeExpr {
if !String_Eq(elemTe.typeName, "") {
part = elemTe.typeName;
} else if elemTe.kind == tekPointer && elemTe.pointerPointee != null as *TypeExpr {
part = String_Concat(elemTe.pointerPointee.typeName, "Ptr");
}
}
if String_Eq(part, "String") || String_Eq(part, "str") { part = "cstr"; }
typeName = String_Concat(typeName, "_");
typeName = String_Concat(typeName, part);
if parserCheck(p, tkComma) {
discard parserAdvance(p);
} else {
break;
}
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple type");
if count == 0 {
typeName = "Tuple_Empty";
}
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekTuple;
te.line = line;
te.column = col;
te.tupleElems = elems;
te.tupleCount = count;
te.typeName = typeName;
return te;
}
// name
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
// self / Self -> tekSelf
@@ -386,12 +437,43 @@ func parserParsePrimary(p: *Parser) -> *Expr {
return e;
}
// ( expr )
// ( expr ) or (a, b, ...) tuple
if kind == tkLParen {
discard parserAdvance(p);
let e: *Expr = parserParseExpr(p);
// Empty tuple ()
if parserCheck(p, tkRParen) {
discard parserAdvance(p);
let te: *Expr = parserMakeExpr(ekTuple, line, col);
te.callArgCount = 0;
return te;
}
let first: *Expr = parserParseExpr(p);
if parserCheck(p, tkComma) {
// Tuple expression
let te: *Expr = parserMakeExpr(ekTuple, line, col);
var firstArg: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
firstArg.expr = first;
firstArg.next = null as *ExprList;
var lastArg: *ExprList = firstArg;
var count: int = 1;
while parserCheck(p, tkComma) {
discard parserAdvance(p);
if parserCheck(p, tkRParen) { break; }
let elem: *Expr = parserParseExpr(p);
let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
node.expr = elem;
node.next = null as *ExprList;
lastArg.next = node;
lastArg = node;
count = count + 1;
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple");
te.callArgs = firstArg;
te.callArgCount = count;
return te;
}
discard parserExpect(p, tkRParen, "expected ')'");
return e;
return first;
}
// Closure: |params| -> Ret { body }
@@ -586,11 +668,20 @@ func parserParsePostfixExpr(p: *Parser) -> *Expr {
}
}
// Field: expr.name
// Field: expr.name or tuple index expr.0 / expr.1
if kind == tkDot {
discard parserAdvance(p);
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
if parserCheck(p, tkIntLiteral) {
let idxTok: LexToken = parserCurToken(p);
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekField, line, col);
e.child1 = left;
e.strValue = String_Concat("_", idxTok.text);
left = e;
continue;
}
let name: LexToken = parserExpectIdentOrKeyword(p, "expected field name");
let e: *Expr = parserMakeExpr(ekField, line, col);
e.child1 = left;
+37
View File
@@ -145,6 +145,11 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
return tyFunc;
}
if te.kind == tekTuple {
// Tuples lower to named C structs (Tuple_int_int, ...)
return tyNamed;
}
return Type_FromName(te.typeName);
}
@@ -688,9 +693,41 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return tyNamed;
}
// Tuple expression (a, b, ...)
if kind == ekTuple {
var cur: *ExprList = expr.callArgs;
while cur != null as *ExprList {
discard Sema_CheckExpr(sema, cur.expr);
cur = cur.next;
}
// Build Tuple_* type name from element types (default int)
var tname: String = "Tuple";
var i: int = 0;
while i < expr.callArgCount {
tname = String_Concat(tname, "_int");
i = i + 1;
}
if expr.callArgCount == 0 { tname = "Tuple_Empty"; }
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekTuple;
te.typeName = tname;
te.tupleCount = expr.callArgCount;
expr.refType = te;
return tyNamed;
}
// Field access
if kind == ekField {
discard Sema_CheckExpr(sema, expr.child1);
// Tuple field .0 / .1 stored as "_0" / "_1" → element type (int for now)
if String_StartsWith(expr.strValue, "_") {
// Propagate element type as int; real C type is Tuple field
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = "int";
expr.refType = te;
return tyInt;
}
return tyUnknown;
}