Files
bux-lang/docs/QUALITY_PLAN.md
T
dimgigov 3eb1ad3a82 feat: match guards, HOF inference, ownership C.2/C.3, LSP sema hover
Sessions 18–23 quality work:

- B.3c match arm guards + sequential found-flag lower (bootstrap + selfhost)
- Generic HOF type inference (Array/Iter map/filter/fold without type args)
- Pattern binding shadowing via unique C locals (__pN_src)
- Ownership C.2 exclusive &mut data-flow + C.4 goldens; *p= store-through fix
- Ownership C.3 auto-drop on early return/branches: scoped defers, move-on-return,
  Drop monomorphization, materialize return before Drop
- LSP 0.3.0: hover from real sema types
- Examples and QUALITY_PLAN session log; selfhost-loop identical
2026-07-18 21:52:14 +03:00

22 KiB
Raw Blame History

Bux — План към „добър“ език (v0.5 → v1.0)

Дата: 2026-07-18
Текущо: v0.5.x — selfhost loop, gradual ownership, green threads, 43+ examples, match + guards + generic HOF inference + pattern bindings + f"..." interp bootstrap+selfhost
Цел: Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден 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], &/&mut, move, Drop ★★★☆☆ (basic)
Concurrency M:N tasks + channels + async ★★★★☆
Stdlib Array/Map/Set/String/Iter HOF разширени ★★★★☆
Tooling test-errors, LSP diagnostics + hover/def/outline ★★★★☆
Ecosystem / registry path+git deps; няма централен registry ★☆☆☆☆
Документация 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-тата
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 hover/def/outline + sema types on hover (v0.3.0) + buxc diags
D.2 bux fmt стабилен + CI check Единен style
D.3 bux test с --filter, exit codes, summary table CI-friendly partial (bux test exists)
D.4 bux doc от /// comments Самодокументиращ се stdlib
D.5 Golden tests за stdlib modules Регресии без изненади

E — Ecosystem & v1.0 (P2)

# Задача Защо
E.1 Package registry protocol (git/HTTP) bux add foo без path hacks
E.2 35 production-quality apps в apps/ Showcase
E.3 Language freeze + semver policy Trust
E.4 Debugger/DWARF basics Systems audience
E.5 Benchmarks vs C/Zig/Nim (micro + nexus) Marketing + regression

Препоръчан ред на работа

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
  • Array/Map/String/Test API покрива 90% от ежедневните нужди
  • @[Checked] хваща use-after-move + double &mut в documented subset
  • bux test + bux fmt + bux check са default developer loop
  • LanguageRef синхронизиран с компилатора
  • Поне един външен проект (не в monorepo) build-ва с git dep

Сесия 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 matchskExpr + 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: matchPatternBindingsalloca 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: ekStringInterpString_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_MapIntIter_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 ✓

Следващи стъпки

  1. C.1 Lifetime elision
  2. Phase D tooling: bux fmt CI, bux test --filter, golden stdlib tests
  3. LSP: position-sensitive locals; inferred let types