From 40bcbeb710a37dc0299916723aebac4ef5addf10 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Tue, 9 Jun 2026 23:33:44 +0300 Subject: [PATCH] Fix generic struct monomorphization and C output for imports - Fix nested Lcx_GenerateStructInstance overwriting parent slot by incrementing structCount BEFORE field processing - Fix Lcx_LowerModule to skip generic structs from hm.structs (was missing continue due to bootstrap compiler codegen limitation) - Fix C backend to forward-declare ALL structs at top, removing second generic-only forward declaration section that came too late - Add empty-name guard in CBE_EmitStructDef - Import system: parser supports ::* glob imports, sema two-pass resolution --- src/c_backend.bux | 25 ++----- src/hir_lower.bux | 76 ++++++++++---------- src/parser.bux | 41 ++++++----- src/sema.bux | 177 ++++++++++++++++++++++------------------------ 4 files changed, 154 insertions(+), 165 deletions(-) diff --git a/src/c_backend.bux b/src/c_backend.bux index d396a0c..dea8de2 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -747,6 +747,7 @@ func CBE_StructHasValueStructField(st: *HirStruct) -> bool { } func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) { + if String_Eq(st.name, "") { return; } StringBuilder_Append(&cbe.sb, "struct "); StringBuilder_Append(&cbe.sb, st.name); StringBuilder_Append(&cbe.sb, " {\n"); @@ -848,10 +849,10 @@ func CBackend_Generate(mod: *HirModule) -> String { StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n"); StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n"); - // Forward declare all struct types (skip generics) + // Forward declare all struct types (skip empty names) var si: int = 0; while si < mod.structCount { - if !CBE_StructHasGeneric(&mod.structs[si]) { + if !String_Eq(mod.structs[si].name, "") { StringBuilder_Append(&cbe.sb, "typedef struct "); StringBuilder_Append(&cbe.sb, mod.structs[si].name); StringBuilder_Append(&cbe.sb, " "); @@ -972,7 +973,7 @@ func CBackend_Generate(mod: *HirModule) -> String { // Pass 1: emit structs with no value-typed struct fields (leaf structs) si = 0; while si < mod.structCount { - if CBE_StructHasGeneric(&mod.structs[si]) { + if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { si = si + 1; continue; } @@ -986,7 +987,7 @@ func CBackend_Generate(mod: *HirModule) -> String { // Pass 2: emit structs that contain value-typed struct fields si = 0; while si < mod.structCount { - if CBE_StructHasGeneric(&mod.structs[si]) { + if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) { si = si + 1; continue; } @@ -998,22 +999,6 @@ func CBackend_Generate(mod: *HirModule) -> String { si = si + 1; } - // Opaque forward declarations for generic structs (used only by pointer) - si = 0; - while si < mod.structCount { - if CBE_StructHasGeneric(&mod.structs[si]) { - StringBuilder_Append(&cbe.sb, "typedef struct "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, " "); - StringBuilder_Append(&cbe.sb, mod.structs[si].name); - StringBuilder_Append(&cbe.sb, ";\n"); - } - si = si + 1; - } - if mod.structCount > 0 { - StringBuilder_Append(&cbe.sb, "\n"); - } - // Forward declarations for all functions (skip generics) var i: int = 0; while i < mod.funcCount { diff --git a/src/hir_lower.bux b/src/hir_lower.bux index 8ab8d38..42383a7 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -245,6 +245,7 @@ func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCou } func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { + if String_Eq(genDecl.strValue, "") { return ""; } let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount); // Check if already generated (linear search in hm.structs) @@ -268,7 +269,10 @@ func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String ctx.substArg1 = typeArg1; // Generate concrete HirStruct with substituted field types + // Reserve the slot BEFORE processing fields so nested generic instantiations + // get their own distinct indices and cannot overwrite our slot. let si: int = ctx.hm.structCount; + ctx.hm.structCount = ctx.hm.structCount + 1; ctx.hm.structs[si].name = mangled; ctx.hm.structs[si].fieldCount = genDecl.fieldCount; ctx.hm.structs[si].fields = bux_alloc(genDecl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField; @@ -289,7 +293,6 @@ func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String } fi = fi + 1; } - ctx.hm.structCount = ctx.hm.structCount + 1; // Restore old substitution ctx.substParam0 = oldParam0; @@ -1651,9 +1654,42 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { ctx.genFuncs[ctx.genFuncCount] = *decl; ctx.genFuncCount = ctx.genFuncCount + 1; } - if decl.kind == dkStruct && decl.typeParamCount > 0 { - ctx.genStructs[ctx.genStructCount] = *decl; - ctx.genStructCount = ctx.genStructCount + 1; + if decl.kind == dkStruct { + if decl.typeParamCount > 0 { + ctx.genStructs[ctx.genStructCount] = *decl; + ctx.genStructCount = ctx.genStructCount + 1; + } else if !String_Eq(decl.strValue, "") { + // Collect struct definition for C codegen + let si: int = hm.structCount; + hm.structs[si].name = decl.strValue; + hm.structs[si].fieldCount = decl.fieldCount; + hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField; + var fi: int = 0; + while fi < decl.fieldCount { + var fname: String = ""; + var ftype: *TypeExpr = null as *TypeExpr; + fname = decl.fields[fi].name; + ftype = decl.fields[fi].refFieldType; + // Skip empty field names + if String_Eq(fname, "") { + fi = fi + 1; + continue; + } + hm.structs[si].fields[fi].name = fname; + if ftype != null as *TypeExpr { + if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr { + // Pointer type: emit "TypeName*" + if !String_Eq(ftype.pointerPointee.typeName, "") { + hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*"); + } + } else if !String_Eq(ftype.typeName, "") { + hm.structs[si].fields[fi].typeName = ftype.typeName; + } + } + fi = fi + 1; + } + hm.structCount = hm.structCount + 1; + } } if decl.kind == dkFunc && decl.refBody != null as *Block { let f: *HirFunc = Lcx_LowerFunc(ctx, decl); @@ -1679,38 +1715,6 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { ctx.externFuncs[ctx.externCount] = *f; ctx.externCount = ctx.externCount + 1; } - if decl.kind == dkStruct { - // Collect struct definition for C codegen - let si: int = hm.structCount; - hm.structs[si].name = decl.strValue; - hm.structs[si].fieldCount = decl.fieldCount; - hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField; - var fi: int = 0; - while fi < decl.fieldCount { - var fname: String = ""; - var ftype: *TypeExpr = null as *TypeExpr; - fname = decl.fields[fi].name; - ftype = decl.fields[fi].refFieldType; - // Skip empty field names - if String_Eq(fname, "") { - fi = fi + 1; - continue; - } - hm.structs[si].fields[fi].name = fname; - if ftype != null as *TypeExpr { - if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr { - // Pointer type: emit "TypeName*" - if !String_Eq(ftype.pointerPointee.typeName, "") { - hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*"); - } - } else if !String_Eq(ftype.typeName, "") { - hm.structs[si].fields[fi].typeName = ftype.typeName; - } - } - fi = fi + 1; - } - hm.structCount = hm.structCount + 1; - } if decl.kind == dkConst && hm.constCount < 512 { let ci: int = hm.constCount; hm.consts[ci].name = decl.strValue; diff --git a/src/parser.bux b/src/parser.bux index 7b0603d..17eb670 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -1434,25 +1434,32 @@ func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl { } d.usePath = pathStr; - // Optional ::{name1, name2} - if parserMatch(p, tkColonColon) && parserCheck(p, tkLBrace) { - discard parserAdvance(p); // { - var names: String = ""; - while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile { - while parserCheck(p, tkNewLine) { - discard parserAdvance(p); + // Optional ::{name1, name2} or ::* + if parserMatch(p, tkColonColon) { + if parserCheck(p, tkLBrace) { + discard parserAdvance(p); // { + var names: String = ""; + while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile { + while parserCheck(p, tkNewLine) { + discard parserAdvance(p); + } + if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { + break; + } + let n: LexToken = parserExpect(p, tkIdent, "expected import name"); + names = String_Concat(names, n.text); + names = String_Concat(names, ","); + if !parserMatch(p, tkComma) { break; } } - if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { - break; - } - let n: LexToken = parserExpect(p, tkIdent, "expected import name"); - names = String_Concat(names, n.text); - names = String_Concat(names, ","); - if !parserMatch(p, tkComma) { break; } + discard parserExpect(p, tkRBrace, "expected '}'"); + d.useNames = names; + d.useKind = 2; // ukMulti + } else if parserCheck(p, tkStar) { + discard parserAdvance(p); // * + d.useKind = 1; // ukGlob + } else { + d.useKind = 0; // ukSingle } - discard parserExpect(p, tkRBrace, "expected '}'"); - d.useNames = names; - d.useKind = 2; // ukMulti } else { d.useKind = 0; // ukSingle } diff --git a/src/sema.bux b/src/sema.bux index 996b91a..6d6d74d 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -997,98 +997,6 @@ func Sema_CollectGlobals(sema: *Sema) { discard Scope_Define(sema.scope, sym); } - // Use (import) - if dk == dkUse { - if decl.useKind == 2 { - // Multi-import: names are comma-separated in useNames - // For now, register the whole useNames string as a single func name - // (permissive fallback — real fix needs string split) - let namesStr: String = decl.useNames; - if !String_Eq(namesStr, "") { - // Simple split by comma using available string functions - var start: uint = 0; - var pos: uint = 0; - let totalLen: uint = String_Len(namesStr); - while pos <= totalLen { - let atEnd: bool = pos == totalLen; - let isComma: bool = false; - if pos < totalLen { - // Check if character at pos is comma - let chStr: String = bux_str_slice(namesStr, pos, 1); - isComma = String_Eq(chStr, ","); - } - if atEnd || isComma { - let nameLen: uint = pos - start; - if nameLen > 0 { - let name: String = bux_str_slice(namesStr, start, nameLen); - var sym: Symbol; - sym.kind = skFunc; - sym.name = name; - sym.typeKind = tyUnknown; - sym.typeName = ""; - sym.refType = null as *TypeExpr; - sym.isMutable = false; - sym.isPublic = true; - sym.decl = null as *Decl; - let existing: Symbol = Scope_LookupLocal(sema.scope, name); - if !String_Eq(existing.name, name) { - discard Scope_Define(sema.scope, sym); - } - } - start = pos + 1; - } - pos = pos + 1; - } - } - } else { - // Single import or glob — add last path segment - let path: String = decl.usePath; - if !String_Eq(path, "") { - // Find last :: segment - var lastSeg: String = path; - let containsColons: int = bux_str_contains(path, "::"); - if containsColons != 0 { - // Try to get last segment by finding last :: - // Use a simple heuristic: slice from various positions - let pathLen: uint = String_Len(path); - var tryPos: uint = 0; - while tryPos < pathLen { - let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos); - if String_StartsWith(slice, "::") { - lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2); - } - tryPos = tryPos + 1; - } - } - var sym: Symbol; - sym.kind = skFunc; - sym.name = lastSeg; - sym.typeKind = tyUnknown; - sym.typeName = ""; - sym.refType = null as *TypeExpr; - sym.isMutable = false; - sym.isPublic = true; - sym.decl = null as *Decl; - let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg); - if !String_Eq(existing.name, lastSeg) { - discard Scope_Define(sema.scope, sym); - } - } - } - } - - // Const - if dk == dkConst { - var sym: Symbol; - Sema_ZeroInitSymbol(&sym); - sym.kind = skConst; - sym.name = decl.strValue; - sym.typeKind = tyUnknown; - sym.isPublic = decl.isPublic; - sym.decl = decl; - discard Scope_Define(sema.scope, sym); - } - // Extern function if dk == dkExternFunc { var sym: Symbol; @@ -1103,6 +1011,91 @@ func Sema_CollectGlobals(sema: *Sema) { decl = decl.childDecl2; } + + // Pass 2: resolve imports by looking up actual symbols + decl = sema.module.firstItem; + while decl != null as *Decl { + let dk: int = decl.kind; + + if dk == dkUse { + if decl.useKind == 1 { + // Glob import: add all public symbols from scope + var scope: *Scope = sema.scope; + while scope != null as *Scope { + var i: int = 0; + while i < scope.count { + let sym: Symbol = scope.symbols[i]; + if sym.isPublic { + let existing: Symbol = Scope_LookupLocal(sema.scope, sym.name); + if String_Eq(existing.name, "") { + discard Scope_Define(sema.scope, sym); + } + } + i = i + 1; + } + scope = scope.parent; + } + } else if decl.useKind == 2 { + // Multi-import: resolve each name + let namesStr: String = decl.useNames; + if !String_Eq(namesStr, "") { + var start: uint = 0; + var pos: uint = 0; + let totalLen: uint = String_Len(namesStr); + while pos <= totalLen { + let atEnd: bool = pos == totalLen; + let isComma: bool = false; + if pos < totalLen { + let chStr: String = bux_str_slice(namesStr, pos, 1); + isComma = String_Eq(chStr, ","); + } + if atEnd || isComma { + let nameLen: uint = pos - start; + if nameLen > 0 { + let name: String = bux_str_slice(namesStr, start, nameLen); + let found: Symbol = Scope_Lookup(sema.scope, name); + if !String_Eq(found.name, "") && found.isPublic { + let existing: Symbol = Scope_LookupLocal(sema.scope, name); + if String_Eq(existing.name, "") { + discard Scope_Define(sema.scope, found); + } + } + } + start = pos + 1; + } + pos = pos + 1; + } + } + } else { + // Single import: resolve last path segment + let path: String = decl.usePath; + if !String_Eq(path, "") { + var lastSeg: String = path; + let containsColons: int = bux_str_contains(path, "::"); + if containsColons != 0 { + let pathLen: uint = String_Len(path); + var tryPos: uint = 0; + while tryPos < pathLen { + let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos); + if String_StartsWith(slice, "::") { + lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2); + } + tryPos = tryPos + 1; + } + } + let found: Symbol = Scope_Lookup(sema.scope, lastSeg); + if !String_Eq(found.name, "") && found.isPublic { + let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg); + if String_Eq(existing.name, "") { + discard Scope_Define(sema.scope, found); + } + } + } + } + } + + decl = decl.childDecl2; + } } // ---------------------------------------------------------------------------