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:
@@ -15,6 +15,15 @@ const Lowering = lower.Lowering;
|
||||
const Scope = lower.Scope;
|
||||
|
||||
pub fn lowerLambda(self: *Lowering, lam: *const ast.Lambda) Ref {
|
||||
// Flow narrowing (issue 0179) does NOT cross into the lambda body: the
|
||||
// body is a separate function whose `Ref` space overlaps the enclosing
|
||||
// function's, so the outer `narrowed_refs` would falsely match body `Ref`s
|
||||
// (unsound unwrap of a captured-but-not-proven-present optional). The body
|
||||
// builds its own narrowing from scratch; the outer state is restored on
|
||||
// return (re-arming narrowing for the rest of the enclosing expression).
|
||||
var narrow_guard = Lowering.NarrowGuard.enter(self);
|
||||
defer narrow_guard.restore();
|
||||
|
||||
// Lower the lambda body as a new anonymous function
|
||||
var buf: [64]u8 = undefined;
|
||||
const name = std.fmt.bufPrint(&buf, "__lambda_{d}", .{self.block_counter}) catch "__lambda";
|
||||
|
||||
Reference in New Issue
Block a user