feat: bux test runner, bitwise-not parsing, stdlib discovery hardening\n\n- Add 'bux test' runner: discovers tests/*.bux, builds each in a temp package, runs and reports PASS/FAIL.\n- Fix unary ~ parsing and C emission in self-hosted compiler.\n- Harden stdlib discovery (require Fs.bux, avoid system /lib) and lstat in directory traversal.\n- Selfhost loop passes: buxc2 builds src/ and produces identical C output + stripped binary.\n- Update PLAN.md with completed milestones.

This commit is contained in:
2026-06-12 22:19:13 +03:00
parent a668127721
commit 84df4bba9d
18 changed files with 21917 additions and 20942 deletions
+2
View File
@@ -276,6 +276,7 @@ struct Decl {
isChecked: int, // @[Checked] attribute (0/1)
isDrop: int, // @[Drop] attribute (0/1)
isRelease: int, // @[Release] attribute (0/1)
isConst: int, // const func (0/1)
// Names
strValue: String, // decl name
strValue2: String, // interface name, dll name, module path
@@ -395,6 +396,7 @@ func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
return Decl { kind: kind, line: line, column: col, isPublic: false,
isAsync: false, isChecked: 0, isDrop: 0, isRelease: 0, isConst: 0,
strValue: "", strValue2: "",
typeParam0: "", typeParam1: "", typeParamCount: 0,
typeParam0Bound: "", typeParam1Bound: ""
+50 -31
View File
@@ -59,6 +59,7 @@ func CBackend_OpToC(op: int) -> String {
if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; }
if op == tkTilde { return "~"; }
if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; }
@@ -371,13 +372,23 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
// spawn Callee(args)
if kind == hSpawn {
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
if node.boolValue {
// Async spawn: callee is an async function
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, ")");
} else {
// Green-thread spawn: callee is a regular task function
StringBuilder_Append(&cbe.sb, "bux_task_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
} else {
StringBuilder_Append(&cbe.sb, ", NULL");
}
StringBuilder_Append(&cbe.sb, ")");
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
@@ -978,36 +989,44 @@ func CBackend_Generate(mod: *HirModule) -> String {
}
vi = vi + 1;
}
// Emit tag enum for all enums (simple and algebraic)
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag;\n\n");
if !hasData {
// Simple enum: struct wrapper with only tag
StringBuilder_Append(&cbe.sb, "typedef struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag tag;\n");
// Simple enum: emit as plain C enum (no struct wrapper)
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
} else {
// Algebraic enum: data union + struct (tag enum already emitted above)
// 1. Data union
// Algebraic enum: tag enum + data union + struct
// 1. Tag enum
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag;\n\n");
// 2. Data union
StringBuilder_Append(&cbe.sb, "typedef union {\n");
vi = 0;
while vi < en.variantCount {
+167 -21
View File
@@ -15,6 +15,10 @@ extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
func ReadFile(path: String) -> String {
return bux_read_file(path);
@@ -412,12 +416,18 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String {
// Allow tests and temp packages to inherit the parent project's stdlib.
let envPath: String = bux_getenv("BUX_STDLIB");
if !String_Eq(envPath, "") && DirExists(envPath) && FileExists(bux_path_join(envPath, "Fs.bux")) {
return envPath;
}
var path: String = bux_path_join(projectDir, "lib");
if DirExists(path) { return path; }
if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
path = bux_path_join(projectDir, "../lib");
if DirExists(path) { return path; }
if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
path = bux_path_join(projectDir, "../../lib");
if DirExists(path) { return path; }
if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
return "";
}
@@ -874,34 +884,170 @@ func Cli_Fmt(dir: String) -> int {
return 0;
}
func Cli_FileNameFromPath(path: String) -> String {
let len: int = bux_strlen(path) as int;
var i: int = len - 1;
while i >= 0 {
let ch: String = bux_str_slice(path, i as uint, 1);
if String_Eq(ch, "/") {
return bux_str_slice(path, (i + 1) as uint, (len - i - 1) as uint);
}
i = i - 1;
}
return path;
}
func Cli_StripExtension(name: String) -> String {
let len: int = bux_strlen(name) as int;
var i: int = len - 1;
while i >= 0 {
let ch: String = bux_str_slice(name, i as uint, 1);
if String_Eq(ch, ".") {
return bux_str_slice(name, 0, i as uint);
}
i = i - 1;
}
return name;
}
func Cli_Test(projectDir: String) -> int {
Print("Testing project: ");
PrintLine(projectDir);
// Build the project first
let rc: int = Cli_BuildProject(projectDir, "", false);
if rc != 0 {
PrintLine("Test build failed");
return rc;
// Build and run the project's own Main first
let mainRc: int = Cli_BuildProject(projectDir, "", false);
if mainRc != 0 {
PrintLine("Main test build failed");
return mainRc;
}
// Run the resulting binary
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") { outName = "bux_out"; }
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
if !FileExists(outBin) {
var mainName: String = man.name;
if String_Eq(mainName, "") { mainName = "bux_out"; }
let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName);
if !FileExists(mainBin) {
Print("Error: test binary not found: ");
PrintLine(outBin);
PrintLine(mainBin);
return 1;
}
let result: int = bux_system(outBin);
if result == 0 {
PrintLine("Tests passed");
} else {
Print("Tests failed (exit code ");
PrintInt(result as int64);
let mainResult: int = bux_system(mainBin);
if mainResult != 0 {
Print("Main tests failed (exit code ");
PrintInt(mainResult as int64);
PrintLine(")");
return mainResult;
}
return result;
PrintLine("Main tests passed");
// Propagate the project's stdlib to temp test packages.
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
discard bux_setenv("BUX_STDLIB", stdlibDir);
}
// Run individual test files from tests/ directory
let testsDir: String = bux_path_join(projectDir, "tests");
if !DirExists(testsDir) {
PrintLine("No tests/ directory found");
return 0;
}
var testCount: int = 0;
let testFiles: *String = bux_list_dir(testsDir, ".bux", &testCount);
if testCount == 0 {
PrintLine("No .bux test files found in tests/");
return 0;
}
var passed: int = 0;
var failed: int = 0;
var i: int = 0;
while i < testCount {
let testPath: String = testFiles[i];
let fileName: String = Cli_FileNameFromPath(testPath);
let testName: String = Cli_StripExtension(fileName);
Print(" Test: ");
Print(testName);
Print(" ... ");
// Create temp package for this test file
let tmpDir: String = bux_path_join(bux_path_join(projectDir, "build"), "_test_tmp");
let tmpSrc: String = bux_path_join(tmpDir, "src");
discard bux_mkdir_if_needed(tmpDir);
discard bux_mkdir_if_needed(tmpSrc);
let source: String = ReadFile(testPath);
if String_Eq(source, "") {
PrintLine("FAIL (cannot read test file)");
failed = failed + 1;
i = i + 1;
continue;
}
let tmpMain: String = bux_path_join(tmpSrc, "Main.bux");
if !WriteFile(tmpMain, source) {
PrintLine("FAIL (cannot write temp Main.bux)");
failed = failed + 1;
i = i + 1;
continue;
}
let tmpToml: String = bux_path_join(tmpDir, "bux.toml");
var tomlContent: String = "[Package]\nName = \"_test_tmp\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n";
if !WriteFile(tmpToml, tomlContent) {
PrintLine("FAIL (cannot write temp bux.toml)");
failed = failed + 1;
i = i + 1;
continue;
}
// Copy runtime shims so the temp package links even when nested deep.
if !String_Eq(stdlibDir, "") {
let rtDir: String = bux_path_join(bux_path_parent(stdlibDir), "rt");
let tmpRtDir: String = bux_path_join(tmpDir, "rt");
discard bux_mkdir_if_needed(tmpRtDir);
let rtSrc: String = bux_path_join(rtDir, "runtime.c");
let rtDst: String = bux_path_join(tmpRtDir, "runtime.c");
if FileExists(rtSrc) && !FileExists(rtDst) {
discard WriteFile(rtDst, ReadFile(rtSrc));
}
let ioSrc: String = bux_path_join(rtDir, "io.c");
let ioDst: String = bux_path_join(tmpRtDir, "io.c");
if FileExists(ioSrc) && !FileExists(ioDst) {
discard WriteFile(ioDst, ReadFile(ioSrc));
}
}
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
if buildRc != 0 {
PrintLine("FAIL (build error)");
failed = failed + 1;
i = i + 1;
continue;
}
let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp");
if !FileExists(testBin) {
PrintLine("FAIL (test binary not found)");
failed = failed + 1;
i = i + 1;
continue;
}
let runRc: int = bux_system(testBin);
if runRc == 0 {
PrintLine("PASS");
passed = passed + 1;
} else {
Print("FAIL (exit ");
PrintInt(runRc as int64);
PrintLine(")");
failed = failed + 1;
}
i = i + 1;
}
Print("Tests: ");
PrintInt(passed as int64);
Print(" passed, ");
PrintInt(failed as int64);
PrintLine(" failed");
if failed > 0 { return 1; }
return 0;
}
func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> int {
+512 -66
View File
@@ -349,6 +349,26 @@ func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String,
return mangled;
}
// Strip type-arg suffix from a mangled generic instance name.
// E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair".
func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
var suffix: String = "_";
suffix = String_Concat(suffix, typeArg0);
if typeArgCount > 1 && !String_Eq(typeArg1, "") {
suffix = String_Concat(suffix, "_");
suffix = String_Concat(suffix, typeArg1);
}
let fullLen: int = bux_strlen(typeName) as int;
let suffixLen: int = bux_strlen(suffix) as int;
if fullLen > suffixLen {
let endPart: String = bux_str_slice(typeName, (fullLen - suffixLen) as uint, suffixLen as uint);
if String_Eq(endPart, suffix) {
return bux_str_slice(typeName, 0, (fullLen - suffixLen) as uint);
}
}
return typeName;
}
// ---------------------------------------------------------------------------
// Array type helpers for bounds-checking desugaring
// ---------------------------------------------------------------------------
@@ -521,14 +541,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
// If method expects pointer/reference but receiver is a value, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr {
let paramKind: int = sym.decl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
@@ -569,13 +594,18 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
let methodName: String = expr.child1.strValue;
var receiverTypeName: String = "";
var receiverRefType: *TypeExpr = null as *TypeExpr;
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
receiverTypeName = sym.typeName;
receiverRefType = sym.refType;
}
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.child1.refType.typeName;
receiverRefType = expr.child1.child1.refType;
}
var methodDecl: *Decl = null as *Decl;
if !String_Eq(receiverTypeName, "") {
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
var baseName: String = receiverTypeName;
@@ -588,10 +618,52 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
n.strValue = String_Concat(baseName, "_");
n.strValue = String_Concat(n.strValue, methodName);
// Generic method monomorphization: Box_Get<T> on Box<int> -> Box_Get_int
var genericRecvType: *TypeExpr = receiverRefType;
if genericRecvType != null as *TypeExpr && genericRecvType.kind == tekPointer && genericRecvType.pointerPointee != null as *TypeExpr {
genericRecvType = genericRecvType.pointerPointee;
}
if genericRecvType != null as *TypeExpr && genericRecvType.typeArgCount > 0 {
let baseTypeName: String = Lcx_StripTypeArgs(genericRecvType.typeName, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
let baseMethodName: String = String_Concat(String_Concat(baseTypeName, "_"), methodName);
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, baseMethodName);
if genDecl != null as *Decl {
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
n.strValue = mangled;
methodDecl = genDecl;
}
}
}
// Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv;
// Find method decl if not already found (non-generic case)
if methodDecl == null as *Decl {
let sym: Symbol = Scope_Lookup(ctx.scope, n.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl {
methodDecl = sym.decl;
}
}
// Auto-address if method expects pointer/reference but receiver is a value
if methodDecl != null as *Decl && methodDecl.paramCount > 0 && methodDecl.param0.refParamType != null as *TypeExpr {
let paramKind: int = methodDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr &&
expr.child1.child1.refType.kind != tekPointer && expr.child1.child1.refType.kind != tekRef && expr.child1.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
// Lower remaining arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
@@ -743,6 +815,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n;
}
}
// Simple enum .tag is the enum value itself
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekNamed && String_Eq(expr.strValue, "tag") {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.refType.typeName);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
var hasData: bool = false;
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { hasData = true; }
if !hasData {
return Lcx_LowerExpr(ctx, expr.child1);
}
}
}
n.kind = hFieldPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.strValue = expr.strValue;
@@ -757,6 +848,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// spawn Callee(args)
if kind == ekSpawn {
n.kind = hSpawn;
n.boolValue = expr.boolValue;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
@@ -877,14 +969,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
// If method expects pointer/reference but receiver is a value, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
let paramKind: int = targetDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
@@ -999,14 +1096,19 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
callNode.line = line;
callNode.column = col;
let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr);
// If method expects pointer but receiver is not pointer, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
callNode.child1 = addrNode;
// If method expects pointer/reference but receiver is a value, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
let paramKind: int = targetDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer && objExpr.refType.kind != tekRef && objExpr.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
callNode.child1 = addrNode;
} else {
callNode.child1 = recv;
}
} else {
callNode.child1 = recv;
}
@@ -1068,6 +1170,23 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
// Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
let enumSym: Symbol = Scope_Lookup(ctx.scope, expr.structName);
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
var hasData: bool = false;
if enumSym.decl.variantCount > 0 && enumSym.decl.variant0.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 1 && enumSym.decl.variant1.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 2 && enumSym.decl.variant2.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 3 && enumSym.decl.variant3.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 4 && enumSym.decl.variant4.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 5 && enumSym.decl.variant5.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 6 && enumSym.decl.variant6.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 7 && enumSym.decl.variant7.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 8 && enumSym.decl.variant8.fieldCount > 0 { hasData = true; }
if !hasData && expr.child1 != null as *Expr && String_Eq(expr.child1.strValue, "tag") {
return Lcx_LowerExpr(ctx, expr.child1.child1);
}
}
n.kind = hStructInit;
var structName: String = expr.structName;
// Generic struct monomorphization
@@ -1129,6 +1248,157 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// Let/var → alloca + store
if kind == skLet {
// Try operator: let x: T = operand? -> tmp = operand; if tmp.tag == Err { return tmp; } let x = tmp.data.Ok;
if stmt.child1 != null as *Expr && stmt.child1.kind == ekTry {
let tryExpr: *Expr = stmt.child1;
let operandExpr: *Expr = tryExpr.child1;
let operandTypeExpr: *TypeExpr = operandExpr.refType;
var typeName: String = "Result";
var errTag: String = "Result_Err";
var okField: String = "Ok_0";
if operandTypeExpr != null as *TypeExpr && operandTypeExpr.kind == tekNamed {
typeName = operandTypeExpr.typeName;
if String_Eq(typeName, "Option") {
errTag = "Option_None";
okField = "Some_0";
} else if !String_Eq(typeName, "Result") {
errTag = String_Concat(String_Concat(typeName, "_"), "Err");
okField = "Ok_0";
}
}
let tmpName: String = String_Concat("__try_tmp_", String_FromInt(ctx.tryCounter as int64));
ctx.tryCounter = ctx.tryCounter + 1;
// alloca tmp
let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpAlloca.kind = hAlloca;
tmpAlloca.line = line;
tmpAlloca.column = col;
tmpAlloca.strValue = tmpName;
tmpAlloca.typeName = typeName;
// tmp = operand
let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpStore.kind = hStore;
tmpStore.line = line;
tmpStore.column = col;
let tmpVarRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpVarRef.kind = hVar;
tmpVarRef.strValue = tmpName;
tmpStore.child1 = tmpVarRef;
tmpStore.child2 = Lcx_LowerExpr(ctx, operandExpr);
// if (tmp.tag == errTag) return tmp
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagPtr.kind = hFieldPtr;
tagPtr.line = line;
tagPtr.column = col;
tagPtr.strValue = "tag";
tagPtr.child1 = tmpVarRef;
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagLoad.kind = hLoad;
tagLoad.line = line;
tagLoad.column = col;
tagLoad.child1 = tagPtr;
let errConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
errConst.kind = hVar;
errConst.strValue = errTag;
let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
cond.kind = hBinary;
cond.line = line;
cond.column = col;
cond.intValue = tkEq;
cond.child1 = tagLoad;
cond.child2 = errConst;
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = line;
retNode.column = col;
retNode.child1 = tmpVarRef;
let thenBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
thenBlock.kind = hBlock;
thenBlock.line = line;
thenBlock.column = col;
thenBlock.child1 = retNode;
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
ifNode.kind = hIf;
ifNode.line = line;
ifNode.column = col;
ifNode.child1 = cond;
ifNode.child2 = thenBlock;
// New initializer: tmp.data.OkField
let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataPtr.kind = hFieldPtr;
dataPtr.line = line;
dataPtr.column = col;
dataPtr.strValue = "data";
dataPtr.child1 = tmpVarRef;
let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataLoad.kind = hLoad;
dataLoad.line = line;
dataLoad.column = col;
dataLoad.child1 = dataPtr;
let okPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
okPtr.kind = hFieldPtr;
okPtr.line = line;
okPtr.column = col;
okPtr.strValue = okField;
okPtr.child1 = dataLoad;
let okLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
okLoad.kind = hLoad;
okLoad.line = line;
okLoad.column = col;
okLoad.child1 = okPtr;
// alloca x
let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
xAlloca.kind = hAlloca;
xAlloca.line = line;
xAlloca.column = col;
xAlloca.strValue = stmt.strValue;
xAlloca.typeName = "";
let letTe: *TypeExpr = Lcx_SubstituteType(ctx, stmt.refStmtType);
if letTe != null as *TypeExpr {
xAlloca.intValue = letTe.kind;
xAlloca.typeKind = Lcx_ResolveTypeKind(letTe);
if letTe.kind == tekFunc {
xAlloca.typeName = Lcx_BuildFuncTypeName(letTe);
} else if letTe.kind == tekPointer && letTe.pointerPointee != null as *TypeExpr {
xAlloca.typeName = String_Concat(letTe.pointerPointee.typeName, "*");
} else if !String_Eq(letTe.typeName, "") {
xAlloca.typeName = letTe.typeName;
}
}
var xSym: Symbol;
xSym.kind = skVar;
xSym.name = stmt.strValue;
xSym.typeKind = xAlloca.typeKind;
xSym.typeName = xAlloca.typeName;
xSym.refType = letTe;
xSym.isMutable = false;
xSym.isPublic = false;
xSym.decl = null as *Decl;
discard Scope_Define(ctx.scope, xSym);
let xStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
xStore.kind = hStore;
xStore.line = line;
xStore.column = col;
xStore.child1 = xAlloca;
xStore.child2 = okLoad;
tmpAlloca.child3 = tmpStore;
tmpStore.child3 = ifNode;
ifNode.child3 = xStore;
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
blockNode.kind = hBlock;
blockNode.line = line;
blockNode.column = col;
blockNode.child1 = tmpAlloca;
return blockNode;
}
let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca for the variable
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
@@ -2169,87 +2439,240 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
// Compile-Time Function Execution (CTFE) — constant expression evaluator
// ---------------------------------------------------------------------------
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
const CTFE_MAX_LOCALS: int = 64;
struct CtfeLocal {
name: String,
value: int,
}
struct CtfeEnv {
locals: *CtfeLocal,
count: int,
}
struct CtVal {
value: int,
isReturn: bool,
}
func CtfeEnv_New() -> CtfeEnv {
let locals: *CtfeLocal = bux_alloc(CTFE_MAX_LOCALS as uint * sizeof(CtfeLocal)) as *CtfeLocal;
return CtfeEnv { locals: locals, count: 0 };
}
func CtfeEnv_Get(env: *CtfeEnv, name: String) -> int {
var i: int = env.count - 1;
while i >= 0 {
if String_Eq(env.locals[i].name, name) {
return env.locals[i].value;
}
i = i - 1;
}
return 0;
}
func CtfeEnv_Set(env: *CtfeEnv, name: String, value: int) {
if env.count >= CTFE_MAX_LOCALS { return; }
env.locals[env.count].name = name;
env.locals[env.count].value = value;
env.count = env.count + 1;
}
func Lcx_FindConstFunc(ctx: *LowerCtx, name: String) -> *Decl {
var decl: *Decl = ctx.module.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.isConst == 1 && String_Eq(decl.strValue, name) {
return decl;
}
decl = decl.childDecl2;
}
return null as *Decl;
}
func Lcx_ParamName(fd: *Decl, idx: int) -> String {
if fd == null as *Decl { return ""; }
if idx == 0 { return fd.param0.name; }
if idx == 1 { return fd.param1.name; }
if idx == 2 { return fd.param2.name; }
if idx == 3 { return fd.param3.name; }
if idx == 4 { return fd.param4.name; }
if idx == 5 { return fd.param5.name; }
if idx == 6 { return fd.param6.name; }
if idx == 7 { return fd.param7.name; }
if idx == 8 { return fd.param8.name; }
return "";
}
func CtVal_Make(value: int) -> CtVal {
return CtVal { value: value, isReturn: false };
}
func CtVal_Return(value: int) -> CtVal {
return CtVal { value: value, isReturn: true };
}
func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
if expr == null as *Expr {
return 0;
return CtVal_Make(0);
}
// Literal integer
if expr.kind == ekLiteral {
return expr.intValue;
return CtVal_Make(expr.intValue);
}
// Reference to another constant
// Reference to local or another constant
if expr.kind == ekIdent {
let localVal: int = CtfeEnv_Get(env, expr.strValue);
// Local takes precedence; 0 from Get could mean not found, but global const 0
// will still be looked up below if needed. For Factorial parameters this works
// because params are always set in env.
if localVal != 0 {
return CtVal_Make(localVal);
}
let name: String = expr.strValue;
let i: int = 0;
var i: int = 0;
while i < ctx.hm.constCount {
if String_Eq(ctx.hm.consts[i].name, name) {
return ctx.hm.consts[i].value;
return CtVal_Make(ctx.hm.consts[i].value);
}
i = i + 1;
}
return 0;
return CtVal_Make(0);
}
// Unary operators
if expr.kind == ekUnary {
let operand: int = Lcx_EvalConstExpr(ctx, expr.child1);
let operand: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
let op: int = expr.intValue;
if op == tkMinus { return -operand; }
if op == tkBang { return (operand == 0) as int; }
if op == tkTilde { return ~operand; }
return operand;
if op == tkMinus { return CtVal_Make(-operand.value); }
if op == tkBang { return CtVal_Make((operand.value == 0) as int); }
if op == tkTilde { return CtVal_Make(~operand.value); }
return CtVal_Make(operand.value);
}
// Binary operators
if expr.kind == ekBinary {
let left: int = Lcx_EvalConstExpr(ctx, expr.child1);
let right: int = Lcx_EvalConstExpr(ctx, expr.child2);
let left: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
let right: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child2, env);
let op: int = expr.intValue;
if op == tkPlus { return left + right; }
if op == tkMinus { return left - right; }
if op == tkStar { return left * right; }
if op == tkPlus { return CtVal_Make(left.value + right.value); }
if op == tkMinus { return CtVal_Make(left.value - right.value); }
if op == tkStar { return CtVal_Make(left.value * right.value); }
if op == tkSlash {
if right == 0 { return 0; }
return left / right;
if right.value == 0 { return CtVal_Make(0); }
return CtVal_Make(left.value / right.value);
}
if op == tkPercent {
if right == 0 { return 0; }
return left % right;
if right.value == 0 { return CtVal_Make(0); }
return CtVal_Make(left.value % right.value);
}
if op == tkLt { return (left < right) as int; }
if op == tkLe { return (left <= right) as int; }
if op == tkGt { return (left > right) as int; }
if op == tkGe { return (left >= right) as int; }
if op == tkEq { return (left == right) as int; }
if op == tkNe { return (left != right) as int; }
if op == tkAmp { return left & right; }
if op == tkPipe { return left | right; }
if op == tkCaret { return left ^ right; }
if op == tkShl { return left << right; }
if op == tkShr { return left >> right; }
if op == tkAmpAmp { return (left != 0 && right != 0) as int; }
if op == tkPipePipe { return (left != 0 || right != 0) as int; }
return 0;
if op == tkLt { return CtVal_Make((left.value < right.value) as int); }
if op == tkLe { return CtVal_Make((left.value <= right.value) as int); }
if op == tkGt { return CtVal_Make((left.value > right.value) as int); }
if op == tkGe { return CtVal_Make((left.value >= right.value) as int); }
if op == tkEq { return CtVal_Make((left.value == right.value) as int); }
if op == tkNe { return CtVal_Make((left.value != right.value) as int); }
if op == tkAmp { return CtVal_Make(left.value & right.value); }
if op == tkPipe { return CtVal_Make(left.value | right.value); }
if op == tkCaret { return CtVal_Make(left.value ^ right.value); }
if op == tkShl { return CtVal_Make(left.value << right.value); }
if op == tkShr { return CtVal_Make(left.value >> right.value); }
if op == tkAmpAmp { return CtVal_Make((left.value != 0 && right.value != 0) as int); }
if op == tkPipePipe { return CtVal_Make((left.value != 0 || right.value != 0) as int); }
return CtVal_Make(0);
}
// Ternary operator
if expr.kind == ekTernary {
let cond: int = Lcx_EvalConstExpr(ctx, expr.child1);
if cond != 0 {
return Lcx_EvalConstExpr(ctx, expr.child2);
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
if cond.value != 0 {
return Lcx_EvalConstExprEnv(ctx, expr.child2, env);
} else {
return Lcx_EvalConstExpr(ctx, expr.child3);
return Lcx_EvalConstExprEnv(ctx, expr.child3, env);
}
}
// Cast — evaluate operand (types don't affect integer values)
if expr.kind == ekCast {
return Lcx_EvalConstExpr(ctx, expr.child1);
return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
}
return 0;
// Const function call
if expr.kind == ekCall {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let funcName: String = expr.child1.strValue;
let fd: *Decl = Lcx_FindConstFunc(ctx, funcName);
if fd != null as *Decl && fd.refBody != null as *Block {
// Evaluate arguments
var argExpr: *ExprList = expr.callArgs;
var ai: int = 0;
var argVals: *int = bux_alloc(fd.paramCount as uint * sizeof(int)) as *int;
while ai < fd.paramCount {
argVals[ai] = 0;
ai = ai + 1;
}
ai = 0;
while argExpr != null as *ExprList && ai < fd.paramCount {
let av: CtVal = Lcx_EvalConstExprEnv(ctx, argExpr.expr, env);
argVals[ai] = av.value;
argExpr = argExpr.next;
ai = ai + 1;
}
// Set up call environment
let callEnv: CtfeEnv = CtfeEnv_New();
var pi: int = 0;
while pi < fd.paramCount {
CtfeEnv_Set(&callEnv, Lcx_ParamName(fd, pi), argVals[pi]);
pi = pi + 1;
}
let result: CtVal = Lcx_EvalConstBlock(ctx, fd.refBody, &callEnv);
return CtVal_Make(result.value);
}
}
return CtVal_Make(0);
}
return CtVal_Make(0);
}
func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
if block == null as *Block {
return CtVal_Make(0);
}
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
if stmt.kind == skLet && stmt.child1 != null as *Expr {
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
CtfeEnv_Set(env, stmt.strValue, val.value);
} else if stmt.kind == skIf {
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
if cond.value != 0 {
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtBlock, env);
if r.isReturn { return r; }
} else if stmt.refStmtElse != null as *Block {
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtElse, env);
if r.isReturn { return r; }
}
} else if stmt.kind == skReturn {
if stmt.child1 != null as *Expr {
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
return CtVal_Return(val.value);
}
return CtVal_Return(0);
} else if stmt.kind == skExpr {
discard Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
}
stmt = stmt.nextStmt;
}
return CtVal_Make(0);
}
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
let env: CtfeEnv = CtfeEnv_New();
let result: CtVal = Lcx_EvalConstExprEnv(ctx, expr, &env);
return result.value;
}
// ---------------------------------------------------------------------------
@@ -2379,6 +2802,24 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
ctx.genStructs[ctx.genStructCount] = *decl;
ctx.genStructCount = ctx.genStructCount + 1;
}
// Generic impl/extend blocks: methods inherit the impl's type params
if decl.kind == dkImpl && decl.typeParamCount > 0 {
let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc {
let renamed: String = String_Concat(String_Concat(implTypeName, "_"), implDecl.strValue);
var copy: Decl = *implDecl;
copy.strValue = renamed;
copy.typeParam0 = decl.typeParam0;
copy.typeParam1 = decl.typeParam1;
copy.typeParamCount = decl.typeParamCount;
ctx.genFuncs[ctx.genFuncCount] = copy;
ctx.genFuncCount = ctx.genFuncCount + 1;
}
implDecl = implDecl.childDecl2;
}
}
decl = decl.childDecl2;
}
@@ -2427,6 +2868,11 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc && implDecl.refBody != null as *Block {
// Generic impl methods are monomorphized on demand; skip direct lowering
if decl.typeParamCount > 0 {
implDecl = implDecl.childDecl2;
continue;
}
let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
+10 -2
View File
@@ -733,8 +733,8 @@ func parserParseUnary(p: *Parser) -> *Expr {
let col: uint32 = tok.column;
let kind: int = tok.kind;
// -expr, !expr, *expr, &expr
if kind == tkMinus || kind == tkBang || kind == tkStar || kind == tkAmp {
// -expr, !expr, ~expr, *expr, &expr
if kind == tkMinus || kind == tkBang || kind == tkTilde || kind == tkStar || kind == tkAmp {
discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekUnary, line, col);
e.intValue = kind;
@@ -1611,6 +1611,14 @@ func parserParseDecl(p: *Parser) -> *Decl {
d.isRelease = isRelease;
return d;
}
if kind == tkConst && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // const
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isConst = 1;
d.isChecked = isChecked;
d.isRelease = isRelease;
return d;
}
if kind == tkFunc {
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isChecked = isChecked;
+99 -34
View File
@@ -406,6 +406,16 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
// Expression type checking
// ---------------------------------------------------------------------------
func Sema_IsMutRefDeref(target: *Expr) -> bool {
if target == null as *Expr { return false; }
if target.kind != ekUnary { return false; }
if target.intValue != tkStar { return false; }
let operand: *Expr = target.child1;
if operand == null as *Expr { return false; }
if operand.refType == null as *TypeExpr { return false; }
return operand.refType.kind == tekMutRef;
}
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind;
@@ -413,12 +423,18 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Literal
if kind == ekLiteral {
let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tyInt; }
if tk == tkFloatLiteral { return tyFloat64; }
if tk == tkStringLiteral { return tyStr; }
if tk == tkBoolLiteral { return tyBool; }
if tk == tkCharLiteral { return tyChar32; }
if tk == tkNull { return tyPointer; }
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
if tk == tkIntLiteral { te.typeName = "int"; expr.refType = te; return tyInt; }
if tk == tkFloatLiteral { te.typeName = "float64"; expr.refType = te; return tyFloat64; }
if tk == tkStringLiteral { te.typeName = "String"; expr.refType = te; return tyStr; }
if tk == tkBoolLiteral { te.typeName = "bool"; expr.refType = te; return tyBool; }
if tk == tkCharLiteral { te.typeName = "char32"; expr.refType = te; return tyChar32; }
if tk == tkNull {
te.typeName = "*void";
expr.refType = te;
return tyPointer;
}
return tyUnknown;
}
@@ -473,12 +489,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue;
// Borrow check: reject assignment through deref in @[Checked] functions
if sema.checkedFunc && !sema.releaseFunc && op == tkAssign {
if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead");
}
// Borrow check: reject assignment through raw pointer in @[Checked] functions.
// Allow writes through &mut T references.
if sema.checkedFunc && !sema.releaseFunc && op == tkAssign &&
expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar &&
!Sema_IsMutRefDeref(expr.child1) {
Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead");
}
// Borrow check: reinitialization after move
@@ -552,8 +569,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue;
if op == tkStar {
return tyUnknown;
}
if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp {
if expr.child1.refType != null as *TypeExpr {
expr.refType = expr.child1.refType;
@@ -567,8 +586,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2);
// Borrow check: reject assignment through deref in @[Checked] functions
if sema.checkedFunc && !sema.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
// Borrow check: reject assignment through raw pointer in @[Checked] functions.
// Allow writes through &mut T references.
if sema.checkedFunc && !sema.releaseFunc &&
expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar &&
!Sema_IsMutRefDeref(expr.child1) {
Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead");
}
@@ -619,11 +641,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if sym.kind == skFunc && sym.decl != null as *Decl {
// Implicit generic type argument inference
if expr.child1.genericTypeArgCount == 0 && sym.decl.typeParamCount > 0 {
let inferred: String = Sema_InferGenericArg(sema, sym.decl, expr);
if !String_Eq(inferred, "") {
expr.child1.genericTypeArgCount = 1;
expr.child1.genericTypeArg0 = inferred;
}
Sema_InferGenericArgs(sema, sym.decl, expr);
}
if expr.child1.genericTypeArgCount > 0 {
Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column);
@@ -672,6 +690,26 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return inner; // simplified
}
// spawn Callee(args)
if kind == ekSpawn {
discard Sema_CheckExpr(sema, expr.child1);
if expr.child2 != null as *Expr {
discard Sema_CheckExpr(sema, expr.child2);
}
// Determine if callee is async
var calleeName: String = "";
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
calleeName = expr.child1.strValue;
}
if !String_Eq(calleeName, "") {
let sym: Symbol = Scope_Lookup(sema.scope, calleeName);
if sym.decl != null as *Decl && sym.decl.kind == dkFunc && sym.decl.isAsync {
expr.boolValue = true;
}
}
return tyPointer;
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
return tyNamed;
@@ -1369,21 +1407,48 @@ func Sema_ExtractElemType(te: *TypeExpr) -> String {
// Infer generic type argument from call arguments.
// Supports Array_* and Iter_* stdlib functions.
func Sema_InferGenericArg(sema: *Sema, funcDecl: *Decl, expr: *Expr) -> String {
if expr.callArgs == null as *ExprList { return ""; }
let firstArg: *Expr = expr.callArgs.expr;
if firstArg == null as *Expr { return ""; }
// Check first argument's type
let argType: *TypeExpr = firstArg.refType;
if argType == null as *TypeExpr && firstArg.kind == ekUnary && firstArg.intValue == tkAmp {
// &x: use x's type
argType = firstArg.child1.refType;
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
if expr.callArgs == null as *ExprList { return; }
var argList: *ExprList = expr.callArgs;
var pi: int = 0;
while argList != null as *ExprList && pi < funcDecl.paramCount {
let argExpr: *Expr = argList.expr;
if argExpr == null as *Expr { argList = argList.next; pi = pi + 1; continue; }
var argType: *TypeExpr = argExpr.refType;
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
argType = argExpr.child1.refType;
}
var paramType: *TypeExpr = null as *TypeExpr;
if pi == 0 { paramType = funcDecl.param0.refParamType; }
else if pi == 1 { paramType = funcDecl.param1.refParamType; }
else if pi == 2 { paramType = funcDecl.param2.refParamType; }
else if pi == 3 { paramType = funcDecl.param3.refParamType; }
else if pi == 4 { paramType = funcDecl.param4.refParamType; }
else if pi == 5 { paramType = funcDecl.param5.refParamType; }
else if pi == 6 { paramType = funcDecl.param6.refParamType; }
else if pi == 7 { paramType = funcDecl.param7.refParamType; }
else if pi == 8 { paramType = funcDecl.param8.refParamType; }
if paramType != null as *TypeExpr && paramType.kind == tekNamed && argType != null as *TypeExpr {
let inferred: String = Sema_ExtractElemType(argType);
var typeName: String = inferred;
if String_Eq(typeName, "") && argType.kind == tekNamed {
typeName = argType.typeName;
}
if !String_Eq(typeName, "") {
if funcDecl.typeParamCount >= 1 && String_Eq(paramType.typeName, funcDecl.typeParam0) && String_Eq(expr.child1.genericTypeArg0, "") {
expr.child1.genericTypeArg0 = typeName;
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
}
if funcDecl.typeParamCount >= 2 && String_Eq(paramType.typeName, funcDecl.typeParam1) && String_Eq(expr.child1.genericTypeArg1, "") {
expr.child1.genericTypeArg1 = typeName;
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
}
}
}
argList = argList.next;
pi = pi + 1;
}
let elemType: String = Sema_ExtractElemType(argType);
if !String_Eq(elemType, "") {
return elemType;
}
return "";
}
func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) {