selfhost: for x in collection works with non-identifier expressions + buxc2 build merges stdlib

- cli: buxc2 build now delegates to Cli_BuildProject for src/Main.bux,
  enabling stdlib merging (Array_Iter, Iter_HasNext, etc. available)
- sema: set expr.refType on ekCall from function return type
  (needed for for-loop type extraction from MakeArr()-style expressions)
- hir_lower: remove ekIdent restriction on collection-based for loops
  - supports arbitrary expressions: for x in MakeArr() { ... }
  - creates temp variable __tmp_coll_N for non-identifier iterators
  - generates proper Array_T struct instances for temp variables
- Test: for x in MakeArr() sums [10,20,30] → 60
- Selfhost loop: C output IDENTICAL
This commit is contained in:
2026-06-10 10:20:16 +03:00
parent bb84693acb
commit 01677001c3
3 changed files with 63 additions and 11 deletions
+11
View File
@@ -114,6 +114,17 @@ func Cli_Compile(source: String, sourceName: String) -> String {
// ---------------------------------------------------------------------------
func Cli_Build(srcPath: String, outPath: String) -> int {
// If building the standard project entry point, use full project build
// which merges stdlib and supports multi-file projects
if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") {
let rc: int = Cli_BuildProject(".");
if rc == 0 && !String_Eq(outPath, "build/main") {
// Rename output if custom path was requested
bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true"));
}
return rc;
}
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
Print("Error: cannot read source file: ");
+50 -11
View File
@@ -1221,9 +1221,9 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// let x = Iter_Next_T(&__iter);
// body
// }
if iterExpr != null as *Expr && iterExpr.kind == ekIdent {
if iterExpr != null as *Expr {
let collTypeExpr: *TypeExpr = iterExpr.refType;
if collTypeExpr == null as *TypeExpr {
if collTypeExpr == null as *TypeExpr && iterExpr.kind == ekIdent {
// Fallback: try scope lookup (may have substituted type)
let collSym: Symbol = Scope_Lookup(ctx.scope, iterExpr.strValue);
collTypeExpr = collSym.refType;
@@ -1309,6 +1309,7 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// __iter = Array_Iter_T(&arr) or just copy if already Iter
var iterInit: *HirNode = null as *HirNode;
var collStore: *HirNode = null as *HirNode;
if isArray {
let callIter: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
callIter.kind = hCall;
@@ -1316,18 +1317,47 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
let addrArr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrArr.kind = hUnary;
addrArr.intValue = tkAmp;
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
arrVar.kind = hVar;
arrVar.strValue = iterExpr.strValue;
addrArr.child1 = arrVar;
if iterExpr.kind == ekIdent {
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
arrVar.kind = hVar;
arrVar.strValue = iterExpr.strValue;
addrArr.child1 = arrVar;
} else {
// Non-identifier: create temp variable for collection
ctx.varCounter = ctx.varCounter + 1;
let tmpName: String = String_Concat("__tmp_coll_", String_FromInt(ctx.varCounter));
// Ensure Array<T> struct instance exists and get mangled name
let arrayGenStruct: *Decl = Lcx_FindGenericStruct(ctx, "Array");
var arrayMangledName: String = collTypeName;
if arrayGenStruct != null as *Decl {
arrayMangledName = Lcx_GenerateStructInstance(ctx, arrayGenStruct, elemTypeName, "", 1);
}
let collAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
collAlloca.kind = hAlloca;
collAlloca.strValue = tmpName;
collAlloca.typeName = arrayMangledName;
collAlloca.typeKind = tyNamed;
collStore = bux_alloc(sizeof(HirNode)) as *HirNode;
collStore.kind = hStore;
collStore.child1 = collAlloca;
collStore.child2 = Lcx_LowerExpr(ctx, iterExpr);
let collVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
collVar.kind = hVar;
collVar.strValue = tmpName;
addrArr.child1 = collVar;
}
callIter.child1 = addrArr;
iterInit = callIter;
} else {
// Already an iterator: __iter = arr
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
arrVar.kind = hVar;
arrVar.strValue = iterExpr.strValue;
iterInit = arrVar;
if iterExpr.kind == ekIdent {
let arrVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
arrVar.kind = hVar;
arrVar.strValue = iterExpr.strValue;
iterInit = arrVar;
} else {
iterInit = Lcx_LowerExpr(ctx, iterExpr);
}
}
let iterStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
@@ -1335,6 +1365,11 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
iterStore.child1 = iterAlloca;
iterStore.child2 = iterInit;
// Chain collStore -> iterStore if temp was created
if collStore != null as *HirNode {
collStore.child3 = iterStore;
}
// condition: Iter_HasNext_T(&__iter)
let condCall: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
condCall.kind = hCall;
@@ -1401,7 +1436,11 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
blockNode.kind = hBlock;
blockNode.line = line;
blockNode.column = col;
blockNode.child1 = iterStore;
if collStore != null as *HirNode {
blockNode.child1 = collStore;
} else {
blockNode.child1 = iterStore;
}
return blockNode;
}
}
+2
View File
@@ -579,6 +579,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if calleeType == tyFunc {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekFunc {
if expr.child1.refType.funcRet != null as *TypeExpr {
expr.refType = expr.child1.refType.funcRet;
return Sema_ResolveType(sema, expr.child1.refType.funcRet);
}
return tyVoid;
@@ -589,6 +590,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl {
if sym.decl.retType != null as *TypeExpr {
expr.refType = sym.decl.retType;
return Sema_ResolveType(sema, sym.decl.retType);
}
}