aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
commitbe43babc1886fc5efee898b2a3d0285012e511fb (patch)
treeb7ae73e7a6d4c896e2c49b7090168ef589b574af /docs
parentmerge work/known-bugs: constant-expression precedence, local const in constan... (diff)
definite assignment: reading a local before it is initialized is a compile error (spec 4.1.5, b.1.9)
track linited per local alongside the ownership state; snapshots, exit records and joins carry it, and a join keeps a local set only when every reaching path set it. x = e, a declaration initializer, &x (out-parameter), a full array initializer, or using an aggregate as an address set a local; field and element writes do not set the aggregate. while/for bodies that may run zero times do not count; do/while does. compiler, runtime, std, actor and lsp sources initialize the locals the rule flagged; zc0 accepts = {} brace lists (zeroed). tests: tests/errors/uninit_*.zc, tests/conformance/4-statements/uninit_*.zc
Diffstat (limited to 'docs')
-rw-r--r--docs/LEARN.md2
-rw-r--r--docs/SPEC.md11
2 files changed, 9 insertions, 4 deletions
diff --git a/docs/LEARN.md b/docs/LEARN.md
index 081498d..e4fcdc6 100644
--- a/docs/LEARN.md
+++ b/docs/LEARN.md
@@ -63,7 +63,7 @@ hello, world!
### variables and while
-`let` infers a variable's type from its initializer. an integer literal stays a plain word. `int i = 0;` writes the type explicitly. the lifetime analyzer runs by default; the optional typechecker is enabled separately with `typecheck on;`.
+`let` infers a variable's type from its initializer. an integer literal stays a plain word. `int i = 0;` writes the type explicitly. a local must be assigned on every path before it is read (locals are not zeroed); passing `&x` counts as initializing `x`, and an array or struct needs a full `{...}` initializer or `&agg`. the lifetime analyzer runs by default; the optional typechecker is enabled separately with `typecheck on;`.
`while` tests before entering the body. `i++` increments the variable, and `break` leaves the loop.
diff --git a/docs/SPEC.md b/docs/SPEC.md
index f655395..b645742 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -553,9 +553,14 @@ let sq = { let t = 3 + 4; t * t }; // 49
4.1.4 `const k = e;` declares a local constant: no type is written, it takes the initializer's type (read-only, 2.10), needs an initializer (error: a const needs an initializer), and cannot be assigned (error: const storage cannot be modified). `const T k = e;` is a typed const.
-4.1.5 a local with no initializer is uninitialised: its value is whatever the frame held. a global with no initializer is 0. an array or struct local has no zeroing.
+4.1.5 definite assignment. a local with no initializer is unset until every path that reaches a read has assigned it (or taken its address). reading an unset local is error: X: used before it is initialized. a local is set by an initializer, by `x = e`, by passing `&x` (the out-parameter pattern), or by a full array initializer list `{...}` (structs have no brace initializer; use `&agg` or pass the array). writing some fields or elements does not set the aggregate: the whole value stays unset until a full array initializer, `&agg`, or using the aggregate as an address (an array decaying to a pointer). at a join (if/else, loop exit, switch, `?:`) a local is set only if every reaching path set it. a `while` or `for` that may run zero times does not count an assignment only inside the body; a `do`/`while` always runs once, so an assignment in its body does. parameters and globals are set from the start (a global with no initializer is 0). locals are not zeroed.
```
-int b; pn(b); // unspecified value
+int b; pn(b); // error: b: used before it is initialized
+int x; x = 1; pn(x); // ok
+int y; if (c) y = 1; pn(y); // error unless every branch sets y
+point p; p.x = 1; pn(p.y); // error: partial fill does not set p
+point q; fill(&q); pn(q.x); // ok: &q sets q (structs have no brace initializer)
+int z; fill(&z); pn(z); // ok: &z sets z
```
4.1.6 a local may shadow a local of an outer block, a parameter, a global or a function name. redeclaring a name in the same block is accepted and the new declaration hides the old one from that point.
@@ -1152,7 +1157,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the
6. fixed (README says it now): README/LEARN did not say that an embedded `\0` ends a string literal: `b"hi\n\0x"` has `len` 3 and `"ab\0cd"` is `ab`.
7. fixed (3.14.2): README ("zc, the language") says `const k = 5;` in a function is a local constant. it is not a constant expression: `const n = 5; int[n] a;` in a function => `expected a constant`.
8. fixed (README now gives 0/1/2): README (reflection) described `run.tparamtag` as "the u/m/d tag of a parameter"; the code returns 0 untagged, 1 `.m`, 2 `.d` (there is no u at a parameter).
-9. README/LEARN do not say that local variables and arrays are not zeroed (4.1.5).
+9. fixed (4.1.5): reading an uninitialized local is a compile error (definite assignment), not an unspecified frame value. README/LEARN describe the rule; locals are still not zeroed.
10. zc0 vs compiler, the case that needed a referee: with `||` and `&&` now at c precedence, zc0 and compiler.zc agree on `1 || 0 && 0` => 1, `0 && 0 || 1` => 1, `1 | 2 && 0` => 0, `1 + 2 << 1` => 6, `7 & 3 ^ 1 | 8` => 10, `6 & 3 == 3` => 0 (checked on both).
11. zc0 vs compiler, integer literals. zc0 reads them with `strtoll` base 0: `rt.put_n(010)` prints 8 in zc0 and 10 in the compiler; `99999999999999999999` and `0xFFFFFFFFFFFFFFFF` saturate to 9223372036854775807 in zc0 and wrap to 7766279631452241919 and -1 in the compiler. this spec follows the compiler.