feat: macros (multi-rep, hygiene), Drop field-move, lean multi-OS CI
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
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
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.
This commit is contained in:
+31
-5
@@ -191,14 +191,40 @@ Use `Std::Test` module for assertions inside test code.
|
||||
|
||||
### Continuous integration
|
||||
```bash
|
||||
make test # what PR CI runs
|
||||
make test # full sequential suite (local)
|
||||
```
|
||||
| Workflow | When | Command |
|
||||
|----------|------|---------|
|
||||
| **`.github/workflows/ci.yml`** | every PR + push to `main` | `make test` |
|
||||
| Workflow | When | What runs |
|
||||
|----------|------|-----------|
|
||||
| **`.github/workflows/ci.yml`** | every PR + push to `main` | **split jobs** (see below) + macOS smoke |
|
||||
| **`.github/workflows/selfhost-loop.yml`** | weekly / manual / path-filtered main | `make selfhost-loop` |
|
||||
|
||||
`make test` includes examples, goldens, registry, apps, DWARF, and selfhost smoke
|
||||
**`ci.yml` layout (faster PR feedback):**
|
||||
|
||||
| Job | OS | Targets |
|
||||
|-----|-----|---------|
|
||||
| `build` | ubuntu | `make build` → upload `buxc` artifact |
|
||||
| `unit` | ubuntu | `fmt-check` + `test-unit` (reuse artifact) |
|
||||
| `examples` | ubuntu | `test-examples` (full list) |
|
||||
| `goldens` | ubuntu | `test-errors` + `test-stdlib` + `test-registry` + `test-dwarf` + `test-drop-move` |
|
||||
| `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 |
|
||||
| `ci-gate` | ubuntu | fails if any required job failed (branch protection) |
|
||||
|
||||
**CI speed helpers:**
|
||||
- Pin Nim **2.0.8**; cache `.nim_runtime` (big win on macOS — Nim is built from source there;
|
||||
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.
|
||||
|
||||
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.
|
||||
`make test-examples-smoke` runs the macOS-sized subset locally.
|
||||
|
||||
`make test` includes examples, goldens, registry, apps, DWARF, unit tests, and selfhost smoke
|
||||
(not the slow gen2↔gen3 fixed-point).
|
||||
|
||||
### Selfhost loop (optional CI)
|
||||
|
||||
+436
-35
@@ -16,11 +16,13 @@ This document describes the Bux programming language as implemented by the boots
|
||||
8. [Pattern Matching](#pattern-matching)
|
||||
9. [Methods and Interfaces](#methods-and-interfaces)
|
||||
10. [Generics](#generics)
|
||||
11. [Error Handling](#error-handling)
|
||||
12. [Modules and Imports](#modules-and-imports)
|
||||
13. [Async/Await](#asyncawait)
|
||||
14. [Operator Overloading](#operator-overloading)
|
||||
15. [Operators](#operators)
|
||||
11. [Gradual Ownership](#gradual-ownership-phase-82--implemented) — Checked / Release / [Drop & RAII](#drop-and-raii)
|
||||
12. [Error Handling](#error-handling)
|
||||
13. [Modules and Imports](#modules-and-imports)
|
||||
14. [Async/Await](#asyncawait)
|
||||
15. [Operator Overloading](#operator-overloading)
|
||||
16. [Operators](#operators)
|
||||
17. [Macros](#macros)
|
||||
|
||||
---
|
||||
|
||||
@@ -522,32 +524,44 @@ func Main() -> int {
|
||||
|
||||
## Gradual Ownership (Phase 8.2) ✅ Implemented
|
||||
|
||||
Bux introduces **gradual ownership** — opt-in borrow checking. By default, Bux is permissive like C. With `@[Checked]`, the borrow checker enforces memory safety rules.
|
||||
Bux has **gradual ownership** — opt-in borrow checking. Default is permissive
|
||||
(C-like). Turn safety on where it matters; turn it off on hot paths with zero cost.
|
||||
|
||||
### Syntax
|
||||
### Three tiers
|
||||
|
||||
| Mode | Attribute | Checks | Cost |
|
||||
|------|-----------|--------|------|
|
||||
| **Default** | (none) | None | Zero — raw `*T`, free aliasing |
|
||||
| **Checked** | `@[Checked]` | Moves, exclusive `&mut`, shared/`&mut` conflicts, dangling returns, elision | Compile-time only |
|
||||
| **Release** | `@[Release]` | **Forced off** (even if also `@[Checked]`) | Zero — same codegen as default |
|
||||
|
||||
**Story:** write most code unchecked for speed of iteration; mark critical APIs
|
||||
`@[Checked]`; mark micro-hotspots `@[Release]` (or both) when you need C-level
|
||||
performance without false positives.
|
||||
|
||||
```bux
|
||||
// Default: permissive mode (like C/Nim) — raw pointers, no checks
|
||||
// Tier 1 — default: C-like, no borrow checker
|
||||
func QuickSort(arr: *int, len: int) {
|
||||
for i in 0..len {
|
||||
arr[i] = arr[i] * 2;
|
||||
}
|
||||
// free to alias, no move tracking
|
||||
}
|
||||
|
||||
// Opt-in: @[Checked] enables borrow checking
|
||||
// Tier 2 — opt-in safety
|
||||
@[Checked]
|
||||
func Scale(val: &mut int) {
|
||||
*val = *val * 2; // OK: &mut T allows mutation
|
||||
*val = *val * 2;
|
||||
}
|
||||
|
||||
@[Checked]
|
||||
func Read(val: &int) -> int {
|
||||
return *val; // OK: &T allows reading
|
||||
// Tier 3 — zero-cost escape (e.g. hot loop helper)
|
||||
@[Release]
|
||||
func HotInc(p: *int) {
|
||||
*p = *p + 1; // no checks; same as default, documents intent
|
||||
}
|
||||
|
||||
// Release wins over Checked when both are present
|
||||
@[Checked]
|
||||
func BadWrite(val: &int) {
|
||||
*val = 42; // ERROR: cannot write through shared reference '&T'
|
||||
@[Release]
|
||||
func HotButDocumented(p: &mut int) {
|
||||
*p = *p + 1; // no borrow checks
|
||||
}
|
||||
```
|
||||
|
||||
@@ -586,37 +600,57 @@ Moves happen in three contexts:
|
||||
- **Assignment**: `b = a` moves `a` into `b`
|
||||
- **Return**: `return x` moves `x` out of the function
|
||||
|
||||
### Rules in @[Checked] functions
|
||||
### Rules in `@[Checked]` functions (not `@[Release]`)
|
||||
|
||||
- `&T` cannot be used to mutate data (compile-time error)
|
||||
- `&mut T` allows mutation
|
||||
- `*T` pointers are unrestricted (escape hatch)
|
||||
- `&mut T` coerces to `&T` and `*T`
|
||||
- **Double mutable borrow**: passing `&mut x` twice to the same call is an error
|
||||
- **Double mutable borrow**: two live `&mut` of the same var (call args or let-bound)
|
||||
```bux
|
||||
Swap(&mut x, &mut x); // ERROR: double mutable borrow of x
|
||||
Swap(&mut x, &mut x); // ERROR
|
||||
let a: &mut int = &mut x;
|
||||
let b: &mut int = &mut x; // ERROR: exclusive mut already live
|
||||
```
|
||||
- **Use after move**: using a moved `own T` value is an error until reassigned
|
||||
```bux
|
||||
let msg: own String = "hello";
|
||||
Process(msg); // move
|
||||
PrintLine(msg); // ERROR: use of moved value
|
||||
msg = "reassigned"; // OK: reinitialization
|
||||
PrintLine(msg);
|
||||
```
|
||||
- **No dangling returns**: cannot return a reference to a local (or by-value parameter)
|
||||
- **Use while mutably borrowed**: assign/use of `x` while a let-bound `&mut x` is live
|
||||
- **Shared while mut**: cannot form `&x` while `&mut x` is live
|
||||
- **Use after move**: using a moved `own T` until reassigned
|
||||
- **No dangling returns**: cannot return a reference to a local
|
||||
```bux
|
||||
@[Checked]
|
||||
func Bad(p: &int) -> &int {
|
||||
var x: int = 1;
|
||||
return &x; // ERROR: cannot return reference to local variable
|
||||
return &x; // ERROR
|
||||
}
|
||||
```
|
||||
|
||||
### `@[Release]` (C.4 zero-cost path)
|
||||
|
||||
Use when a function must stay check-free:
|
||||
|
||||
1. **Documented hot path** — same IR as unchecked, but the attribute states intent.
|
||||
2. **Override Checked** — `@[Checked] @[Release]` on a method that would otherwise inherit team-wide Checked defaults.
|
||||
|
||||
There is **no runtime cost**: the attribute only disables the checker for that function body. Prefer `@[Release]` on the smallest possible surface; keep call boundaries `@[Checked]` when you still want API-level safety.
|
||||
|
||||
```bux
|
||||
@[Checked]
|
||||
func SafeApi(buf: &mut int) {
|
||||
// checked here
|
||||
HotPath(buf);
|
||||
}
|
||||
|
||||
@[Release]
|
||||
func HotPath(p: &mut int) {
|
||||
// no move / borrow tracking — write like C
|
||||
*p = *p + 1;
|
||||
}
|
||||
```
|
||||
|
||||
### Lifetime elision (C.1)
|
||||
|
||||
In `@[Checked]` functions, most reference signatures need **no** lifetime annotations.
|
||||
Elision applies the usual single-input rules:
|
||||
In `@[Checked]` functions (and not `@[Release]`), most reference signatures need
|
||||
**no** lifetime annotations. Elision applies the usual single-input rules:
|
||||
|
||||
1. Each elided input `&T` / `&mut T` parameter gets a distinct lifetime.
|
||||
2. If there is **exactly one** input lifetime, it is assigned to all elided outputs.
|
||||
@@ -640,8 +674,180 @@ func Pick<'a>(a: &'a int, b: &'a int) -> &'a int {
|
||||
// Type parameters: func F<'a, T>(...)
|
||||
```
|
||||
|
||||
Unchecked functions ignore lifetime rules (C-like). Explicit `'a` is optional
|
||||
documentation when a single input would already elide correctly.
|
||||
Default and `@[Release]` functions ignore lifetime rules (C-like). Explicit `'a`
|
||||
is optional documentation when a single input would already elide correctly.
|
||||
|
||||
### Drop and RAII
|
||||
|
||||
Bux uses **static destructors** (no GC): when a value goes out of scope, the
|
||||
compiler may emit `TypeName_Drop(&local)`. That is the RAII story — resources
|
||||
are released at every exit path without manual `defer` on every return.
|
||||
|
||||
#### Declaring cleanup
|
||||
|
||||
Two equivalent ways to opt a type into auto-drop:
|
||||
|
||||
```bux
|
||||
// 1) Attribute — compiler looks up TypeName_Drop
|
||||
@[Drop]
|
||||
struct Token {
|
||||
id: int,
|
||||
counter: *int,
|
||||
}
|
||||
|
||||
func Token_Drop(self: *Token) {
|
||||
// free / close / decrement …
|
||||
}
|
||||
|
||||
// 2) Interface (stdlib `lib/Drop.bux`) — same static call, no vtable
|
||||
import Drop;
|
||||
|
||||
extend Buffer for Drop {
|
||||
func Drop(self: *Buffer) {
|
||||
Mem_Free(self.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Stdlib collections implement Drop (`Array_Drop`, `Map_Drop`, …). Calling
|
||||
`Array_Drop` is the same cleanup as `Array_Free` for `Array<T>`.
|
||||
|
||||
#### When auto-drop runs
|
||||
|
||||
Auto-drop is **not** gated on `@[Checked]`. Any function can receive injected
|
||||
`Type_Drop` at:
|
||||
|
||||
| Exit | Behavior |
|
||||
|------|----------|
|
||||
| End of block / function | Drop locals still owned |
|
||||
| Early `return` | Drop all live locals **after** materializing the return value |
|
||||
| Branch scope end | Only locals from the taken branch |
|
||||
| Nested scopes | Drop in reverse order of declaration |
|
||||
|
||||
```bux
|
||||
@[Drop]
|
||||
struct Token { id: int, counter: *int }
|
||||
func Token_Drop(self: *Token) { /* … */ }
|
||||
|
||||
func Early(flag: int, counter: *int) -> int {
|
||||
let t: Token = Token { id: 1, counter: counter };
|
||||
if flag == 0 {
|
||||
return 0; // still runs Token_Drop(&t)
|
||||
}
|
||||
return 1; // Token_Drop(&t) here too
|
||||
}
|
||||
```
|
||||
|
||||
See `examples/drop_early_return.bux` for branch-local vs fallthrough counts.
|
||||
|
||||
#### Field-move: skip Drop of the source (critical)
|
||||
|
||||
**Problem:** a local is moved **by value** into a struct field (or another local).
|
||||
If the compiler still auto-dropped the source, you get a **double free** — the
|
||||
field and the original local would both run `Array_Drop` on the same buffer.
|
||||
|
||||
**Rule:** after a **value move** out of a local, that local is **not** dropped.
|
||||
|
||||
```bux
|
||||
struct Box {
|
||||
items: Array<int>;
|
||||
}
|
||||
|
||||
func MakeBox() -> Box {
|
||||
var items: Array<int> = Array_New<int>(4);
|
||||
Array_Push<int>(&items, 10);
|
||||
Array_Push<int>(&items, 20);
|
||||
// Move `items` into the field — compiler skips Drop of `items`
|
||||
let b: Box = Box { items: items };
|
||||
return b; // also: return-by-value skips Drop of `b` (caller owns it)
|
||||
}
|
||||
```
|
||||
|
||||
What the C backend does for `MakeBox` (simplified):
|
||||
|
||||
```c
|
||||
Box MakeBox(void) {
|
||||
Array_int items = Array_New_int(4);
|
||||
Array_Push_int(&items, 10);
|
||||
Array_Push_int(&items, 20);
|
||||
Box b = (Box){ .items = items };
|
||||
return b;
|
||||
/* no Array_Drop_int(&items); — moved into b.items */
|
||||
/* no Array_Drop on b; — moved to caller via return */
|
||||
}
|
||||
```
|
||||
|
||||
Ownership after `MakeBox`:
|
||||
|
||||
1. Heap buffer lives inside `b.items` (and later the caller's `Box`).
|
||||
2. `items` is **moved-out** → skip auto-Drop.
|
||||
3. `b` is **returned by value** → skip auto-Drop at the return site; the caller
|
||||
(or the next owner) is responsible.
|
||||
|
||||
The same skip applies to:
|
||||
|
||||
- **Struct field init** — `S { field: local }` (field-move)
|
||||
- **Assignment** — `a = b` when `b` is moved (value types with Drop)
|
||||
- **Call argument** by value into a consuming parameter
|
||||
- **`return x`** — move-on-return
|
||||
|
||||
Live, unmoved Drop locals still clean up on error paths (e.g. early `return`
|
||||
before the move). That is intentional: only the **successful transfer** path
|
||||
skips Drop.
|
||||
|
||||
Runnable check: `examples/move_field.bux` (also covered by
|
||||
`make test-selfhost-smoke` on buxc2).
|
||||
|
||||
#### Partial field moves
|
||||
|
||||
Moving a **droppable field** out of a local (return or `let`) also skips Drop
|
||||
of the **parent** local:
|
||||
|
||||
```bux
|
||||
@[Drop]
|
||||
struct Bag {
|
||||
items: Array<int>,
|
||||
tag: int,
|
||||
}
|
||||
func Bag_Drop(self: *Bag) {
|
||||
Array_Drop<int>(&self.items);
|
||||
}
|
||||
|
||||
func TakeItems() -> Array<int> {
|
||||
var items: Array<int> = Array_New<int>(4);
|
||||
Array_Push<int>(&items, 42);
|
||||
let bag: Bag = Bag { items: items, tag: 7 };
|
||||
return bag.items; // Bag_Drop skipped — items ownership transferred
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Applies only when the **field type** is droppable (`Array_*`, `@[Drop]` types,
|
||||
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).
|
||||
|
||||
Golden smoke: `make test-drop-move` / `examples/move_field_partial.bux`.
|
||||
|
||||
#### Manual Drop and non-Drop types
|
||||
|
||||
- Types **without** `@[Drop]` / `Drop` impl are never auto-dropped (plain C layout).
|
||||
- You can still call `Type_Drop(&x)` or use `defer` for explicit cleanup.
|
||||
- `@[Release]` / default functions still get auto-drop for Drop types — Release
|
||||
only turns off the **borrow checker**, not RAII.
|
||||
|
||||
#### 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.
|
||||
- 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
|
||||
still possible if you free without invalidating the value — prefer one owner.
|
||||
|
||||
---
|
||||
|
||||
@@ -893,3 +1099,198 @@ Overloadable operators use the naming convention `TypeName_operator_<op>`:
|
||||
- `..` — Range (exclusive): `0..10`
|
||||
- `..=` — Range (inclusive): `0..=10`
|
||||
- `sizeof` — Size of type: `sizeof(Type)`
|
||||
|
||||
---
|
||||
|
||||
## Macros
|
||||
|
||||
Bux supports **declarative macros**. Expansion runs after parse and before
|
||||
type-checking. Expanded AST uses **call-site** source locations (quote hygiene).
|
||||
Both bootstrap and selfhost (`buxc2`) expand macros.
|
||||
|
||||
### Definition
|
||||
|
||||
```bux
|
||||
macro! twice {
|
||||
($x:expr) => {
|
||||
($x) + ($x)
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing repetition
|
||||
macro! sum_n {
|
||||
( $($x:expr),* ) => {
|
||||
var acc: int = 0;
|
||||
$( acc = acc + $x; )*
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
// Compound / zip: parallel lists from interleaved args
|
||||
macro! add_pairs {
|
||||
( $($a:expr, $b:expr),* ) => {
|
||||
var acc: int = 0;
|
||||
$( acc = acc + ($a + $b); )*
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-rep groups: `;` separates arg groups at the call site
|
||||
macro! sum_groups {
|
||||
( $($x:expr),* ; $($y:expr),* ) => {
|
||||
var s: int = 0;
|
||||
$( s = s + $x; )*
|
||||
$( s = s + $y; )*
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
// Nested template repetition (outer list → inner expands once per item)
|
||||
macro! double_each_sum {
|
||||
( $($x:expr),* ) => {
|
||||
var t: int = 0;
|
||||
$(
|
||||
$( t = t + $x; )*
|
||||
$( t = t + $x; )*
|
||||
)*
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
// ident fragment: bare identifier at the call site
|
||||
macro! call0 {
|
||||
( $f:ident ) => {
|
||||
$f()
|
||||
}
|
||||
}
|
||||
|
||||
// literal (alias: lit) — only int/float/string/char/bool literals
|
||||
macro! only_lit {
|
||||
( $x:literal ) => { $x }
|
||||
}
|
||||
|
||||
// block — only `{ … }` block expressions
|
||||
macro! wrap_block {
|
||||
( $b:block ) => { $b }
|
||||
}
|
||||
|
||||
// gensym: template locals renamed per expansion
|
||||
macro! with_acc {
|
||||
( $start:literal ) => {
|
||||
var n: int = $start;
|
||||
n = n + 1;
|
||||
n
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Introduced with the `macro!` keyword.
|
||||
- Each **rule** is `( pattern ) => { template }`.
|
||||
- **Fragment kinds:**
|
||||
|
||||
| Kind | Matches |
|
||||
|------|---------|
|
||||
| `expr` | any expression |
|
||||
| `ident` | bare identifier (`ekIdent`) |
|
||||
| `tt` | token-tree (MVP: same as `expr`) |
|
||||
| `literal` / `lit` | int/float/string/char/bool literal only |
|
||||
| `block` | block expression `{ … }` |
|
||||
|
||||
- Fragment names start with `$` (lexer `$ident`).
|
||||
- **Repetition:** `$( $x:expr ),*` / `$( $x:expr )*` — one or more rep fragments per pattern.
|
||||
- **Compound rep:** `$( $a:expr, $b:expr ),*` — interleaved args zip into parallel lists.
|
||||
- **Multi-rep:** two (or more) `$(…)*` in one pattern; call site uses `;` between groups:
|
||||
`sum_groups!(1, 2; 10, 20, 30)`.
|
||||
- Template `$( stmt; … )*` expands once per list item (zip when multiple lists used).
|
||||
- Nested `$( $(…)* )*`: after outer binds list items as singles, inner expands once.
|
||||
|
||||
### Invocation
|
||||
|
||||
```bux
|
||||
let n = twice!(21);
|
||||
let s = sum_n!(1, 2, 3); // 6
|
||||
let z = sum_n!(); // 0
|
||||
let p = add_pairs!(1, 10, 2, 20); // (1+10)+(2+20) = 33
|
||||
let g = sum_groups!(1, 2; 10, 20, 30); // 63
|
||||
let d = double_each_sum!(3, 4); // 14
|
||||
call0!(SomeFunc);
|
||||
let a = with_acc!(10); // 11
|
||||
let b = with_acc!(20); // 21 — different gensym'd `n`
|
||||
let c = only_lit!(7);
|
||||
// only_lit!(1 + 2); // ERROR: no matching rule
|
||||
let w = wrap_block!({ 1 + 2 }); // 3
|
||||
```
|
||||
|
||||
- Syntax: `name!( arg, … )` (not unwrap: unwrap is `expr!` without `(`).
|
||||
- Matching: fixed-arity by count; kind constraints; rep by groups / remaining args / chunk.
|
||||
|
||||
### Built-in `quote!`
|
||||
|
||||
```bux
|
||||
let x = quote!(1 + 2); // identity expand; locations grafted to call site
|
||||
```
|
||||
|
||||
### Hygiene
|
||||
|
||||
Two layers (both bootstrap + selfhost):
|
||||
|
||||
1. **Call-site graft** — expanded AST uses the call site’s line/col/`sourceFile`
|
||||
(so diagnostics and `#line` point at the user call, not the macro definition).
|
||||
2. **Gensym of template binders** — each expansion renames:
|
||||
- `let` / `var` locals introduced by the template
|
||||
- `for` loop binders in the template
|
||||
- Nested scopes (if/while/for bodies, MacroRep bodies)
|
||||
|
||||
so two expansions of the same macro in one function do not collide under the
|
||||
C backend’s **function-scoped** locals (e.g. `__m1_n` and `__m2_n`).
|
||||
|
||||
Spliced `$frags` in expression positions are **not** gensym’d — they keep
|
||||
call-site names/values.
|
||||
|
||||
#### Unhygienic binders (`var $name`)
|
||||
|
||||
To **introduce a binder whose name comes from the call site**, use a `$frag`
|
||||
as the binder itself. That name is **not** gensym’d:
|
||||
|
||||
```bux
|
||||
macro! let_mut {
|
||||
( $name:ident, $init:literal ) => {
|
||||
var $name: int = $init; // unhygienic: becomes `counter`, not __m1_…
|
||||
$name = $name + 1;
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
// expands with local `counter` (and hygienic locals still unique)
|
||||
let a = let_mut!(counter, 10); // 11
|
||||
let b = let_mut!(other, 20); // 21
|
||||
|
||||
macro! double_acc {
|
||||
( $start:literal ) => {
|
||||
var acc: int = $start; // hygienic → __m1_acc / __m2_acc
|
||||
acc = acc + acc;
|
||||
acc
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Binder form | After expand | Gensym? |
|
||||
|-------------|--------------|---------|
|
||||
| `var acc = …` (plain name in template) | `__mN_acc` | yes |
|
||||
| `var $name = …` with `$name:ident` | call-site ident | **no** |
|
||||
| `for $i in …` with `$i:ident` | call-site ident | **no** |
|
||||
|
||||
The binder must be a **`:ident` fragment** bound to a bare identifier. A plain
|
||||
template name is always hygienic.
|
||||
|
||||
Examples: `examples/macro_hygiene.bux`, `examples/macro_unhygienic.bux`.
|
||||
|
||||
### Limits
|
||||
|
||||
- Up to two named rep lists per rule on selfhost (enough for zip + multi-rep).
|
||||
- Compound chunk size currently 1 or 2.
|
||||
- Nested macro *calls* expanded recursively (depth limit 32).
|
||||
- Unhygienic binders only rename `let`/`var`/`for` binders — not full
|
||||
Scheme/Rust colored identifiers or `stmt`/`pat` token trees.
|
||||
- Macro expansion still yields a **block expression**; unhygienic names are
|
||||
scoped to that block (not automatically injected into the caller scope).
|
||||
|
||||
+259
-7
@@ -1,7 +1,7 @@
|
||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||
|
||||
> **Дата:** 2026-07-19
|
||||
> **Текущо:** v0.5.x — quote/graft hygiene, LSP 0.15, CI, fixed-point
|
||||
> **Дата:** 2026-07-20
|
||||
> **Текущо:** v0.5.x — macros (unhygienic binders + multi-rep), partial field-move, lean CI
|
||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||
|
||||
---
|
||||
@@ -72,7 +72,7 @@
|
||||
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата | ✅ bootstrap + selfhost |
|
||||
| C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives | ✅ let-bound + use-while + call conflict |
|
||||
| C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден | ✅ bootstrap + selfhost |
|
||||
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path | ✅ partial (unchecked path + goldens) |
|
||||
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path | ✅ full (docs + Release wins + tests + example) |
|
||||
|
||||
### D — Tooling (P1)
|
||||
|
||||
@@ -864,9 +864,261 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
||||
|
||||
---
|
||||
|
||||
## Сесия 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. Parenthesize binary ops in CBE for full C precedence safety
|
||||
2. CI matrix (macOS) or split jobs for faster PR feedback
|
||||
3. Type hierarchy for multi-file closed docs without open (workspace type index)
|
||||
4. User-facing `macro!` / `quote` syntax on top of graft/clone
|
||||
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)
|
||||
|
||||
+25
-1
@@ -95,6 +95,12 @@ struct Array<T> {
|
||||
| `Array_Clear<T>` | `func Array_Clear<T>(arr: *Array<T>)` | Set length to 0 (keeps capacity) |
|
||||
| `Array_Reserve<T>` | `func Array_Reserve<T>(arr: *Array<T>, minCap: uint)` | Grow capacity if needed |
|
||||
| `Array_Free<T>` | `func Array_Free<T>(arr: *Array<T>)` | Free memory |
|
||||
| `Array_Drop<T>` | `func Array_Drop<T>(self: *Array<T>)` | Drop trait entry (same as `Array_Free`) |
|
||||
|
||||
**RAII:** `Array<T>` is auto-dropped at scope exit. Prefer letting the compiler call
|
||||
`Array_Drop` over manual `Array_Free` when ownership is clear. If you move an
|
||||
array into a struct field, the **source local is not dropped** (see LanguageRef
|
||||
[Drop and RAII](LanguageRef.md#drop-and-raii) / `examples/move_field.bux`).
|
||||
|
||||
### Example
|
||||
```bux
|
||||
@@ -105,13 +111,31 @@ func Main() -> int {
|
||||
Array_Push<int>(&arr, 10);
|
||||
Array_Push<int>(&arr, 20);
|
||||
PrintInt(Array_Get<int>(&arr, 0)); // 10
|
||||
Array_Free<int>(&arr);
|
||||
// Array_Drop runs at end of Main (or call Array_Free manually)
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Std::Drop
|
||||
|
||||
Trait for automatic cleanup (RAII). Defined in `lib/Drop.bux`:
|
||||
|
||||
```bux
|
||||
interface Drop {
|
||||
func Drop(self: *Self);
|
||||
}
|
||||
```
|
||||
|
||||
Implement with `extend Type for Drop { func Drop(self: *Type) { … } }` or mark
|
||||
the type `@[Drop]` and provide `Type_Drop`. Full rules (early return, field-move
|
||||
skip Drop, move-on-return): **LanguageRef → Drop and RAII**.
|
||||
|
||||
Examples: `examples/drop_early_return.bux`, `examples/move_field.bux`.
|
||||
|
||||
---
|
||||
|
||||
## Std::Iter
|
||||
|
||||
Lightweight iterator over `Array<T>` (index-based, no allocation).
|
||||
|
||||
Reference in New Issue
Block a user