docs(fork-c/S0): setup contract — byte-baseline + commit-discipline, E6b disposition + two-corpus partition, A–E6 reuse/delete ledger
S0 of the ratified Fork C plan (zero-legacy name-resolution redesign, S0→S6).
Pure setup/documentation: NO production code change, NO behavior change.
Single-author output byte-identical to wt-stdlib-base by construction.
Deliverables under docs/fork-c/ (docs/, not current/, because current/ is
gitignored and the contract must be committed):
S0.1 — byte-baseline + commit-discipline: the committed examples/expected/*
snapshots are the single-author byte-identity reference; the zero-diff repro is
`zig build && zig build test && bash tests/run_examples.sh`. Resolver-target set
explicitly excluded + listed. Commit-classification rule: mirror | consumer-cutover | deletion.
S0.2 — E6b disposition + two-corpus partition: transitional E6b src NOT merged
(grep-clean: no resolveRegistrationSigTypeInSource / sig_registration_mode /
e6br_gate.test.zig on baseline). Harvested 0811–0829 trees + goldens (never the
src), empirically partitioned by running each through the base compiler vs the
E6b target:
- baseline-green (mirror-equivalence): 0795–0798 (merged) + 0823, 0828 — given
examples/expected/ markers, locked into the S0 baseline.
- resolver-target (known-wrong old behavior): 0811–0822, 0824–0827, 0829 + the
re-filed E6BR-5 nested-pattern regression — a listed xfail harness under
tests/resolver-target/ (manifest + TARGET goldens, NO active marker), flips
active+green at S3.9. 0811/0829 noted as old-selector-wrong on the E6b-unmerged
base; E6BR-5 subsumed by the whole-AST resolver, NOT an E6b attempt-6.
S0.3 — A–E6 reuse/delete ledger: every load-bearing A–E6 artifact mapped REUSED
(Fork C home) or DELETED/TRANSITIONAL (S3/S6 phase); E6c/d/e dropped, F/H/I/K
absorbed/superseded.
Gate over the baseline-green corpus: zig build + zig build test (LSP corpus sweep
574 files, no crash) + bash tests/run_examples.sh (540 passed, 0 failed) all exit 0.
This commit is contained in:
57
docs/fork-c/README.md
Normal file
57
docs/fork-c/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Fork C — setup contract (S0)
|
||||
|
||||
Zero-legacy name-resolution redesign (S0→S6), replacing E6c–K. This directory is the
|
||||
**committed S0 contract** the remaining 26 steps (S1→S6) execute against. It is pure
|
||||
setup/documentation — **S0 introduces no production code change and no behavior
|
||||
change**; single-author output is byte-identical to `wt-stdlib-base` by construction.
|
||||
|
||||
**Authority:** `runs/stdlib/design/fork-c-deepdive/reconciled.md` (§5 by-construction
|
||||
audit, §6 staged roadmap + deletion lists, §8 fold-in) and
|
||||
`runs/stdlib/design/fork-c-plan/planspec-r3.json` (the S0.1/S0.2/S0.3 intent +
|
||||
acceptance, Adi-confirmed — they govern).
|
||||
|
||||
## Doc-area decision
|
||||
|
||||
Deliverables live under **`docs/fork-c/`**, NOT `current/fork-c/`. Reason: `current/`
|
||||
is **gitignored** in this repo (`.gitignore` → `current/`), so a new `current/fork-c/`
|
||||
tree would not be committed; the S0 contract must be committed. `docs/` is tracked.
|
||||
|
||||
## Baseline
|
||||
|
||||
- **Base:** `wt-stdlib-base @ 1f755284d98c6e8ebba953045c06e35d8cbe6278` (A–E6a merged).
|
||||
- **E6b:** `flow/stdlib/E6b @ af737b0` — PAUSED, **unmerged**, all transitional, destined
|
||||
for S3/S6 deletion. Its semantics goldens are harvested; its src is never merged.
|
||||
- **This branch:** `flow/stdlib/S0` (branched from the base; S0 HEAD == base).
|
||||
|
||||
## Contents
|
||||
|
||||
| file | sub-step | what |
|
||||
|---|---|---|
|
||||
| `S0.1-byte-baseline-and-commit-discipline.md` | S0.1 | the byte-identity reference + the zero-diff reproduction command + resolver-target exclusion + the `mirror \| consumer-cutover \| deletion` commit-classification discipline |
|
||||
| `S0.2-e6b-disposition-and-two-corpus-partition.md` | S0.2 | E6b src not merged (grep-clean) + the harvested corpus partitioned baseline-green vs resolver-target + 0811/0829 placement + the E6BR-5 re-file + the mirror/flip statement |
|
||||
| `S0.3-reuse-delete-ledger.md` | S0.3 | every load-bearing A–E6 artifact mapped REUSED (Fork C home) or DELETED/TRANSITIONAL (S3/S6 phase); E6c/d/e dropped, F/H/I/K absorbed/superseded |
|
||||
| `../../tests/resolver-target/` | S0.2 | the listed resolver-target harness: `manifest.md` (18 cases), `expected/` TARGET goldens, the E6BR-5 reproducer under `cases/`, and `run_resolver_target.sh` (xfail runner — NOT part of the gate) |
|
||||
|
||||
## The two-corpus law (the one thing the next 26 steps must never conflate)
|
||||
|
||||
1. **BASELINE-GREEN / mirror-equivalence corpus** — tests where the old selector is
|
||||
already correct today (A–E6a merged + the 6 harvested baseline-green cases + FFI
|
||||
12xx–14xx + the LSP smoke). Stays **green and single-author byte-identical at every
|
||||
step S0→S6**, and is the S2 assert-only Debug mirror's **only** oracle.
|
||||
2. **RESOLVER-TARGET corpus** — harvested goldens that encode **known-wrong old
|
||||
behavior** (silent resolve / under-diagnose / wrong author / own-wins-fails) + the
|
||||
E6BR-5 regression. Held **inactive/xfail** (listed, never silently dropped) from S0
|
||||
through S3.8, then **flips to active + green at S3.9** against its TARGET output —
|
||||
never against the old selectors (a wrong oracle for it on the E6b-unmerged base).
|
||||
|
||||
## Gate (this branch)
|
||||
|
||||
```sh
|
||||
export PATH="$HOME/.zvm/bin:$PATH"
|
||||
zig build && zig build test && bash tests/run_examples.sh # exit 0 over the baseline-green corpus
|
||||
```
|
||||
|
||||
Since S0 changes no production code, single-author output is byte-identical by
|
||||
construction. The resolver-target xfail diagnostic
|
||||
(`bash tests/resolver-target/run_resolver_target.sh`) is separate and not part of the
|
||||
gate.
|
||||
102
docs/fork-c/S0.1-byte-baseline-and-commit-discipline.md
Normal file
102
docs/fork-c/S0.1-byte-baseline-and-commit-discipline.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# S0.1 — Byte-baseline (baseline-green only) + commit-classification discipline
|
||||
|
||||
Authority: `runs/stdlib/design/fork-c-plan/planspec-r3.json` (S0.1) +
|
||||
`runs/stdlib/design/fork-c-deepdive/reconciled.md` (§6 green-lock). Base:
|
||||
`wt-stdlib-base @ 1f755284d98c6e8ebba953045c06e35d8cbe6278` (A–E6a merged). This is
|
||||
documentation only — no production code change, no behavior change.
|
||||
|
||||
## 1. The byte-identity reference
|
||||
|
||||
The single-author byte-identity reference that **every later Fork C commit is checked
|
||||
against** is the committed `examples/expected/*` snapshot set on the baseline commit
|
||||
above. We do **not** copy every file into this doc; the snapshots ARE the reference,
|
||||
and the reproduction is a documented zero-diff command (§2). Single-author byte
|
||||
identity is held structurally by `nominal_id == 0 ≡ structural intern`
|
||||
(`src/ir/types.zig` `internNominal`), which S1–S2 keep additive and S3 preserves
|
||||
through ordinal-0 materialization.
|
||||
|
||||
The baseline-green corpus that the reference covers:
|
||||
|
||||
| segment | what | how it is exercised | count @ S0 |
|
||||
|---|---|---|---|
|
||||
| baseline-green examples | every `examples/<name>.sx` with an active `examples/expected/<name>.exit` marker (incl. the 6 harvested baseline-green cases `0795–0798`, `0823`, `0828`) | `bash tests/run_examples.sh` | **540** active markers |
|
||||
| FFI corpus | `examples/12xx–14xx` (96 entry trees; 95 with active markers; ~418 files incl. module/`.c`/`.h`/`.m` companions) | same runner (markers) | 95 active markers |
|
||||
| LSP completion/hover smoke | LSP unit tests under `zig build test` — `analyzeDocument` flat/namespaced import + the `lsp corpus sweep: every examples/*.sx analyzes without panicking` sweep + definition/references/inlayHint | `zig build test` | — |
|
||||
|
||||
> Count note: `reconciled.md §1` cites "116 files in `examples/12xx–14xx`". The live
|
||||
> tree has **96 entry `.sx` trees** (95 with active markers); the "116" is a stale
|
||||
> historical figure. What is load-bearing is the invariant — *all FFI 12xx–14xx
|
||||
> examples stay byte-stable* — which `run_examples.sh` enforces via their markers,
|
||||
> not the exact historical count.
|
||||
|
||||
## 2. The zero-diff reproduction method
|
||||
|
||||
`tests/run_examples.sh` runs `sx run <entry>` for every `<name>.sx` that has an
|
||||
`expected/<name>.exit` marker, normalizes stdout/stderr identically for expected and
|
||||
actual (address hashing → `0xADDR`; absolute `…/examples|issues/` paths → repo-relative),
|
||||
and diffs exit + stdout + stderr (+ optional `.ir`). A **zero-diff** run is the
|
||||
byte-identity check:
|
||||
|
||||
```sh
|
||||
export PATH="$HOME/.zvm/bin:$PATH"
|
||||
zig build # build the compiler under test
|
||||
zig build test # unit tests incl. the LSP completion/hover smoke + corpus sweep
|
||||
bash tests/run_examples.sh # must print "<N> passed, 0 failed, 0 skipped*, 0 timed out"
|
||||
```
|
||||
|
||||
Any later commit whose single-author output drifts shows up as a `FAIL` with a printed
|
||||
diff. `tests/run_examples.sh --update` regenerates the snapshots — it must **only** be
|
||||
run intentionally when a commit's classification (§3) authorizes an output change, and
|
||||
the diff reviewed. (* `skipped` counts `.exit` markers whose `.sx` is absent; it is 0
|
||||
on a clean tree.)
|
||||
|
||||
## 3. Resolver-target EXCLUSION (recorded, not silently absent)
|
||||
|
||||
The **resolver-target set** is **excluded from both** the byte-baseline AND the active
|
||||
`run_examples.sh` set, because it encodes known-wrong old behavior (silent resolve /
|
||||
exit 0 / under-diagnosis / wrong author) — a stale old-selector verdict must never
|
||||
enter the baseline as if it were correct. The exclusion is explicit and **listed**, not
|
||||
absent:
|
||||
|
||||
- the 17 harvested `08xx` resolver-target cases (`0811, 0812, 0813, 0814, 0815, 0816,
|
||||
0817, 0818, 0819, 0820, 0821, 0822, 0824, 0825, 0826, 0827, 0829`) + the re-filed
|
||||
**E6BR-5** regression;
|
||||
- enumerated in `tests/resolver-target/manifest.md`, with TARGET goldens in
|
||||
`tests/resolver-target/expected/`, held inactive (no `examples/expected/` marker) and
|
||||
asserted currently-failing by `tests/resolver-target/run_resolver_target.sh`;
|
||||
- full disposition in `S0.2-e6b-disposition-and-two-corpus-partition.md`.
|
||||
|
||||
They flip to active + green at **S3.9** and only then join the baseline.
|
||||
|
||||
## 4. Commit-classification discipline
|
||||
|
||||
Every future Fork C migration commit (S1→S6) is tagged with exactly one of three
|
||||
classes, stated in the commit subject/body so a reviewer knows what byte-effect to
|
||||
expect:
|
||||
|
||||
| tag | meaning | expected byte-effect on the baseline-green corpus |
|
||||
|---|---|---|
|
||||
| **`mirror`** | builds new facts / a new resolver path **in parallel**, while lowering still consumes the old path (S1–S2; the assert-only Debug mirror) | **zero** — single-author output byte-identical; provably zero byte-risk |
|
||||
| **`consumer-cutover`** | switches a consumer from the old path to the resolved facts (S3 materializer / calls / consts / protocol-registration / `#using`; S5 LSP) | **zero on baseline-green** — byte-identical by ordinal-0 materialization + payload-preserving facts; the only commits that may change resolver-target (the S3.9 flip is a cutover) |
|
||||
| **`deletion`** | removes a now-dead artifact (old name selectors, `*_by_source` mirrors, `type_bridge`, `findByName`, the grep gate, the S2 mirror) | **zero** — the deleted code had no live readers after its cutover; a surviving reader fails to compile |
|
||||
|
||||
Rules:
|
||||
- A commit is exactly one class; a cutover that also deletes its now-dead source is
|
||||
still a `consumer-cutover` if the delete is the same atomic cutover (e.g. S3.10
|
||||
removes the last old selector **and** the S2 mirror in the cutover commit).
|
||||
- `mirror` and `deletion` commits MUST be byte-zero on baseline-green; if a `mirror`
|
||||
commit changes a byte, it was not actually parallel — stop.
|
||||
- Only `consumer-cutover` may legitimately change output, and only the **resolver-target**
|
||||
corpus (never baseline-green) — that is the S3.9 flip.
|
||||
|
||||
## 5. Acceptance (S0.1) — self-check
|
||||
|
||||
- ✅ Byte-baseline of all baseline-green examples + FFI 12xx–14xx + LSP smoke captured
|
||||
and reproducible via the documented zero-diff command (§1–§2); the reference is the
|
||||
committed `examples/expected/*` at the baseline commit, re-checked by a zero-`FAIL`
|
||||
`run_examples.sh`.
|
||||
- ✅ Resolver-target set explicitly excluded from the byte-baseline AND the active
|
||||
`run_examples.sh` set, and recorded/listed (§3) — not silently absent.
|
||||
- ✅ The `mirror | consumer-cutover | deletion` classification rule is written (§4).
|
||||
- ✅ `zig build && zig build test && bash tests/run_examples.sh` green over the
|
||||
baseline-green corpus; no behavior change (S0 adds no production code).
|
||||
143
docs/fork-c/S0.2-e6b-disposition-and-two-corpus-partition.md
Normal file
143
docs/fork-c/S0.2-e6b-disposition-and-two-corpus-partition.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# S0.2 — E6b disposition, the two-corpus partition, and the E6BR-5 re-file
|
||||
|
||||
Authority: `runs/stdlib/design/fork-c-plan/planspec-r3.json` (S0.2) +
|
||||
`runs/stdlib/design/fork-c-deepdive/reconciled.md` (§3 one-resolver-two-timings, §6
|
||||
roadmap, §8 fold-in). Base: `wt-stdlib-base @ 1f75528` (A–E6a merged). E6b:
|
||||
`flow/stdlib/E6b @ af737b0` (PAUSED, unmerged). This is documentation only — no
|
||||
production code change.
|
||||
|
||||
## 1. E6b transitional src is NOT merged
|
||||
|
||||
The transitional E6b source on `flow/stdlib/E6b @ af737b0` is **not** brought onto the
|
||||
Fork C baseline. All of it is destined for **S3/S6 deletion** under Fork C:
|
||||
|
||||
- the **route-all engine** + the type-reference **choke-point**
|
||||
(`resolveRegistrationSigTypeInSource` / `sig_registration_mode`) — `lower.zig`,
|
||||
`protocols.zig`;
|
||||
- the executable **grep gate** `src/ir/e6br_gate.test.zig` (+ its `ir.zig` import);
|
||||
- the supporting `calls` / `lower` / `protocols` / `type_bridge` / `type_resolver` /
|
||||
`types` changes.
|
||||
|
||||
Grounded reasons it is transitional, not load-bearing: the whole-AST resolver walks
|
||||
**every** reference position, so it closes the protocol/param-impl signature surface
|
||||
the choke-point policed (reconciled §3/§4 T4); and the grep gate is unnecessary once
|
||||
the leaf it polices no longer exists (reconciled §5). A–E6a stays merged purely as
|
||||
**proof-of-semantics + per-decl nominal infra** (`internNominal` / `nominal_id==0`
|
||||
byte-identity rule / `RawDeclRef` facts → the `DeclId` seed) — see
|
||||
`S0.3-reuse-delete-ledger.md`.
|
||||
|
||||
**Grep-clean (verified on this branch, S0 == base):**
|
||||
|
||||
| check | base / S0 | E6b |
|
||||
|---|---|---|
|
||||
| `resolveRegistrationSigTypeInSource` in `src/` | **absent** | present (`lower.zig`) |
|
||||
| `sig_registration_mode` in `src/` | **absent** | present (`lower.zig`, `protocols.zig`) |
|
||||
| `src/ir/e6br_gate.test.zig` | **absent** | present |
|
||||
| `walkConcreteSigArgs` in `src/ir/lower.zig` | **absent** | present (the E6BR-5 site) |
|
||||
|
||||
Because S0 introduces **no production code change**, these remain absent on the Fork C
|
||||
baseline by construction.
|
||||
|
||||
## 2. Harvested resolver-acceptance corpus + the two-corpus partition
|
||||
|
||||
Harvested by copying the example **trees + their expected goldens ONLY** (never the
|
||||
transitional src):
|
||||
|
||||
- `examples/0811–0829` (19 trees) from `flow/stdlib/E6b @ af737b0` — error-set /
|
||||
protocol / param-impl / route-all same-name ambiguity & own-wins surfaces;
|
||||
- `examples/0795–0798` (already merged on the base) — the enum/union ambiguous/own-wins
|
||||
pair.
|
||||
|
||||
The corpus is **partitioned by empirically running each harvested case through the
|
||||
current base compiler and comparing its output to the E6b TARGET golden** (the exact
|
||||
method: build, then `sx run examples/<name>.sx`, normalize with the
|
||||
`run_examples.sh` rules, diff against the E6b golden):
|
||||
|
||||
### (a) BASELINE-GREEN — the mirror-equivalence corpus
|
||||
|
||||
Harvested cases whose **old selector is already CORRECT on `wt-stdlib-base` today**
|
||||
(actual == E6b target, byte-for-byte). They get standard
|
||||
`examples/expected/<name>.{exit,stdout,stderr}` markers, are run by
|
||||
`tests/run_examples.sh`, and are **locked into the S0 baseline**. This set is the S2
|
||||
assert-only Debug mirror's **ONLY** oracle.
|
||||
|
||||
| case | exit | proof it is already-correct on base |
|
||||
|---|---|---|
|
||||
| `0795-modules-same-name-enum-ambiguous` | 1 | already merged + green (E6a) |
|
||||
| `0796-modules-same-name-enum-own-wins` | 0 | already merged + green |
|
||||
| `0797-modules-same-name-union-ambiguous` | 1 | already merged + green |
|
||||
| `0798-modules-same-name-union-own-wins` | 0 | already merged + green |
|
||||
| `0823-route-all-own-wins-subform-wrappers` | 0 | base output == E6b target (`opt=1 arr=2 sl=3 dep=99`) |
|
||||
| `0828-protocols-param-impl-arg-wrapped-own-wins` | 0 | base output == E6b target (`tag=7 dep=9`) |
|
||||
|
||||
### (b) RESOLVER-TARGET — known-wrong old behavior
|
||||
|
||||
Harvested cases (and the re-filed E6BR-5) where the **old selector is wrong on the
|
||||
E6b-unmerged base** (silently resolves last-wins / under-diagnoses / picks the wrong
|
||||
author / fails own-author resolution). They are held in a **documented, listed,
|
||||
separate harness** at `tests/resolver-target/` with **NO active `examples/expected/`
|
||||
marker** (so `run_examples.sh` does not run them), but **WITH** their TARGET output
|
||||
recorded and an **enumerated manifest** (`tests/resolver-target/manifest.md`, 18
|
||||
cases). The sibling xfail runner `run_resolver_target.sh` runs each and asserts it
|
||||
currently FAILS — so the set is **never silently dropped**. It flips to active + green
|
||||
at **S3.9**.
|
||||
|
||||
The 17 harvested `08xx` resolver-target cases: `0811, 0812, 0813, 0814, 0815, 0816,
|
||||
0817, 0818, 0819, 0820, 0821, 0822, 0824, 0825, 0826, 0827, 0829`. Per-case class,
|
||||
base-now behavior, and target are in the manifest.
|
||||
|
||||
**0811 and 0829 placement (required one-liners):**
|
||||
|
||||
- **0811 → resolver-target.** *Old selector is wrong here on the E6b-unmerged base:*
|
||||
the five bare error-set forms (`size_of` / annotation / type-as-value / match-arm /
|
||||
`!IoErr` channel) each silently resolved one global last-wins `IoErr` via the
|
||||
`type_bridge.resolveInlineErrorSet` `findByName` short-circuit and the program exited
|
||||
0; the target is five loud "type 'IoErr' is ambiguous" diagnostics + exit 1.
|
||||
- **0829 → resolver-target.** *Old selector is wrong here on the E6b-unmerged base:*
|
||||
the concrete `*Box` prefix of the mixed pack-closure source fell to the no-author
|
||||
`type_bridge.resolveTemplateSignatureType` wrapper (global last-wins) and registered
|
||||
silently (exit 0); the target is a loud `Box` ambiguity + exit 1.
|
||||
|
||||
## 3. E6BR-5 re-filed into resolver-target
|
||||
|
||||
**E6BR-5** (the open nested-pattern ambiguity hole that paused E6b) is re-filed into
|
||||
the resolver-target set as a regression — **explicitly NOT an E6b attempt-6.**
|
||||
|
||||
- *What it is:* `walkConcreteSigArgs` (E6b `lower.zig:14686`) **skips** any direct arg
|
||||
that has an unbound part instead of **recursing** into it, so a nested concrete leaf
|
||||
(`*Box` inside `Closure(Closure(*Box, ..$inner) -> $IR, ..$args) -> $R`) is never
|
||||
ambiguity-checked; the impl compiled rc=0. On `wt-stdlib-base` (E6BR-4 unmerged) both
|
||||
the direct and nested concrete leaf silently resolve via the pre-E6BR-4 no-author
|
||||
wrapper path, so the reproducer exits 0 with no diagnostic.
|
||||
- *Subsumption (one line):* the whole-AST resolver walks **every** reference position —
|
||||
including nested parameterized-pattern leaves — so the nested `Box` is
|
||||
ambiguity-checked by construction and the "skip a nested arg" hole cannot exist.
|
||||
- *Form:* an authored reproducer lives at
|
||||
`tests/resolver-target/cases/e6br5-nested-pack-source-ambiguous.sx` (self-contained;
|
||||
verified to exit 0 silently on the base — the fail-before). Its target is a **spec**
|
||||
(exit 1 + `Box` ambiguous), with exact bytes produced by the resolver at S3.9
|
||||
(`expected/e6br5-…target.md`).
|
||||
|
||||
## 4. The mirror / flip statement (locked)
|
||||
|
||||
- **S2's mirror asserts `resolver == old-selector` over the BASELINE-GREEN corpus
|
||||
ONLY.** It never asserts new == old over the resolver-target corpus (the old selector
|
||||
is a wrong oracle there), and it never selects for lowering (assert-only, Debug-only,
|
||||
deleted in the S3.10 commit that completes the cutover).
|
||||
- **S3.9 flips the resolver-target corpus to active + green**, validated against each
|
||||
case's TARGET output (not against the old selectors). After the flip, the goldens
|
||||
move from `tests/resolver-target/expected/` to `examples/expected/`, the harness goes
|
||||
empty, and these cases stay green through S6.
|
||||
|
||||
## 5. Acceptance (S0.2) — self-check
|
||||
|
||||
- ✅ Written disposition: E6b transitional src not merged (§1, grep-clean table).
|
||||
- ✅ Harvested corpus recorded + partitioned: baseline-green (markers, locked,
|
||||
mirror-equivalence) vs resolver-target (enumerated manifest, separate listed harness,
|
||||
no active marker, TARGET goldens present, currently xfail, never dropped) — §2.
|
||||
- ✅ 0811 and 0829 each in resolver-target with the "old selector is wrong on the
|
||||
E6b-unmerged base" one-liner — §2.
|
||||
- ✅ E6BR-5 re-filed into resolver-target with the subsumption one-liner, NOT an E6b
|
||||
attempt-6 — §3.
|
||||
- ✅ Mirror/flip statement recorded — §4.
|
||||
- ✅ No transitional E6b src on the Fork C baseline (grep-clean) — §1.
|
||||
72
docs/fork-c/S0.3-reuse-delete-ledger.md
Normal file
72
docs/fork-c/S0.3-reuse-delete-ledger.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# S0.3 — A–E6 reuse / delete ledger
|
||||
|
||||
Authority: `runs/stdlib/design/fork-c-deepdive/reconciled.md` (§6 roadmap + per-phase
|
||||
deletion lists, §8 fold-in) + `runs/stdlib/design/migration.md` ("what happens to
|
||||
already-merged A–E6 work") + `planspec-r3.json` (S0.3). Base: `wt-stdlib-base @
|
||||
1f75528`. Symbol/file refs are grounded against the base tree. This is documentation
|
||||
only — no code change.
|
||||
|
||||
This ledger is the contract the later phases execute against: every load-bearing A–E6
|
||||
artifact is mapped to **REUSED** (with its Fork C home) or **DELETED/TRANSITIONAL**
|
||||
(with the S3/S6 phase that removes it). A–E6a stays merged; the transitional E6b src
|
||||
is never merged (see `S0.2-…`).
|
||||
|
||||
## A. REUSED — A–E6 work that becomes Fork C infrastructure
|
||||
|
||||
| A–E6 artifact (base location) | Fork C home | phase |
|
||||
|---|---|---|
|
||||
| **Phase A import facts** — `RawDeclRef` / `RawAuthor` / `ModuleDecls` / `NamespaceEdges` (`src/imports.zig`), built in `resolveImports` (`core.zig`) | **seed `DeclId` construction** — `DeclTable` keys every `RawDeclRef` into a stable `DeclId` (source + name + AST ptr + `DeclKind`); namespace members get ids | S1 |
|
||||
| **Phase B visibility** — `collectVisibleAuthors` / `collectNamespaceAuthors` (`src/ir/resolver.zig`), "the one graph iterator" over `flat_import_graph`/`namespace_edges` | **resolver internals** — become the resolver's visibility walk, with own-wins / single-flat-visible / ≥2-ambiguous **verdicts above them**, producing `ResolvedRef` | S2 |
|
||||
| **Phase C callable selection** | **`ResolvedRef.function` / `.type_function`** keyed by `DeclId` | S2 (select) → S3 (consume) |
|
||||
| **Phase D nominal identity** — `internNominal` / `updatePreservingKey` + the **`nominal_id == 0 ≡ structural intern` ordinal-0 byte-identity rule** (`src/ir/types.zig`, `lower.zig`) | **reused inside materialization** — `materializeType(ResolvedTypeNode)` interns in old scan order with ordinal 0 for non-colliding decls ⇒ byte-identical single-author output | S3 (+ green-lock every phase) |
|
||||
| **E-series selection rules** — own-wins / not-visible / ambiguity / direct-flat (the E1–E6a behaviors) | **resolver behavior + regression tests** (the baseline-green corpus is the mirror oracle) | S2 behavior; regressions locked S0 |
|
||||
| **CP rule** — body-author == layout-author | **keyed by `InstantiationId{template_decl, resolved_args}`** in the fact store | S4 |
|
||||
| **E6BR routed-signature cases** (the E6BR-1…4 behavioral cells) | **resolver-signature regressions** — the resolver walks every signature reference position; cases live in the resolver-target corpus, flip at S3.9 | S3.9 |
|
||||
| **FFI `foreign_class_map` consumers + 116-class corpus** | parallel `DeclId`s land at S1 (map still the consumer); foreign classes keyed by `DeclId` at S4; runtime names stay **payload strings on facts** | S1 → S4 |
|
||||
|
||||
## B. DELETED / TRANSITIONAL — removed in S3/S6
|
||||
|
||||
| artifact (base location) | why it goes | removal phase |
|
||||
|---|---|---|
|
||||
| **Stateless `type_bridge` leaves** — `resolveAstType` / `resolveTypeName` / `resolveInline{Enum,Union,Struct,ErrorSet}` / `resolveParameterizedType` / `resolveErrorType` (`src/ir/type_bridge.zig`); the whole `type_bridge.zig` (E6b renamed the entry leaf `resolveAstTypeNoAuthorSelection`) | no-author `findByName` leaves — replaced by `materializeType(ResolvedTypeNode)` which cannot take a name | selectors S3; file S6 |
|
||||
| **`TypeResolver.resolveName` / `resolveNamed`** (`src/ir/type_resolver.zig`) (E6b: `resolveNamedGlobalNoAuthorSelection`) | name→global resolution; superseded by the resolver | S3 (name-selection) / S6 (`resolveName*`) |
|
||||
| **Old name selectors** — `selectNominalLeaf`, `resolveNominalLeaf`, `moduleTypeAuthor`, `namedRefTid`, `flatTypeAuthorCount`, `nameAuthoredAsTypeAnywhere`, `selectModuleConst` (+ const-source pins), `selectGenericStructHead`, `headTypeGate`, `headFnLeak`, `flatFnAuthor*`, the name-selection in `resolveTypeCallWithBindings`/`resolveParameterizedWithBindings` (`src/ir/lower.zig`) | the duality leak — replaced by `ResolvedRef`/`ResolvedTypeNode` consumed in lowering | S3 |
|
||||
| **`*_by_source` mirrors + source pins** (`src/ir/program_index.zig`) + their writers + the `lower.zig` unified writers | dual-write mirrors of the global maps — superseded by the `DeclId`-keyed fact store | S4 |
|
||||
| **`type_decl_tids`** (`src/ir/types.zig`, `lower.zig`) | name→TypeId mirror — superseded by `DeclId` facts | S4/S6 |
|
||||
| **`ShadowTypeDecl` / shadow-slot reservation helpers** (`src/ir/lower.zig`) + lower-side nominal selectors | shadow reservation is a name-keyed pre-pass artifact — subsumed by `DeclId` pre-pass | S3/S4 |
|
||||
| **`TypeTable.findByName` / `findUniqueByName`** (`src/ir/types.zig`) | the global name table — deleted **last** (after the ~15 category-(b) stdlib lookups are re-homed to resolved-once `DeclId`s, per the §6 critical ordering constraint) | S6 |
|
||||
| **the type-reference choke-point + route-all engine** (`resolveRegistrationSigTypeInSource` / `sig_registration_mode`) | **transitional E6b src — never merged**; destined for deletion under Fork C | S3/S6 (already off-baseline) |
|
||||
| **the grep gate `e6br_gate.test.zig`** (+ its `ir.zig` import) | **transitional E6b src — never merged**; unnecessary once the leaf it polices is gone | S6 (already off-baseline) |
|
||||
| **the S2→S3 assert-only Debug mirror** | a test oracle, not a code path — must be deleted in the **same S3.10 commit** that removes the last old selector | S3.10 |
|
||||
|
||||
## C. Dropped / absorbed / superseded plan items
|
||||
|
||||
- **E6c / E6d / E6e** (protocol / foreign / type-fn per-kind identity): **DROPPED as
|
||||
steps.** They become resolver behavior + regression tests — a whole-AST resolver
|
||||
walks every reference position (annotation, `size_of`, dispatch head, `Self`,
|
||||
vtable), closing the protocol surface the per-kind patch structurally could not
|
||||
(reconciled §4 T4).
|
||||
- **F** (namespace resolver + 0104): **ABSORBED** into S2 as resolver internals
|
||||
(`namespace_edges` → `ResolvedRef.namespace` / member).
|
||||
- **H** (constructor heads): **ABSORBED** into S3 `materializeType` over resolved
|
||||
generic/protocol/type-fn heads.
|
||||
- **I** (protocol + foreign selection, loud-on-≥2): **ABSORBED** into S2 selection + S4
|
||||
`DeclId` facts.
|
||||
- **K** (delete dead readers): **SUPERSEDED** by the S4 `DeclId`-keyed fact store + the
|
||||
S6 deletions — "just delete the maps" is upgraded to "replace with `DeclId` facts."
|
||||
- **the per-kind taxonomy**: **REIFIED** as the `ResolvedRef` union itself; the
|
||||
exhaustive `switch` is the live taxonomy check (the grep gate is gone because the leaf
|
||||
it policed no longer exists).
|
||||
|
||||
## D. Acceptance (S0.3) — self-check
|
||||
|
||||
- ✅ Every load-bearing A–E6 artifact mapped to REUSED (with Fork C home) or
|
||||
DELETED/TRANSITIONAL (with the S3/S6 phase that removes it) — tables A & B; covers
|
||||
Phase A import facts → `DeclId` seed; B `collectVisibleAuthors` → resolver internals;
|
||||
C callable selection → `ResolvedRef.function/type_function`; D `internNominal` +
|
||||
ordinal-0 byte-identity → materialization; E-series rules → resolver behavior +
|
||||
regressions; CP → `InstantiationId`; the stateless `type_bridge`/`type_resolver`
|
||||
leaves, `*_by_source` mirrors + source pins, `type_decl_tids`/shadow helpers +
|
||||
lower-side selectors, the choke-point + route-all + grep gate → S3/S6 deletion.
|
||||
- ✅ States E6c/d/e dropped and F/H/I/K absorbed/superseded — table C.
|
||||
- ✅ No code change.
|
||||
44
examples/0811-modules-same-name-error-set-ambiguous.sx
Normal file
44
examples/0811-modules-same-name-error-set-ambiguous.sx
Normal file
@@ -0,0 +1,44 @@
|
||||
// E6b — per-decl nominal identity for ERROR-SET decls. A bare ERROR-SET reference
|
||||
// is non-transitive AND ambiguity-checked at every site, exactly like the struct
|
||||
// leaf (0755) and the enum/union leaves (0795/0797). `main` flat-imports two
|
||||
// modules that each author a same-name `IoErr` error set and authors none itself,
|
||||
// so EACH of the following bare ERROR-SET forms is a genuine collision the source
|
||||
// cannot disambiguate — and each must emit the LOUD "type 'IoErr' is ambiguous"
|
||||
// diagnostic and poison the result, NEVER silently pick a global `findByName`
|
||||
// last-wins author:
|
||||
//
|
||||
// - reflection / type-arg slot `size_of(IoErr)`
|
||||
// - typed error-value annotation `e : IoErr = error.Disk`
|
||||
// - type-as-value `t : Type = IoErr`
|
||||
// - type-category match arm `case IoErr:`
|
||||
// - `!Named` failable channel `-> !IoErr` (E6b audited use surface)
|
||||
//
|
||||
// Fail-before (pre-E6b): the stateless `type_bridge.resolveInlineErrorSet`
|
||||
// `findByName` short-circuit interned ONE global last-wins `IoErr`, so every bare
|
||||
// form silently resolved to it; the `!IoErr` channel resolved through
|
||||
// `resolveErrorType -> resolveTypeName` straight to that global slot. The program
|
||||
// exited 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0811-modules-same-name-error-set-ambiguous/a.sx";
|
||||
#import "0811-modules-same-name-error-set-ambiguous/b.sx";
|
||||
|
||||
describe :: ($T: Type) -> s32 {
|
||||
r := if T == {
|
||||
case IoErr: 1;
|
||||
else: 0;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
fail_io :: () -> !IoErr {
|
||||
raise error.Disk;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
sz := size_of(IoErr);
|
||||
e : IoErr = error.Disk;
|
||||
t : Type = IoErr;
|
||||
k := describe(s64);
|
||||
0
|
||||
}
|
||||
4
examples/0811-modules-same-name-error-set-ambiguous/a.sx
Normal file
4
examples/0811-modules-same-name-error-set-ambiguous/a.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// One of two flat-imported authors of a same-name `IoErr` error set. With both
|
||||
// modules flat-visible from a file that authors none itself, every bare reference
|
||||
// to the name is genuinely ambiguous.
|
||||
IoErr :: error { Disk, Net }
|
||||
4
examples/0811-modules-same-name-error-set-ambiguous/b.sx
Normal file
4
examples/0811-modules-same-name-error-set-ambiguous/b.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// The second flat-imported author of a same-name `IoErr` error set. A separate
|
||||
// nominal identity from a.sx's `IoErr`, so each bare reference is a real collision
|
||||
// the importing source cannot disambiguate.
|
||||
IoErr :: error { Disk, Net }
|
||||
28
examples/0812-modules-same-name-error-set-own-wins.sx
Normal file
28
examples/0812-modules-same-name-error-set-own-wins.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// E6b — own-wins-over-flat for ERROR-SET per-decl nominal identity. `main`
|
||||
// flat-imports `dep.sx` (which authors `IoErr { Net }`) AND authors its OWN
|
||||
// `IoErr { Disk }`. A bare `IoErr` reference in `main` resolves to `main`'s OWN
|
||||
// author, not the flat-imported one (the querying source's author wins outright —
|
||||
// no ambiguity), so `e : IoErr = error.Disk` binds `main`'s set (whose `Disk` tag
|
||||
// dep's `IoErr` lacks) while `dep_err()` returns dep's DISTINCT `IoErr`.
|
||||
//
|
||||
// Fail-before (pre-E6b): the stateless `type_bridge.resolveInlineErrorSet`
|
||||
// `findByName` short-circuit interned ONE global last-wins `IoErr`, so `main`'s
|
||||
// `IoErr` and dep's `IoErr` collapsed to a single nominal. Whichever author won
|
||||
// the global slot, the OTHER module's tag is not in it — so exactly one of
|
||||
// `error.Disk` (main) / `error.Net` (dep, in `dep_err`'s body) fails its
|
||||
// membership check and the program exits 1.
|
||||
//
|
||||
// Pass-after: distinct nominal TypeIds — `error.Disk` validates against main's
|
||||
// `{ Disk }` and `error.Net` against dep's `{ Net }`, both pass, exit 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0812-modules-same-name-error-set-own-wins/dep.sx";
|
||||
|
||||
IoErr :: error { Disk }
|
||||
|
||||
main :: () -> s32 {
|
||||
e : IoErr = error.Disk;
|
||||
d := dep_err();
|
||||
print("own={} dep={}\n", e, d);
|
||||
0
|
||||
}
|
||||
10
examples/0812-modules-same-name-error-set-own-wins/dep.sx
Normal file
10
examples/0812-modules-same-name-error-set-own-wins/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A flat-imported module authors its OWN `IoErr { Net }`. The importing file
|
||||
// (`main`) ALSO authors an `IoErr` — its own author must win there (own-wins),
|
||||
// while this module's `IoErr` stays a DISTINCT nominal type used by `dep_err`.
|
||||
// The tag sets are disjoint, so a cross-binding to the wrong `IoErr` is a hard
|
||||
// compile error (`Net` is not in main's `{ Disk }`, and vice-versa).
|
||||
IoErr :: error { Net }
|
||||
|
||||
dep_err :: () -> IoErr {
|
||||
return error.Net;
|
||||
}
|
||||
30
examples/0813-modules-same-name-error-set-lambda-own-wins.sx
Normal file
30
examples/0813-modules-same-name-error-set-lambda-own-wins.sx
Normal file
@@ -0,0 +1,30 @@
|
||||
// E6b — own-wins for an ERROR-SET name in a CLOSURE-LITERAL return annotation
|
||||
// (`closure(() -> !IoErr { … })`). `main` flat-imports `dep.sx` (which authors
|
||||
// `IoErr { Net }`) AND authors its OWN `IoErr { Disk }`. The closure literal's
|
||||
// `-> !IoErr` channel must resolve to `main`'s OWN author (own-wins), so the
|
||||
// `raise error.Disk` inside the closure body validates against main's `{ Disk }`;
|
||||
// meanwhile `dep_err` keeps dep's DISTINCT `IoErr { Net }`.
|
||||
//
|
||||
// Fail-before (attempt-1): `lowerLambda` resolved the explicit `lam.return_type`
|
||||
// through the STATELESS `type_bridge.resolveAstType`, so the closure's `!IoErr`
|
||||
// channel bound the global last-wins author (dep's `{ Net }`); `error.Disk` was
|
||||
// then "not in error set 'IoErr'" and the program exited 1.
|
||||
//
|
||||
// Pass-after: the lambda return annotation routes through the source-aware
|
||||
// `resolveTypeWithBindings` (same path as the regular function-return channel),
|
||||
// main's own `IoErr` wins, `error.Disk` validates, exit 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0813-modules-same-name-error-set-lambda-own-wins/dep.sx";
|
||||
|
||||
IoErr :: error { Disk }
|
||||
|
||||
main :: () -> s32 {
|
||||
fail_own := closure(() -> !IoErr { raise error.Disk; });
|
||||
fail_own() catch e {
|
||||
if e == error.Disk { print("own=Disk\n"); }
|
||||
};
|
||||
d := dep_err();
|
||||
print("dep={}\n", d);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// A flat-imported module authors its OWN `IoErr { Net }`. The importing file
|
||||
// (`main`) ALSO authors an `IoErr` AND uses it as a closure-literal return
|
||||
// annotation (`-> !IoErr`); main's author must win THERE too (own-wins),
|
||||
// while this module's `IoErr` stays a DISTINCT nominal used by `dep_err`.
|
||||
// The tag sets are disjoint, so binding the wrong `IoErr` is a hard compile
|
||||
// error (`Net` is not in main's `{ Disk }`, and vice-versa).
|
||||
IoErr :: error { Net }
|
||||
|
||||
dep_err :: () -> IoErr {
|
||||
return error.Net;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// E6b — ambiguity guard for an ERROR-SET name in a CLOSURE-LITERAL return
|
||||
// annotation (`closure(() -> !IoErr { … })`). `main` flat-imports two modules
|
||||
// that each author a same-name `IoErr` and authors none itself, so the closure
|
||||
// literal's `-> !IoErr` channel is a genuine collision the source cannot
|
||||
// disambiguate. It must emit the LOUD "type 'IoErr' is ambiguous" diagnostic and
|
||||
// poison the channel — NEVER silently pick a global `findByName` last-wins author.
|
||||
//
|
||||
// NOTE on fail-before/pass-after: unlike the OWN-WINS sibling (0813), this
|
||||
// AMBIGUITY case does NOT fail-before on attempt-1's binary. The pre-lowering
|
||||
// closure-shape pass (`convergeClosureShapeSets` → `recordClosureShape`) resolves
|
||||
// every closure literal's return annotation through the source-aware
|
||||
// `resolveType` (`resolveTypeWithBindings`), which attempt-1 already routed
|
||||
// error-set authors through — so the ambiguity is detected there and reported
|
||||
// regardless of `lowerLambda`. (For a `-> !Named` set that pass bails early after
|
||||
// resolving, so the OWN-WINS membership in 0813 still flowed through the stateless
|
||||
// `lowerLambda` path that attempt-2 fixes.) This example is the standing GUARD
|
||||
// that the lambda `-> !Named` ambiguity stays reported across both source-aware
|
||||
// sites; 0813 is the one that exercises attempt-2's `lowerLambda` channel fix.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0814-modules-same-name-error-set-lambda-ambiguous/a.sx";
|
||||
#import "0814-modules-same-name-error-set-lambda-ambiguous/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
fail_io := closure(() -> !IoErr { return; });
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// One of two flat-imported authors of a same-name `IoErr` error set. With both
|
||||
// modules flat-visible from a file that authors none itself, the `-> !IoErr`
|
||||
// closure-literal channel is genuinely ambiguous.
|
||||
IoErr :: error { Disk, Net }
|
||||
@@ -0,0 +1,4 @@
|
||||
// The second flat-imported author of a same-name `IoErr` error set. A separate
|
||||
// nominal identity from a.sx's `IoErr`, so the `-> !IoErr` closure-literal channel
|
||||
// is a real collision the importing source cannot disambiguate.
|
||||
IoErr :: error { Disk, Net }
|
||||
35
examples/0815-route-all-new-surfaces-ambiguous.sx
Normal file
35
examples/0815-route-all-new-surfaces-ambiguous.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// E6b-R — route-all: the NEW use surfaces a same-name type reaches now funnel
|
||||
// through the source-aware engine, so an ambiguous bare name poisons LOUDLY at
|
||||
// each of them instead of silently picking a global `findByName` last-wins author.
|
||||
// `main` flat-imports two same-name `Box` struct authors and authors none itself,
|
||||
// so every bare `Box` below is a genuine collision. `Box` is the kind-agnostic
|
||||
// canary (the bare-leaf forms — annotations / size_of / match / `!Named` — are
|
||||
// already locked per kind by 0755/0795/0797/0811); these cells exercise the
|
||||
// surfaces E6b-R added:
|
||||
//
|
||||
// - wrapper-alias element `BoxPtr :: *Box` (engine wrapper aliasing)
|
||||
// - union body-builder child `WrapU :: union { b: Box }` (C1, registerUnionDecl)
|
||||
// - enum body-builder child `WrapE :: enum { V: Box }` (C1, registerEnumDecl)
|
||||
// - tuple-literal element `size_of((Box, s32))` (O1/K5)
|
||||
// - inline-anonymous body child `x : union { b: Box }` (inline-anon engine arm)
|
||||
//
|
||||
// Fail-before (pre-E6b-R): each of these resolved `Box` through the stateless
|
||||
// `type_bridge.resolveAstType` global last-wins, silently binding one arbitrary
|
||||
// author with NO diagnostic. Pass-after: every surface emits the LOUD
|
||||
// "type 'Box' is ambiguous" and the build exits 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0815-route-all-new-surfaces-ambiguous/a.sx";
|
||||
#import "0815-route-all-new-surfaces-ambiguous/b.sx";
|
||||
|
||||
BoxPtr :: *Box;
|
||||
|
||||
WrapU :: union { b: Box; n: s32; }
|
||||
|
||||
WrapE :: enum { V: Box; }
|
||||
|
||||
main :: () -> s32 {
|
||||
sz := size_of((Box, s32));
|
||||
x : union { b: Box; n: s32 } = ---;
|
||||
0
|
||||
}
|
||||
6
examples/0815-route-all-new-surfaces-ambiguous/a.sx
Normal file
6
examples/0815-route-all-new-surfaces-ambiguous/a.sx
Normal file
@@ -0,0 +1,6 @@
|
||||
// One of two flat-imported authors of a same-name `Box` struct. With both modules
|
||||
// flat-visible from a file that authors none itself, every bare reference to the
|
||||
// name is genuinely ambiguous — at EVERY use surface, including the ones E6b-R
|
||||
// newly routed through the source-aware engine (wrapper aliases, tuple-literal
|
||||
// elements, enum/union body-builder child types, inline-anonymous types).
|
||||
Box :: struct { x: s32; }
|
||||
4
examples/0815-route-all-new-surfaces-ambiguous/b.sx
Normal file
4
examples/0815-route-all-new-surfaces-ambiguous/b.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// The second flat-imported author of a same-name `Box` struct. A separate nominal
|
||||
// identity from a.sx's `Box`, so each bare reference is a real collision the
|
||||
// importing source cannot disambiguate.
|
||||
Box :: struct { x: s32; }
|
||||
29
examples/0816-route-all-new-surfaces-own-wins.sx
Normal file
29
examples/0816-route-all-new-surfaces-own-wins.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// E6b-R — own-wins-over-flat at the NEW use surfaces route-all funnels through the
|
||||
// source-aware engine. `main` flat-imports `dep.sx` (which authors `Box { a }`) AND
|
||||
// authors its OWN `Box { m }`; the field sets are DISJOINT, so the observable is
|
||||
// field access: `.m` typechecks only against main's `Box`, `.a` only against dep's.
|
||||
//
|
||||
// `WrapU` is authored only by `main`, so its union field type `Box` resolves at
|
||||
// registration (`registerUnionDecl` → the source-aware body builder, E6b-R C1) to
|
||||
// MAIN's `Box`. Accessing `w.b.m` therefore typechecks, while `dep_box` uses dep's
|
||||
// DISTINCT `Box { a }`.
|
||||
//
|
||||
// Fail-before (pre-E6b-R): `registerUnionDecl` built the union body through the
|
||||
// stateless `type_bridge.buildUnionInfo`, whose `findByName` short-circuit bound
|
||||
// the global last-wins `Box`. If dep's `Box { a }` won the slot, `WrapU.b` was
|
||||
// dep's `Box` and `w.b.m` was a hard "field not found" — silently wrong nominal.
|
||||
// Pass-after: the union field is main's own `Box`, `w.b.m` resolves, exit 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0816-route-all-new-surfaces-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
|
||||
WrapU :: union { b: Box; n: s32; }
|
||||
|
||||
main :: () -> s32 {
|
||||
w : WrapU = ---;
|
||||
w.b.m = 5;
|
||||
print("own={} dep={}\n", w.b.m, dep_box());
|
||||
0
|
||||
}
|
||||
11
examples/0816-route-all-new-surfaces-own-wins/dep.sx
Normal file
11
examples/0816-route-all-new-surfaces-own-wins/dep.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// A flat-imported module authors its OWN `Box { a }`. The importing file (`main`)
|
||||
// ALSO authors a `Box { m }` — its own author must win there (own-wins), while this
|
||||
// module's `Box` stays a DISTINCT nominal type used by `dep_box`. The field sets
|
||||
// are disjoint, so a cross-binding to the wrong `Box` is a hard compile error.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 9;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// E6BR-1 — a plain qualified `ns.Type` type ANNOTATION resolves to the namespace
|
||||
// target module's OWN member type. `a.Box` parses as a single dotted `type_expr`,
|
||||
// so it reaches the bare nominal-leaf path; before E6BR-1 that path had no
|
||||
// dot-qualified split and fabricated an empty-struct stub literally named
|
||||
// "a.Box", so `x.a` failed with `field 'a' not found on type 'a.Box'` — even with
|
||||
// this SINGLE namespace import and NO same-name flat author.
|
||||
//
|
||||
// Pass-after: `resolveNominalLeaf` dot-splits the name and routes it through the
|
||||
// namespace-author selector (`qualifiedNamedTypeTid`), binding `a`'s real
|
||||
// `Box { a }`, so `x.a` resolves, exit 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0817-modules-qualified-annotation-single-import-resolve/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x : a.Box = ---;
|
||||
x.a = 4;
|
||||
print("x.a={}\n", x.a);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// The namespace target module authors its OWN `Box { a }`. A qualified `a.Box`
|
||||
// type annotation in the importer must bind THIS type, not a stub named "a.Box".
|
||||
Box :: struct { a: s32; }
|
||||
21
examples/0818-modules-qualified-annotation-own-wins.sx
Normal file
21
examples/0818-modules-qualified-annotation-own-wins.sx
Normal file
@@ -0,0 +1,21 @@
|
||||
// G1 (struct) — a qualified `a.Box` annotation selects the NAMESPACE target's OWN
|
||||
// `Box`, distinct from `main`'s same-name bare author. `main` authors its OWN
|
||||
// `Box { m }` AND namespace-imports `a` (which authors `Box { a }`). A bare `Box`
|
||||
// binds main's own author (own-wins); the qualified `a.Box` binds `a`'s DISTINCT
|
||||
// nominal even though main has a same-name bare author. The field sets are
|
||||
// disjoint, so a cross-binding (`q.m` / `own.a`) is a hard compile error — the
|
||||
// example compiling and running proves the two `Box`es are distinct nominals.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0818-modules-qualified-annotation-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
|
||||
main :: () -> s32 {
|
||||
own : Box = ---;
|
||||
own.m = 5;
|
||||
q : a.Box = ---;
|
||||
q.a = 9;
|
||||
print("own.m={} q.a={}\n", own.m, q.a);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Namespace target authoring its OWN `Box { a }`. The importer (`main`) also
|
||||
// authors a same-name bare `Box { m }`; the qualified `a.Box` must bind THIS one.
|
||||
Box :: struct { a: s32; }
|
||||
@@ -0,0 +1,19 @@
|
||||
// G1 (non-struct kind = error-set) — a qualified `a.IoErr` annotation selects the
|
||||
// namespace target's OWN error-set per-decl nominal, distinct from `main`'s
|
||||
// same-name `IoErr`. `main` authors `IoErr { Disk }` and namespace-imports `a`
|
||||
// (`IoErr { Net }`). The bare `IoErr` binds main's own (so `error.Disk` is valid);
|
||||
// the qualified `a.IoErr` binds `a`'s (so `error.Net` is valid). The tag sets are
|
||||
// disjoint, so a cross-binding (`a.IoErr = error.Disk`) is a hard membership error
|
||||
// — distinct error-set nominals reached through the qualified annotation surface.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0819-modules-qualified-annotation-error-set-own-wins/dep.sx";
|
||||
|
||||
IoErr :: error { Disk }
|
||||
|
||||
main :: () -> s32 {
|
||||
e : IoErr = error.Disk;
|
||||
q : a.IoErr = error.Net;
|
||||
print("own={} q={}\n", e, q);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Namespace target authoring its OWN error-set `IoErr { Net }`. The importer
|
||||
// (`main`) also authors a same-name `IoErr { Disk }`; the qualified `a.IoErr` must
|
||||
// bind THIS one (whose `Net` tag main's `IoErr` lacks).
|
||||
IoErr :: error { Net }
|
||||
37
examples/0820-protocols-same-name-method-own-wins.sx
Normal file
37
examples/0820-protocols-same-name-method-own-wins.sx
Normal file
@@ -0,0 +1,37 @@
|
||||
// E6BR-2 / G8 (own-wins) — a protocol method-signature CONCRETE named return type
|
||||
// resolves SOURCE-AWARE, pinned to the protocol's defining module. `main`
|
||||
// flat-imports `dep.sx` (`Box { a }`) AND authors its OWN `Box { m }`; the
|
||||
// protocol `Provider` returns `Box` and the impl builds main's `Box`. Dispatching
|
||||
// `p.get()` must type the result as MAIN's `Box`, so `b.m` resolves.
|
||||
//
|
||||
// Fail-before (pre-E6BR-2): the method signature went through the no-author
|
||||
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins), so the
|
||||
// return typed as dep's `Box { a }` and `b.m` was `field 'm' not found on type
|
||||
// 'Box'`. Pass-after: source-aware → main's `Box`, exit 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0820-protocols-same-name-method-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
|
||||
Provider :: protocol {
|
||||
get :: () -> Box;
|
||||
}
|
||||
|
||||
Holder :: struct { val: s32 = 7; }
|
||||
|
||||
impl Provider for Holder {
|
||||
get :: (self: *Holder) -> Box {
|
||||
b : Box = ---;
|
||||
b.m = self.val;
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
h : Holder = .{};
|
||||
p : Provider = xx @h;
|
||||
b := p.get();
|
||||
print("m={} dep={}\n", b.m, dep_box());
|
||||
0
|
||||
}
|
||||
10
examples/0820-protocols-same-name-method-own-wins/dep.sx
Normal file
10
examples/0820-protocols-same-name-method-own-wins/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's same-name `Box { m }`. The protocol method-signature return must NOT bind
|
||||
// this one — the disjoint field sets make a wrong binding a hard compile error.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 9;
|
||||
return b.a;
|
||||
}
|
||||
18
examples/0821-protocols-same-name-method-ambiguous.sx
Normal file
18
examples/0821-protocols-same-name-method-ambiguous.sx
Normal file
@@ -0,0 +1,18 @@
|
||||
// G8 (ambiguous half) — because a protocol method-signature concrete return type
|
||||
// is now resolved SOURCE-AWARE, a genuinely ambiguous same-name type poisons
|
||||
// LOUDLY at the protocol declaration instead of silently picking a global
|
||||
// `findByName` last-wins author. `main` flat-imports two `Box` authors and
|
||||
// declares none itself, so the `Provider.get` return `Box` is an unresolvable
|
||||
// collision and the build exits 1.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0821-protocols-same-name-method-ambiguous/a.sx";
|
||||
#import "0821-protocols-same-name-method-ambiguous/b.sx";
|
||||
|
||||
Provider :: protocol {
|
||||
get :: () -> Box;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
0
|
||||
}
|
||||
3
examples/0821-protocols-same-name-method-ambiguous/a.sx
Normal file
3
examples/0821-protocols-same-name-method-ambiguous/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// One of two flat-imported same-name `Box` authors — the bare `Box` in the
|
||||
// protocol return is a genuine collision the source cannot disambiguate.
|
||||
Box :: struct { a: s32; }
|
||||
3
examples/0821-protocols-same-name-method-ambiguous/b.sx
Normal file
3
examples/0821-protocols-same-name-method-ambiguous/b.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// The second flat-imported same-name `Box` author. Two distinct flat authors and
|
||||
// no own author → the protocol return `Box` is ambiguous.
|
||||
Box :: struct { b: s32; }
|
||||
39
examples/0822-route-all-own-wins-surfaces.sx
Normal file
39
examples/0822-route-all-own-wins-surfaces.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
// G3 — own-wins HALVES for the route-all surfaces 0815 covered only on the
|
||||
// ambiguous half. `main` authors its OWN `Box { m }` and flat-imports `dep.sx`
|
||||
// (`Box { a }`); each surface below must bind main's OWN `Box`, observed by a
|
||||
// `.m` access (disjoint field sets → a wrong-author binding is a hard compile
|
||||
// error). Complements 0816 (which covered only the union body-builder child):
|
||||
// - pointer wrapper-alias element `BoxPtr :: *Box`
|
||||
// - tuple element `(Box, s32)`
|
||||
// - enum body-builder child `WrapE :: enum { V: Box }`
|
||||
// - inline-anonymous union child `x : union { b: Box }`
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0822-route-all-own-wins-surfaces/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
BoxPtr :: *Box;
|
||||
WrapE :: enum { V: Box; }
|
||||
|
||||
main :: () -> s32 {
|
||||
own : Box = ---;
|
||||
own.m = 10;
|
||||
|
||||
// *Named wrapper-alias element own-wins
|
||||
bp : BoxPtr = @own;
|
||||
|
||||
// tuple element own-wins
|
||||
t : (Box, s32) = ---;
|
||||
t.0.m = 12;
|
||||
|
||||
// enum body-builder child own-wins (payload must be main's `Box`)
|
||||
we : WrapE = .V(own);
|
||||
ev := we.V.m;
|
||||
|
||||
// inline-anonymous union child own-wins
|
||||
x : union { b: Box; n: s32 } = ---;
|
||||
x.b.m = 13;
|
||||
|
||||
print("bp={} t={} ev={} x={} dep={}\n", bp.m, t.0.m, ev, x.b.m, dep_box());
|
||||
0
|
||||
}
|
||||
10
examples/0822-route-all-own-wins-surfaces/dep.sx
Normal file
10
examples/0822-route-all-own-wins-surfaces/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A flat-imported module authoring its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's `Box { m }`. The four route-all surfaces in `main` must each bind main's
|
||||
// own `Box`; the disjoint field sets make a wrong binding a hard compile error.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 99;
|
||||
return b.a;
|
||||
}
|
||||
32
examples/0823-route-all-own-wins-subform-wrappers.sx
Normal file
32
examples/0823-route-all-own-wins-subform-wrappers.sx
Normal file
@@ -0,0 +1,32 @@
|
||||
// G4 — own-wins halves for the §D row-2 SUB-FORM wrappers (`?Named`, `[]Named`,
|
||||
// `[N]Named`). Each wraps a named element type whose resolution recurses through
|
||||
// the SAME source-aware `resolveCompound` element path that `*Named` uses (0822),
|
||||
// so they share one routing mechanism rather than a per-form path. `main` authors
|
||||
// its OWN `Box { m }` and flat-imports `dep.sx` (`Box { a }`); each wrapped
|
||||
// element must bind main's own `Box`, observed by a `.m` access (disjoint field
|
||||
// sets → a wrong-author binding is a hard compile error).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0823-route-all-own-wins-subform-wrappers/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
|
||||
main :: () -> s32 {
|
||||
seed : Box = ---;
|
||||
seed.m = 1;
|
||||
|
||||
// ?Named element own-wins
|
||||
opt : ?Box = seed;
|
||||
o := opt!;
|
||||
|
||||
// [N]Named element own-wins
|
||||
arr : [2]Box = ---;
|
||||
arr[0].m = 2;
|
||||
arr[1].m = 3;
|
||||
|
||||
// []Named element own-wins
|
||||
sl : []Box = arr[0..2];
|
||||
|
||||
print("opt={} arr={} sl={} dep={}\n", o.m, arr[0].m, sl[1].m, dep_box());
|
||||
0
|
||||
}
|
||||
10
examples/0823-route-all-own-wins-subform-wrappers/dep.sx
Normal file
10
examples/0823-route-all-own-wins-subform-wrappers/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A flat-imported module authoring its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's `Box { m }`. The `?Box` / `[N]Box` / `[]Box` element wrappers in `main`
|
||||
// must each bind main's own `Box`; disjoint field sets make a wrong binding fail.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 99;
|
||||
return b.a;
|
||||
}
|
||||
78
examples/0824-protocols-same-name-method-wrapped-own-wins.sx
Normal file
78
examples/0824-protocols-same-name-method-wrapped-own-wins.sx
Normal file
@@ -0,0 +1,78 @@
|
||||
// E6BR-4 (own-wins) — a protocol method-signature names a same-name `Box` under
|
||||
// every WRAPPER / COMPOUND form, and each must resolve SOURCE-AWARE (pinned to the
|
||||
// protocol's defining module = main), selecting main's OWN `Box { m }` rather than
|
||||
// the global last-wins `Box { a }` from the flat-imported `dep.sx`.
|
||||
//
|
||||
// The reconciled choke-point (resolveRegistrationSigTypeInSource → the recursive
|
||||
// source-aware engine) recurses every structural shape and resolves the leaf
|
||||
// author own-wins. The E6BR-4 RED CELL is the WRAPPED RETURN `() -> *Box`: pre-fix
|
||||
// the wrapped sig fell to the no-author `type_bridge.resolveTemplateSignatureType`
|
||||
// (global last-wins) and typed the result as `dep`'s `Box { a }`, so `bp.m` was
|
||||
// `field 'm' not found on type 'Box'`. Discriminating returns: `*Box`, `?Box`,
|
||||
// `(Box,Box)`, `[2]Box` (each observed by a `.m` access). Routing-only params:
|
||||
// `*Box`, `?Box`, `[]Box`, `[2]Box`, `(Box,Box)`, nested `*?[]Box` — all resolved
|
||||
// through the same `resolveCompound` recursion at protocol-decl registration.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0824-protocols-same-name-method-wrapped-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
Holder :: struct { b: Box = ---; }
|
||||
|
||||
Provider :: protocol {
|
||||
// discriminating wrapped/compound RETURNS
|
||||
getp :: () -> *Box;
|
||||
geto :: () -> ?Box;
|
||||
gett :: () -> (Box, Box);
|
||||
geta :: () -> [2]Box;
|
||||
// routing-only wrapped/compound PARAMS
|
||||
sump :: (p: *Box) -> s32;
|
||||
sumo :: (o: ?Box) -> s32;
|
||||
sums :: (s: []Box) -> s32;
|
||||
suma :: (a: [2]Box) -> s32;
|
||||
sumt :: (t: (Box, Box)) -> s32;
|
||||
sumn :: (n: *?[]Box) -> s32;
|
||||
}
|
||||
|
||||
impl Provider for Holder {
|
||||
getp :: (self: *Holder) -> *Box { @self.b }
|
||||
geto :: (self: *Holder) -> ?Box { self.b }
|
||||
gett :: (self: *Holder) -> (Box, Box) { (self.b, self.b) }
|
||||
geta :: (self: *Holder) -> [2]Box { r : [2]Box = ---; r[0] = self.b; r[1] = self.b; r }
|
||||
sump :: (self: *Holder, p: *Box) -> s32 { p.m }
|
||||
sumo :: (self: *Holder, o: ?Box) -> s32 { o!.m }
|
||||
sums :: (self: *Holder, s: []Box) -> s32 { s[0].m }
|
||||
suma :: (self: *Holder, a: [2]Box) -> s32 { a[0].m }
|
||||
sumt :: (self: *Holder, t: (Box, Box)) -> s32 { t.0.m }
|
||||
sumn :: (self: *Holder, n: *?[]Box) -> s32 { if n == null { 0 } else { 6 } }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
h : Holder = ---;
|
||||
h.b.m = 7;
|
||||
p : Provider = xx @h;
|
||||
|
||||
// discriminating returns — `.m` only resolves if each binds main's `Box`
|
||||
bp := p.getp();
|
||||
bo := p.geto();
|
||||
bt := p.gett();
|
||||
ba := p.geta();
|
||||
|
||||
// routing-only params, constructed in main and passed through the protocol
|
||||
one : Box = ---; one.m = 1;
|
||||
arr : [2]Box = ---; arr[0].m = 2; arr[1].m = 3;
|
||||
sl : []Box = arr[0..2];
|
||||
osl : ?[]Box = sl;
|
||||
tup : (Box, Box) = (one, one);
|
||||
|
||||
sp := p.sump(@one);
|
||||
so := p.sumo(one);
|
||||
ss := p.sums(sl);
|
||||
sa := p.suma(arr);
|
||||
st := p.sumt(tup);
|
||||
sn := p.sumn(@osl);
|
||||
|
||||
print("p={} o={} t={} a={} | sp={} so={} ss={} sa={} st={} sn={} dep={}\n",
|
||||
bp.m, bo!.m, bt.0.m, ba[0].m, sp, so, ss, sa, st, sn, dep_box());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's same-name `Box { m }`. The protocol method signatures below name `Box`
|
||||
// under every wrapper/compound form; a wrong (last-wins) author binds this `Box`,
|
||||
// whose disjoint field set makes a `.m` access a hard compile error.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 9;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// E6BR-4 (ambiguous) — because a protocol method-signature WRAPPED return type is
|
||||
// now resolved SOURCE-AWARE (the reconciled choke-point recurses `*Box` and
|
||||
// resolves its leaf via `resolveNominalLeaf`), a genuinely ambiguous same-name
|
||||
// element poisons LOUDLY at the protocol declaration instead of silently picking a
|
||||
// global `findByName` last-wins author. `main` flat-imports two `Box` authors and
|
||||
// declares none itself, so the `Provider.getp` return `*Box` is an unresolvable
|
||||
// collision and the build exits 1.
|
||||
//
|
||||
// Fail-before (pre-E6BR-4): the wrapped `*Box` fell to the no-author
|
||||
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins, no
|
||||
// diagnostic), so the build did NOT report the collision.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0825-protocols-same-name-method-wrapped-ambiguous/a.sx";
|
||||
#import "0825-protocols-same-name-method-wrapped-ambiguous/b.sx";
|
||||
|
||||
Provider :: protocol {
|
||||
getp :: () -> *Box;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// One of two flat-imported `Box` authors. With no own author in main, the
|
||||
// protocol method-signature `() -> *Box` is a genuine collision.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
a_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 1;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`.
|
||||
Box :: struct { b: s32; }
|
||||
|
||||
b_box :: () -> s32 {
|
||||
x : Box = ---;
|
||||
x.b = 2;
|
||||
return x.b;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// E6BR-4 (param-impl SOURCE, own-wins) — a parameterised-impl SOURCE type under a
|
||||
// wrapper (`impl Into(Marker) for *Box`) registers SOURCE-AWARE, pinned to the
|
||||
// impl's defining module (main), and the `xx` lookup at the use site mangles to the
|
||||
// SAME author, so the conversion is found and runs. `main` authors its OWN
|
||||
// `Box { m }` and flat-imports `dep.sx` (`Box { a }`); the `*Box` source binds
|
||||
// main's `Box`, so `convert`'s `self.m` resolves and `xx @b` selects this impl.
|
||||
//
|
||||
// This locks the route-all registration path for the param-impl-source surface:
|
||||
// the source `*Box` flows through `resolveRegistrationSigTypeInSource` →
|
||||
// `resolveCompound` → `resolveNominalLeaf` (own-wins), never the no-author leaf.
|
||||
// The param_impl_map key is name-based (`Into\0Marker\0*Box`), so registration and
|
||||
// `tryUserConversion` agree by construction.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0826-protocols-param-impl-source-wrapped-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
Marker :: struct { v: s32; }
|
||||
|
||||
impl Into(Marker) for *Box {
|
||||
convert :: (self: *Box) -> Marker {
|
||||
.{ v = self.m }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.m = 7;
|
||||
mk : Marker = xx @b;
|
||||
print("v={} dep={}\n", mk.v, dep_box());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's same-name `Box { m }`. The param-impl SOURCE `*Box` must register against
|
||||
// main's `Box`, and the `xx` lookup at the use site must mangle to the SAME author,
|
||||
// so the conversion is found and `self.m` resolves.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 9;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// E6BR-4 (param-impl SOURCE, ambiguous) — because a parameterised-impl wrapped
|
||||
// SOURCE type is now registered SOURCE-AWARE (the source `*Box` flows through
|
||||
// `resolveRegistrationSigTypeInSource` → `resolveCompound` → `resolveNominalLeaf`),
|
||||
// a genuinely ambiguous same-name element poisons LOUDLY at registration instead of
|
||||
// silently selecting a global `findByName` last-wins author. `main` flat-imports two
|
||||
// `Box` authors and declares none itself, so `impl Into(Marker) for *Box` cannot
|
||||
// pick a `Box` and the build exits 1.
|
||||
//
|
||||
// Fail-before (pre-E6BR-4): the source `*Box` fell to the no-author
|
||||
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins, no
|
||||
// diagnostic), so the collision was registered silently.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0827-protocols-param-impl-source-wrapped-ambiguous/a.sx";
|
||||
#import "0827-protocols-param-impl-source-wrapped-ambiguous/b.sx";
|
||||
|
||||
Marker :: struct { v: s32; }
|
||||
|
||||
impl Into(Marker) for *Box {
|
||||
convert :: (self: *Box) -> Marker {
|
||||
.{ v = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// One of two flat-imported `Box` authors; with no own author the param-impl
|
||||
// SOURCE `*Box` is a genuine collision.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
a_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 1;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`.
|
||||
Box :: struct { b: s32; }
|
||||
|
||||
b_box :: () -> s32 {
|
||||
x : Box = ---;
|
||||
x.b = 2;
|
||||
return x.b;
|
||||
}
|
||||
31
examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx
Normal file
31
examples/0828-protocols-param-impl-arg-wrapped-own-wins.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// E6BR-4 (param-impl ARG, own-wins) — a parameterised-impl PROTOCOL TYPE-ARG under
|
||||
// a wrapper (`impl Tagged(*Box) for Holder`) is resolved through the same
|
||||
// source-aware registration helper (`resolveRegistrationSigTypeInSource` with the
|
||||
// `.param_impl_arg` purpose), pinned to the impl's defining module (main). The arg
|
||||
// `*Box` flows through `resolveCompound` → `resolveNominalLeaf` and selects main's
|
||||
// OWN `Box { m }` rather than the flat-imported `dep.sx` `Box { a }`, so the impl
|
||||
// registers and `Holder.tag` is callable. (The `param_impl_map` key is name-based,
|
||||
// so own-wins registration and any later lookup agree by construction; this cell
|
||||
// locks that the wrapped arg routes through the engine, not the no-author leaf.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0828-protocols-param-impl-arg-wrapped-own-wins/dep.sx";
|
||||
|
||||
Box :: struct { m: s32; }
|
||||
Holder :: struct { n: s32; }
|
||||
|
||||
Tagged :: protocol(T: Type) {
|
||||
tag :: () -> s32;
|
||||
}
|
||||
|
||||
impl Tagged(*Box) for Holder {
|
||||
tag :: (self: *Holder) -> s32 {
|
||||
self.n
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
h : Holder = .{ n = 7 };
|
||||
print("tag={} dep={}\n", h.tag(), dep_box());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// A flat-imported module authors its OWN `Box { a }`, a DISTINCT nominal from
|
||||
// main's same-name `Box { m }`. The parameterised-impl protocol type-ARG `*Box`
|
||||
// registers SOURCE-AWARE against main's `Box` (own-wins), never the global
|
||||
// last-wins author.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
dep_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 9;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// E6BR-4 (MIXED pack source — the trap cell) — a pack-closure param-impl source
|
||||
// `Closure(*Box, ..$args) -> $R` mixes a CONCRETE fixed prefix (`*Box`) with pack
|
||||
// metadata (`..$args`, `$R`). The reconciled choke-point decides template-vs-author
|
||||
// AT THE LEAF: `*Box` is resolved SOURCE-AWARE through `resolveCompound` while
|
||||
// `..$args`/`$R` stay pack metadata via `PackResolver` — NOT a top-level
|
||||
// "contains-unbound → no-author wrapper" router, which would send `*Box` down the
|
||||
// global last-wins path (the trap both engines flagged). With two flat `Box`
|
||||
// authors and none own, the concrete `*Box` prefix is a genuine collision and the
|
||||
// build exits 1 — proving the prefix IS routed source-aware (it caught the
|
||||
// ambiguity) while the pack parts did not spuriously error.
|
||||
//
|
||||
// Fail-before (pre-E6BR-4): the wrapped/pack source fell to the no-author
|
||||
// `type_bridge.resolveTemplateSignatureType` wrapper (global last-wins, no
|
||||
// diagnostic), so the `*Box` collision registered silently. Protects the pure-pack
|
||||
// `Closure(..$args) -> $R` bridge in `library/modules/std/objc_block.sx` (0504 /
|
||||
// 1302 / 1304 stay byte-identical), whose single author keeps the prefix-free path.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0829-packs-param-impl-mixed-pack-source-ambiguous/a.sx";
|
||||
#import "0829-packs-param-impl-mixed-pack-source-ambiguous/b.sx";
|
||||
|
||||
Block :: struct { tag: s32; }
|
||||
|
||||
Sink :: protocol(T: Type) {
|
||||
convert :: () -> T;
|
||||
}
|
||||
|
||||
impl Sink(Block) for Closure(*Box, ..$args) -> $R {
|
||||
convert :: (self: Closure(*Box, ..$args) -> $R) -> Block {
|
||||
.{ tag = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// One of two flat-imported `Box` authors; with no own author the FIXED PREFIX
|
||||
// `*Box` of the pack-closure source is a genuine collision.
|
||||
Box :: struct { a: s32; }
|
||||
|
||||
a_box :: () -> s32 {
|
||||
b : Box = ---;
|
||||
b.a = 1;
|
||||
return b.a;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`.
|
||||
Box :: struct { b: s32; }
|
||||
|
||||
b_box :: () -> s32 {
|
||||
x : Box = ---;
|
||||
x.b = 2;
|
||||
return x.b;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
opt=1 arr=2 sl=3 dep=99
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
tag=7 dep=9
|
||||
49
tests/resolver-target/README.md
Normal file
49
tests/resolver-target/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Resolver-target corpus (Fork C)
|
||||
|
||||
The second of the two Fork C resolver-acceptance corpora. See the full contract in
|
||||
`../../docs/fork-c/S0.2-e6b-disposition-and-two-corpus-partition.md`.
|
||||
|
||||
- **What it is:** harvested E6b semantics goldens (+ the re-filed E6BR-5 regression)
|
||||
that encode the **TARGET** behavior of the Fork C resolver for cases where the
|
||||
**old name-selector is known-wrong on `wt-stdlib-base`** (E6b unmerged) — it
|
||||
silently resolves a global last-wins author / under-diagnoses / picks the wrong
|
||||
author. On this base the old selector is **not a valid oracle**, so these are NOT
|
||||
baseline-green.
|
||||
- **Why separate:** the S2 assert-only mirror proves `resolver == old-selector` over
|
||||
the **baseline-green corpus ONLY**. Asserting that over these cases would force the
|
||||
new resolver to reproduce the old bug. So they live here, inactive, with NO active
|
||||
`examples/expected/` marker — `tests/run_examples.sh` does not run them.
|
||||
- **Never silently dropped:** every case is enumerated in `manifest.md`, its TARGET
|
||||
golden is recorded in `expected/`, and `run_resolver_target.sh` asserts each case
|
||||
currently FAILS to match its target (xfail). If any case unexpectedly MATCHES on
|
||||
the base, the runner flags it `LEAKED` — it is actually baseline-green and must be
|
||||
re-classified (moved to `examples/expected/`), never left here.
|
||||
- **Flip at S3.9:** the Fork C resolver makes these pass; each golden moves to
|
||||
`examples/expected/<name>.{exit,stdout,stderr}` and this harness goes empty.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
manifest.md enumerated list of all 18 cases (class / base-now / target / note)
|
||||
expected/<name>.exit TARGET exit (08xx: exact E6b bytes; e6br5: spec)
|
||||
expected/<name>.stdout TARGET stdout (08xx only — exact E6b bytes)
|
||||
expected/<name>.stderr TARGET stderr (08xx only — exact E6b bytes)
|
||||
expected/e6br5-*.target.md E6BR-5 spec target (exact bytes finalized at S3.9)
|
||||
cases/e6br5-*.sx + dir/ E6BR-5 authored reproducer (self-contained)
|
||||
run_resolver_target.sh xfail runner (NOT part of the baseline gate)
|
||||
```
|
||||
|
||||
The 08xx **source trees** live under `examples/` (harvested as authored, so their
|
||||
`#import` paths resolve exactly as their baseline-green siblings do) but carry **no**
|
||||
`examples/expected/` marker, so they are inert to `run_examples.sh`. Only their
|
||||
goldens live here. The E6BR-5 reproducer lives entirely under `cases/` (it is
|
||||
self-contained — `modules/std.sx` resolves via the `library/` search path).
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
zig build # build the compiler first
|
||||
bash tests/resolver-target/run_resolver_target.sh
|
||||
```
|
||||
|
||||
Expected today (S0): all 18 cases print `xfail`, `0 leaked`, exit 0.
|
||||
@@ -0,0 +1,48 @@
|
||||
// E6BR-5 regression — the open nested-pattern ambiguity hole that PAUSED E6b,
|
||||
// re-filed under Fork C as a RESOLVER-TARGET regression (NOT an E6b attempt-6).
|
||||
//
|
||||
// Shape: a param-impl SOURCE pattern whose concrete `*Box` leaf is NESTED inside
|
||||
// an inner parameterized form that itself carries unbound pack parts
|
||||
// (`Closure(*Box, ..$inner) -> $IR`). On flow/stdlib/E6b the bug lived in
|
||||
// `walkConcreteSigArgs` (lower.zig:14686): it SKIPS any direct arg that has an
|
||||
// unbound part instead of RECURSING into it, so the nested concrete `*Box` leaf
|
||||
// is never ambiguity-checked — `impl ... for Closure(Closure(*Box, ..)->.., ..)->..`
|
||||
// compiled rc=0 while the DIRECT `Closure(*Box, ..)` form (0829) errored. It was
|
||||
// the 3rd consecutive major on the parameterized-pattern surface
|
||||
// (bare E6BR-2 -> wrapped E6BR-4 -> nested E6BR-5).
|
||||
//
|
||||
// CURRENT BASE (wt-stdlib-base, E6b unmerged): both the direct AND the nested
|
||||
// concrete leaf silently resolve via the pre-E6BR-4 no-author wrapper path
|
||||
// (`type_bridge.resolveTemplateSignatureType`, global last-wins) — this program
|
||||
// exits 0 with NO diagnostic. Run it to see the fail-before.
|
||||
//
|
||||
// TARGET (Fork C, flips active+green at S3.9): the whole-AST resolver walks EVERY
|
||||
// reference position — including nested parameterized-pattern leaves — so the
|
||||
// nested `Box` is ambiguity-checked like any other reference and the build emits
|
||||
// the loud "type 'Box' is ambiguous" diagnostic and exits 1. The exact golden
|
||||
// bytes are produced by the resolver at S3.9 (no oracle produces them today);
|
||||
// see ../expected/e6br5-nested-pack-source-ambiguous.target.md for the spec.
|
||||
//
|
||||
// WHY SUBSUMED (one line): a resolver that resolves every reference position has
|
||||
// no notion of "skip a nested arg with an unbound sibling" — the nested `*Box`
|
||||
// leaf is resolved by construction, so the hole cannot exist.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "e6br5-nested-pack-source-ambiguous/a.sx";
|
||||
#import "e6br5-nested-pack-source-ambiguous/b.sx";
|
||||
|
||||
Block :: struct { tag: s32; }
|
||||
|
||||
Sink :: protocol(T: Type) {
|
||||
convert :: () -> T;
|
||||
}
|
||||
|
||||
impl Sink(Block) for Closure(Closure(*Box, ..$inner) -> $IR, ..$args) -> $R {
|
||||
convert :: (self: Closure(Closure(*Box, ..$inner) -> $IR, ..$args) -> $R) -> Block {
|
||||
.{ tag = 0 }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// One of two flat-imported `Box` authors. With no own author, the concrete
|
||||
// `*Box` leaf — even when NESTED inside a parameterized pattern — is a genuine
|
||||
// collision the importing source cannot disambiguate.
|
||||
Box :: struct { a: s32; }
|
||||
@@ -0,0 +1,3 @@
|
||||
// The second flat-imported `Box` author — a DISTINCT nominal from a.sx's `Box`,
|
||||
// so the nested `*Box` leaf is a real ambiguity the source cannot resolve.
|
||||
Box :: struct { b: s32; }
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,29 @@
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0811-modules-same-name-error-set-ambiguous.sx:34:18
|
||||
|
|
||||
34 | fail_io :: () -> !IoErr {
|
||||
| ^^^^^^
|
||||
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0811-modules-same-name-error-set-ambiguous.sx:39:19
|
||||
|
|
||||
39 | sz := size_of(IoErr);
|
||||
| ^^^^^
|
||||
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0811-modules-same-name-error-set-ambiguous.sx:40:9
|
||||
|
|
||||
40 | e : IoErr = error.Disk;
|
||||
| ^^^^^
|
||||
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0811-modules-same-name-error-set-ambiguous.sx:41:16
|
||||
|
|
||||
41 | t : Type = IoErr;
|
||||
| ^^^^^
|
||||
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0811-modules-same-name-error-set-ambiguous.sx:28:14
|
||||
|
|
||||
28 | case IoErr: 1;
|
||||
| ^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own=Disk dep=Net
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1,2 @@
|
||||
own=Disk
|
||||
dep=Net
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: type 'IoErr' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0814-modules-same-name-error-set-lambda-ambiguous.sx:25:30
|
||||
|
|
||||
25 | fail_io := closure(() -> !IoErr { return; });
|
||||
| ^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,29 @@
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0815-route-all-new-surfaces-ambiguous.sx:25:12
|
||||
|
|
||||
25 | BoxPtr :: *Box;
|
||||
| ^^^
|
||||
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0815-route-all-new-surfaces-ambiguous.sx:27:21
|
||||
|
|
||||
27 | WrapU :: union { b: Box; n: s32; }
|
||||
| ^^^
|
||||
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0815-route-all-new-surfaces-ambiguous.sx:29:20
|
||||
|
|
||||
29 | WrapE :: enum { V: Box; }
|
||||
| ^^^
|
||||
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0815-route-all-new-surfaces-ambiguous.sx:32:20
|
||||
|
|
||||
32 | sz := size_of((Box, s32));
|
||||
| ^^^
|
||||
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0815-route-all-new-surfaces-ambiguous.sx:33:20
|
||||
|
|
||||
33 | x : union { b: Box; n: s32 } = ---;
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
own=5 dep=9
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
x.a=4
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own.m=5 q.a=9
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own=Disk q=Net
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
m=7 dep=9
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0821-protocols-same-name-method-ambiguous.sx:13:18
|
||||
|
|
||||
13 | get :: () -> Box;
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bp=10 t=12 ev=10 x=13 dep=99
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
p=7 o=7 t=7 a=7 | sp=1 so=1 ss=2 sa=2 st=1 sn=6 dep=9
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0825-protocols-same-name-method-wrapped-ambiguous.sx:18:20
|
||||
|
|
||||
18 | getp :: () -> *Box;
|
||||
| ^^^
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user