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
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:
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user