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:
agra
2026-06-25 13:57:48 +03:00
parent 6c89a0aa3e
commit 468461becc
38 changed files with 576 additions and 3 deletions

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,9 @@
i32 41
i32 7
sum 7
guard 9: 9
guard null: -1
guard2: 11
guard2 null: 0
i32 100
i32 200

View File

@@ -0,0 +1,5 @@
error: cannot use a value of type '?i64' where 'i32' is expected: an optional does not implicitly unwrap; force-unwrap with '!', supply a fallback with '?? <default>', bind it (`if v := ...`), or guard with '!= null'
--> examples/optionals/0920-optionals-no-implicit-unwrap.sx:15:5
|
15 | takes_i32(n); // error: optional does not implicitly unwrap
| ^^^^^^^^^^^^

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,5 @@
add 42
lt false
sum 11: 11
sum null: -1
times 18

View File

@@ -0,0 +1,5 @@
error: cannot use a value of type '?i64' as an operand: an optional does not implicitly unwrap; force-unwrap with '!', supply a fallback with '?? <default>', or guard with '!= null'
--> examples/optionals/0922-optionals-binop-no-implicit-unwrap.sx:12:10
|
12 | c := a + b; // error: optional operand does not implicitly unwrap
| ^

View File

@@ -0,0 +1,5 @@
error: cannot use a value of type '?i64' where 'i64' is expected: an optional does not implicitly unwrap; force-unwrap with '!', supply a fallback with '?? <default>', bind it (`if v := ...`), or guard with '!= null'
--> examples/optionals/0923-optionals-narrowing-no-closure-leak.sx:20:22
|
20 | g := () => { takes_i64(n); };
| ^^^^^^^^^^^^