Files
bux-lang/docs/QUALITY_PLAN.md
T
dimgigov fe3b1e8b6a
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
Sessions 56–69: declarative macro! with rep/zip/literal/block and
unhygienic var $name binders; partial field-move skip Drop; @[Release]
polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
2026-07-20 17:19:46 +03:00

1125 lines
57 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bux — План към „добър“ език (v0.5 → v1.0)
> **Дата:** 2026-07-20
> **Текущо:** v0.5.x — macros (unhygienic binders + multi-rep), partial field-move, lean CI
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден 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 | ✅ full (docs + Release wins + tests + example) |
### 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 | 35 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“
- [x] Всички examples + apps + selfhost smoke на CI (`make test` via `.github/workflows/ci.yml`); selfhost-loop optional
- [ ] 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 (~27GB) 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<T,U>` / `Filter<T>` / `Fold<T,Acc>` / `Any` / `All` / `ForEach`** — fat `func` params + monomorphization
2. **Int aliases** keep working: `Iter_MapInt``Iter_Map<int,int>`, …
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<T>(…)` 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<T>` / `*Array<T>` → 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<T,Acc>`
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<T>` 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<T>(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 <s>` / `--filter=<s>` — 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 <name>` 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.94ms (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<HeaderEntry>` 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 ~7680k 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<int>` → `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`
---
## Сесия 47 (HirNode-level sourceFile / mid-function #line)
1. **`HirNode.sourceFile`** — per-statement path for `#line` (rare multi-file spans)
2. **`Lcx_StampSourceFile`**: after lowering a func/closure body, fill empty
node paths from `Decl.sourceFile` / `HirFunc.sourceFile` (keeps pre-set paths)
3. **`LowerCtx.currentSourceFile`** + closures inherit enclosing file
4. **C backend:** `lastDebugFile` + prefer `node.sourceFile` over func
`currentFile`; re-emit `#line` when **line or file** changes mid-function
5. `BUX_DEBUG_FILE` truly forces one path (no longer overwritten by per-func)
6. Smoke: `tools/smoke_selfhost.sh` — Util_Double body `#line` → Util.bux only
7. `make test-selfhost-smoke` / rebuild selfhost
---
## Сесия 48 (optional CI: selfhost-loop)
1. **`tools/selfhost_loop.sh`**: bootstrap determinism (buxc × 2)
- path-normalized `#line` C compare (abs path / build-dir noise stripped)
- stripped ELF compare; **fails** on real C or ELF mismatch
2. **Lexer:** `maxTokens` 32k → 131k (hir_lower was at the ceiling for buxc2 parse)
3. **Optional fixed-point** `BUX_SELFHOST_FIXED_POINT=1`: buxc2 → buxc3
- experimental; selfhost still diverges on full compiler C emit
4. **GitHub Actions** `.github/workflows/selfhost-loop.yml`
- `workflow_dispatch` (+ optional fixed_point input)
- weekly cron (Sunday 06:00 UTC)
- push to `main` when `src/` / `lib/` / loop script change
- **not** on every PR / not in default `make test`
5. Verified: `make selfhost-loop` PASS (determinism)
---
## Сесия 49 (LSP 0.13 textDocument/implementation)
1. **`implementationProvider`** + `textDocument/implementation`
2. **Interface type** under cursor → locations of implementing types
(`extend Type for Iface` / type decl)
3. **Interface method** under cursor → implementor method decls
(reuses `collectImplementorFuncs` + `workspaceImpls`)
4. Also: call-site / shared method name matching known iface methods;
implementor method → sibling implementors of same iface method
5. Smoke: `tools/smoke_lsp_implementation.sh`
- Drawable → ≥2 types; Draw → ≥2 methods (Circle + Square)
6. Version **bux-lsp 0.13.0**; `make test-lsp`
---
## Сесия 50 (LSP 0.14 workspace import path index)
1. **`workspaceImportPaths`**: URI → full import paths (`["Std","Io"]`)
2. **`registerWorkspaceImports`** at end of `analyzeFile` (scan + open/edit)
3. **`isKnownImportPathPrefix`** uses workspace index first — no open doc required
4. Path segment snapshot fix (no shared seq mutation across segments)
5. Smoke: `tools/smoke_lsp_workspace_imports.sh`
- only Main opened; Util closed on disk via `rootUri` scan
- Io→Net ≥2 edits including `Util.bux` in WorkspaceEdit
6. Version **bux-lsp 0.14.0**; `make test-lsp`
---
## Сесия 51 (Expr/Stmt sourceFile for multi-file / macros)
1. **`Expr.sourceFile` / `Stmt.sourceFile` / `Block.sourceFile`**
2. **`Ast_StampExprFile` / `Ast_StampStmtFile` / `Ast_StampBlockFile` /
`Ast_StampPatternFile`** — fill empty slots only (grafted nodes keep path)
3. **`Cli_StampSourceFile`** stamps decl body + const init + default params
4. **Lower:** `Lcx_SetNodeLoc` / `Lcx_SourceFileFor` prefer AST file, else
`currentSourceFile`; `Lcx_StampSourceFile` still fills empties at func end
5. Smoke: Main body `#line` → Main.bux only (no Util leak); Util isolation kept
6. Rebuild selfhost; `make test-selfhost-smoke`
---
## Сесия 52 (selfhost fixed-point buxc2→buxc3→buxc4 green)
1. **Sema global scope:** allocate via `Scope_New` (8192), not 1024 — heap
overflow on ~1200 decls crashed buxc2 in `Scope_Lookup`
2. **HIR buffers:** funcs 512→4096, structs 64→512, enums/consts/gen* raised
3. **`CBE_FuncParam`:** avoid nested `.paramN.name` on `*HirParam` (wrong `.` vs `->`)
4. **Adapters:** `__fat_env` not `env` (no clash with `CtfeEnv* env`)
5. **Unary `!`:** parenthesize operand so `!(a && b)` ≠ `!a && b`
6. **Fat typedef skip-void:** rewritten without nested `!` for older gens
7. **Fixed-point loop:** compare gen2 vs gen3 (same CBE), not bootstrap vs selfhost
8. Verified: `BUX_SELFHOST_FIXED_POINT=1 make selfhost-loop` — C+ELF IDENTICAL
---
## Сесия 53 (main CI workflow — `make test`)
1. **`.github/workflows/ci.yml`**
- triggers: `pull_request`, `push` to `main`, `workflow_dispatch`
- job: install Nim 2.0.x + gcc/make/ssl → **`make test`**
- timeout 90m; concurrency cancel-in-progress
- failure artifact: selfhost `main.c` (best-effort)
2. **selfhost-loop.yml** remains optional (not on every PR)
3. Docs: README / BuildAndTest / QUALITY_PLAN point to ci vs selfhost-loop
---
## Сесия 54 (LSP 0.15 type hierarchy)
1. **`typeHierarchyProvider`** + prepare / supertypes / subtypes
2. **prepare** on `struct` / `enum` / `interface` / `type` → TypeHierarchyItem
3. **subtypes** of interface → types with `extend T for I` (Circle, Square)
4. **supertypes** of type → interfaces it implements
5. Uses open-doc `impls` + `workspaceImpls` + symbol resolve
6. Smoke: `tools/smoke_lsp_type_hierarchy.sh`
7. Version **bux-lsp 0.15.0**; `make test-lsp`
---
## Сесия 55 (macro / quote hygiene foundation)
1. **AST graft API** (force overwrite): `Ast_GraftExpr/Stmt/Block/PatternFile`
2. **Clone**: `Ast_CloneExpr/Stmt/Block/Pattern` (+ list / match arms)
3. **Quote policies**:
- `Ast_QuoteDefSite` — clone, keep `sourceFile` (macro body / template)
- `Ast_QuoteCallSite` / `Ast_QuoteStmtCallSite` — clone + graft call-site path
4. **Helpers**: `Ast_SetExprLoc` / `Ast_ExprSourceFile`
5. **HIR**: `Lcx_GraftSourceFile`; mono instances force-graft `genDecl.sourceFile`
6. Smoke: `tools/smoke_graft_hygiene.sh` (Array mono → lib; Main no leak)
7. Wired into `make test-selfhost-smoke`
---
## Сесия 56 (CBE binary parentheses — C precedence safety)
1. **Bug (selfhost):** tree HIR→C emitted nested binaries **without** parens.
- `return (a + b) * c` → C `return a + b * c;` → **7** instead of **9**
- `return (a - b) / c` → `a - b / c` → **8** instead of **3**
2. **Fix selfhost** (`src/c_backend.bux`):
- `hBinary` always emits `(left op right)` (same policy as bootstrap HIR CBE)
- Unary operand parens (session 52) kept: `!(a && b)`
3. **Bootstrap LIR** (`bootstrap/lir_c_backend.nim`): defensive parens on
arith/bitwise and unary `!`/`-`/`~` (operands are temps today; future-proof)
4. **Example:** `examples/c_precedence.bux` — MulSum/SubDiv/ShiftSum/Mix
5. **Smoke:** `tools/smoke_selfhost.sh` checks run values **and** generated C
contains `(a + b) * c` / `(a - b) / c`
6. Wired into `EXAMPLES` + `make test-selfhost-smoke`
7. Verified: bootstrap + **buxc2** → `9/3/6/7` + `PASS c_precedence`;
smoke PASS
---
## Сесия 57 (CI split jobs + macOS smoke)
1. **`.github/workflows/ci.yml`** — no longer one 90m monolithic `make test`:
- **`build`** (ubuntu): `make build` → artifact `buxc-linux`
- **Parallel** (reuse artifact, `BUX_SKIP_BUILD=1`):
- `unit` — `fmt-check` + `test-unit`
- `examples` — `test-examples`
- `goldens` — errors + stdlib + registry + dwarf
- `apps` — `test-apps`
- `selfhost` — `test-selfhost-smoke`
- **`macos`**: Homebrew OpenSSL + rebuild + fmt/unit/examples
- **`ci-gate`**: single required status (all of the above must succeed)
2. **Makefile:**
- `test-unit` extracted from `test`
- `ensure-buxc` + `BUX_SKIP_BUILD=1` for CI artifact reuse
- `$(OUT)` rebuild only when `bootstrap/*.nim` changes
- portable examples runner (optional `timeout`; macOS without coreutils OK)
3. **macOS / non-GNU ld:**
- bootstrap: `-Wl,--build-id=none` only `when defined(linux)`
- selfhost: `bux_cc_ld_stable()` in `rt/runtime.c` (Linux-only build-id)
- CI sets `BUX_CFLAGS=-I… -L…` for Homebrew `libcrypto`
4. Docs: BuildAndTest + README CI table; local `make test` still full sequential
5. Verified locally: `BUX_SKIP_BUILD=1 make fmt-check test-unit test-errors`
---
## Сесия 58 (LSP 0.16 workspace type hierarchy index)
1. **Gap:** type hierarchy subtypes/supertypes only saw open-doc `impls` or
method-keyed `workspaceImpls`. Closed files with **empty**
`extend T for I {}` (no methods) returned **[]**.
2. **`workspaceTypeRels`** (`tools/lsp_server.nim`):
- URI → `seq[(typeName, iface, line)]` from every `analyzeFile`
- `registerWorkspaceTypeRels` replaces per-URI (re-open / re-scan safe)
- filled on `scanWorkspace` + open/edit — **no open doc required**
3. **Consumers:**
- `collectSubtypeItems` / `collectSupertypeItems` prefer type-rel index
- `collectTypeImplementorLocs` (textDocument/implementation) same
4. **Smoke:** `tools/smoke_lsp_type_hierarchy_ws.sh`
- open only Main; Drawable.bux + Shapes.bux closed
- empty extends → Drawable subtypes Circle+Square; Circle supers Drawable+Named
5. Version **bux-lsp 0.16.0**; wired into `make test-lsp`
6. Verified: single-file hierarchy + workspace smoke PASS
---
## Сесия 59 (user-facing `macro!` / `quote!` — declarative MVP)
1. **Syntax (bootstrap):**
- `macro! name { ($x:expr, …) => { template } }`
- Invoke: `name!(args…)` — distinct from unwrap `expr!` via following `(`
- Built-in **`quote!(e)`** — identity expand + call-site graft
2. **Lexer:** keyword `macro`; `$ident` fragment tokens (`$x`)
3. **AST:** `dkMacro` + `MacroRule`/`MacroFragment`; `ekMacroCall`
4. **Expansion** (`bootstrap/macroexpand.nim`) before sema:
- Collect macro decls; match rule by arity
- Deep clone + substitute `$frags` + graft call-site `SourceLocation`
- Nested expand (depth ≤ 32)
5. **CLI:** `build` / `check` / `run` call `expandMacros` after merge
6. **Example:** `examples/macro_twice.bux` → 42 / 42 / 43 + PASS
7. **LanguageRef:** Macros section (limits documented)
8. Verified: `./buxc run macro_twice`; hello + lexer/parser tests green
9. **Not yet:** selfhost expand parity; `$(…)*` repetition; more frag kinds
---
## Сесия 60 (selfhost `macro!` / `quote!` expand parity)
1. **Lexer/token:** `tkMacro`, keyword `macro`, `$ident` fragments
2. **AST:** `dkMacro` (rules in `childDecl1` chain), `ekMacroCall`
3. **Parser:** `macro! name { ($x:expr) => {…} }`, invoke `name!(…)`
4. **`src/macroexpand.bux`:**
- collect macros → match rule by arity → clone+subst `$frags`
- call-site graft (line/col/`sourceFile`)
- built-in `quote!(e)`
5. **CLI:** expand before sema (project / check / compile paths)
6. **Sema fixes** (needed for block templates):
- `ekBlock` value = last `skExpr` type (was always `tyVoid`)
- `let x: T = …` sets `sym.typeKind` from annotation (not only init)
7. **Smoke:** `tools/smoke_selfhost.sh` runs `examples/macro_twice.bux` via buxc2
8. Verified: **buxc2** + bootstrap → `42/42/43` + `PASS macro_twice`
---
## Сесия 61 (macro `$(…)*` + `ident`/`tt` fragments)
1. **Fragment kinds:** `expr` | `ident` | `tt` (tt ≡ expr for now)
2. **Pattern rep (trailing):** `$( $x:expr ),*` / `$( $x:expr )*`
3. **Template rep:** `$( stmts… )*` → `skMacroRep`, expanded per list item
4. **Lexer:** bare `tkDollar` for `$(…)` (vs `$ident`)
5. **Bootstrap** `macroexpand.nim`: list bindings, match rules, gensym locals
6. **Selfhost** parity: `useNames` encodes kinds/`rep:`, `Subst_Block_Flat`, gensym
7. **Sema:** block-as-expr checks last value **inside** child scope (no UAF of locals)
8. **Example:** `examples/macro_repeat.bux` — sum_n / empty / call0 / id_tt
9. Verified: bootstrap + **buxc2** → `6/0/42/7` + `PASS macro_repeat`
---
## Сесия 62 (C.4 `@[Release]` polish + Checked docs)
1. **Three-tier model** documented in LanguageRef:
- default (no checks) → `@[Checked]` → `@[Release]` (force off)
2. **Bootstrap:** `releaseFunc`; `checkedFunc = Checked ∧ ¬Release`
3. **Selfhost:** same rule; **stacked attrs** loop (`@[Checked]` + `@[Release]`)
4. **Parser:** multi-line stacked `@[…]` (skip newlines between attrs)
5. **Tests** (`borrow_test`): Release alone; Checked+Release wins; Checked still errors
6. **Example:** `examples/ownership_release.bux` — Unchecked / Safe / Hot / HotDangle
7. Verified: 27/27 borrow tests; example PASS
---
## Сесия 63 (nested `$(…)*` / multi-rep / compound zip)
1. **Bootstrap** (`macroexpand.nim` + parser):
- Compound rep: `$( $a:expr, $b:expr ),*` → parallel lists, zip in template
- Multi-rep: `$(…)* ; $(…)*` with call-site `;` groups (`exprMacroGroupLens`)
- Nested template: outer binds list → inner `$(…)*` expands once (no list names left)
- `MacroFragment.names` / `.kinds` for multi-name frags
2. **Selfhost** parity (`src/macroexpand.bux`, `parser.bux`):
- Two named rep lists + zip in `Subst_Block_Flat`
- Kinds encoding `rep:expr+expr,@2` / multi-seg `rep:…;rep:…`
- Macro call `;` groups → `genericCallee` group-length string
3. **Example:** `examples/macro_nested.bux`
- `add_pairs` → 33, `sum_groups` → 63, `double_each_sum` → 14, `named_sum` → 18
4. **LanguageRef:** multi-rep / compound / nested docs; limits updated
5. Verified: bootstrap + **buxc2** → PASS macro_nested / macro_repeat / macro_twice
---
## Сесия 64 (CI Nim cache + faster macOS)
1. **Nim pin + toolchain cache:**
- `NIM_VERSION: 2.0.8` (stable cache keys; was `2.0.x`)
- Cache `.nim_runtime` on `build` / `unit` / `macos` / `selfhost-loop`
- Skip `setup-nim-action` on cache hit; restore `PATH` only
2. **`nimcache` project-local:**
- `Makefile` `NIMFLAGS ?= --nimcache:nimcache` for bootstrap + unit tests
- `actions/cache` keyed on `bootstrap/**/*.nim` (+ tests for unit)
3. **Leaner macOS job:**
- Runner `macos-14`; timeout 35m
- `make test-unit` + `make test-examples-smoke` (not full EXAMPLES / not fmt)
- `EXAMPLES_SMOKE`: hello, ownership*, strings, map, c_precedence, macro_*
- OpenSSL: install only if missing (`brew list`)
4. **Docs:** BuildAndTest CI table; `.gitignore` `.nim_runtime/`
5. Verified locally: `make build` uses `nimcache/`; `test-examples-smoke` PASS
---
## Сесия 65 (Drop / RAII docs — field-move story)
1. **LanguageRef — Drop and RAII** (under Gradual Ownership):
- `@[Drop]` vs `extend T for Drop` (static `Type_Drop`, no vtable)
- When auto-drop runs (block end, early return, branches) — not gated on Checked
- **Field-move skip Drop** with `MakeBox` + simplified C (no `Array_Drop(&items)`)
- Move-on-return, assignment, call-arg transfers; error-path still Drops
- Limits: whole-local moves, static dispatch, manual free pitfalls
2. **TOC** links Ownership + Drop; **Stdlib** `Array_Drop` + `Std::Drop` section
3. **README** Drop line mentions field-move
4. Cross-refs: `examples/move_field.bux`, `examples/drop_early_return.bux`,
selfhost smoke
5. Verified: `move_field` C has no `Array_Drop` on moved `items`; example PASS
---
## Сесия 66 (macro hygiene + frag kinds `literal` / `block`)
1. **Fragment kinds** (bootstrap + selfhost):
- `literal` / `lit` — only `ekLiteral` (rejects `1 + 2`)
- `block` — only `ekBlock` `{ … }`
- Shared `fragMatches` / `Macro_FragMatches` at match time
2. **Hygiene gensym:**
- Bootstrap: also rename **`for` binders**; walk for bodies in collect
- Selfhost: gensym `skFor` + recurse if/while/for/MacroRep bodies
- CBE: two `with_acc!` → `__m1_n` / `__m2_n` (no collision)
3. **Example:** `examples/macro_hygiene.bux` → 11/21/7/3/42 + PASS
4. **LanguageRef:** kind table + hygiene layers (graft + gensym)
5. **Makefile:** `macro_hygiene` in EXAMPLES + EXAMPLES_SMOKE
6. Verified: bootstrap + **buxc2**; negative `only_lit!(1+2)` → no matching rule
---
## Сесия 67 (CI Windows smoke)
1. **`.github/workflows/ci.yml` — `windows` job** (`windows-latest`, bash shell):
- Cache Nim **2.0.8** (prebuilt zip — fast) + `nimcache`
- `nim c -o:buxc.exe` bootstrap
- Pure Nim unit tests: lexer / parser / sema / hir / borrow
- CLI smoke: `buxc.exe new` + `--version`
2. **Scope (honest):** no `bux run` examples on Windows yet —
`rt/runtime.c` is POSIX (`ucontext`, `pthread`, sockets, OpenSSL link).
Job still gates bootstrap regressions on Win.
3. **`ci-gate`:** `windows` is a required job
4. **Docs:** BuildAndTest CI table + Windows note
5. Locally: YAML validated; full Win run is on GHA only
---
## Сесия 68 (partial field moves + Drop goldens)
1. **Bug:** `return bag.items` still ran `Bag_Drop(&bag)` → double-free /
corrupt Array (ASSERT fail). Also dead double-Drop after terminal `return`.
2. **Bootstrap** (`hir_lower.nim`):
- `markMovedOutFromAst` handles `ekField` when **field type is droppable**
(`autoDropFuncName`) — not for `return a.id` (int)
- Scope exit: skip re-emitting drops when last stmt always-returns; pop defers
3. **Selfhost** (`c_backend.bux`):
- `CBE_MarkMovedFromNodeHint` + droppable type check; return uses `currentRetType`
- Store/let rhs walks field access for partial moves
4. **Example + golden smoke:**
- `examples/move_field_partial.bux`
- `tools/smoke_drop_move.sh` + `make test-drop-move` (CI goldens job)
5. Verified: partial PASS; `TakeItems` has **no** `Bag_Drop`; drop_early_return 5;
move_field PASS
---
## Сесия 69 (macro unhygienic binders)
1. **Problem:** gensym renamed *all* template `let`/`var` binders, so
`var $name: int = …` with `$name:ident` could not introduce a call-site name.
2. **Bootstrap** (`macroexpand.nim`):
- `binderIdentFromFrag` + `expandUnhygienic` set
- `substStmt`: rewrite `skLet`/`skFor` binder when name is `$frag` → ekIdent
- `collectLetNames` skips unhygienic names
3. **Selfhost** (`macroexpand.bux`):
- `Env_AddUnhy` / `Env_IsUnhy` / `Env_BinderFromFrag`
- `Subst_Stmt` rewrites binders; `Macro_GensymBlock(ex, body, env)` skips them
4. **Example:** `examples/macro_unhygienic.bux` → 11/21/6/10/1 + PASS
- C: `counter` / `other` / `n` kept; `acc` / `scratch` → `__mN_*`
5. **LanguageRef:** unhygienic binder table; EXAMPLES + EXAMPLES_SMOKE
6. Verified: bootstrap + **buxc2**; macro_hygiene still PASS
---
## Следващи стъпки
1. Windows: MinGW + runtime stubs for `hello` smoke (stretch)
2. Per-field Drop after partial move (stretch)
3. Macro: true `stmt`/`pat` token-tree frags (stretch)