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
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp
+8 -7
View File
@@ -919,10 +919,11 @@ func Main() -> int {
### Next Actions (Priority Order)
1.**Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations
2.**All 26 examples passing** — bootstrap compiler builds and runs every example
2.**All 30 examples passing** — bootstrap and self-host compilers build and run every example
3.**Selfhost build working**`make selfhost` produces working `buxc2`
4. 🔄 **Selfhost loop verification**`buxc2` compiles `src/``buxc3`
5. 🔄 **Golden tests for C codegen**Prevent future regressions in LIR → C
4. **Selfhost loop verification**`buxc2` compiles `src/``buxc3`; C output and stripped binary identical
5. **Golden tests for C codegen**`make test-golden` passes for 8 critical examples
6.**`bux test` runner** — discovers `tests/*.bux`, builds each as a temp package, and reports PASS/FAIL
---
@@ -941,9 +942,9 @@ func Main() -> int {
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.2.1` `buxc2 build` on `src/` | | P0 | Verify selfhost compiler can build itself |
| `11.2.2` Deterministic C codegen | | P0 | Remove non-deterministic ordering in `emitModule` |
| `11.2.3` `make selfhost-loop` | | P0 | Makefile target: buxc2 → buxc3 → binary compare |
| `11.2.1` `buxc2 build` on `src/` | | P0 | Verify selfhost compiler can build itself |
| `11.2.2` Deterministic C codegen | | P0 | Remove non-deterministic ordering in `emitModule` |
| `11.2.3` `make selfhost-loop` | | P0 | Makefile target: buxc2 → buxc3 → binary compare |
| `11.2.4` Cross-module generics in selfhost | ⏳ | P1 | `Map<K,V>` and `Array<T>` from stdlib in selfhost context |
### 11.3 — Gradual Ownership (v0.5.0) ⭐⭐
@@ -958,7 +959,7 @@ func Main() -> int {
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.4.1` `bux test` runner | | P0 | Built-in test framework with assertions |
| `11.4.1` `bux test` runner | | P0 | Built-in test framework with assertions |
| `11.4.2` `bux fmt` formatter | ⏳ | P1 | Auto-format Bux source |
| `11.4.3` LSP prototype | ⏳ | P2 | Autocomplete, hover types, go-to-definition |
| `11.4.4` `bux doc` generator | ⏳ | P2 | HTML from `///` doc comments |
+2 -2
View File
@@ -675,7 +675,7 @@ static int bux_count_files_recursive(const char* dir, const char* ext, size_t ex
if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st;
if (stat(path, &st) == 0) {
if (lstat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
count += bux_count_files_recursive(path, ext, ext_len);
} else {
@@ -702,7 +702,7 @@ static int bux_collect_files_recursive(const char* dir, const char* ext, size_t
if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st;
if (stat(path, &st) == 0) {
if (lstat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
idx = bux_collect_files_recursive(path, ext, ext_len, result, idx);
free(path);
+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: ""
+33 -14
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 {
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, ")");
}
return;
}
@@ -978,7 +989,27 @@ func CBackend_Generate(mod: *HirModule) -> String {
}
vi = vi + 1;
}
// Emit tag enum for all enums (simple and algebraic)
if !hasData {
// 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: tag enum + data union + struct
// 1. Tag enum
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
@@ -995,19 +1026,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
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");
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
// 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 {
+496 -50
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,9 +541,11 @@ 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 {
// 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;
@@ -535,6 +557,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
@@ -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);
// 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,9 +969,11 @@ 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 {
// 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;
@@ -891,6 +985,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
@@ -999,9 +1096,11 @@ 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 {
// 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;
@@ -1013,6 +1112,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else {
callNode.child1 = recv;
}
} else {
callNode.child1 = recv;
}
callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2);
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
extra.node = Lcx_LowerExpr(ctx, expr.child2);
@@ -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 {
if expr == null as *Expr {
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 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;
+96 -31
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,13 +489,14 @@ 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 {
// 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
if sema.checkedFunc && !sema.releaseFunc && op == tkAssign {
@@ -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;
}
let elemType: String = Sema_ExtractElemType(argType);
if !String_Eq(elemType, "") {
return elemType;
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;
}
return "";
}
func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) {
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+2628 -2592
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2615 -2579
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff