fix: gate implicit optional unwrap on flow narrowing (issue 0179)
Optional (?T) operands were implicitly unwrapped without proof of
presence, silently miscompiling a NULL ?T to garbage. Unwraps in
binary ops and other expression positions are now gated on flow
narrowing: a ?T value is only auto-unwrapped where control flow has
established it is non-null (the narrowed_refs set). Outside a narrowed
region, an implicit unwrap is rejected rather than producing garbage.
Touches the lowering pipeline (lower.zig + lower/{call,closure,coerce,
comptime,control_flow,expr,ffi,generic,pack,stmt}.zig). Adds optionals
examples 0919-0923 and closures example 0312 covering flow narrowing,
binop narrowing, no-implicit-unwrap rejection, and no closure leak of
narrowed state. Updates specs.md and readme.md.
This commit is contained in:
@@ -1,5 +1,26 @@
|
||||
# 0179 — implicit `?T → <concrete>` unwrap silently miscompiles a NULL optional to garbage (whole family)
|
||||
|
||||
> **RESOLVED.** Root cause: `coerceMode`'s `.optional_unwrap` arm
|
||||
> (`src/ir/lower/coerce.zig`) unwrapped any `?T → concrete` UNCONDITIONALLY,
|
||||
> never reading the has_value flag — a null optional yielded its zero payload
|
||||
> with no diagnostic. Fix took path (a) from the investigation prompt: **real
|
||||
> flow-sensitive narrowing**. `?T → concrete` is now REJECTED at the coercion
|
||||
> site (loud diagnostic listing `!` / `??` / binding / `!= null`) UNLESS the
|
||||
> source is a local proven present by flow narrowing. Narrowing is tracked by
|
||||
> name (`Lowering.narrowed`, region-scoped by `lowerBlock` / the if-then arm /
|
||||
> a divergent `== null` guard / the `else` arm, killed on reassignment) and
|
||||
> bridged to `coerceMode` via `narrowed_refs` (the loaded `Ref` of a narrowed
|
||||
> identifier, tagged in `lowerIdentifier`). Closes the `?bool → bool` hole too
|
||||
> (issue 0169's carve-out). specs.md §Optional Types + readme updated.
|
||||
> Regressions: `examples/optionals/0919-optionals-flow-narrowing.sx` (narrowing
|
||||
> works) + `examples/optionals/0920-optionals-no-implicit-unwrap.sx` (rejection);
|
||||
> `0900-optionals-optionals.sx` now exercises genuine narrowing.
|
||||
>
|
||||
> **Out of scope / follow-up:** the binary-op operand auto-unwrap
|
||||
> (`src/ir/lower/expr.zig` ~line 3211) is a SEPARATE silent-unwrap path that
|
||||
> does NOT route through `classify`/`coerceMode` — `a + b` with a null `?T`
|
||||
> still yields garbage with no diagnostic. Filed separately as issue 0185.
|
||||
|
||||
## Symptom
|
||||
|
||||
Passing an optional `?T` where a concrete `T`/other builtin is expected (function
|
||||
|
||||
Reference in New Issue
Block a user