feat: field-move Drop (partial/nested/ptr), stmt/pat macros, Windows runtime
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (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 / CI gate (push) Has been cancelled

Sessions 70–74: partialMovedPaths + ptrAliases in bootstrap/selfhost CBE,
remaining drops after field moves, macro stmt/pat fragments, runtime_win.c
and MinGW hello CI, examples + drop-move smoke coverage, QUALITY_PLAN update.
This commit is contained in:
2026-07-21 12:29:53 +03:00
parent fe3b1e8b6a
commit a939f74b1b
22 changed files with 2672 additions and 117 deletions
+6 -4
View File
@@ -209,7 +209,7 @@ make test # full sequential suite (local)
| `apps` | ubuntu | `test-apps` |
| `selfhost` | ubuntu | `test-selfhost-smoke` |
| `macos` | macos-14 | rebuild + `test-unit` + `test-examples-smoke` (subset) |
| `windows` | windows-latest | rebuild `buxc.exe` + pure Nim unit tests + CLI smoke |
| `windows` | windows-latest | `buxc.exe` + Nim unit tests + CLI + **MinGW `hello`** |
| `ci-gate` | ubuntu | fails if any required job failed (branch protection) |
**CI speed helpers:**
@@ -217,8 +217,9 @@ make test # full sequential suite (local)
Windows uses a prebuilt Nim zip)
- Project-local `nimcache/` via `NIMFLAGS=--nimcache:nimcache`, cached per job by source hash
- macOS skips full EXAMPLES (Linux already runs them) and skips `fmt-check` (Linux unit job)
- **Windows** does **not** run `bux run` examples yet: `rt/runtime.c` needs POSIX
(`ucontext`, `pthread`, BSD sockets). Smoke still validates bootstrap + unit tests on Win.
- **Windows** runs `tools/smoke_windows_hello.sh` with **MinGW gcc** + `rt/runtime_win.c`
(no pthread/OpenSSL/ucontext). Full POSIX runtime (`rt/runtime.c`) remains Unix-only.
Locally on Linux/macOS: `BUX_RUNTIME=win ./tools/smoke_windows_hello.sh`.
Parallel Linux jobs set `BUX_SKIP_BUILD=1` after downloading the `buxc` artifact.
Locally, `make test` still runs the full suite sequentially and builds once.
@@ -330,7 +331,8 @@ bux/
│ ├── Task.bux
│ └── Channel.bux
├── rt/ # C runtime
│ ├── runtime.c
│ ├── runtime.c # full POSIX + OpenSSL (Unix)
│ ├── runtime_win.c # MinGW minimal (Windows / BUX_RUNTIME=win)
│ └── io.c
├── examples/ # Example programs
├── tests/ # Unit tests (Nim)
+78 -6
View File
@@ -827,10 +827,59 @@ Rules:
etc.). Reading `bag.tag` (`int`) does **not** mark `bag` moved.
- After `let moved = bag.items`, `Bag_Drop(&bag)` is skipped; `moved` owns the
array and is auto-dropped at scope end.
- Avoid using other droppable fields of the parent after a partial move (they
may be left in a moved-from state without per-field Drop).
- **Remaining fields:** if the parent has other droppable fields that were *not*
moved out, those still run their `Type_Drop` / collection Drop (session 70).
Example: move `pair.left` → skip `PairBag_Drop`, still `Tracked_Drop(&pair.right)`.
Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux`.
```bux
@[Drop]
struct PairBag {
left: Array<int>,
right: Tracked, // also @[Drop]
}
func TakeLeft() -> Array<int> {
let pair: PairBag = …;
return pair.left; // Tracked_Drop(&pair.right) still runs
}
```
#### Nested path moves (`a.b.c`)
Moving a **deep** droppable field also works. The full dotted path is recorded
so remaining fields at every level still Drop:
```bux
@[Drop]
struct Outer {
inner: Inner, // Inner has items: Array + note: Tracked
tag: Tracked,
}
func TakeNested() -> Array<int> {
let outer: Outer = …;
return outer.inner.items;
// skips Outer_Drop
// still: Tracked_Drop(&outer.inner.note) + Tracked_Drop(&outer.tag)
}
```
#### Field moves through pointers
When a local pointer aliases a local owner (`let p = &bag`), moving a field
through the pointer marks the **owner**, not the pointer:
```bux
let bag: Bag = …;
let p: *Bag = &bag;
return p.items; // same as (*p).items
// skips Bag_Drop; still Tracked_Drop(&bag.tag)
```
Nested paths work the same: `p.inner.items` resolves `p → outer` then path
`inner.items`.
Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux` /
`examples/move_field_remaining.bux` / `examples/move_field_nested.bux` /
`examples/move_field_ptr.bux`.
#### Manual Drop and non-Drop types
@@ -841,9 +890,11 @@ Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux`.
#### Limits (honest)
- Partial field moves mark the **whole parent local** as moved for Drop purposes
(not per-field Drop of remaining fields).
- Nested `a.b.c` path moves and moving through pointers are limited.
- Partial field moves skip the **parent** `Type_Drop` and drop **remaining**
droppable fields individually, including nested paths `a.b.c` and pointer
aliases `p = &owner` (sessions 70/73/74).
- Pointer aliases are tracked for **local** `p = &local` only (not parameters
that point at caller-owned data across function boundaries).
- Interface Drop uses a static `TypeName_Drop` symbol (zero cost), not dynamic
dispatch through a vtable.
- Double-free bugs in **unchecked** code that manually free *and* auto-drop are
@@ -1174,6 +1225,25 @@ macro! wrap_block {
( $b:block ) => { $b }
}
// stmt — one statement (let/if/… or expression-statement)
macro! with_setup {
( $s:stmt, $body:expr ) => {
{
$s
$body
}
}
}
// call: with_setup!(let x: int = 10, x + 1)
// pat — match/let pattern (literals, `_`, enum variants, …)
macro! matches {
( $p:pat, $e:expr ) => {
match $e { $p => 1, _ => 0 }
}
}
// call: matches!(1, 1) · matches!(_, 99) · matches!(Opt::Some(v), opt)
// gensym: template locals renamed per expansion
macro! with_acc {
( $start:literal ) => {
@@ -1195,6 +1265,8 @@ macro! with_acc {
| `tt` | token-tree (MVP: same as `expr`) |
| `literal` / `lit` | int/float/string/char/bool literal only |
| `block` | block expression `{ … }` |
| `stmt` | one statement (`let`/`if`/… or expression-stmt) |
| `pat` / `pattern` | match pattern (`_`, literals, `Enum::Var(…)`, …) |
- Fragment names start with `$` (lexer `$ident`).
- **Repetition:** `$( $x:expr ),*` / `$( $x:expr )*` — one or more rep fragments per pattern.
+96 -6
View File
@@ -1,7 +1,7 @@
# Bux — План към „добър“ език (v0.5 → v1.0)
> **Дата:** 2026-07-20
> **Текущо:** v0.5.x — macros (unhygienic binders + multi-rep), partial field-move, lean CI
> **Дата:** 2026-07-21
> **Текущо:** v0.5.x — macros, field-move via pointers, Windows hello
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
---
@@ -14,7 +14,7 @@
| 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** | ★★★★★ |
| Gradual ownership | `@[Checked]`, move, Drop, elision, **field-move + remaining-field 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 | ★★★★★ |
@@ -1117,8 +1117,98 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
---
## Сесия 70 (per-field Drop + mono defer restore)
1. **Bug (critical):** `lowerFunc` restored `deferStmts` / `movedOutLocals` only
when the *inner* mono function still had pending defers. Nested
`generateMethodInstance` → `lowerFunc` (e.g. `Array_Len` inside
`PeekTagAndTake`) wiped the caller's Drop stack → leaked moved Arrays.
2. **Fix bootstrap:** always restore `deferStmts` / `movedOutLocals` /
`partialMovedFields` after lowering a function body.
3. **Per-field Drop after partial move:**
- Track `partialMovedFields: local → {field names}`
- Skip parent `Type_Drop`; emit Drop for **remaining** droppable fields
- `emitDropOrPartial` at return / block exit / function tail
4. **Selfhost CBE** (`c_backend.bux`):
- partial (var, field) slots + local type registry on `hAlloca`
- `CBE_EmitRemainingFieldDrops` when skipping moved parent Drop
5. **Example + smoke:**
- `examples/move_field_remaining.bux` (PairBag left move → Tracked_Drop right)
- `tools/smoke_drop_move.sh` checks PeekTag Array_Drop + remaining Tracked_Drop
6. **LanguageRef:** remaining-field rule; limits updated
7. Verified: bootstrap + **buxc2** remaining/partial/move_field; smoke; EXAMPLES
---
## Сесия 71 (Windows MinGW + `hello` smoke)
1. **`rt/runtime_win.c`** — minimal runtime without pthread / ucontext / sockets /
OpenSSL. Real alloc, strings, files, time, env; stubs for tasks/crypto/net.
2. **Bootstrap CLI** (`bootstrap/cli.nim`):
- Windows (or `BUX_RUNTIME=win`) copies `runtime_win.c` instead of `runtime.c`
- Link: `-ffunction-sections -Wl,--gc-sections -lm` (no `-pthread` / `-lcrypto`)
- Host `gcc` on Windows; `.exe` suffix on build/run
- Fixed: `-l` libs **after** `.c` inputs (GNU ld order)
3. **CI** (`.github/workflows/ci.yml` windows job):
- MinGW via `msys2/setup-msys2` (`mingw-w64-x86_64-gcc`)
- `tools/smoke_windows_hello.sh` after unit/CLI smoke
4. **Docs:** BuildAndTest CI table + `rt/` tree
5. Verified locally: normal `hello` + `BUX_RUNTIME=win` smoke PASS
---
## Сесия 72 (macro `stmt` / `pat` fragments)
1. **Kinds:** `mfkStmt` / `mfkPat` (+ aliases `pattern`, `lit` already)
2. **AST wrappers:** `ekMacroStmt` / `ekMacroPat` (expand-only)
3. **Call-site parse:**
- stmt keywords → `parseStmt` → MacroStmt
- `_` → `parsePattern` → MacroPat
- else expr; `pat` coerces via `exprToPattern` (ident/lit/path/call/tuple/struct/range)
4. **Expand:**
- `coerceArg` at match; store normalized MacroStmt/MacroPat
- `$s` as skExpr splices MacroStmt into the statement list
- `$p` as pkIdent pattern substitutes bound MacroPat
5. **Selfhost:** same kinds, coerce, splice, pattern subst
6. **Example:** `examples/macro_stmt_pat.bux` — setup/do_twice/matches/if_let_like
7. LanguageRef kind table + docs
8. Verified: bootstrap + **buxc2** `macro_stmt_pat` PASS
---
## Сесия 73 (nested `a.b.c` field-move Drop)
1. **Bootstrap** (`hir_lower.nim`):
- `fieldPathFromAst` → base local + path `@["inner","items"]`
- `partialMovedFields` stores **dotted paths** (`"inner.items"`)
- `remainingDropsAt` recursive: exact path = skip; prefix = recurse;
other droppable fields → `Type_Drop(&(base.a.b))`
- Typed intermediate `hFieldAccess` so LIR/C keep `Inner` not `int`
2. **Selfhost CBE:** full dotted path on mark; recursive `CBE_EmitRemainingAt`
3. **Example:** `examples/move_field_nested.bux` — `outer.inner.items` → 2 Tracked drops
4. Smoke + EXAMPLES; LanguageRef nested path section
5. Selfhost: recursive remaining drops + skip Drop when `HasPartialMoved`
(struct emit multi-pass topo for Outer{Inner})
6. Verified: bootstrap + **buxc2** `nested_drops=4` PASS; full `test-drop-move`
---
## Сесия 74 (field moves through pointers)
1. **Pointer aliases:** `let p = &bag` / `p = &bag` → `ptrAliases[p] = bag`
2. **fieldPathFromAst:** peel `(*p)` (ekUnary tkStar); resolve alias to owner
3. **`p.field`** (auto-deref) and **`(*p).field`** mark owner + path
4. Nested via ptr: `p.inner.items` → owner + `"inner.items"`
5. Selfhost CBE: alias slots + resolve in `CBE_BaseVarName`; record on store/assign
6. **Example:** `examples/move_field_ptr.bux` → `ptr_drops=5`
7. Smoke + LanguageRef; limits: local aliases only (not cross-function params)
8. Selfhost: unary C parens fix `(*p).field`; alias slots + BaseVar resolve
9. Verified: bootstrap + **buxc2** `ptr_drops=5` PASS; full `test-drop-move`
---
## Следващи стъпки
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)
1. Windows: more examples (strings/ownership) on MinGW; optional Win OpenSSL
2. Macro: true token-tree `tt` / nested pattern rewrite depth
3. Cross-function pointer ownership transfer (callee `*Bag` param)