# Bux — План към „добър“ език (v0.5 → v1.0) > **Дата:** 2026-07-19 > **Текущо:** v0.5.x — multi-file #line, selfhost CI, **LSP 0.11 interface hierarchy**, Nexus KA > **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain. --- ## Диагноза (къде сме) | Слой | Състояние | Оценка | |------|-----------|--------| | Frontend (lex/parse) | Пълен Pratt parser, recovery | ★★★★☆ | | Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ | | HIR → C | Tuples + fat `func` ABI в bootstrap **и** selfhost | ★★★★☆ | | Selfhost (`src/`) | ~12k LOC, binary-identical loop, closures+tuples | ★★★★★ | | Gradual ownership | `@[Checked]`, move, Drop, elision, **field-move skip Drop** | ★★★★★ | | Concurrency | M:N tasks + channels + async | ★★★★☆ | | Stdlib | Array/Map/Set/String/Iter HOF разширени | ★★★★☆ | | Tooling | LSP 0.5 hover/def/outline/**refs/rename** + fmt/test/doc | ★★★★★ | | Ecosystem / registry | path+git + file **+ HTTP** index (`bux search/add`) | ★★★★☆ | | Документация | README + QUALITY_PLAN синхронизирани (2026-07-15) | ★★★★☆ | **Силна ниша:** gradual ownership (C-скорост на писане + opt-in Rust-safety). **Слабо място:** ergonomics на stdlib + maturity на tooling + пълнота на borrow checker. --- ## Какво значи „добър“ за Bux 1. **Ежедневен DX** — колекции, string, assert, грешки, които разбираш за секунди. 2. **Предвидима безопасност** — `@[Checked]` да хваща 80% от UAF/double-borrow без lifetime hell. 3. **Selfhost като dogfood** — компилаторът и apps (`nexus`, `boko`) са proof. 4. **Инструменти** — fmt, test, LSP, package install без ръчна магия. 5. **Стабилна спецификация** — LanguageRef = реалното поведение. Не целим „по-добър Rust“. Целим **единствения език с gradual safety + Go-стил concurrency без GC**. --- ## Фази ### A — Ergonomics & Stdlib (P0) ✅ (core done) | # | Задача | Защо | Статус | |---|--------|------|--------| | A.1 | Array: Pop, Clear, IsEmpty, First, Last, Cap, Reserve | Без това колекциите са неудобни | ✅ (тази сесия) | | A.2 | String: IsEmpty, ReplaceAll | Чести операции; само first-replace досега | ✅ (тази сесия) | | A.3 | Os_Exit + Test_AssertEqString / richer asserts | Тестове и CLI без raw `bux_exit` | ✅ (тази сесия) | | A.4 | Map_Remove / Set polish | Completeness на колекциите | ✅ (тази сесия) | | A.5 | Iter: map/filter/fold върху closures | Higher-order без boilerplate | ✅ generic `Iter_Map`/`Filter`/`Fold` + Int aliases | | A.6 | Result helpers: Expect, UnwrapErr, Or | По-малко match boilerplate | ✅ (тази сесия) | ### B — Compiler Correctness (P0) | # | Задача | Защо | Статус | |---|--------|------|--------| | B.1 | Proper tuple types в C backend | `(T,U)` → `Tuple_T_U` struct + `.0`/`.1` | ✅ bootstrap + selfhost | | B.2 | Function pointer types | `func(T)->U` fat ABI | ✅ bootstrap + selfhost | | B.3 | Match expression lowering (literals, ranges, enums) | Expression-context match → if-else | ✅ bootstrap + selfhost | | B.3b | Pattern bindings (`Some(value) => value`) | Payload idents bound in arm body | ✅ bootstrap + selfhost | | B.3c | Match arm guards (`p if cond => …`) | Bindings visible in guard; sequential found-flag lower | ✅ bootstrap + selfhost | | B.4 | Closures multi-instance | Fat `BuxFn` + heap env | ✅ bootstrap + selfhost | | B.4b | Closures: `\|\|` empty params + loop/return body | Lexer `\|\|` vs empty closure; while/break/return | ✅ bootstrap + selfhost | | B.5 | По-добри diagnostics (snippet + hint) | DX #1 за нови потребители | ✅ | | B.6 | Bootstrap ↔ selfhost feature parity | empty `\|\|`, match-as-expr, **string interp `f"..."`** bootstrap+selfhost | ✅ | ### C — Gradual Ownership 2.0 (P1) | # | Задача | Защо | Статус | |---|--------|------|--------| | C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата | ✅ bootstrap + selfhost | | C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives | ✅ let-bound + use-while + call conflict | | C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден | ✅ bootstrap + selfhost | | C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path | ✅ partial (unchecked path + goldens) | ### D — Tooling (P1) | # | Задача | Защо | Статус | |---|--------|------|--------| | D.1 | LSP: hover, go-to-def, diagnostics | IDE = adoption | ✅ v0.11.0: + **interface dispatch hierarchy** | | D.2 | `bux fmt` стабилен + CI check | Единен style | ✅ full-tree format + `make fmt-check` enforce | | D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | ✅ `--filter` / summary / exit 0\|1 | | D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ✅ bootstrap+selfhost + `make docs` | | D.5 | Golden tests за stdlib modules | Регресии без изненади | ✅ `tests/stdlib_golden/` + `make test-stdlib` | ### E — Ecosystem & v1.0 (P2) | # | Задача | Защо | Статус | |---|--------|------|--------| | E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks | ✅ local index + **HTTP(S) URL** cache + file/git + `search` | | E.2 | 3–5 production-quality apps в `apps/` | Showcase | ✅ 4 apps + `make test-apps` smoke (build + CLI) | | E.3 | Language freeze + semver policy | Trust | ✅ draft `docs/SEMVER.md` | | E.4 | Debugger/DWARF basics | Systems audience | ✅ `#line`→`.bux` + `-g` / `--release`; `make test-dwarf` | | E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression | ✅ micro + C/Nim/Zig twins + `make bench-nexus` (wrk) | --- ## Препоръчан ред на работа ``` A (stdlib ergonomics) → B (compiler holes) → C (ownership depth) ↓ ↓ D (tooling) ←────────── dogfood apps ↓ E (v1.0 ecosystem) ``` **Правило:** всяка сесия ship-ва нещо runnable (stdlib API, fix, example), не само docs. --- ## Acceptance criteria за „добър v1.0“ - [ ] Всички examples + selfhost-loop + 3 apps минават на CI (apps: `make test-apps` ready) - [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди - [x] `@[Checked]` хваща use-after-move + double `&mut` + dangling return / elision fail - [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped) - [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision) - [x] Поне един външен/temp проект build-ва с registry dep (`tools/smoke_registry.sh` + HTTP) --- ## Сесия 1 (stdlib ergonomics) 1. `Array_Pop`, `Array_Clear`, `Array_IsEmpty`, `Array_First`, `Array_Last`, `Array_Cap`, `Array_Reserve` 2. `String_IsEmpty`, `String_ReplaceAll` 3. `Os_Exit` 4. `Test_AssertEqString`, `Test_AssertNeqInt`, `Test_AssertEqBool` 5. Example + docs update ## Сесия 2 (collections + tuples) 1. `Map_Remove` / `Map_Clear` / `Map_IsEmpty` (+ StringMap) 2. `Set_Remove` / `Set_Clear` / `Set_IsEmpty` 3. `Result_Expect` / `Result_UnwrapErr` / `Result_Or` 4. `Option_Expect` / `Option_Or` 5. **Tuples:** `(T, U)` → `typedef struct { T _0; U _1; } Tuple_T_U` + field access `.0`/`.1` 6. Examples: `tuples`, `func_ptr`, `map_remove` ## Сесия 3 (diagnostics + collections) 1. **Rust-style errors** in bootstrap CLI: `--> file:line:col`, source snippet, `^` caret, `= help:` hints 2. `SourceLocation.file` propagated from lexer 3. Better caret for type-mismatch on `let` (points at initializer) 4. `Array_Contains` / `Array_IndexOf` / `Array_Extend` 5. `Iter_AnyEq` / `Iter_AllEq` / `Iter_Collect` 6. Selfhost `Diagnostic_Hint` for common messages ## Сесия 4 (diagnostics depth + LSP + strings) 1. **Multi-char underlines** (`^^^^^^^` under tokens/strings/idents) 2. Quoted-name highlighting for `undeclared identifier 'x'` 3. **Golden error tests** (`tests/error_golden/`, `make test-errors`) 4. **LSP** runs `buxc check` and publishes real diagnostics 5. `String_IsBlank` / `String_Repeat` ## Сесия 5 (multi-instance closures) 1. **Fat function pointers** for all `func(...)` types: `BuxFn { code(env, args...), env }` 2. Capturing closures: heap-allocate env per creation site (independent instances) 3. Capture-less closures + named funcs: adapters with `env = NULL` 4. Calls through func values: `f.code(f.env, args...)` 5. Example `multi_closure.bux` — MakeAdder(10)/MakeAdder(20) yield 11 and 21 6. **Selfhost parity:** same fat ABI in `src/hir_lower.bux` + `src/c_backend.bux` (makers, adapters) ## Сесия 6 (selfhost tuples) 1. Parser: `(T, U)` types, `(a, b)` exprs, field access `.0`/`.1` 2. Sema: tekTuple / ekTuple 3. HIR lower → `hStructInit` of `Tuple_int_int` 4. C backend: `typedef struct Tuple_int_int { int _0; int _1; }` 5. Verified with `buxc2` on `examples/tuples.bux` ## Сесия 7 (Iter HOF) 1. `Iter_MapInt` / `FilterInt` / `FoldInt` / `ForEachInt` / `AnyInt` / `AllInt` / `SumInt` 2. Named funcs + capturing closures (fat ABI) 3. Example `iter_hof.bux` (sum=15, product=120) 4. Selfhost fix: pointer `->` field access; no bogus bounds check on `arr.data[len]` (Push) ## Сесия 8 (match expression + error goldens) 1. **Match expression lowering** (B.3): `pkLiteral`, `pkRange` (`a..b` / `a..=b`), enum tags, wildcard - Bugfix: literal arms were always-true (`else` branch) → `match 1 { 1=>10, 2=>20 }` returned 10 for all - `skMatch` now also lowers via `lowerMatch` (void result) 2. Expanded `examples/pattern_matching.bux` (enums + classify ranges + string arms) 3. **Error golden cases** (`tests/error_golden/`): - `parse_error` — incomplete `let x = ;` - `use_after_move` — `@[Checked]` + `own String` - `double_mut_borrow` — two `&mut` of same var 4. `run.sh` normalize: absolute paths in "parse errors in …" lines ## Сесия 9 (selfhost match — B.6 parity) 1. **AST:** `MatchArm` linked list; `Pattern.patChild1/2` for ranges; `Expr.matchArms` 2. **Parser:** full `parserParsePattern` / `parserParseMatchExpr` (was skip-arms stub) - literals, `_`, ident, `Enum::Variant` / `Enum::Variant(...)`, ranges - statement `match` → `skExpr` + `ekMatch` 3. **Sema:** type-check subject + arms; propagate first-arm type to `expr.refType` 4. **HIR lower:** `Lcx_LowerMatch` → alloca result + if-else stores (enum `.tag` / simple / literal / range) 5. **Last-expr return:** `Lcx_LowerBlock` converts final `skExpr` into `return` (needed for `func F() -> T { match ... }`) 6. Verified: `pattern_matching` via **buxc2**; simple enum + ranges; **selfhost-loop IDENTICAL ✓** ## Сесия 10 (pattern bindings — B.3b) 1. **Bootstrap sema:** `extractPatternBindings` resolves enum payload field types from variant decl 2. **Bootstrap HIR:** `matchPatternBindings` → `alloca name; name = subject.data.Variant_i` before arm body 3. **Match result type:** fall back to `currentFuncRetType` when arm bodies only use bindings 4. **Multi-field enum layout:** positional `fields.len > 1` → nested struct in `_Data` union (no overlay) 5. **Selfhost:** parse `patArgs` linked list; `Sema_BindPattern`; `Lcx_PatternBindings` in match arms 6. **Example:** `pattern_matching.bux` uses `Option::Some(value) => value` (real binding, not `opt.data.Some_0`) 7. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓** ## Сесия 11 (empty `||` closures + match-as-expr — B.4b) 1. **Bug:** `|| -> int { ... }` lexed as `tkPipePipe` (logical-or), not two `tkPipe` → empty-param closures failed to parse 2. **Fix bootstrap:** primary `of tkPipePipe:` → zero-param `ekClosure` 3. **Fix selfhost:** `parserParseEmptyClosure` for `tkPipePipe` 4. **Match-as-expr:** expression-form match now skips newlines (same as statement form) → `let x = match n { ... }` works 5. **Verified control-flow in closure body:** while/break, early return from loop, multi-return if-chain 6. **Pattern bind reuse:** one alloca per binding name per function (`patternBoundNames`) so two matches can both use `v` 7. **Selfhost match-as-expr:** let-init + binary operands expand yield blocks (`Lcx_IsMatchYield` / `__binop_N`) 8. Examples: `closure_control.bux`, `match_let.bux` 9. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓** ## Сесия 12 (string interpolation selfhost — B.6) 1. **Selfhost parser:** `parserParseStringInterp` — interleaved lit/expr parts in `callArgs`, nested fragment parse via `Lexer_Tokenize` + sub-parser 2. **Selfhost sema/HIR:** `ekStringInterp` → `String_Concat` + `String_FromInt`/`FromBool`/`FromFloat` 3. **Bootstrap fix:** `f"plain"` no longer keeps the `f` prefix in the literal; `\{`/`\}` preserved by lexer and unescaped by interp parser 4. **Lexer:** `\{` / `\}` allowed (bootstrap + selfhost) so LanguageRef escape rules work 5. **Compiler hygiene:** original dense `&&`/`||` in the large interp loop caused bootstrap OOM (~27 GB) when compiling `parser.bux` — rewrite with simpler control flow 6. Example: `examples/string_interp.bux` (name/int/bool/plain/escaped braces) 7. Verified: bootstrap + **buxc2** + all 41 examples + error goldens + **selfhost-loop IDENTICAL ✓** ## Сесия 13 (LSP hover / go-to-def / outline — D.1) 1. **Richer symbol index:** `func` signatures (`params` + `-> Ret`), `let`/`var`/`const` with types, `struct`/`enum`/`union`/`interface`/`type`/`module` 2. **Skip comments/strings** during scan (no false `func` hits) 3. **Hover:** markdown ```bux signature``` + kind; accurate word range 4. **Go-to-definition:** current file + workspace index (scan `.bux` under rootUri) 5. **Document symbols** (outline) via `textDocument/documentSymbol` 6. **didChange** refreshes symbols immediately; **didSave/didOpen** still run `buxc check` diagnostics 7. **Fix:** responses write to **stdout** (was writing to stdin stream → broken pipe) 8. Version `bux-lsp` **0.2.0**; smoke-tested via JSON-RPC --- ## Сесия 14 (generic Iter map/filter/fold) 1. **`Iter_Map` / `Filter` / `Fold` / `Any` / `All` / `ForEach`** — fat `func` params + monomorphization 2. **Int aliases** keep working: `Iter_MapInt` → `Iter_Map`, … 3. **Bootstrap fixes:** - call return type for local fat-func (`f: func(T)->U`) after mono (was always `int` → String map truncated pointers) - generic call `Foo(…)` now type-checks args (closures get capture analysis) 4. **Selfhost fixes:** - `Lcx_SubstituteType` recurses into `tekFunc` (was leaving `BuxFn_U_T`) - fat typedef emit covers cstr shapes + `#ifndef` guards 5. Example: `examples/iter_generic.bux` (int↔String map, filter, fold, closures) 6. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ --- ## Сесия 15 (struct/tuple patterns) 1. **Tuple patterns:** `match t { (a, b) => a + b }` — bind `subject._0` / `._1` 2. **Struct patterns:** `Point { x: px, y: py }` + shorthand `Point { x, y }` 3. Bootstrap: `matchPatternBindings` for pkTuple/pkStruct; field types from struct decl; range registration of local tuple typedefs 4. Selfhost: parse `()` / `Name { … }` patterns; `Sema_BindPattern` + `Lcx_PatternBindings` with Scope_Define 5. Fix: operator-overload path treated `String_Eq(null, "")` as non-empty → crash on `a + b` after pattern bind 6. Example: `examples/struct_tuple_pat.bux` 7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ --- ## Сесия 16 (match block arms + block expressions) 1. **Block-as-expression:** `let r = { let x = 1; x + 2 }` — last skExpr is the value 2. **Multi-stmt match arms:** `1 => { PrintLine("…"); let a = 10; a + 1 }` 3. Bootstrap: `lowerBlock(..., asExpr)` promotes last expression; nested enum/struct pattern bindings 4. Selfhost: parse `{ … }` as `ekBlock`; yield temps `__blk_N`; fix `IsMatchYield` null-strValue false positive; retTypeKind `-2` for expr blocks 5. Nested: `Shape::Dot(Point { x, y })` works on bootstrap; selfhost covers struct/tuple/enum + block arms 6. Example: `examples/match_block.bux` 7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ --- ## Сесия 17 (deeper nested patterns + multi-field enum layout) 1. **Multi-field payload type:** nested struct `Enum_Variant_Payload` (suffix avoids clash with tag `Enum_Variant`) 2. Bootstrap: full `resolveTypeExpr` for enum field types (tuples/pointers); sema synthetic field lookup on payload types; `data.Two.Two_0` construction 3. LIR C backend: emit tuple typedefs **before** enums that embed them; topo deps for `*_Payload` 4. Selfhost: enum variants parse full types (`parserParseType`) — fixes `Val((int,int))`; store `fieldTypeName*`; structs before enums (e.g. `Dot(Point)`) 5. Patterns: `Pair::Two(a, b)`, `Box::Val((a, c))`, `Shape::Dot(Point { x, y })` + block arms 6. Example: `examples/nested_patterns.bux` 7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓ --- ## Сесия 18 (match arm guards — B.3c) 1. **Syntax:** `p if cond => body` (also after ranges: `1..10 if x % 2 == 0`) 2. **Parser fix (critical):** `isTypeArgListAhead` treated `x < 0` as generic when a later `x > 0` existed in another arm → infinite parse. Stop lookahead on `=>`, keywords, literals, arithmetic, comparisons. 3. **Sema:** bind inner pattern first; type-check guard as bool in arm scope 4. **HIR lower:** sequential `found` flag (no shared if-else DAG): ``` if (!found) { if (inner_cond) { binds; if (guard) { result = body; found = true; } } } ``` Bindings are in scope for the guard expression. 5. **Selfhost:** `pkGuarded` + `patGuardExpr`; same lower strategy; fix `return match {…}` to expand yield block before return 6. Example: `examples/match_guards.bux` (ident/literal/range/enum payload guards) 7. Verified: bootstrap + **buxc2** + all examples + error goldens + **selfhost-loop IDENTICAL ✓** --- ## Сесия 19 (generic HOF type inference) 1. **Bootstrap `inferTypeArgs`:** structural unify of param TypeExpr vs arg Type - `*Iter` / `*Array` → extract T from pointee type args or mangled `Array_int` - `func(T)->U` → bind T/U from function-value type (not whole func as T) - bare `Acc` from init; multi-param `Iter_Fold` 2. **Selfhost:** improved `Sema_InferGenericArgs` + return-type subst after inference - Fix: `ekCast` must type-check operand (was skipping → no inference under `as`) - HIR fallback mono from first *Array/*Iter arg when count=0 3. Works without explicit type args: - `Array_Push(&nums, 1)`, `Array_Get`, `Array_Len`, `Array_Iter` - `Iter_Map(&it, f)`, `Iter_Filter`, `Iter_Fold`, `Iter_Any` (int↔String) 4. Example: `examples/generic_infer_hof.bux` 5. Verified: bootstrap + **buxc2** + all examples + **selfhost-loop IDENTICAL ✓** --- ## Сесия 20 (LSP hover from real sema — D.1) 1. **`bux-lsp` 0.3.0** links bootstrap (`--path:../bootstrap`) and runs `analyzeFull` on open/save 2. **typeIndex:** global scope (stdlib + file) → hover signatures with real types 3. **File-local priority:** user decls override stdlib name collisions (`Max` vs `Math.Max`) 4. **Locals:** walk function bodies for `let`/`var` with explicit type annotations 5. **didChange:** fast lightweight rescan; keeps previous typeIndex until save/hover refresh 6. Hover shows ```bux signature``` + `_kind_ · sema` 7. Smoke: `Main() -> int`, `PrintLine(String) -> void`, `Max(a: T, b: T) -> T` --- ## Сесия 21 (pattern binding shadowing) 1. **Problem:** C/LIR function-scoped locals — nested `Some(n) => match … Some(n)` emitted store before `int n`, and `let v` + pattern `v` caused redeclaration. 2. **Bootstrap:** every pattern binding → unique C name `__pN_src` via `patternRenames` map; body/guard idents rewritten; **binds before body lower** inside `lowerMatch`. 3. **Selfhost:** same model — `Lcx_BindPatIdent` + `patMapFrom/To` rename table; arm-scoped push/pop of map. 4. Semantics: nested pattern name shadows correctly; outer `let v` survives after match that binds `v`. 5. Example: `examples/pattern_shadow.bux` 6. Verified: bootstrap + **buxc2** + all examples + **selfhost-loop IDENTICAL ✓** --- ## Сесия 22 (Ownership 2.0 — C.2 + C.4 + *p= fix) 1. **C.2 Exclusive &mut data-flow** (`@[Checked]`): - Track long-lived let-bound borrows (`activeMutBorrows` / `activeSharedBorrows`) - Reject: second `&mut x`, use/assign of `x` while mutably borrowed, `&x` while `&mut` live - Call-site temps conflict with existing let-bound borrows 2. **C.4 Golden tests:** - `tests/error_golden/exclusive_mut_let/` - `tests/error_golden/use_while_mut_borrow/` 3. **Bugfix:** `*p = expr` now stores through the pointer (was assigning to a temp) — ownership examples finally mutate correctly 4. Example: `examples/ownership_checked.bux` (unchecked zero-cost + checked OK path) 5. Verified: 7 error goldens + borrow_test + all examples + selfhost-loop --- ## Сесия 23 (Ownership 2.0 — C.3 auto-drop early return / branches) 1. **Bootstrap auto-drop for `@[Drop]` + collections:** - Parser: `@[Drop]` / `@[Release]` on structs and funcs (`declAttrs`) - `autoDropFuncName` + monomorphize `Array_Drop`/`Free` (etc.) so stdlib links - Inject `Type_Drop(&x)` on `let` via `deferStmts` 2. **Early return / multi-path:** - Every `return` snapshots the full live defer stack (no clear-after-first-return) - Materialize return value **before** Drop (`return a.id` is not use-after-drop) - Move-on-return: skip Drop for a local returned by value (`return out`) 3. **Branch / loop scopes:** - Bootstrap: `lowerBlock` scopes `deferStmts` — branch-local drops at block exit; siblings do not see each other - Selfhost C backend: `CBE_EmitDefers` keeps stack for multi-return; `CBE_EmitAndPopDefersFrom` pops branch/loop locals after `if`/`while`/`loop` 4. **Selfhost fixes:** null-safe ret type; temp name counter; use function `retTypeName` for `__retdrop_N` 5. Example: `examples/drop_early_return.bux` (Early + Branched + Scoped → 5 drops) 6. Verified: bootstrap + **buxc2** drop tests, 7 error goldens, key examples, **selfhost-loop IDENTICAL ✓** --- ## Сесия 24 (tooling — D.2 fmt --check + D.3 test --filter) 1. **Bootstrap `bux fmt`** (`bootstrap/fmt.nim`): - Indent-by-brace-depth formatter (parity with `src/fmt.bux`) - `bux fmt [path...]` writes; `bux fmt --check` exits 1 if any file would change - Collects single file or recursive `.bux` under directories 2. **Bootstrap `bux test --filter`**: - `--filter ` / `--filter=` — only run `tests/*.bux` whose name contains `s` - Summary table (`PASS` / `FAIL[:code]`) + `Results: N passed, M failed, T total` - Exit `0` all pass, `1` failures or no match 3. **Selfhost parity** (`src/cli.bux`, `src/fmt.bux`): - `Fmt_WouldChange` / `Fmt_CheckFile`; `Cli_Fmt(dir, checkOnly)` - `Cli_Test(dir, filter)` with summary + skip count; filter skips Main package run 4. **CI hooks:** `make fmt-check` smoke (clean→0, dirty→1); full-tree enforce deferred until a one-shot format pass on `lib/`/`examples/` 5. **Idempotence fix:** drop trailing split-empty so re-format is a no-op 6. Verified: unit suite + `./buxc test --filter first _test_runner` + selfhost `buxc2` fmt/test parity --- ## Сесия 25 (Ownership 2.0 — C.1 lifetime elision) 1. **Elision rules** in `@[Checked]` (`bootstrap/sema.nim`): - Each elided input `&`/`&mut` → distinct `#elidedN` - One input lifetime → assigned to elided return - First param `self`/`Self` preferred when multiple inputs - Multiple inputs + elided return → `lifetime elision failed` (need `'a`) 2. **Return checks:** - `cannot return reference to local variable` (`return &local` / let-bound local ref) - `no input reference to borrow from` (return ref with zero input refs) - Explicit `'a` mismatch between return and value 3. **Body check for lifetime-only generics** (`func F<'a>(...)`) — no longer skipped 4. **Diagnostics hints** for elision / dangling / mismatch 5. **Tests:** 8 new borrow_test cases; goldens `return_local_ref`, `elision_multi_input` 6. **Example:** `examples/lifetime_elision.bux` (Identity / explicit / ViaLet / self) 7. LanguageRef + QUALITY_PLAN updated 8. Verified: borrow_test 24/24, 9 error goldens, example runs ## Сесия 26 (C.1 selfhost parity) 1. **Lexer** (`src/lexer.bux` + `tkLifetime=111`): `'a` vs char `'x'` (same heuristic as bootstrap) 2. **Parser:** - `&'a T` / `&'a mut T` → `TypeExpr.refLifetime` - `func F<'a, T>(…)` — lifetime params accepted and **skipped** for mono slots 3. **Sema** lifetime elision (fixed 8-slot maps, same rules as bootstrap): - single-input elision, `self` preference, multi-input fail - return-local / no-input-ref / explicit mismatch - let-bound ref lifetime propagation 4. Fixed `checkFunc` else-branch that wiped `checkedFunc` when retType was void 5. Verified: `buxc2 run lifetime_elision` PASS; goldens on buxc2 show same errors; bootstrap still green; **selfhost-loop** expected IDENTICAL --- ## Сесия 27 (tooling — D.4 bux doc + D.5 stdlib goldens) 1. **D.5 Stdlib goldens** (`tests/stdlib_golden/`): - Packages: `array`, `string`, `collections` (Map/Set/Result/Option) - `run.sh` builds via `buxc run` and matches expected PASS lines - `make test-stdlib` wired into `make test` 2. **D.4 `bux doc`**: - Bootstrap: `bootstrap/docgen.nim` — `///` + adjacent `/* */` - Selfhost: `Cli_Doc` line scanner for `///` - `bux doc [--out file] [path]` (default path `lib/`) - `make docs` → `docs/api/stdlib.md` 3. **Stdlib docs:** `///` on Array / String / Test public helpers 4. Verified: `make test-stdlib`, `./buxc doc lib/Array.bux | head`, selfhost build --- ## Сесия 28 (LSP v0.4.0 — position-sensitive locals + inferred lets) 1. **`LocalBinding`** with scope range (`scopeStartLine`…`scopeEndLine`) per let/param 2. **Sema-backed inference** (`checkExprForLsp` / `resolveType`): - `let x = 42` → hover `let x: int` · inferred - `let s: String = "…"` → annotated, not inferred - params: `param a: int` visible for whole function 3. **Position-sensitive** hover / go-to-def / completion (innermost scope wins on shadowing) 4. Nested scopes: if/while/for/match/block arms 5. Version **bux-lsp 0.4.0**; tests: `tools/test_lsp_locals.nim`, `tools/smoke_lsp_hover.sh` 6. Verified: hover shows `let sum: int · inferred`, `param a: int`, `let n: int · inferred` --- ## Сесия 29 (full-tree `bux fmt` + CI enforce) 1. **One-shot format** of `lib/` (33), `examples/` (23), `src/` (15), `tests/` (8), `apps/` (12) 2. **Idempotent:** second `--check` → 0 would reformat on all trees 3. **CI:** `make fmt-check` enforces full tree + dirty-path smoke (exit 1) 4. **`make fmt`** helper to reformat the same roots 5. Verified: `test-stdlib`, key examples, **selfhost + selfhost-loop IDENTICAL ✓** --- ## Сесия 30 (E.1 package registry + E.3 semver draft) 1. **Registry index** (`config/registry.toml`, `$BUX_REGISTRY`, `~/.bux/registry.toml`) - `[[package]]` with `name` / `version` / `source` / `description` - `file:` / `path:` (relative to index) or git URL 2. **CLI:** `bux search [q]`, `bux add ` resolves registry, `bux install` locks path/git 3. **Demo package:** `registry/packages/greet` (`Greet_Hello`, `Greet_Version`) 4. **Smoke:** `tools/smoke_registry.sh` / `make test-registry` — temp app outside tree 5. **Semver policy:** `docs/SEMVER.md` (0.x vs 1.0, registry version match) 6. Packages.md updated --- ## Сесия 31 (E.1b HTTP registry + E.2 apps smoke + E.5 micro-bench) 1. **HTTP registry index** (`bootstrap/registry.nim`): - `$BUX_REGISTRY` accepts `http://` / `https://` URLs - Fetch via `curl` (fallback `wget`) → `~/.bux/cache/registry_http.toml` - `BUX_REGISTRY_REFRESH=1` forces re-download; cache keyed by URL meta file - `bux search` shows URL + cached path; clear errors on fetch failure 2. **Smoke:** `tools/smoke_registry.sh` — local path flow **+** python `http.server` HTTP search 3. **E.2 apps smoke** (`tools/smoke_apps.sh` / `make test-apps`): - Build `simpledb`, `jwt-pitbul`, `nexus`, `boko-framework` - simpledb set/get/has/count/del; jwt-pitbul sign/verify/decode - Removed obsolete JWT-disable workaround from simpledb README 4. **E.5 micro-benchmarks** (`benches/micro`, `benches/c`, `make bench`): - Bux: `int_loop`, `fib30`, `string_concat`, `array_push` - C refs (`gcc -O2`): `fib30`, `int_loop` for relative comparison 5. Docs: `Packages.md`, `config/registry.toml`, `benches/README.md` 6. Verified: `make test-registry`, `tools/smoke_apps.sh`, `tools/bench.sh` --- ## Сесия 32 (E.5 nexus throughput + language twins) 1. **Nexus env config** (`apps/nexus/src/Main.bux`): - `NEXUS_PORT`, `NEXUS_WORKERS`, `NEXUS_BIND`, `NEXUS_PUBLIC` 2. **Throughput harness** (`tools/bench_nexus.sh` / `make bench-nexus`): - Start nexus on `:18080`, `wrk -t4 -c64 -d5s` → `/api/health` - Sample (this machine): **~44.5k req/s**, p50 ~0.94 ms (Connection: close) 3. **Language twins** for micro kernels: - `benches/nim/` fib + int_loop (`nim c -d:release`) - `benches/zig/` sources (built when `zig` is on PATH) - `make bench` runs Bux + C + Nim (+ Zig if present) 4. Docs: `benches/README.md`, nexus README, BuildAndTest 5. Verified: `tools/bench.sh`, `tools/bench_nexus.sh` --- ## Сесия 33 (E.4 Debugger / DWARF basics) 1. **Source map:** HIR `loc` → LIR `locLine`/`locFile` on every stmt/expr (`setSourceLoc`) 2. **C backend:** emit `#line N "path.bux"` when location changes; force map at each func entry 3. **Build modes:** - default: `cc -O0 -g` + `#line` maps - `--release` / `build --release`: `-O2 -DNDEBUG`, no `#line`, no `-g` - `BUX_CFLAGS` appended for custom flags 4. **Smoke:** `tools/smoke_dwarf.sh` / `make test-dwarf` - `#line` for stdlib + user `Main.bux` - `.debug_info` present in debug binary - `gdb list Main` shows real Bux source 5. Verified: smoke PASS; `gdb list Main` → hello.bux body --- ## Сесия 34 (LSP refs/rename + CI wiring) 1. **bux-lsp 0.5.0** (`tools/lsp_server.nim`): - `textDocument/references` — scoped locals (same binding via `lookupLocalAt`) + workspace globals - `textDocument/prepareRename` + `rename` → `WorkspaceEdit.changes` - Ident scan skips strings/comments; keyword rename rejected 2. **Smoke:** `tools/smoke_lsp_rename.sh` (sum→total ≥2 edits); wired into `make test-lsp` 3. **CI:** `make test` now runs `test-registry` + `test-dwarf` + `test-apps` after stdlib goldens 4. Verified: rename/hover smokes PASS --- ## Сесия 35 (Nexus HTTP/1.1 keep-alive) 1. **Server loop** (`apps/nexus/src/Server.bux`): - `HandleConnection` serves up to 1000 requests per TCP fd - `BuildResponse(..., keepAlive)` → `Connection: keep-alive` + `Keep-Alive:` or `close` 2. **Policy** (`RawRequest_WantsKeepAlive` on raw bytes): - HTTP/1.1 default keep-alive; `Connection: close` forces close - HTTP/1.0 needs explicit keep-alive - (Avoided fragile `Array` walk — keys corrupted under for-in/Get) 3. **Bench:** ~**81k req/s** vs ~45k with close-only (`wrk -t4 -c64 -d5s /api/health`) 4. Version banner **0.3.0**; README / benches notes updated 5. Verified: curl headers + `make bench-nexus` --- ## Сесия 36 (header ownership + selfhost flags + LSP workspace/symbol) 1. **Root cause (Nexus headers UAF):** auto-drop of local `headers` after shallow-copy into `HttpRequest` / `ParseResult` freed the buffer while still referenced. Not Array ABI — **move-out-of-field not tracked**. 2. **Fix** (`apps/nexus/src/Parser.bux`): after embedding, zero `headers.data/len/cap` so auto-drop is a no-op; `Request_WantsKeepAlive` again uses `RequestHeader_Get` safely. Bench still ~76–80k RPS. 3. **Selfhost compile flags** (`src/cli.bux`): - default **`-O0 -g`** (was always `-O2`) - `--release` → **`-O2 -DNDEBUG`** (was `-O3 -flto`) - `BUX_CFLAGS` appended via `bux_getenv` - `#line` maps remain bootstrap-only (selfhost C backend has no LIR #line yet) 4. **LSP 0.6.0:** `workspace/symbol` over open docs + workspace index (cap 200); `tools/smoke_lsp_workspace.sh` + `make test-lsp` 5. Verified: nexus keep-alive + header Get; `make lsp` + workspace smoke --- ## Сесия 37 (compiler: field-move skip auto-Drop) 1. **Root cause (formal):** auto-Drop of locals that were **moved by value** into a struct field / let / return still ran → UAF (Nexus `headers` in `HttpRequest`). 2. **`bootstrap/hir_lower.nim`:** - `movedOutLocals: HashSet[string]` - `markMovedOutFromAst` on `ekStructInit` fields, `let` init, `return` value - `shouldSkipDrop` at return / block exit / function tail 3. **Nexus:** removed zeroing workaround; error path still Drops; success path transfers ownership; `HandleConnection` Drops `req.headers` after response 4. **Example:** `examples/move_field.bux` (Array into `Box { items }`) 5. Verified: move_field PASS; ParseRequest C has Drop only on error path; `bench-nexus` ~88k RPS; drop_early_return still 5 drops --- ## Сесия 38 (selfhost field-move + #line) 1. **Field-move Drop** (`src/c_backend.bux`): - `CBE_MarkMovedFromNode` on `hStructInit` / return values (nested fields) - Already skipped Drop via `CBE_IsMoved` in defer emit 2. **Struct emit fix** (`src/hir_lower.bux`): - Field types `Array` → `Array_int` (etc.) so parent structs are not skipped as “generic” (was incomplete `typedef struct Box Box` only) 3. **#line maps** (selfhost C backend): - Emit `#line N "file"` on statements when `node.line > 0` - `BUX_DEBUG_FILE` sets the path; `BUX_NO_LINE=1` disables 4. Verified: selfhost `move_field` PASS; Make() has **no** `Array_Drop(&items)` after field move; `#line` points at `.bux` sources --- ## Сесия 39 (LSP 0.7 deeper rename) 1. **Member index** in `analyzeFile`: - struct/union/interface fields (`name: Type`) - enum variants (`Name` / `Name(...)`) 2. **Access classification** for each hit: - bare / `.member` / `::Variant` / `name:` field-init 3. **Rename targets**: - **local** — same binding only (shadowing-safe) - **member** — decl + `.x` + `::Red` + `{ x: }` — **not** bare locals named `x` - **global** — bare + `::` paths; skips `.member` false positives 4. Smoke: `tools/smoke_lsp_rename_deep.sh` (x→px ≥3, Red→Crimson ≥2, local x→xx =2) 5. Version **bux-lsp 0.7.0**; wired into `make test-lsp` --- ## Сесия 40 (LSP 0.8 call hierarchy) 1. **Providers:** - `textDocument/prepareCallHierarchy` - `callHierarchy/incomingCalls` — who calls F - `callHierarchy/outgoingCalls` — what F calls 2. **Graph:** textual scan of known `func` symbols + `Name(` call sites; enclosing function via nearest prior `func` decl line 3. Skips the declaration itself; workspace disk scan for other `.bux` files 4. Smoke: `tools/smoke_lsp_call_hierarchy.sh` (Add ← Compute; Compute → Add/Mul) 5. Version **bux-lsp 0.8.0**; `make test-lsp` --- ## Сесия 41 (selfhost multi-file #line paths) 1. **`Decl.sourceFile`** stamped when parsing/merging each `.bux` file (`Cli_StampSourceFile` / `Cli_MergeFileInto` / project `src/` loop) 2. **`HirFunc.sourceFile`** copied in `Lcx_LowerFunc` 3. **C backend:** before each function, set `currentFile` from `sourceFile` and emit `#line 1 "path"` + per-stmt `#line N "path"` 4. **No env required** — stdlib + user multi-file paths appear automatically (`lib/Fs.bux`, `./src/Util.bux`, `./src/Main.bux`, …) 5. Overrides: `BUX_DEBUG_FILE` (force one path), `BUX_NO_LINE=1` (disable) 6. Verified: multi-file project runs; `#line` paths distinct per source --- ## Сесия 42 (selfhost CI smoke) 1. **`tools/smoke_selfhost.sh`:** - build/use `buxc2` (`make selfhost`) - **move_field**: run PASS + no `Array_Drop(&items)` after field move - **multi-file #line**: Util.bux + Main.bux + `lib/*.bux` paths in `main.c` 2. **`make test-selfhost-smoke`** depends on `selfhost` 3. Wired into default **`make test`** 4. Verified: smoke script PASS --- ## Сесия 43 (LSP 0.9 method call hierarchy) 1. **Index methods** in `analyzeFile`: - Track `extend Type` / `impl Type` brace body - `func` inside → kind `method`, container `Type`, detail `Type.func …` 2. **Call graph** includes methods as callables - `.Method(` sites (iaDot) + free `Func(` calls - CallHierarchyItem: SymbolKind.Method (6), display `Type.Method` - `data` field keeps bare name for graph match 3. Smoke: `tools/smoke_lsp_method_hierarchy.sh` - `Scale` → `Len`; `Main` → `Scale`; prepare on method 4. Version **bux-lsp 0.9.0**; `make test-lsp` --- ## Сесия 44 (LSP 0.10 method + type + receiver rename) 1. **`rtkMethod`**: rename method decl + `.Method(` + bare `Method(` - (previous global path skipped `iaDot` → broke method rename) 2. **Type rename** (`isType`): `struct`/`extend Type`/`self: Type`/ctors; skip `.field` 3. **`self` receiver**: synthetic local when sema omits method params; clip to enclosing `func` body via textual bounds (sibling methods safe) 4. Smoke: `tools/smoke_lsp_rename_method.sh` - Len→Length ≥2, Point→Vec2 ≥4, self→this =3 (one method only) 5. Version **bux-lsp 0.10.0**; `make test-lsp` --- ## Сесия 45 (LSP 0.11 interface dispatch hierarchy) 1. **Index:** - `interface I { func M… }` → iface methods + symbol - `extend Type for I` → `impls` relation + implementor methods 2. **Call hierarchy:** - prepare on interface method → item with `data: "I#M"`, kind Interface - **outgoing** on iface method → implementor methods (dispatch targets) - **incoming** on iface method → callers of `.M(` 3. Smoke: `tools/smoke_lsp_iface_hierarchy.sh` - Drawable.Draw → Circle implementor; Render → Draw 4. Version **bux-lsp 0.11.0** --- ## Сесия 46 (LSP 0.12 module-path segment rename) 1. **Index** `import A::B::C` / `import A::B::{…}` path segments (`PathSegInfo`) 2. **`rtkPathSeg` rename** with left-prefix match: - `Std::Io` → only segments under prefix `Std` (not `Foo::Io`, not bare `Io`) - path head `Std` only when followed by `::` (not bare locals) - does not clobber enum `Color::Red` (member path still wins on variants) 3. Last import segment that is also a known symbol falls through to global rename 4. Workspace scan for unopened `.bux` files 5. Smoke: `tools/smoke_lsp_rename_path.sh` - Io→Net ≥2 (Main+Util), not Foo::Io; Std→Core ≥2; Red→Crimson ≥2 6. Version **bux-lsp 0.12.0**; `make test-lsp` --- ## Следващи стъпки 1. HirNode-level file for statements spanning multiple files (rare) 2. Optional: selfhost-loop as optional CI job (slow) 3. LSP find-implementations request (dedicated, beyond call hierarchy) 4. Workspace-wide import path index without open documents (optional polish)