fibers B1.2: UNBLOCKED — remove invalid issue 0151, correct the async idiom

The B1.2 "blockers" were not real:
- Issue 0151 was INVALID: its repro used the non-idiomatic `($A) -> $R`
  bare-fn-ptr form. The canonical higher-order pack idiom
  `Closure(..$args) -> $R` + `..$args` (see examples/0543-packs-canonical-map)
  infers $R fine and runs today with no compiler change. Removed 0151.
- The correct async idiom is verified working live (42 42 for homo + hetero
  args): async :: (io, worker: Closure(..$args) -> $R, ..$args) -> Future($R)
  with a lambda worker (annotated params) + a `result = ---; result.v = ...`
  build form. No compiler change needed.

Issue 0150 (void struct field -> SIGTRAP exit 133) IS a real bug but is only
reached via Future(void) (void-returning worker / timeout) — deferred to B1.4;
B1.2 supports non-void workers.

Updates the PLAN/CHECKPOINT B1.2 status to UNBLOCKED with the corrected idiom
and the resume plan. No compiler/library code changed in this commit.
This commit is contained in:
agra
2026-06-20 20:00:36 +03:00
parent f0a918f3c8
commit 7bf65565bd
3 changed files with 48 additions and 131 deletions

View File

@@ -1,98 +0,0 @@
# 0151 — a type-var inferred from a fn-pointer parameter's RETURN type is not bound in the function body
## Status
OPEN — blocks Stream B1 (fibers) B1.2's `async(io, worker: ($A) -> $R, arg: $A)`
free-fn (it needs `Future(R)` in its body). Independent of the fibers work
(reproduces with a tiny `Wrap($R)` standalone).
## Symptom
A generic free function whose type-var `$R` is introduced **inside a
fn-pointer parameter's return type** (`worker: ($A) -> $R`) infers `$R` fine for
the call's type-checking, but `R` is **not in scope as a usable type name in the
function body**. Referencing `Wrap(R)` (or any `R`) in the body errors:
```
error: unknown type 'R'
--> repro.sx:4:14
|
4 | w : Wrap(R) = .{ v = worker(arg) };
```
By contrast a type-var introduced **directly** by a parameter (`arg: $A`) IS
usable in the body (`Wrap(A)` works — see the second repro). So the gap is
specific to type-vars that appear only nested in a fn-pointer (or closure)
parameter's signature.
Observed: `error: unknown type 'R'` for the body reference.
Expected: `R` binds to the worker's return type (here `i64`), so `Wrap(R)`
resolves to `Wrap(i64)`, exactly as `$A``A` does.
## Reproduction (fails)
```sx
#import "modules/std.sx";
Wrap :: struct($T: Type) { v: T; }
runit :: (worker: ($A) -> $R, arg: $A) -> Wrap($R) {
w : Wrap(R) = .{ v = worker(arg) }; // error: unknown type 'R'
return w;
}
dbl :: (n: i64) -> i64 { return n * 2; }
main :: () -> i32 {
r := runit(dbl, 21);
print("{}\n", r.v); // want: 42
return 0;
}
```
## Reproduction (works — shows the contrast)
```sx
#import "modules/std.sx";
Wrap :: struct($T: Type) { v: T; }
runit :: (arg: $A) -> Wrap($A) {
w : Wrap(A) = .{ v = arg }; // OK — `A` (direct param type-var) binds
return w;
}
main :: () -> i32 { r := runit(21); print("{}\n", r.v); return 0; } // prints 21
```
## Suspected area
Generic monomorphization / type-binding collection (the pass that walks a generic
function's parameter signatures to discover `$X` type-vars and record the
caller-inferred binding for use in the body). It descends into direct param types
(`arg: $A` → binds `A`) but does NOT descend into a fn-pointer / closure
parameter's nested signature (`($A) -> $R`) to also bind `$R` from the matched
argument function's return type (and likewise `$A` from its params, if only named
there). Look for where `$`-type-params are gathered from `fd.params` and the
per-instance `type_bindings` map is seeded — likely in `src/ir/generic.zig`
and/or the call-site argument→param type-var unifier in `src/ir/lower/call.zig`.
The unifier already infers `$R` well enough to type-check the call (the error is
only in the BODY), so the binding exists at the call site but isn't propagated
into the monomorphized body's `type_bindings`.
## Investigation prompt (paste into a fresh session)
> A type-var that appears only inside a fn-pointer parameter's signature
> (`worker: ($A) -> $R`) is inferred at the call site (the call type-checks) but
> is NOT available as a type name in the generic function's BODY — `Wrap(R)` in
> the body errors `unknown type 'R'`, while a direct `arg: $A` makes `A` usable.
> Repro: `issues/0151-...` (the failing + the working contrast are both inline in
> the `.md`; the `.sx` is the failing one). Fix the generic type-binding pass so
> that when a generic fn is monomorphized, type-vars discovered inside a
> fn-pointer/closure parameter's nested signature (its params AND its return
> type) are added to the instance's `type_bindings` from the matched argument
> function's concrete signature — mirroring how direct param type-vars are bound.
> Suspected sites: `src/ir/generic.zig` (binding collection from `fd.params`) +
> the call-site unifier in `src/ir/lower/call.zig` (it already infers `$R` for
> overload/type-check, so reuse that result to seed the body bindings). Verify:
> the failing repro prints `42`; then move it to `examples/` as a regression test.
## Why this matters for B1 (fibers)
`async(io, worker: ($A) -> $R, arg: $A) -> Future($R)` is the central B1.2
ergonomic free-fn; its body builds `Future(R)`. Without this fix `async` can't be
written in its spec-faithful form. Routing around it (an explicit `$R: Type`
param the caller must pass) would change the surface and HIDE the gap — not done.
The rest of the B1.2 Io surface (the `Io` protocol on `Context`, the blocking
`CBlockingIo` default, `context.io.now_ms()`) works; only the `async`/`await`
generics are blocked by this. Saved WIP: `.sx-tmp/b12-wip/`.