summaryrefslogtreecommitdiff
path: root/chapter29/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29/README.md')
-rw-r--r--chapter29/README.md152
1 files changed, 108 insertions, 44 deletions
diff --git a/chapter29/README.md b/chapter29/README.md
index 5dae8de6..db6e90fd 100644
--- a/chapter29/README.md
+++ b/chapter29/README.md
@@ -4,9 +4,15 @@ English | [日本語](README.ja.md)
[Previous: Chapter 28b — Separate Compilation](../chapter28b/README.md)
-This chapter makes the compiler easier to read and change. It starts by breaking
-the parser into grammar-sized pieces. Further cleanup belongs here; earlier
-chapters retain their teaching snapshots.
+This chapter makes the compiler easier to read and change. It starts by
+breaking the parser into grammar-sized pieces. Constructors now check field
+initialization with ordinary memory types, and can pass an incomplete `self` to
+other calls in a type-safe way.
+
+There is lots more cleanup here; earlier chapters are left alone instead of
+backporting the cleanups, in part to show the normal progression of large
+project development.
+
## Following the grammar
@@ -17,60 +23,118 @@ those responsibilities without introducing an AST: parsing still builds
Sea-of-Nodes IR directly.
`Parser` owns the source cursor, active scope, type registry, and common token
-and IR helpers. Each component shares that state through `Parser P`.
+and IR helpers. Each component shares that state through `Parser P`.
`Statements`, `Expressions`, `Declarations`, `Types`, `Functions`, and `Structs`
-follow the corresponding grammar productions. `References` handles name and
+follow the corresponding grammar productions. `References` handles name and
field resolution; `Allocations`, `Conversions`, and `ControlFlow` hold shared
-graph-building operations. `Lexer` owns tokenization and source locations, and
+graph-building operations. `Lexer` owns tokenization and source locations, and
`Var` records lexical bindings.
The [grammar and code map](docs/29-grammar.md) connects productions to methods.
Start with `Statements.parseStatement` or `Expressions.parseAssignment` and
-follow the calls alongside it. Deferred parsing remains explicit: `for` updates
+follow the calls alongside it. Deferred parsing remains explicit: `for` updates
are parsed after their bodies, and declared constructors after field discovery.
The [source layout](src/main/java/com/seaofnodes/simple/README.md) describes the
-other packages. `IterPeeps` now lives in `codegen/` beside the compilation passes.
+other packages. `IterPeeps` lives in `codegen/` beside the compilation passes.
+
## Checking constructor completion
-Instance fields use ordinary Loads and Stores during declaration initialization
-and constructor execution. One hidden private-memory value flows through scope
-merges and `addReturn`; field values are no longer copied into memory at exit.
-
-Fresh fields start at `TypeScalar.BOT`, which preserves a missing live arm at a
-merge. Forward memory types supply the initialization proof at Loads and merged
-Returns. Checks run at TypeCheck using the existing optimization passes; there
-is no extra construction solve, initialization walk, or field-state map.
-The earlier experiments are recorded in [the late-check notes](docs/deferred-construction.md).
-The [outside-world escape flow](docs/escape-flow.md) now restores the four
-recursive-class IR expectations and gives hidden class receivers their owning
-singleton as the unknown-caller default. The frozen public-interface inventory
-is removed; late imported identities remain a precision follow-up.
-Class singleton initialization
-retains its existing store-at-exit path.
-
-`ConstructorCompletionTest` covers partial assignments, early returns, loops,
-declaration defaults and immediate field access, and the formerly disabled
-required-number/nullable-default regression. The large comment on
+How constructors are handled has gone back and forth over the chapters; this
+testament to how implementing a language is a journey of exploration more than
+a rote rewriting of the grammer.
+
+Instance fields now use ordinary Loads and Stores throughout initialization.
+Instead of keeping field values in locals until construction ends, the
+constructor works on a real `self` object with private memory. This lets it
+lend `self` to another constructor or function while construction is underway.
+
+Fresh fields start at `TypeScalar.BOT`, which is the uninitialized indicator.
+An initialized integer with an unknown value has `TypeInteger.BOT`, distinct
+from the uninitialized `TypeScalar.BOT`, and similarly for other `BOT` types.
+Stores replace `TypeScalar.BOT` with that field's type in private memory.
+`TypeScalar.BOT` value survives merging at Phis and other control flow, and we
+use it to make sure a field is initialized on all paths.
+
+The completion form of `ConstructNode` checks that every required field is
+initialized on every constructor exit. Once the types prove completion and the
+node adds no pointer restriction, it folds away during optimization.
+Otherwise, `TypeCheck` asks it for an error. This uses the existing type flow
+and error walk; there is no extra construction pass.
+
+Loads check for loading `TypeScalar.BOT` (i.e., loading from uninitialized
+memory), and report an error.
+
+Constructors keep their private-memory and precise aliasing, only losing it
+after the constructor completes.
+
+
+## Incomplete Types
+
+A constructor can pass its partially constructed `self` to other constructors
+and functions. This permits doubly linked lists and parent/child tree nodes to
+cross-link during construction without reading uninitialized fields.
+
+To pass an incomplete type the receiving parameter must declare the partial
+qualifier `^`, for example a`val foo = { Point ^p -> ... }`. The function may
+forward `p`, retain it in another object's private construction memory, and
+read fields proven initialized by every caller. It cannot write through `p`,
+even to non-final fields, or store it into an ordinary object or global. The
+original constructor retains its write permission, including writes to final
+fields until construction finishes.
+
+
+```simple
+struct _Point {
+ int~ x; // final int x
+ int~ y; // final int y
+ new _Point = { int n -> x=n; y=_read(self); };
+};
+val _read = { _Point ^p -> return p.x; };
+return new _Point(arg).y;
+```
+
+Here `x` is readable during `_read`, while `y` is still uninitialized. The
+private names keep the callers within this compilation: unknown callers of a
+public `^` interface promise no initialized fields. If any live caller passes
+a partial without `x` set, the merged parameter loses that guarantee and the
+Load reports an error.
+
+A `new` object is *incomplete* when it starts life and if any of its actual
+arguments are incomplete. With only complete arguments, including null, the
+object is complete after construction. Otherwise it completes with the
+original object; the whole collection completes at once. This allows
+construction to assemble a cycle: after the root construction completes, the
+reachable objects can be used normally. Each constructor must still initialize
+its own fields before returning.
+
+`TypeMemPtr` carries the *partial* qualifier through ordinary type flow. An
+`InitViewNode` combines `self` with its current private memory to describe which
+fields are readable at that point: declared types for initialized fields,
+`TypeScalar.BOT` for the rest. Actual values still come from memory. Calls
+borrowing `self` carry its current Stores on their normal memory edge.
+
+Redundant views fold during optimization. Other views preserve the partial
+restriction or mask unreadable fields until `TypeCheck` checks their consumers
+and removes them. A surviving view is not itself an error: `_read` above needs
+only `x`, not a fully initialized object.
+
+These guarantees remain conservative. Reading a set object-reference field
+does not prove that its referent's fields are initialized. Arrays are already
+initialized and cannot contain partial references. Borrowed reads use aliasable
+memory, so allocating another object of the same class is allowed. Reading an
+initialized final field during construction does not freeze its value: the
+owning constructor may still change it afterward.
+
+Separate compilation of `^` interfaces remains unsupported. Inferred field
+requirements and nested initialization guarantees remain on the roadmap.
+
+The comment in
[`Construction`](src/main/java/com/seaofnodes/simple/parser/Construction.java)
-explains the type proof and the separate yes/no restriction on references.
-
-The [construction-group experiment](docs/construction-groups.md) adds `C ^arg`
-constructor parameters. A new result is incomplete iff an actual argument is
-incomplete. A `TypeMemPtr` qualifier propagates this through normal type flow;
-a temporary `ConstructNode` types each new result from that call's arguments.
-Only self may be read while incomplete; ordinary calls require completed
-objects. Field sharpening and inferred requirements are deferred,
-with their litmus tests preserved in `ConstructionRequirementsTest`. Separate
-compilation of constructors using `^` remains unsupported.
-
-## Further cleanup
-
-Source privacy is deferred. Leading underscores retain the existing export
-filtering behavior; this chapter does not yet enforce access restrictions on
-source uses. The [roadmap](../docs/roadmap.md) retains that proposal and the other
-language candidates.
+and the [construction notes](docs/construction-groups.md) explain the type flow,
+memory edges, and remaining restrictions.
+
## Build and test