diff --git a/README.md b/README.md index 94b0651..94f784b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Bux Language](bux-lang-01.jpeg) -> **Status:** **v1.0.0** — language freeze. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary. +> **Status:** **v1.0.2** — language freeze (1.0) + patch fixes. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary. > **Selfhost loop:** deterministic C codegen + ELF verified. > **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions. > **Closures:** multi-instance capturing closures via fat function pointers (`BuxFn { code, env }`) in both compilers. @@ -316,6 +316,7 @@ make vscode # VS Code extension (syntax + client; auto-finds tools/bux | [`docs/Packages.md`](docs/Packages.md) | Package manager + registry | | [`docs/SEMVER.md`](docs/SEMVER.md) | Semver policy (**active** post-1.0) | | [`docs/RELEASE_v1.0.0.md`](docs/RELEASE_v1.0.0.md) | v1.0.0 freeze notes | +| [`docs/RELEASE_v1.0.2.md`](docs/RELEASE_v1.0.2.md) | v1.0.2 patch (stdlib grow + checked div) | | [`docs/ROADMAP.md`](docs/ROADMAP.md) | Language construct status | | [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Session history + path to v1.0 (archive) | | [`vscode/README.md`](vscode/README.md) | VS Code extension install & settings | diff --git a/bootstrap/c_backend.nim b/bootstrap/c_backend.nim index 10ffcde..fc83ccd 100644 --- a/bootstrap/c_backend.nim +++ b/bootstrap/c_backend.nim @@ -256,6 +256,12 @@ proc emitExpr(be: var CBackend, node: HirNode): string = of hBinary: let left = be.emitExpr(node.binaryLeft) let right = be.emitExpr(node.binaryRight) + # Checked integer / and % (float keeps raw C operators) + let isFloat = node.typ != nil and node.typ.kind in {tkFloat32, tkFloat64} + if node.binaryOp == tkSlash and not isFloat: + return &"bux_div_i64((int64_t)({left}), (int64_t)({right}))" + if node.binaryOp == tkPercent and not isFloat: + return &"bux_mod_i64((int64_t)({left}), (int64_t)({right}))" let op = operatorToC(node.binaryOp) return &"({left} {op} {right})" @@ -658,6 +664,9 @@ proc emitModule*(be: var CBackend, module: HirModule): string = be.emitLine("#include ") be.emitLine("#include ") be.emitLine("") + be.emitLine("extern int64_t bux_div_i64(int64_t a, int64_t b);") + be.emitLine("extern int64_t bux_mod_i64(int64_t a, int64_t b);") + be.emitLine("") # Pre-collect slice types so we can emit forward declarations early let sliceTypes = collectSliceTypes(module) diff --git a/bootstrap/cli.nim b/bootstrap/cli.nim index 706c22a..313306e 100644 --- a/bootstrap/cli.nim +++ b/bootstrap/cli.nim @@ -1350,7 +1350,7 @@ proc cmdDoc*(args: seq[string], opts: GlobalOptions): int = return 0 proc cmdVersion*(args: seq[string], opts: GlobalOptions): int = - echo "bux 1.0.0 (bootstrap)" + echo "bux 1.0.2 (bootstrap)" return 0 proc runCli*(args: seq[string]): int = diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim index 41e63e1..41570c7 100644 --- a/bootstrap/lir_c_backend.nim +++ b/bootstrap/lir_c_backend.nim @@ -106,14 +106,17 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) = be.emitLine(&"{v(instr.dst)} = {v(instr.src)};") # ── Arithmetic ── - of lirAdd, lirSub, lirMul, lirDiv, lirMod, + of lirDiv: + # Checked integer division (runtime panics on divisor 0 instead of SIGFPE). + be.emitLine(&"{v(instr.dst)} = bux_div_i64((int64_t)({v(instr.src)}), (int64_t)({v(instr.src2)}));") + of lirMod: + be.emitLine(&"{v(instr.dst)} = bux_mod_i64((int64_t)({v(instr.src)}), (int64_t)({v(instr.src2)}));") + of lirAdd, lirSub, lirMul, lirAnd, lirOr, lirXor, lirShl, lirShr: let op = case instr.kind of lirAdd: "+" of lirSub: "-" of lirMul: "*" - of lirDiv: "/" - of lirMod: "%" of lirAnd: "&" of lirOr: "|" of lirXor: "^" @@ -629,6 +632,10 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s be.emitLine("#include ") be.emitLine("#include ") be.emitLine("") + # Checked integer div/mod (rt/runtime*.c) — always available + be.emitLine("extern int64_t bux_div_i64(int64_t a, int64_t b);") + be.emitLine("extern int64_t bux_mod_i64(int64_t a, int64_t b);") + be.emitLine("") # Forward struct declarations for s in module.structs: diff --git a/bux.toml b/bux.toml index 57098f5..0114355 100644 --- a/bux.toml +++ b/bux.toml @@ -1,6 +1,6 @@ [Package] Name = "buxc" -Version = "1.0.0" +Version = "1.0.2" Type = "bin" [Build] diff --git a/docs/IMPROVEMENTS.md b/docs/IMPROVEMENTS.md index 89060cd..24cacb5 100644 --- a/docs/IMPROVEMENTS.md +++ b/docs/IMPROVEMENTS.md @@ -1,10 +1,21 @@ # Bux — План за подобрения (post-v1.0.0) > **Дата:** 2026-07-28 -> **Статус:** Всички приоритетни задачи изпълнени ✅ · follow-up DX/correctness shipped +> **Статус:** Всички приоритетни задачи изпълнени ✅ · follow-up DX/correctness shipped · **v1.0.2** patch --- +## Сесия 6 — v1.0.2 runtime correctness (2026-07-28) + +| # | Задача | Файлове | +|---|--------|---------| +| S.1 | `Array_Push` grow from cap 0 (no segfault) | `lib/Array.bux` | +| S.2 | `Map`/`StringMap`/`Set` min cap + auto-rehash (no hang / FPE) | `lib/Map.bux`, `lib/Set.bux` | +| S.3 | Integer `/` `%` → `bux_div_i64` / `bux_mod_i64` | `bootstrap/lir_c_backend.nim`, `bootstrap/c_backend.nim`, `src/c_backend.bux` | +| S.4 | Version **1.0.2** + golden regressions | `bootstrap/cli.nim`, `src/cli.bux`, `bux.toml`, `tests/stdlib_golden/` | + +**Verified:** stdlib golden PASS; `Array_New(0)+Push` OK; `Map_New(2)+5 inserts` OK; div-by-zero → `bux panic: division by zero`; examples PASS; fmt-check PASS. + ## Сесия 4 — Try payload type + LSP formatting (2026-07-28) | # | Задача | Файлове | diff --git a/docs/RELEASE_v1.0.2.md b/docs/RELEASE_v1.0.2.md new file mode 100644 index 0000000..1884c92 --- /dev/null +++ b/docs/RELEASE_v1.0.2.md @@ -0,0 +1,24 @@ +# Bux v1.0.2 — Patch + +**Date:** 2026-07-28 +**Tag:** `v1.0.2` + +Patch release on the **v1.0 language freeze**. No language-surface breakage; +stdlib + codegen correctness only. + +## Fixes + +| Area | Issue | Fix | +|------|--------|-----| +| `Array_Push` | `Array_New(0)` + push → segfault (`cap * 2 == 0`) | Grow to at least 4 when capacity was 0 (same as `Array_Insert`) | +| `Map` / `StringMap` | Full table → infinite loop in open-addressing probe; `cap == 0` → SIGFPE on `%` | Default min cap 8; auto-rehash when load ≥ 50% | +| `Set` | Same as Map | Same grow / min-cap policy | +| Integer `/` and `%` | Divisor 0 → raw SIGFPE | Emit `bux_div_i64` / `bux_mod_i64` (bootstrap LIR + C backends, selfhost C backend) | + +## Verify + +```bash +make build +./buxc --version # bux 1.0.2 (bootstrap) +make test-stdlib test-examples +``` diff --git a/lib/Array.bux b/lib/Array.bux index c0a98f2..8cf1f14 100644 --- a/lib/Array.bux +++ b/lib/Array.bux @@ -21,7 +21,11 @@ module Std::Array { /// Append `value`, growing capacity if needed. func Array_Push(self: *Array, value: T) { if self.len >= self.cap { - self.cap = self.cap * 2; + var newCap: uint = self.cap * 2; + if newCap == 0 { + newCap = 4; + } + self.cap = newCap; self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T; } self.data[self.len] = value; diff --git a/lib/Map.bux b/lib/Map.bux index e109e57..ab9ca5f 100644 --- a/lib/Map.bux +++ b/lib/Map.bux @@ -2,6 +2,8 @@ module Std::Map { extern func bux_hash_bytes(ptr: *void, size: uint) -> uint; extern func bux_hash_string(s: String) -> uint; + extern func bux_alloc(size: uint) -> *void; + extern func bux_free(ptr: *void); // --------------------------------------------------------------------------- // Generic Map — works with value-type keys (int, float, etc.) @@ -20,18 +22,48 @@ module Std::Map { len: uint, } + /// Create a map with at least `cap` slots (open addressing). + /// `cap == 0` defaults to 8 so inserts never hit `% 0` or a full table with no growth room. func Map_New(cap: uint) -> Map { - let total: uint = cap * sizeof(MapEntry); + var c: uint = cap; + if c == 0 { + c = 8; + } + let total: uint = c * sizeof(MapEntry); let data: *MapEntry = bux_alloc(total) as *MapEntry; var i: uint = 0; - while i < cap { + while i < c { data[i].occupied = false; i = i + 1; } - return Map { entries: data, cap: cap, len: 0 }; + return Map { entries: data, cap: c, len: 0 }; } - func Map_Set(m: *Map, key: K, value: V) { + /// Grow / rehash to `newCap` (must be > 0). Transfers ownership; does not Drop `fresh`. + func Map_Rehash(m: *Map, newCap: uint) { + var nc: uint = newCap; + if nc == 0 { + nc = 8; + } + var fresh: Map = Map_New(nc); + var i: uint = 0; + while i < m.cap { + if m.entries[i].occupied { + Map_SetInsertOnly(&fresh, m.entries[i].key, m.entries[i].value); + } + i = i + 1; + } + bux_free(m.entries as *void); + m.entries = fresh.entries; + m.cap = fresh.cap; + m.len = fresh.len; + fresh.entries = null as *MapEntry; + fresh.cap = 0; + fresh.len = 0; + } + + /// Insert assuming free slots exist (no grow). Used by rehash. + func Map_SetInsertOnly(m: *Map, key: K, value: V) { var keyPtr: *K = &key; let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K)); var idx: uint = hash % m.cap; @@ -48,7 +80,23 @@ module Std::Map { m.len = m.len + 1; } + func Map_Set(m: *Map, key: K, value: V) { + // Keep load factor under ~50% for open addressing (also handles cap==0). + if m.cap == 0 || m.len * 2 >= m.cap { + var nc: uint = m.cap * 2; + if nc < 8 { + nc = 8; + } + Map_Rehash(m, nc); + } + Map_SetInsertOnly(m, key, value); + } + func Map_Get(m: *Map, key: K) -> V { + if m.cap == 0 { + var zero: V = 0 as V; + return zero; + } var keyPtr: *K = &key; let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K)); var idx: uint = hash % m.cap; @@ -72,6 +120,9 @@ module Std::Map { } func Map_Has(m: *Map, key: K) -> bool { + if m.cap == 0 { + return false; + } var keyPtr: *K = &key; let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K)); var idx: uint = hash % m.cap; @@ -97,12 +148,16 @@ module Std::Map { if !Map_Has(m, key) { return false; } - var fresh: Map = Map_New(m.cap); + var keepCap: uint = m.cap; + if keepCap == 0 { + keepCap = 8; + } + var fresh: Map = Map_New(keepCap); var i: uint = 0; while i < m.cap { if m.entries[i].occupied { if m.entries[i].key != key { - Map_Set(&fresh, m.entries[i].key, m.entries[i].value); + Map_SetInsertOnly(&fresh, m.entries[i].key, m.entries[i].value); } } i = i + 1; @@ -155,17 +210,43 @@ module Std::Map { } func StringMap_New(cap: uint) -> StringMap { - let total: uint = cap * sizeof(StringMapEntry); + var c: uint = cap; + if c == 0 { + c = 8; + } + let total: uint = c * sizeof(StringMapEntry); let data: *StringMapEntry = bux_alloc(total) as *StringMapEntry; var i: uint = 0; - while i < cap { + while i < c { data[i].occupied = false; i = i + 1; } - return StringMap { entries: data, cap: cap, len: 0 }; + return StringMap { entries: data, cap: c, len: 0 }; } - func StringMap_Set(m: *StringMap, key: String, value: V) { + func StringMap_Rehash(m: *StringMap, newCap: uint) { + var nc: uint = newCap; + if nc == 0 { + nc = 8; + } + var fresh: StringMap = StringMap_New(nc); + var i: uint = 0; + while i < m.cap { + if m.entries[i].occupied { + StringMap_SetInsertOnly(&fresh, m.entries[i].key, m.entries[i].value); + } + i = i + 1; + } + bux_free(m.entries as *void); + m.entries = fresh.entries; + m.cap = fresh.cap; + m.len = fresh.len; + fresh.entries = null as *StringMapEntry; + fresh.cap = 0; + fresh.len = 0; + } + + func StringMap_SetInsertOnly(m: *StringMap, key: String, value: V) { let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -181,7 +262,22 @@ module Std::Map { m.len = m.len + 1; } + func StringMap_Set(m: *StringMap, key: String, value: V) { + if m.cap == 0 || m.len * 2 >= m.cap { + var nc: uint = m.cap * 2; + if nc < 8 { + nc = 8; + } + StringMap_Rehash(m, nc); + } + StringMap_SetInsertOnly(m, key, value); + } + func StringMap_Get(m: *StringMap, key: String) -> V { + if m.cap == 0 { + var zero: V = 0 as V; + return zero; + } let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -203,6 +299,9 @@ module Std::Map { } func StringMap_Has(m: *StringMap, key: String) -> bool { + if m.cap == 0 { + return false; + } let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -226,12 +325,16 @@ module Std::Map { if !StringMap_Has(m, key) { return false; } - var fresh: StringMap = StringMap_New(m.cap); + var keepCap: uint = m.cap; + if keepCap == 0 { + keepCap = 8; + } + var fresh: StringMap = StringMap_New(keepCap); var i: uint = 0; while i < m.cap { if m.entries[i].occupied { if !String_Eq(m.entries[i].key, key) { - StringMap_Set(&fresh, m.entries[i].key, m.entries[i].value); + StringMap_SetInsertOnly(&fresh, m.entries[i].key, m.entries[i].value); } } i = i + 1; diff --git a/lib/Set.bux b/lib/Set.bux index 960a757..55778d7 100644 --- a/lib/Set.bux +++ b/lib/Set.bux @@ -16,18 +16,45 @@ module Std::Set { len: uint, } + /// Create a set with at least `cap` slots. `cap == 0` defaults to 8. func Set_New(cap: uint) -> Set { - let total: uint = cap * sizeof(SetEntry); + var c: uint = cap; + if c == 0 { + c = 8; + } + let total: uint = c * sizeof(SetEntry); let data: *SetEntry = bux_alloc(total) as *SetEntry; var i: uint = 0; - while i < cap { + while i < c { data[i].occupied = false; i = i + 1; } - return Set { entries: data, cap: cap, len: 0 }; + return Set { entries: data, cap: c, len: 0 }; } - func Set_Add(s: *Set, value: T) { + func Set_Rehash(s: *Set, newCap: uint) { + var nc: uint = newCap; + if nc == 0 { + nc = 8; + } + var fresh: Set = Set_New(nc); + var i: uint = 0; + while i < s.cap { + if s.entries[i].occupied { + Set_AddInsertOnly(&fresh, s.entries[i].value); + } + i = i + 1; + } + bux_free(s.entries as *void); + s.entries = fresh.entries; + s.cap = fresh.cap; + s.len = fresh.len; + fresh.entries = null as *SetEntry; + fresh.cap = 0; + fresh.len = 0; + } + + func Set_AddInsertOnly(s: *Set, value: T) { var valuePtr: *T = &value; let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T)); var idx: uint = hash % s.cap; @@ -43,7 +70,21 @@ module Std::Set { s.len = s.len + 1; } + func Set_Add(s: *Set, value: T) { + if s.cap == 0 || s.len * 2 >= s.cap { + var nc: uint = s.cap * 2; + if nc < 8 { + nc = 8; + } + Set_Rehash(s, nc); + } + Set_AddInsertOnly(s, value); + } + func Set_Has(s: *Set, value: T) -> bool { + if s.cap == 0 { + return false; + } var valuePtr: *T = &value; let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T)); var idx: uint = hash % s.cap; @@ -70,14 +111,18 @@ module Std::Set { if !Set_Has(s, value) { return false; } - var fresh: Set = Set_New(s.cap); + var keepCap: uint = s.cap; + if keepCap == 0 { + keepCap = 8; + } + var fresh: Set = Set_New(keepCap); var i: uint = 0; while i < s.cap { if s.entries[i].occupied { var entryPtr: *T = &s.entries[i].value; var valuePtr: *T = &value; if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 { - Set_Add(&fresh, s.entries[i].value); + Set_AddInsertOnly(&fresh, s.entries[i].value); } } i = i + 1; diff --git a/src/c_backend.bux b/src/c_backend.bux index 470e9c9..21a180a 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -996,7 +996,24 @@ module CBackend { // Binary — always parenthesize so C precedence cannot rewrite the AST. // Without parens, Mul(Add(a,b), c) emits `a + b * c` (= a+(b*c)) instead of (a+b)*c. + // Integer / and % use checked runtime helpers (panic on zero divisor). if kind == hBinary { + if node.intValue == tkSlash { + StringBuilder_Append(&cbe.sb, "bux_div_i64((int64_t)("); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, "), (int64_t)("); + CBE_EmitExpr(cbe, node.child2); + StringBuilder_Append(&cbe.sb, "))"); + return; + } + if node.intValue == tkPercent { + StringBuilder_Append(&cbe.sb, "bux_mod_i64((int64_t)("); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, "), (int64_t)("); + CBE_EmitExpr(cbe, node.child2); + StringBuilder_Append(&cbe.sb, "))"); + return; + } StringBuilder_Append(&cbe.sb, "("); CBE_EmitExpr(cbe, node.child1); StringBuilder_Append(&cbe.sb, " "); @@ -2108,6 +2125,8 @@ module CBackend { StringBuilder_Append(&cbe.sb, "#include \n"); StringBuilder_Append(&cbe.sb, "#include \n"); StringBuilder_Append(&cbe.sb, "#include \n\n"); + StringBuilder_Append(&cbe.sb, "extern int64_t bux_div_i64(int64_t a, int64_t b);\n"); + StringBuilder_Append(&cbe.sb, "extern int64_t bux_mod_i64(int64_t a, int64_t b);\n\n"); // Type aliases StringBuilder_Append(&cbe.sb, "typedef const char* String;\n"); StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n"); diff --git a/src/cli.bux b/src/cli.bux index 795cc8c..bfc88f4 100644 --- a/src/cli.bux +++ b/src/cli.bux @@ -2337,7 +2337,7 @@ func Cli_Run(args: *String, argCount: int) -> int { } if argCount < 2 { - PrintLine("Bux Self-Hosting Compiler v1.0.0"); + PrintLine("Bux Self-Hosting Compiler v1.0.2"); PrintLine("Usage: buxc [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); @@ -2355,12 +2355,12 @@ func Cli_Run(args: *String, argCount: int) -> int { let cmd: String = args[1]; if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") { - PrintLine("Bux 1.0.0 (self-hosting)"); + PrintLine("Bux 1.0.2 (self-hosting)"); return 0; } if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") { - PrintLine("Bux Self-Hosting Compiler v1.0.0"); + PrintLine("Bux Self-Hosting Compiler v1.0.2"); PrintLine("Usage: buxc [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); diff --git a/src/hir_lower.bux b/src/hir_lower.bux index 9f84e2b..053eb5b 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -2579,7 +2579,7 @@ module HirLower { } // Fallback: type annotation on the is-expression operand if String_Eq(enumName, "") && expr.child1 != null as *Expr && - expr.child1.refType != null as *TypeExpr { + expr.child1.refType != null as *TypeExpr { let te: *TypeExpr = Lcx_SubstituteType(ctx, expr.child1.refType); if te != null as *TypeExpr && !String_Eq(te.typeName, "") { enumName = te.typeName; diff --git a/src/sema.bux b/src/sema.bux index 2fbbba6..7213575 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -358,7 +358,7 @@ module Sema { } if !foundName { Sema_EmitError(sema, expr.line, expr.column, - String_Concat("unknown argument name '", String_Concat(checkArg.argName, "'"))); + String_Concat("unknown argument name '", String_Concat(checkArg.argName, "'"))); } } checkArg = checkArg.next; @@ -1079,8 +1079,8 @@ module Sema { // Arity check (runs after Sema_ResolveCallArgs so defaults/named args align) if callDecl != null as *Decl && expr.callArgCount != callDecl.paramCount && sema.diagCount == diagsBeforeCallArgs { Sema_EmitError(sema, expr.line, expr.column, - String_Concat("expected ", String_Concat(bux_int_to_str(callDecl.paramCount as int64), - String_Concat(" arguments, got ", bux_int_to_str(expr.callArgCount as int64))))); + String_Concat("expected ", String_Concat(bux_int_to_str(callDecl.paramCount as int64), + String_Concat(" arguments, got ", bux_int_to_str(expr.callArgCount as int64))))); callDecl = null as *Decl; } var argIdx: int = 0; @@ -1109,9 +1109,9 @@ module Sema { let wantName: String = Sema_TypeNameForDiag(cp.refParamType, Sema_ResolveType(sema, cp.refParamType)); let gotName: String = Sema_TypeNameForDiag(arg.expr.refType, argKind); Sema_EmitError(sema, arg.expr.line, arg.expr.column, - String_Concat("argument ", String_Concat(bux_int_to_str((argIdx + 1) as int64), - String_Concat(": expected ", String_Concat(wantName, - String_Concat(", got ", gotName)))))); + String_Concat("argument ", String_Concat(bux_int_to_str((argIdx + 1) as int64), + String_Concat(": expected ", String_Concat(wantName, + String_Concat(", got ", gotName)))))); } } } @@ -1565,7 +1565,7 @@ module Sema { let gotName: String = Sema_TypeNameForDiag(stmt.child1.refType, initType); let wantName: String = Sema_TypeNameForDiag(stmt.refStmtType, Sema_ResolveType(sema, stmt.refStmtType)); let msg: String = String_Concat("cannot assign ", - String_Concat(gotName, String_Concat(" to ", wantName))); + String_Concat(gotName, String_Concat(" to ", wantName))); Sema_EmitError(sema, stmt.line, stmt.column, msg); } } diff --git a/tests/stdlib_golden/array/src/Main.bux b/tests/stdlib_golden/array/src/Main.bux index c78709c..59c6816 100644 --- a/tests/stdlib_golden/array/src/Main.bux +++ b/tests/stdlib_golden/array/src/Main.bux @@ -11,6 +11,15 @@ import Std::Test::{ }; func Main() -> int { + // Zero-capacity grow (v1.0.2): must not segfault + var z: Array = Array_New(0); + Array_Push(&z, 7); + Array_Push(&z, 8); + Test_AssertEqInt(Array_Len(&z) as int, 2); + Test_AssertEqInt(Array_Get(&z, 0), 7); + Test_AssertEqInt(Array_Get(&z, 1), 8); + Array_Free(&z); + var arr: Array = Array_New(2); Array_Reserve(&arr, 8); Test_AssertTrue(Array_Cap(&arr) >= 8); diff --git a/tests/stdlib_golden/collections/src/Main.bux b/tests/stdlib_golden/collections/src/Main.bux index 486b10d..855c965 100644 --- a/tests/stdlib_golden/collections/src/Main.bux +++ b/tests/stdlib_golden/collections/src/Main.bux @@ -34,6 +34,23 @@ func Main() -> int { Test_AssertTrue(Map_IsEmpty(&m)); Map_Free(&m); + // Auto-grow past initial cap (v1.0.2): must not hang + var tiny: Map = Map_New(2); + Map_Set(&tiny, 1, 10); + Map_Set(&tiny, 2, 20); + Map_Set(&tiny, 3, 30); + Map_Set(&tiny, 4, 40); + Map_Set(&tiny, 5, 50); + Test_AssertEqInt(Map_Len(&tiny) as int, 5); + Test_AssertEqInt(Map_Get(&tiny, 5), 50); + Map_Free(&tiny); + + // Zero-cap request defaults to usable table (v1.0.2) + var z: Map = Map_New(0); + Map_Set(&z, 9, 99); + Test_AssertEqInt(Map_Get(&z, 9), 99); + Map_Free(&z); + var s: Set = Set_New(16); Set_Add(&s, 10); Set_Add(&s, 20); @@ -45,6 +62,16 @@ func Main() -> int { Test_AssertFalse(Set_IsEmpty(&s)); Set_Free(&s); + // Set grow (v1.0.2) + var s2: Set = Set_New(2); + Set_Add(&s2, 1); + Set_Add(&s2, 2); + Set_Add(&s2, 3); + Set_Add(&s2, 4); + Test_AssertEqInt(Set_Len(&s2) as int, 4); + Test_AssertTrue(Set_Has(&s2, 4)); + Set_Free(&s2); + let ok: Result = Result_NewOk(42); let err: Result = Result_NewErr("boom"); Test_AssertTrue(Result_IsOk(ok));