The type system now keeps its nullability promise: a `?T` value cannot be
used, stored, or dereferenced as a plain `T` without narrowing. The canonical
evidence probe (return b.v where v: ?Int, fn -> Int) that compiled clean for
months now fails with WO-E211.
- WO-E211 (un-narrowed use): arithmetic and </<=/>/>= operands, and/or
operands (?Bool), interpolation segments, for-iterables, and returns whose
declared type is not nullable.
- WO-E212 (boundary): nil or ?T stored into a non-nullable slot — annotated
let, assignment to a confidently-typed local (cenv, never the placeholder
env — a placeholder target must stay silent) or a resolvable class field.
- WO-E213 (deref): field/index access through a possibly-nil base.
- Narrowing (locals only — a field place can be re-assigned between check
and use, so chains bind to a local first): `if x != nil { }` narrows the
branch; a DIVERGING then-branch (`if x == nil { return }`) narrows after
the if; `x != nil and x.n > 3` narrows and/or right operands
(short-circuit); `while x != nil` narrows the body. The narrow is
un-applied when an else-less then-env leaks out un-diverged (the existing
env-leak convention must not leak the narrow).
- No false positives by construction: env/cenv types are declared or
confidently inferred; the placeholder fallbacks are plain scalars, never
?T. The whole golden suite passed untouched (540/0).
- Samples updated to the bind-then-narrow idiom (log-watcher config decode +
supervisor lock/next_fire, gc-cycle ring print) — 22 genuine unnarrowed-nil
sites; employee needed zero changes. All acceptances green.
- Corpus: compile-fail/{nullable-unnarrowed-use,nullable-nil-into-plain,
nullable-deref-unchecked} + run/nullable-narrowing (all four forms) — 83/0.
- Catalog: E211/E212/E213 move from "Reserved, not yet emitted" to the main
table; nullable-types-implementation.md status flipped to ENFORCED
(historical record kept); plan 8 Task 6 ticked (boxed scalar cells
superseded by WO_NIL_SCALAR); board updated.
Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 83/0; oop-accept ALL MET;
log-watcher 7/0; employee 8/0; gc-cycle ring prints + reclaims.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
36 lines
852 B
Text
36 lines
852 B
Text
-- ?T narrowing, all four shipped forms: the if-guard, the early-return
|
|
-- (a diverging then-branch proves the false facts after the `if`), the
|
|
-- short-circuit `and` chain, and the while condition. Each narrows a LOCAL
|
|
-- from ?Int to Int; field places never narrow by design.
|
|
class Box {
|
|
v: ?Int
|
|
}
|
|
|
|
fn guard(b: Box) -> Int {
|
|
let x = b.v;
|
|
if x != nil { return x + 1; }
|
|
return -1
|
|
}
|
|
|
|
fn early(b: Box) -> Int {
|
|
let x = b.v;
|
|
if x == nil { return -1; }
|
|
return x + 10
|
|
}
|
|
|
|
fn chain(b: Box) -> Bool {
|
|
let x = b.v;
|
|
return x != nil and x > 3
|
|
}
|
|
|
|
fn main() -> Int {
|
|
let full = Box { v: 7 };
|
|
let empty = Box { v: nil };
|
|
print_int(guard(full));
|
|
print_int(guard(empty));
|
|
print_int(early(full));
|
|
print_int(early(empty));
|
|
if chain(full) { print("chain full: gt3"); }
|
|
if chain(empty) == false { print("chain empty: no"); }
|
|
return 0
|
|
}
|