fix(bootstrap): implement collection for-in lowering

- Export typeToTypeExpr from sema and preserve generic type args
- Derive loop variable type from collection element type in sema
- Substitute generic struct type params on field access
- Add getCollectionElementTypeExpr helper in hir_lower
- Replace placeholder collection for-in lowering
- Add Array/Iter lowering: Array_Iter_T / Iter_HasNext_T / Iter_Next_T
- Add Channel lowering: Channel_Recv_Ok_T loop
- Register loop variable in varTypeExprs before body lowering

Fixes _test_forin_stdlib, _test_forin_channel, _test_generic_trait, _test_import, _test_mono
This commit is contained in:
2026-06-14 17:17:56 +03:00
parent 06db4926a7
commit 44bac83471
4 changed files with 712 additions and 7 deletions
@@ -0,0 +1,172 @@
# Fix `for ... in` Iterator Lowering in Bootstrap
> **Date:** 2026-06-14
> **Status:** Approved
> **Scope:** Bootstrap compiler (`bootstrap/hir_lower.nim`) — `for <var> in <collection>` loops
## 1. Problem Statement
The `for ... in` collection iterator is already parsed and sema-accepted, but the HIR→C lowering produces broken code for concrete generic collections such as `Array<int>` or `Channel<int>`.
Five integration tests fail because of this:
- `_test_forin_stdlib`: C compilation error `x undeclared` inside `Main`.
- `_test_forin_channel`: C compilation error `msg undeclared` inside `Main`.
- `_test_generic_trait`: sema error `cannot assign float32 to int at 12:9` — the loop variable `x` is typed as `float32` instead of `int`.
- `_test_import`: sema error `cannot assign float32 to int at 15:9` — same issue.
- `_test_mono`: sema error `cannot assign float32 to int at 38:9` — same issue.
Two independent bugs are at play:
1. **Missing C declaration:** the loop variable is not emitted as a local variable in the generated C function.
2. **Wrong loop-variable type:** generic `Iter_Next_T` returns an unsubstituted type parameter `T`, which sema treats as `float32` by default.
## 2. Goals
1. Make `_test_forin_stdlib` pass.
2. Make `_test_forin_channel` pass.
3. Make `_test_generic_trait`, `_test_import`, and `_test_mono` pass (or at least no longer fail with the `float32` loop-variable error).
4. Keep the fix minimal and confined to the bootstrap compiler.
5. Ensure `make test` and `make selfhost-loop` continue to pass.
## 3. Non-Goals
1. Rewriting the `apps/` example applications (excluded per user request).
2. Adding new language features such as `break`/`continue` in loops or iterator traits.
3. Changing the selfhost compiler (`src/*.bux`) unless required for selfhost-loop parity.
4. Fixing the unrelated `_test_slice` C typedef bug (tracked separately).
5. Implementing full Destructors / Drop trait (next roadmap milestone after stabilization).
## 4. Background: How `for ... in` Is Lowered
The parser produces `stmtForIn(ident, expr, body)`.
Sema checks the collection expression and infers the loop-variable type from it.
HIR lowering currently desugars the loop to a placeholder infinite `hLoop` that ignores the iterator expression and never declares the loop variable.
The intended lowering depends on the collection type:
- **Array / Iter collections** (from `lib/Iter.bux`):
```bux
let __iter = Array_Iter_T(&collection);
while (Iter_HasNext_T(&__iter)) {
let x = Iter_Next_T(&__iter);
// body
}
```
- **Channel collections** (from `lib/Channel.bux`):
```bux
var x: T;
while (true) {
if (!Channel_Recv_Ok_T(&ch, &x)) { break; }
// body
}
```
## 5. Investigation Plan
1. Read `_test_forin_stdlib/src/Main.bux`, `_test_forin_channel/src/Main.bux`, `_test_generic_trait/src/Main.bux`.
2. Locate `stmtForIn` handling in `bootstrap/hir_lower.nim`.
3. Verify how the iterator variable is introduced into HIR/C:
- Is it registered in `ctx.varTypes` / `ctx.varTypeExprs`?
- Is a `hirLet` emitted before the loop body?
4. Verify how `Array_Iter_Next_T` is monomorphized:
- Does the call get the `_int` suffix?
- Does the returned `T` get substituted with `int`?
5. Identify the minimal edit that fixes both symptoms.
## 6. Hypothesis
### 6.1 Missing Declaration
The lowering creates the loop body with references to the loop variable, but the declaration site is omitted or not added to the function's local-variable list. As a result, the C backend never emits `int x;`, causing the `undeclared identifier` error.
### 6.2 Wrong Type
When the collection is `Array<int>`, the lowering calls `Array_Iter_Next` (without `_int`) or calls a monomorphized `Array_Iter_Next_int` but assigns its result to a variable whose type was resolved from the generic signature (`T`). Because sema does not substitute `T` for the loop variable, the variable defaults to `float32`.
The fix is likely to:
- Explicitly declare the loop variable with the concrete element type derived from the collection type.
- Ensure `Array_Iter` / `Array_Iter_HasNext` / `Array_Iter_Next` are monomorphized with the collection's type arguments.
## 7. Proposed Fix
The fix spans sema and HIR lowering:
1. **Sema fixes**:
- In `skFor`, derive the loop-variable type from the collection's element type (`Array<T>` / `Channel<T>` inner type).
- In `ekField` for structs, build a type-parameter substitution map from the object's concrete type args and apply it to the field type, so `arr.data` on `Array<int>` resolves to `*int`.
- Fix `typeToTypeExpr` to preserve generic type arguments on named types.
2. **HIR lowering**:
- Replace the placeholder `skFor` collection branch with real lowering that selects by collection type.
- **Array / Iter collections**:
- Extract element type from `Array<T>` or `Iter<T>`.
- Generate or reuse `Iter_T` struct instance.
- Generate function instances `Array_Iter_T`, `Iter_HasNext_T`, `Iter_Next_T`.
- If the collection expression is not a simple identifier, spill it to a temporary `Array_T` variable first.
- Emit `alloca __iter`, store `Array_Iter_T(&collection)`, then `while (Iter_HasNext_T(&__iter)) { let x = Iter_Next_T(&__iter); body }`.
- Register `x` in `ctx.varTypeExprs` before lowering the body.
- **Channel collections**:
- Extract element type from `Channel<T>`.
- Generate function instance `Channel_Recv_Ok_T`.
- Emit `alloca x`, then `while (true) { if (!Channel_Recv_Ok_T(&ch, &x)) break; body }`.
## 8. Affected Files
| File | Expected Change |
|------|-----------------|
| `bootstrap/sema.nim` | Derive loop var type; substitute struct type params on field access; preserve type args in `typeToTypeExpr`. |
| `bootstrap/hir_lower.nim` | Replace placeholder collection `skFor` lowering with Array/Iter and Channel lowerings. |
| `_test_forin_stdlib/src/Main.bux` | No changes. |
| `_test_forin_channel/src/Main.bux` | No changes. |
| `_test_generic_trait/src/Main.bux` | No changes. |
| `_test_import/src/Main.bux` | No changes. |
| `_test_mono/src/Main.bux` | No changes. |
## 9. Testing Plan
### 9.1 Target Tests
```bash
cd _test_forin_stdlib && /home/ziko/z-git/bux/bux/buxc run
cd _test_forin_channel && /home/ziko/z-git/bux/bux/buxc run
cd _test_generic_trait && /home/ziko/z-git/bux/bux/buxc run
cd _test_import && /home/ziko/z-git/bux/bux/buxc run
cd _test_mono && /home/ziko/z-git/bux/bux/buxc run
```
Expected: all exit 0.
### 9.2 Regression Tests
```bash
cd /home/ziko/z-git/bux/bux
make test
make selfhost-loop
```
Expected: all pass / no changes in selfhost-loop output.
## 10. Success Criteria
- `_test_forin_stdlib` reports PASS.
- `_test_forin_channel` reports PASS.
- `_test_generic_trait`, `_test_import`, `_test_mono` no longer fail with the `float32` loop-variable error.
- `make test` reports no new failures.
- `make selfhost-loop` remains deterministic.
## 11. Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Fix changes loop lowering and breaks range-based `for i in 0..10` | Keep range-based path separate; only touch `stmtForIn` collection path. |
| Same bug exists in selfhost compiler | Selfhost-loop will catch output differences; fix selfhost only if required. |
| Root cause is deeper than loop lowering | Time-box investigation; report findings if not resolved quickly. |
## 12. Relation to Other Work
- Previous commit `06db492` fixed generic `operator []` resolution. This work continues generic-type propagation into iterator loops.
- `_test_slice` has a separate C typedef bug and is out of scope for this task.