Merge branch 'wt-stdlib-base'
This commit is contained in:
60
docs/fork-c/README.md
Normal file
60
docs/fork-c/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 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). **Production/compiler
|
||||
behavior is base-equivalent** — zero `src/` changes, single-author output
|
||||
byte-identical to base by construction — but S0 HEAD is a distinct commit carrying
|
||||
the docs/examples/tests diff (it does **not** equal 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 — `src/` is base-equivalent, zero src changes):**
|
||||
|
||||
| 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 + FFI corpus (96 entry trees / 95 active markers)** | 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.
|
||||
@@ -1,4 +1,5 @@
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
|
||||
#import "modules/math/math.sx";
|
||||
#import "modules/compiler.sx";
|
||||
#import "modules/test.sx";
|
||||
|
||||
20
examples/0170-types-anon-struct-field-distinct.sx
Normal file
20
examples/0170-types-anon-struct-field-distinct.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Two top-level structs each carry an inline anonymous-struct field named
|
||||
// `inner`, but of DIFFERENT shapes. Each `inner` must resolve to its OWN
|
||||
// anonymous type (`A.inner` has `x`; `B.inner` has `y, z`) — they must not
|
||||
// cross-bind on the shared field spelling.
|
||||
//
|
||||
// Regression (folded from the Phase-D `replaceKeyedInfo` re-key, which made the
|
||||
// per-parent anon rename key-safe): on master 7ffc0c1 the two anon types
|
||||
// cross-bound and `b.inner.y` failed with "field 'y' not found on type
|
||||
// 'B.inner'". Pins fail-before / pass-after.
|
||||
#import "modules/std.sx";
|
||||
|
||||
A :: struct { inner: struct { x: s64; }; }
|
||||
B :: struct { inner: struct { y: s64; z: s64; }; }
|
||||
|
||||
main :: () -> s32 {
|
||||
a := A.{ inner = .{ x = 1 } };
|
||||
b := B.{ inner = .{ y = 2, z = 3 } };
|
||||
print("{} {} {}\n", a.inner.x, b.inner.y, b.inner.z);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// A genuinely-undeclared type name used as a field type inside a MAIN-file
|
||||
// GENERIC struct must emit a clean "unknown type" diagnostic, not silently
|
||||
// compile.
|
||||
//
|
||||
// The `UnknownTypeChecker` used to SKIP generic structs entirely ("their field
|
||||
// types reference the struct's own `$T`, resolved at instantiation"). That skip
|
||||
// was too broad: a field type like `bad: MissingType` — which is NOT a type
|
||||
// param and names no declared type — fell through the type leaf's empty-struct
|
||||
// stub and the struct silently compiled, mis-sizing every downstream load.
|
||||
//
|
||||
// The checker now walks generic-struct fields with the struct's own type params
|
||||
// (`$T`) in scope: `good: T` resolves (it IS a param) while `bad: MissingType`
|
||||
// is reported. A value-param position (a `Vector` lane count, a `$N: u32` arg)
|
||||
// is still skipped, so a valid generic struct keeps compiling unchanged.
|
||||
//
|
||||
// Expected: `error: unknown type 'MissingType'` pointing at the field; exit 1.
|
||||
// Regression (stdlib E3).
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct($T: Type) {
|
||||
good: T;
|
||||
bad: MissingType;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
b : Box(s64) = .{ good = 7, bad = 0 };
|
||||
print("{}\n", b.good);
|
||||
return 0;
|
||||
}
|
||||
29
examples/0172-types-value-param-as-field-type.sx
Normal file
29
examples/0172-types-value-param-as-field-type.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// A generic struct's VALUE param (`$N: u32`) is a compile-time integer, not a
|
||||
// type. Naming it in a TYPE position — here a field type `x: N` — must emit a
|
||||
// clean diagnostic, NOT silently compile.
|
||||
//
|
||||
// The parser marks any reference to a struct's own type param `is_generic`
|
||||
// (so `x: T` for a real `$T: Type` resolves without a `$`). That marking is
|
||||
// the same for a value param, so the unknown-type walk used to skip `x: N`
|
||||
// entirely; the field's type leaf then resolved to the `.unresolved` sentinel
|
||||
// and PANICKED at LLVM emission ("unresolved type reached LLVM emission").
|
||||
//
|
||||
// The checker now distinguishes TYPE params (`$T: Type`, `$T: SomeProtocol`,
|
||||
// the `..$Ts` pack) from VALUE params (`$N: u32`) using the binder's own rule:
|
||||
// a value param named in a type position gets the tailored hint. A value param
|
||||
// in a VALUE position (a `[N]u8` array dimension, a `Vector` lane count) still
|
||||
// works (see 0147 / 0201).
|
||||
//
|
||||
// Expected: `error: 'N' is a value parameter, not a type`; exit 1.
|
||||
// Regression (stdlib E3).
|
||||
#import "modules/std.sx";
|
||||
|
||||
Bad :: struct($N: u32) {
|
||||
x: N;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
b : Bad(3) = .{ x = 1 };
|
||||
print("{}\n", b.x);
|
||||
return 0;
|
||||
}
|
||||
31
examples/0210-generics-resolver-legacy-paths.sx
Normal file
31
examples/0210-generics-resolver-legacy-paths.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Resolver E1 lock: the bare-type-leaf cutover to the source-aware
|
||||
// `selectNominalLeaf` must NOT touch the NON-leaf type heads. A generic-struct
|
||||
// instantiation (`Box(s32)`), a `Vector(N, T)` builtin, and a type-returning
|
||||
// function (`Make(3, s64)`) are all resolved by `resolveTypeWithBindings`
|
||||
// ABOVE the bare-name leaf switch (`resolveParameterizedWithBindings` /
|
||||
// `resolveTypeCallWithBindings` / the `Vector` builtin path), so they stay on
|
||||
// the legacy resolution and never reach `selectNominalLeaf`. Parameterized
|
||||
// protocols share the same `resolveParameterizedWithBindings` pre-leaf path
|
||||
// (covered by 0204/0206). This example pins that all three still resolve
|
||||
// identically after the cutover.
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct($T: Type) {
|
||||
value: T;
|
||||
}
|
||||
|
||||
Make :: ($K: u32, $T: Type) -> Type { return [K]T; }
|
||||
|
||||
N :: 3;
|
||||
|
||||
main :: () {
|
||||
b : Box(s32) = .{ value = 42 };
|
||||
print("box: {}\n", b.value);
|
||||
|
||||
v : Vector(4, f32) = .[1, 2, 3, 4];
|
||||
print("vec: {} {}\n", v.x, v.w);
|
||||
|
||||
a : Make(N, s64) = ---;
|
||||
a[0] = 10; a[2] = 30;
|
||||
print("typefn: len={} a0={} a2={}\n", a.len, a[0], a[2]);
|
||||
}
|
||||
@@ -13,6 +13,9 @@
|
||||
// both impl modules.
|
||||
|
||||
#import "modules/std.sx";
|
||||
// `Wrap` is declared in the shared types module; bare-import visibility is
|
||||
// non-transitive, so naming it here means importing it here (not via impl-a/b).
|
||||
#import "./0411-protocols-impl-duplicate-types.sx";
|
||||
#import "./0411-protocols-impl-duplicate-impl-a.sx";
|
||||
#import "./0411-protocols-impl-duplicate-impl-b.sx";
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Regression (stdlib E4): an imported pack function whose fixed-prefix param
|
||||
// type is visible only in its defining module must resolve during pack
|
||||
// monomorphization. `lib.sx` imports `dep.sx` (which defines `Needs`) and
|
||||
// exposes `make() -> Needs` plus `use_pack(n: Needs, ..$args)`. `main` imports
|
||||
// ONLY `lib.sx`, so `Needs` is two flat hops away and not bare-visible here —
|
||||
// main never names it.
|
||||
//
|
||||
// Before the fix, `monomorphizePackFn` restored the caller's source before
|
||||
// re-binding the fixed-prefix params, so `n: Needs` was resolved in main's
|
||||
// context and rejected with "type 'Needs' is not visible" — even though the
|
||||
// control plain fn `use_plain(n: Needs)` (typed via the source-pinned call-arg
|
||||
// path) ran fine. The fixed-prefix param is now resolved under the pack fn's own
|
||||
// source (`fd.body.source_file`), matching the rest of the pack signature/body.
|
||||
#import "modules/std.sx";
|
||||
#import "0544-packs-imported-pack-fn-fixed-param-source-pin/lib.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := make();
|
||||
print("plain {}\n", use_plain(x));
|
||||
print("pack {}\n", use_pack(x, 1, 2));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// `Needs` lives two flat-import hops away from `main` (main -> lib -> dep), so
|
||||
// it is NOT bare-visible at the call site under non-transitive visibility.
|
||||
Needs :: struct { v: s64; }
|
||||
@@ -0,0 +1,16 @@
|
||||
// `lib.sx` imports `dep.sx`, so `Needs` is bare-visible HERE. A module that
|
||||
// imports only `lib.sx` cannot see `Needs` (non-transitive). The pack fn's
|
||||
// fixed-prefix param `n: Needs` must therefore resolve in this module's
|
||||
// context, not the caller's.
|
||||
#import "modules/std.sx";
|
||||
#import "dep.sx";
|
||||
|
||||
make :: () -> Needs => Needs.{ v = 7 };
|
||||
|
||||
// Control: a plain (non-pack) fn with the same fixed param already resolves in
|
||||
// its defining module — the cross-module call-arg typing path is source-pinned.
|
||||
use_plain :: (n: Needs) -> s64 => n.v;
|
||||
|
||||
// Pack fn: the fixed-prefix param `n: Needs` is bound during pack
|
||||
// monomorphization. Its type must resolve under this module's source.
|
||||
use_pack :: (n: Needs, ..$args) -> s64 => n.v + args[0];
|
||||
@@ -9,6 +9,9 @@
|
||||
// registration tripped: `duplicate impl 'Into' for source 's64'`. Now the flat
|
||||
// decl list also dedups by node identity, so this builds and prints 7.
|
||||
#import "modules/std.sx";
|
||||
// `Wrapped` lives in the shared `common.sx`; bare-import visibility is
|
||||
// non-transitive, so naming it here means importing it here (not via mid_a/b).
|
||||
#import "0709-modules-issue-0056/common.sx";
|
||||
#import "0709-modules-issue-0056/mid_a.sx";
|
||||
#import "0709-modules-issue-0056/mid_b.sx";
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// and freed in one `deinit`; the writer path allocates nothing.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
|
||||
#import "modules/std/json.sx";
|
||||
#import "modules/fs.sx";
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
// `JsonParseError` variant on the error channel, never a bogus value.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
|
||||
#import "modules/std/json.sx";
|
||||
|
||||
// Canonical document: no insignificant whitespace, escapes in the writer's
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
// and decoded strings go through `alloc`, and the writer allocates nothing.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
|
||||
#import "modules/std/json.sx";
|
||||
|
||||
// The writer's EXACT output for the PART A document (insertion order,
|
||||
|
||||
@@ -12,6 +12,15 @@
|
||||
// independent identities.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
|
||||
// `cli` is imported BOTH flat (so its types — `FlagSpec` / `Command` / `Diag` —
|
||||
// are bare-visible) AND namespaced (so the same-name `cli.parse` stays a
|
||||
// distinct qualified identity from `json.parse`). Post-E1 a bare reference to a
|
||||
// namespaced-ONLY type is a "not visible" error, so the flat import is what makes
|
||||
// the bare type names below resolve; `json` stays namespaced-only (its `Value`
|
||||
// reaches `main` only as `json.parse`'s return type, resolved in `json.sx`'s own
|
||||
// context).
|
||||
#import "modules/std/cli.sx";
|
||||
cli :: #import "modules/std/cli.sx";
|
||||
json :: #import "modules/std/json.sx";
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Bare module-const visibility under a NAMESPACED-only import — the const
|
||||
// sibling of 0736 (folded req #1 of the source-aware resolver, Phase E1).
|
||||
// `dep.sx` is imported only as `dep :: #import`, so its top-level `DEP_LEN`
|
||||
// is reachable ONLY as `dep.DEP_LEN`. A BARE `DEP_LEN` in a comptime
|
||||
// array-dimension position must NOT resolve: bare module-const visibility
|
||||
// joins over the FLAT import edges (`flat_import_graph`), and a namespaced
|
||||
// alias is not a flat edge. Before the fix the bare const leaked through the
|
||||
// global `module_const_map` first-match (and its float-fold fallback) straight
|
||||
// into the `[VAL]s32` dimension, so the array silently got length 3.
|
||||
dep :: #import "0742-modules-namespaced-only-bare-const-not-visible/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
arr : [DEP_LEN]s32 = ---;
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
DEP_LEN :: 3;
|
||||
@@ -0,0 +1,16 @@
|
||||
// Bare TYPE visibility under a NAMESPACED-only import — the struct sibling of
|
||||
// 0736 (bare call) and 0742 (bare const), and the core of the source-aware
|
||||
// nominal leaf (Phase E1). `dep.sx` is imported only as `dep :: #import`, so its
|
||||
// top-level `Secret` struct is reachable ONLY as `dep.Secret`. A BARE `Secret`
|
||||
// in a type position must NOT resolve: bare-TYPE visibility joins over the FLAT
|
||||
// import edges (`flat_import_graph`, transitively), and a namespaced alias is not
|
||||
// a flat edge. Before the fix the bare type leaked through the global
|
||||
// `findByName` first-match (the leaf returned it ahead of the visibility check),
|
||||
// so `s.x` compiled and ran. The qualified form `dep.Secret` stays the supported
|
||||
// spelling (its member resolution lands fully in Phase F).
|
||||
dep :: #import "0743-modules-namespaced-only-bare-type-not-visible/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
s : Secret = .{ x = 5, y = 6 };
|
||||
s.x
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Secret :: struct {
|
||||
x: s32;
|
||||
y: s32;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Bare TYPE visibility under a NAMESPACED-only import — the ENUM sibling of 0743
|
||||
// (struct), covering the second registered nominal kind for the source-aware
|
||||
// nominal leaf (Phase E1). `dep.sx` is imported only as `dep :: #import`, so its
|
||||
// top-level `Color` enum is reachable ONLY as `dep.Color`. A BARE `Color` in a
|
||||
// type position must NOT resolve — bare-TYPE visibility joins over the FLAT
|
||||
// import edges, and a namespaced alias is not a flat edge. Before the fix the
|
||||
// bare enum leaked through the global `findByName` first-match.
|
||||
dep :: #import "0744-modules-namespaced-only-bare-enum-not-visible/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
c : Color = .green;
|
||||
if c == .green { 0 } else { 9 }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Color :: enum {
|
||||
red;
|
||||
green;
|
||||
blue;
|
||||
}
|
||||
15
examples/0745-modules-flat-value-shadows-ns-only-type.sx
Normal file
15
examples/0745-modules-flat-value-shadows-ns-only-type.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// Type-author-aware bare-TYPE visibility gate (Phase E1, R1). `flatval.sx` is
|
||||
// flat-imported and authors a VALUE/FUNCTION `Secret`; `nstype.sx` is namespaced
|
||||
// (`nst :: #import`) and authors a TYPE `Secret`. A bare `Secret` in a type
|
||||
// position must NOT resolve: the only flat-visible `Secret` author is a FUNCTION,
|
||||
// and a same-name flat value does NOT make the namespaced-only TYPE bare-visible.
|
||||
// The leak this closes: a name-only gate would see the flat function and let the
|
||||
// global `findByName` first-match return the namespaced-only struct. The type is
|
||||
// reachable only as `nst.Secret`.
|
||||
#import "0745-modules-flat-value-shadows-ns-only-type/flatval.sx";
|
||||
nst :: #import "0745-modules-flat-value-shadows-ns-only-type/nstype.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
s : Secret = .{ x = 5, y = 6 };
|
||||
s.x
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// A flat-visible VALUE/FUNCTION named `Secret` (not a type).
|
||||
Secret :: () -> s32 { 0 }
|
||||
@@ -0,0 +1,4 @@
|
||||
Secret :: struct {
|
||||
x: s32;
|
||||
y: s32;
|
||||
}
|
||||
13
examples/0746-modules-local-type-shadows-ns-only-type.sx
Normal file
13
examples/0746-modules-local-type-shadows-ns-only-type.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// A block-LOCAL type resolves even when a namespaced-only import authors a
|
||||
// top-level type of the same name (Phase E1, R2). `dep.sx` is namespaced
|
||||
// (`dep :: #import`) and authors a top-level `Secret`; `main` declares its OWN
|
||||
// block-local `Secret`. The local must resolve to ITS fields (a legitimately-
|
||||
// scoped local is never a namespaced-only leak), not be rejected by the bare-TYPE
|
||||
// visibility gate just because the namespaced import shares the name.
|
||||
dep :: #import "0746-modules-local-type-shadows-ns-only-type/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
Secret :: struct { z: s32; }
|
||||
s : Secret = .{ z = 7 };
|
||||
s.z
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Secret :: struct {
|
||||
x: s32;
|
||||
y: s32;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Bare TYPE-ALIAS visibility under a NAMESPACED-only import — the alias sibling
|
||||
// of 0743 (bare named type) and 0742 (bare const). A type ALIAS is a `const_decl`
|
||||
// whose value resolved to a type, so it never registers a `findByName` named-type
|
||||
// entry; the source-aware leaf must still treat it as a TYPE author (R4). `dep.sx`
|
||||
// is imported only as `dep :: #import`, so its top-level `Secret :: s32` alias is
|
||||
// reachable ONLY as `dep.Secret`. A BARE `Secret` in a type position must NOT
|
||||
// resolve: bare-TYPE visibility joins over the FLAT import edges, and a namespaced
|
||||
// alias is not a flat edge. Before the fix the ns-only alias was NOT a recognised
|
||||
// type author, so the bare reference fell to the legacy empty-struct stub with NO
|
||||
// diagnostic (the value silently came out 0). Regression (issue R4).
|
||||
dep :: #import "0747-modules-namespaced-only-bare-alias-not-visible/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x : Secret = 7;
|
||||
x
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Secret :: s32;
|
||||
18
examples/0748-modules-flat-alias-shadows-ns-only-type.sx
Normal file
18
examples/0748-modules-flat-alias-shadows-ns-only-type.sx
Normal file
@@ -0,0 +1,18 @@
|
||||
// A flat-visible (here own-module) type ALIAS must resolve even when a
|
||||
// namespaced-only import authors a same-name NAMED type — the alias↔named-type
|
||||
// analog of 0745/0746 (R4, FALSE-REJECTION direction). `dep.sx` is namespaced
|
||||
// (`ns :: #import`) and authors a top-level `Secret` STRUCT; `main` authors its
|
||||
// OWN top-level alias `Secret :: s32`. A bare `Secret` must resolve to MAIN's
|
||||
// alias (`s32`), NOT be poisoned by the invisible same-name struct: the alias is
|
||||
// the only flat-visible TYPE author. Before the fix the leaf saw the global
|
||||
// `findByName` struct and, finding no NAMED-type author in `main` (an alias is a
|
||||
// `const_decl`, not a named type), wrongly rejected the bare reference as "not
|
||||
// visible". Regression (issue R4).
|
||||
ns :: #import "0748-modules-flat-alias-shadows-ns-only-type/dep.sx";
|
||||
|
||||
Secret :: s32;
|
||||
|
||||
main :: () -> s32 {
|
||||
x : Secret = 42;
|
||||
x
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
Secret :: struct {
|
||||
x: s32;
|
||||
y: s32;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Bare PARAMETERIZED-struct alias visibility under a NAMESPACED-only import —
|
||||
// the generic-struct sibling of 0747 (plain alias) and 0743 (named type). A
|
||||
// generic-struct instantiation alias (`Secret :: Box(s32)`) registers ONLY a
|
||||
// named struct type in the TypeTable; its raw import fact stays `.const_decl`,
|
||||
// so before the fix it was NOT recognised as a type author and a BARE `Secret`
|
||||
// leaked to the registered struct with NO diagnostic (the value silently came
|
||||
// out 42). The unified declaration-fact writer routes the instantiation alias
|
||||
// through `type_aliases_by_source`, so the bare-TYPE gate treats it like any
|
||||
// other alias: `dep.sx` is imported only as `dep :: #import`, so bare `Secret`
|
||||
// is reachable ONLY as `dep.Secret` and must NOT resolve. Regression
|
||||
// (attempt-5 R4-parameterized-alias-leak).
|
||||
dep :: #import "0749-modules-namespaced-only-bare-param-alias-not-visible/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
s : Secret = .{ value = 42 };
|
||||
s.value
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Box :: struct($T: Type) {
|
||||
value: T;
|
||||
}
|
||||
|
||||
Secret :: Box(s32);
|
||||
31
examples/0750-modules-forward-alias-source-aware.sx
Normal file
31
examples/0750-modules-forward-alias-source-aware.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Source-aware forward-alias fixpoint (R5 §4, E1.5). A forward identifier alias
|
||||
// `A :: B` must resolve its target `B` AS SEEN FROM ITS OWN SOURCE, not via the
|
||||
// global `type_alias_map` (which is last-wins across every module).
|
||||
//
|
||||
// `main` authors a forward alias `A :: B` and its own `B :: u64`. The namespaced
|
||||
// import `ns :: #import ".../dep.sx"` ALSO authors a top-level `B :: u8`; being
|
||||
// scanned after main's `B`, dep's alias is what the GLOBAL `type_alias_map["B"]`
|
||||
// ends up holding (last-wins). A global forward-alias fixpoint therefore bound
|
||||
// `A` to dep's `u8` — re-opening 0105 one layer down. The source-aware fixpoint
|
||||
// resolves `A`'s target against MAIN's source, binding the local `B :: u64`.
|
||||
//
|
||||
// Observable: a runtime 300 coerced into an `A`-typed slot round-trips as 300
|
||||
// when `A` is `u64` (correct) and truncates to 44 when `A` is wrongly `u8`.
|
||||
// The direct reference `b : B` already resolves source-aware via E1's nominal
|
||||
// leaf, so it pins the same `u64` for contrast. Regression (stdlib E1.5).
|
||||
#import "modules/std.sx";
|
||||
|
||||
A :: B;
|
||||
B :: u64;
|
||||
|
||||
ns :: #import "0750-modules-forward-alias-source-aware/dep.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
n : s64 = 300;
|
||||
a : A = xx n;
|
||||
b : B = xx n;
|
||||
print("forward A (u64=300): {}\n", cast(s64) a);
|
||||
print("direct B (u64=300): {}\n", cast(s64) b);
|
||||
print("ns.width(): {}\n", ns.width());
|
||||
return 0;
|
||||
}
|
||||
10
examples/0750-modules-forward-alias-source-aware/dep.sx
Normal file
10
examples/0750-modules-forward-alias-source-aware/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// Namespaced helper module. It authors a top-level type alias `B` whose
|
||||
// spelling collides with the importer's own `B`. Because the import is
|
||||
// NAMESPACED (`ns :: #import`), `dep.B` is NOT flat-visible to the importer —
|
||||
// but its alias write still lands in the global `type_alias_map` (last-wins),
|
||||
// which is exactly what the source-aware forward-alias fixpoint must ignore.
|
||||
B :: u8;
|
||||
|
||||
width :: () -> s32 {
|
||||
return 8;
|
||||
}
|
||||
36
examples/0751-modules-forward-alias-ns-before.sx
Normal file
36
examples/0751-modules-forward-alias-ns-before.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
// Source-aware forward-alias fixpoint — the INITIAL scan registration (E1.5).
|
||||
// Sibling of 0750, but the namespaced import is placed BEFORE the forward alias:
|
||||
//
|
||||
// ns :: #import ".../dep.sx"; // authors a same-name `B :: u8`
|
||||
// A :: B; // forward alias
|
||||
// B :: u64; // its LOCAL target, declared later
|
||||
//
|
||||
// Because `ns` is scanned first, dep's `B :: u8` is already in the global
|
||||
// `type_alias_map` when `A :: B` is scanned. The pre-E1.5-attempt-2 `scanDecls`
|
||||
// identifier-RHS branch resolved the RHS through that GLOBAL map and bound `A` to
|
||||
// dep's `u8` right there; the per-source fixpoint guard then saw `A` already
|
||||
// resolved and skipped it, so the source-aware leaf never corrected it (`A` came
|
||||
// out `u8`, truncating 300 -> 44). The source-aware scan registration resolves
|
||||
// `B` AS SEEN FROM main's source: dep's `B` is namespaced-only (not bare-visible)
|
||||
// and main's `B` is not registered yet, so `A` is left UNWRITTEN and the fixpoint
|
||||
// binds it to the local `B :: u64` once that registers.
|
||||
//
|
||||
// Observable: a runtime 300 coerced into an `A`-typed slot round-trips as 300
|
||||
// when `A` is `u64` (correct) and truncates to 44 when `A` is wrongly `u8`.
|
||||
// Regression (stdlib E1.5).
|
||||
#import "modules/std.sx";
|
||||
|
||||
ns :: #import "0751-modules-forward-alias-ns-before/dep.sx";
|
||||
|
||||
A :: B;
|
||||
B :: u64;
|
||||
|
||||
main :: () -> s32 {
|
||||
n : s64 = 300;
|
||||
a : A = xx n;
|
||||
b : B = xx n;
|
||||
print("forward A (u64=300): {}\n", cast(s64) a);
|
||||
print("direct B (u64=300): {}\n", cast(s64) b);
|
||||
print("ns.width(): {}\n", ns.width());
|
||||
return 0;
|
||||
}
|
||||
11
examples/0751-modules-forward-alias-ns-before/dep.sx
Normal file
11
examples/0751-modules-forward-alias-ns-before/dep.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// Namespaced helper module. It authors a top-level type alias `B` whose spelling
|
||||
// collides with the importer's own `B`. Because the import is NAMESPACED
|
||||
// (`ns :: #import`), `dep.B` is NOT flat-visible to the importer — but its alias
|
||||
// write still lands in the global `type_alias_map`, which the source-aware
|
||||
// forward-alias scan registration must ignore even when this module is scanned
|
||||
// (and so populates the global map) BEFORE the importer's own `A :: B`.
|
||||
B :: u8;
|
||||
|
||||
width :: () -> s32 {
|
||||
return 8;
|
||||
}
|
||||
14
examples/0752-modules-same-name-struct-distinct-fields.sx
Normal file
14
examples/0752-modules-same-name-struct-distinct-fields.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// issue 0105 case 1 — same-name struct, DIFFERENT fields. Two flat-imported
|
||||
// modules each declare a top-level `Box` with a different field set. Each
|
||||
// module's function builds and returns ITS OWN `Box`; `main` (which authors no
|
||||
// `Box`) prints both. Each value resolves against its declaring module's type,
|
||||
// so the formatter shows A's `{x}` and B's `{p, q}` — proving the two `Box`
|
||||
// types are distinct nominal identities, not a single last-wins collapse.
|
||||
#import "modules/std.sx";
|
||||
#import "0752-modules-same-name-struct-distinct-fields/a.sx";
|
||||
#import "0752-modules-same-name-struct-distinct-fields/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("a={} b={}\n", a_box(), b_box());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Module A authors its OWN `Box` (one `s64` field `x`).
|
||||
Box :: struct { x: s64; }
|
||||
a_box :: () -> Box { return Box.{ x = 7 }; }
|
||||
@@ -0,0 +1,5 @@
|
||||
// Module B authors a DIFFERENT `Box` (two fields `p`, `q`) — a same-name shadow
|
||||
// of A's `Box`. Pre-0105 the two collapsed last-wins in the type table, so one
|
||||
// module's field set vanished; now each `Box` is a distinct nominal type.
|
||||
Box :: struct { p: s64; q: s64; }
|
||||
b_box :: () -> Box { return Box.{ p = 3, q = 4 }; }
|
||||
15
examples/0753-modules-same-name-struct-same-fields.sx
Normal file
15
examples/0753-modules-same-name-struct-same-fields.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// issue 0105 case 2 — same-name struct, SAME fields. Two flat-imported modules
|
||||
// each declare `Pair { x, y }` with identical shape. They are STILL distinct
|
||||
// nominal identities (each holds its own per-source TypeId / nominal id), not
|
||||
// folded into one — both register, both monomorphize their own formatter, and
|
||||
// each module's value prints correctly. (The nominal-distinctness mechanism is
|
||||
// pinned at the unit level in `types.test.zig`; this example pins that two
|
||||
// identically-shaped same-name structs coexist without collapse or crash.)
|
||||
#import "modules/std.sx";
|
||||
#import "0753-modules-same-name-struct-same-fields/a.sx";
|
||||
#import "0753-modules-same-name-struct-same-fields/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("a={} b={}\n", a_pair(), b_pair());
|
||||
0
|
||||
}
|
||||
3
examples/0753-modules-same-name-struct-same-fields/a.sx
Normal file
3
examples/0753-modules-same-name-struct-same-fields/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Module A authors `Pair { x, y }`.
|
||||
Pair :: struct { x: s64; y: s64; }
|
||||
a_pair :: () -> Pair { return Pair.{ x = 1, y = 2 }; }
|
||||
5
examples/0753-modules-same-name-struct-same-fields/b.sx
Normal file
5
examples/0753-modules-same-name-struct-same-fields/b.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// Module B authors `Pair { x, y }` with the SAME field shape as A's. The two
|
||||
// still get distinct nominal identities (not collapsed): each keeps its own
|
||||
// TypeId / per-source author, so both register and format independently.
|
||||
Pair :: struct { x: s64; y: s64; }
|
||||
b_pair :: () -> Pair { return Pair.{ x = 5, y = 6 }; }
|
||||
15
examples/0754-modules-same-name-struct-own-wins.sx
Normal file
15
examples/0754-modules-same-name-struct-own-wins.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// issue 0105 case 3 — own-wins-over-flat. `main` flat-imports `dep.sx` (which
|
||||
// authors `Widget { a }`) AND authors its OWN `Widget { m }`. A bare `Widget`
|
||||
// reference in `main` resolves to `main`'s OWN author, not the flat-imported one
|
||||
// (the querying source's author wins outright — no ambiguity), so `Widget.{ m }`
|
||||
// builds `main`'s type while `dep_widget()` returns `dep`'s distinct `Widget`.
|
||||
#import "modules/std.sx";
|
||||
#import "0754-modules-same-name-struct-own-wins/dep.sx";
|
||||
|
||||
Widget :: struct { m: s64; }
|
||||
|
||||
main :: () -> s32 {
|
||||
w := Widget.{ m = 5 };
|
||||
print("own={} dep={}\n", w, dep_widget());
|
||||
0
|
||||
}
|
||||
5
examples/0754-modules-same-name-struct-own-wins/dep.sx
Normal file
5
examples/0754-modules-same-name-struct-own-wins/dep.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// A flat-imported module authors its OWN `Widget { a }`. The importing file
|
||||
// (`main`) ALSO authors a `Widget` — its own author must win there (0105 case 3),
|
||||
// while this module's `Widget` stays a distinct type used by `dep_widget`.
|
||||
Widget :: struct { a: s64; }
|
||||
dep_widget :: () -> Widget { return Widget.{ a = 9 }; }
|
||||
13
examples/0755-modules-same-name-struct-ambiguous.sx
Normal file
13
examples/0755-modules-same-name-struct-ambiguous.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// issue 0105 case 4 — two-flat-visible → AMBIGUOUS. `main` flat-imports two
|
||||
// modules that each author a same-name `Thing`, and authors none itself. A bare
|
||||
// `Thing` reference can't be disambiguated, so the compiler emits a LOUD
|
||||
// diagnostic ("ambiguous … qualify the reference or remove the duplicate
|
||||
// import") and poisons the result — never a silent first-/last-wins pick.
|
||||
#import "modules/std.sx";
|
||||
#import "0755-modules-same-name-struct-ambiguous/a.sx";
|
||||
#import "0755-modules-same-name-struct-ambiguous/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
t : Thing = .{ a = 1 };
|
||||
0
|
||||
}
|
||||
2
examples/0755-modules-same-name-struct-ambiguous/a.sx
Normal file
2
examples/0755-modules-same-name-struct-ambiguous/a.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// One of two flat-imported authors of a same-name `Thing`.
|
||||
Thing :: struct { a: s64; }
|
||||
3
examples/0755-modules-same-name-struct-ambiguous/b.sx
Normal file
3
examples/0755-modules-same-name-struct-ambiguous/b.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// The second flat-imported author of a same-name `Thing`. With both visible and
|
||||
// no own author in `main`, a bare `Thing` reference is genuinely ambiguous.
|
||||
Thing :: struct { b: s64; }
|
||||
13
examples/0756-modules-same-name-alias-per-source.sx
Normal file
13
examples/0756-modules-same-name-alias-per-source.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// issue 0105 case 5 — same-name type ALIAS, per-source visibility. Two
|
||||
// flat-imported modules each alias `Id` to a DIFFERENT type (A: `s32`, B:
|
||||
// `f64`). Each module's bare `Id` resolves against its OWN source alias, so A's
|
||||
// `x : Id` is a 32-bit integer (prints 100) and B's `x : Id` is a float (prints
|
||||
// 2.5) — proving aliases are source-keyed, never folded last-wins.
|
||||
#import "modules/std.sx";
|
||||
#import "0756-modules-same-name-alias-per-source/a.sx";
|
||||
#import "0756-modules-same-name-alias-per-source/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("a={} b={}\n", a_val(), b_val());
|
||||
0
|
||||
}
|
||||
4
examples/0756-modules-same-name-alias-per-source/a.sx
Normal file
4
examples/0756-modules-same-name-alias-per-source/a.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// Module A aliases `Id` to `s32`. A bare `Id` in this module resolves to A's
|
||||
// alias regardless of B's same-name alias (per-source alias visibility).
|
||||
Id :: s32;
|
||||
a_val :: () -> s64 { x : Id = 100; y : s64 = xx x; return y; }
|
||||
5
examples/0756-modules-same-name-alias-per-source/b.sx
Normal file
5
examples/0756-modules-same-name-alias-per-source/b.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// Module B aliases the SAME name `Id` to a DIFFERENT type `f64`. A bare `Id` in
|
||||
// this module resolves to B's `f64` alias, not A's `s32` — each module's alias
|
||||
// is keyed to its own source, so the two never collide last-wins.
|
||||
Id :: f64;
|
||||
b_val :: () -> f64 { x : Id = 2; return x + 0.5; }
|
||||
16
examples/0757-modules-same-name-struct-self-ref.sx
Normal file
16
examples/0757-modules-same-name-struct-self-ref.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// issue 0105 / F1 regression — a SELF-REFERENCE inside a same-name struct shadow.
|
||||
// Two flat-imported modules each declare a top-level `Box`; module B's `Box` has
|
||||
// a field `next: *Box` referencing its own name. The shadow must resolve that
|
||||
// self-ref to ITS OWN nominal identity, not the first same-name author (A's
|
||||
// `Box`), so `head.next.*.y` reads B's `y` (= 42). Proves the reserve-before-
|
||||
// fields ordering: a shadow author's decl key is recorded before its fields are
|
||||
// resolved, so a self / forward ref binds via `type_decl_tids`, never the global
|
||||
// findByName first-author fallback.
|
||||
#import "modules/std.sx";
|
||||
#import "0757-modules-same-name-struct-self-ref/a.sx";
|
||||
#import "0757-modules-same-name-struct-self-ref/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("a={} b={}\n", a_box(), b_chain());
|
||||
0
|
||||
}
|
||||
3
examples/0757-modules-same-name-struct-self-ref/a.sx
Normal file
3
examples/0757-modules-same-name-struct-self-ref/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Module A authors its OWN `Box` (one field `x`) — the FIRST same-name author.
|
||||
Box :: struct { x: s64; }
|
||||
a_box :: () -> Box { return Box.{ x = 7 }; }
|
||||
12
examples/0757-modules-same-name-struct-self-ref/b.sx
Normal file
12
examples/0757-modules-same-name-struct-self-ref/b.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// Module B authors a same-name `Box` shadow whose field SELF-REFERENCES its own
|
||||
// name (`next: *Box`). Pre-fix the self-ref resolved to A's `Box` (registered
|
||||
// first under the bare name), so `next.*.y` failed with "field 'y' not found on
|
||||
// type 'Box'". The shadow's slot is now reserved BEFORE its fields resolve, so
|
||||
// `*Box` binds to B's OWN nominal TypeId and the deref sees B's `y`.
|
||||
Box :: struct { y: s64; next: *Box; }
|
||||
b_chain :: () -> s64 {
|
||||
tail := Box.{ y = 42, next = null };
|
||||
head := Box.{ y = 1, next = @tail };
|
||||
// Walk the self-referential link; reads B's own `y`, not A's `x`.
|
||||
return head.next.*.y;
|
||||
}
|
||||
15
examples/0758-modules-same-name-struct-mutual-ref.sx
Normal file
15
examples/0758-modules-same-name-struct-mutual-ref.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// issue 0105 / F1 regression — FORWARD + MUTUAL refs between same-name struct
|
||||
// shadows. Two flat-imported modules each declare `Box` and `Node`; module B's
|
||||
// `Box` forward-refs B's `Node` (declared later) and B's `Node` back-refs B's
|
||||
// `Box`. Every cross-reference must bind to B's OWN nominal identities, proving
|
||||
// the up-front genuine-shadow reservation: ALL of a genuine shadow's authors are
|
||||
// reserved in `type_decl_tids` before any field resolves, so a forward / mutual
|
||||
// ref never falls back to the global findByName first-author (A's `Node`).
|
||||
#import "modules/std.sx";
|
||||
#import "0758-modules-same-name-struct-mutual-ref/a.sx";
|
||||
#import "0758-modules-same-name-struct-mutual-ref/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("b={}\n", b_test());
|
||||
0
|
||||
}
|
||||
3
examples/0758-modules-same-name-struct-mutual-ref/a.sx
Normal file
3
examples/0758-modules-same-name-struct-mutual-ref/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Module A authors its OWN `Box` and `Node` (the FIRST same-name authors).
|
||||
Box :: struct { x: s64; }
|
||||
Node :: struct { n: s64; }
|
||||
13
examples/0758-modules-same-name-struct-mutual-ref/b.sx
Normal file
13
examples/0758-modules-same-name-struct-mutual-ref/b.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// Module B authors same-name `Box` and `Node` shadows that reference EACH OTHER.
|
||||
// B's `Box` has a FORWARD ref to B's `Node` (declared after it), and B's `Node`
|
||||
// back-refs B's `Box`. Both forward and mutual refs must resolve to B's OWN
|
||||
// nominal TypeIds, not the first same-name authors in A.
|
||||
Box :: struct { y: s64; peer: *Node; }
|
||||
Node :: struct { m: s64; owner: *Box; }
|
||||
b_test :: () -> s64 {
|
||||
nd := Node.{ m = 99, owner = null };
|
||||
bx := Box.{ y = 7, peer = @nd };
|
||||
// Reads B's Node.m (99); pre-fix the forward ref bound to A's Node (which has
|
||||
// field `n`, not `m`) → "field 'm' not found on type 'Node'".
|
||||
return bx.peer.*.m;
|
||||
}
|
||||
22
examples/0759-modules-undeclared-type-in-import.sx
Normal file
22
examples/0759-modules-undeclared-type-in-import.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// A genuinely-undeclared type name used in an IMPORTED (non-main) module must
|
||||
// emit a clean "unknown type" diagnostic, not silently compile.
|
||||
//
|
||||
// The `UnknownTypeChecker` only walks MAIN-file decls — imported / library
|
||||
// modules are trusted and never checked. So an undeclared type name in an
|
||||
// imported module used to fall through the type leaf's empty-struct stub and
|
||||
// silently fabricate a 0-field struct: `make_thing()` below compiled and ran
|
||||
// (printing `thing.x = 42`) even though `lib.sx` references the non-existent
|
||||
// type `Coordnate`. The source-aware nominal leaf now poisons a genuinely-
|
||||
// undeclared name with the `.unresolved` sentinel and emits the diagnostic at
|
||||
// the reference, so the typo surfaces instead of mis-sizing `Thing` downstream.
|
||||
//
|
||||
// Expected: `error: unknown type 'Coordnate'` pointing into lib.sx; exit 1.
|
||||
// Regression (stdlib E3).
|
||||
#import "modules/std.sx";
|
||||
|
||||
#import "0759-modules-undeclared-type-in-import/lib.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("thing.x = {}\n", make_thing());
|
||||
return 0;
|
||||
}
|
||||
14
examples/0759-modules-undeclared-type-in-import/lib.sx
Normal file
14
examples/0759-modules-undeclared-type-in-import/lib.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// Flat-imported helper. `Coordnate` is a typo — no such type is declared
|
||||
// anywhere. Because this module is imported (not the main file), the
|
||||
// `UnknownTypeChecker` trusts it and never walks it, so the type leaf is the
|
||||
// sole guard against the silently-fabricated empty-struct stub.
|
||||
Thing :: struct {
|
||||
x: s32;
|
||||
y: Coordnate;
|
||||
}
|
||||
|
||||
make_thing :: () -> s32 {
|
||||
t : Thing = ---;
|
||||
t.x = 42;
|
||||
return t.x;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// A generic struct's VALUE param (`$N: u32`) named in a TYPE position must emit
|
||||
// a clean diagnostic even when the struct is declared in an IMPORTED module —
|
||||
// not just the main file (examples/0172 covers the main-file case). Before the
|
||||
// fix the imported template's field `x: N` resolved to the `.unresolved`
|
||||
// sentinel and PANICKED at LLVM emission ("unresolved type reached LLVM
|
||||
// emission"); the field type leaf now diagnoses the imported value-param misuse
|
||||
// at the reference, the same way the main-file `UnknownTypeChecker` does.
|
||||
//
|
||||
// Expected: `error: 'N' is a value parameter, not a type` pointing into lib.sx;
|
||||
// exit 1. Regression (stdlib E3).
|
||||
#import "modules/std.sx";
|
||||
#import "0760-modules-imported-generic-value-param-as-field-type/lib.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
b : Bad(3) = .{ x = 1 };
|
||||
print("{}\n", b.x);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Flat-imported generic struct whose VALUE param `$N: u32` is named in a TYPE
|
||||
// position (`x: N`). Because this module is imported (not the main file), the
|
||||
// `UnknownTypeChecker` trusts it and never walks it — so the field type leaf is
|
||||
// the sole guard. `instantiateGenericStruct` resolves the template's field nodes
|
||||
// in THIS source context, so the leaf classifies the value-param misuse as
|
||||
// imported and emits the tailored hint instead of poisoning the field with the
|
||||
// `.unresolved` sentinel (which panicked at LLVM emission before the fix).
|
||||
Bad :: struct($N: u32) {
|
||||
x: N;
|
||||
}
|
||||
19
examples/0761-modules-imported-generic-undeclared-field.sx
Normal file
19
examples/0761-modules-imported-generic-undeclared-field.sx
Normal file
@@ -0,0 +1,19 @@
|
||||
// A genuinely-undeclared type in an IMPORTED GENERIC struct field must emit
|
||||
// "unknown type" even when the struct is instantiated from the main file —
|
||||
// 0759 only covered a non-generic imported struct instantiated in-module.
|
||||
// Before the fix the generic template's fields resolved in the main-file
|
||||
// instantiation context, so the leaf trusted them as main-file decls and
|
||||
// silently stubbed `Missing` (the program compiled and printed `b.x`). The
|
||||
// template's fields now resolve in the template's own source context, so the
|
||||
// undeclared name surfaces.
|
||||
//
|
||||
// Expected: `error: unknown type 'Missing'` pointing into lib.sx; exit 1.
|
||||
// Regression (stdlib E3).
|
||||
#import "modules/std.sx";
|
||||
#import "0761-modules-imported-generic-undeclared-field/lib.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
b : Bad(s32) = .{ x = 1, y = 2 };
|
||||
print("{}\n", b.x);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Flat-imported generic struct with a genuinely-undeclared field type
|
||||
// (`y: Missing`). 0759 covers a NON-generic imported struct; a generic one is
|
||||
// instantiated cross-module, so before the fix its field nodes were resolved in
|
||||
// the (main-file) instantiation context — the source-aware leaf saw "main",
|
||||
// trusted it to the `UnknownTypeChecker` (which never walks imports), and
|
||||
// silently fabricated a 0-field stub. `instantiateGenericStruct` now resolves
|
||||
// the template's fields in THIS module's source context, so the undeclared name
|
||||
// surfaces at the reference.
|
||||
Bad :: struct($T: Type) {
|
||||
x: T;
|
||||
y: Missing;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// An IMPORTED generic template's field that names a type the CALLER declared
|
||||
// only as a BLOCK-LOCAL must NOT bind that caller-local type — a block-local is
|
||||
// visible only within its OWN source. `lib.sx` defines
|
||||
// `Bad :: struct($T) { x: T; y: LocalOnly; }`; `main` declares `LocalOnly` only
|
||||
// inside its own body before instantiating `Bad(s32)`. The imported template's
|
||||
// module cannot see a caller block-local, so `y: LocalOnly` is undeclared in the
|
||||
// lib file.
|
||||
//
|
||||
// Before the fix the global `local_type_names` set was source-insensitive: the
|
||||
// template's field resolution (run in the template's source context, E3
|
||||
// attempt-4) consulted it, found the caller's `LocalOnly`, and silently compiled
|
||||
// (printed a value, exit 0). `local_type_names` is now keyed by declaring source,
|
||||
// so a cross-source block-local no longer leaks into another source's resolution.
|
||||
//
|
||||
// Expected: `error: unknown type 'LocalOnly'` pointing into lib.sx; exit 1.
|
||||
// Regression (stdlib E3 attempt-5).
|
||||
#import "modules/std.sx";
|
||||
#import "0762-modules-imported-generic-caller-local-field-leak/lib.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
LocalOnly :: struct { v: s32; }
|
||||
b : Bad(s32) = .{ x = 1, y = .{ v = 9 } };
|
||||
print("{} {}\n", b.x, b.y.v);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Flat-imported generic struct whose field `y: LocalOnly` names a type that is
|
||||
// declared only as a BLOCK-LOCAL in the CALLER (`main`). The template's fields
|
||||
// resolve in THIS module's source context (E3 attempt-4), and a block-local type
|
||||
// is visible only within its own source, so `LocalOnly` is genuinely undeclared
|
||||
// here — the source-aware leaf surfaces it instead of binding the caller's local.
|
||||
Bad :: struct($T: Type) {
|
||||
x: T;
|
||||
y: LocalOnly;
|
||||
}
|
||||
20
examples/0763-modules-import-type-non-transitive.sx
Normal file
20
examples/0763-modules-import-type-non-transitive.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// `#import` is non-transitive for TYPES, exactly like values/functions (0706):
|
||||
// when A imports B and B imports C, A must NOT see C's top-level TYPE names.
|
||||
// This file imports `b.sx` (which in turn imports `c.sx`) and then references
|
||||
// C's type `COnly` directly — the compiler rejects it with a
|
||||
// "type ... is not visible; #import the module that declares it" diagnostic.
|
||||
//
|
||||
// `b.sx` ↔ `c.sx` together still compile: `b_make`'s return type `COnly`
|
||||
// resolves because b.sx directly imports c.sx.
|
||||
//
|
||||
// Regression (Phase E4): before the bare-TYPE gate went single-hop this
|
||||
// 2-flat-hop type was wrongly visible (the interim transitive closure).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0763-modules-import-type-non-transitive/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x : COnly = .{ v = 5 };
|
||||
print("{}\n", x.v);
|
||||
0
|
||||
}
|
||||
7
examples/0763-modules-import-type-non-transitive/b.sx
Normal file
7
examples/0763-modules-import-type-non-transitive/b.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "c.sx";
|
||||
|
||||
// b.sx directly imports c.sx, so it CAN name `COnly` — proving the type is
|
||||
// only one flat hop away here, two hops away from a file that imports b.sx.
|
||||
b_make :: () -> COnly {
|
||||
.{ v = 99 }
|
||||
}
|
||||
1
examples/0763-modules-import-type-non-transitive/c.sx
Normal file
1
examples/0763-modules-import-type-non-transitive/c.sx
Normal file
@@ -0,0 +1 @@
|
||||
COnly :: struct { v: s64 = 0; }
|
||||
24
examples/0764-modules-import-generic-head-non-transitive.sx
Normal file
24
examples/0764-modules-import-generic-head-non-transitive.sx
Normal file
@@ -0,0 +1,24 @@
|
||||
// `#import` is non-transitive for a PARAMETERIZED TYPE HEAD (a generic-struct
|
||||
// constructor like `Box(s64)`), exactly like a bare leaf type (0763) and like
|
||||
// values/functions (0706): when A imports B and B imports C, A must NOT see C's
|
||||
// top-level generic type `Box`. This file imports `b.sx` (which imports `c.sx`)
|
||||
// and instantiates C's generic `Box(s64)` directly — the compiler rejects the
|
||||
// head with a "type ... is not visible; #import the module that declares it"
|
||||
// diagnostic, BEFORE instantiating the template.
|
||||
//
|
||||
// `b.sx` ↔ `c.sx` together still compile: `b_make`'s `Box(s64)` resolves because
|
||||
// b.sx directly imports c.sx (the head is one flat hop away there, two from a
|
||||
// file that imports b.sx).
|
||||
//
|
||||
// Regression (Phase E4): before the bare-head gate went single-hop this 2-flat-
|
||||
// hop generic head was wrongly visible — the head lookup hit the global
|
||||
// `struct_template_map` before any source-aware visibility check.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0764-modules-import-generic-head-non-transitive/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x : Box(s64) = .{ v = 3 };
|
||||
print("{}\n", x.v);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#import "c.sx";
|
||||
|
||||
// b.sx directly imports c.sx, so it CAN instantiate `Box(s64)` — proving the
|
||||
// generic head is only one flat hop away here, two hops from a file that
|
||||
// imports b.sx.
|
||||
b_make :: () -> Box(s64) {
|
||||
.{ v = 99 }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Box :: struct($T: Type) { v: T; }
|
||||
@@ -0,0 +1,27 @@
|
||||
// `#import` is non-transitive for a type named in a REFLECTION / type-arg slot
|
||||
// (`size_of(T)`, `size_of(*T)`) and in a TYPED ARRAY-LITERAL annotation
|
||||
// (`T.[...]`), exactly like a bare leaf annotation (0763), a parameterized head
|
||||
// (0764), and values/functions (0706): when A imports B and B imports C, A must
|
||||
// NOT see C's top-level types here either. This file imports `b.sx` (which
|
||||
// imports `c.sx`) and references C's `Nums` / `COnly` in those positions — each
|
||||
// is rejected with a "type ... is not visible; #import the module that declares
|
||||
// it" diagnostic, BEFORE the global type table can resolve it.
|
||||
//
|
||||
// `b.sx` ↔ `c.sx` together still compile: `b_sizes` / `b_arr` resolve `Nums` /
|
||||
// `COnly` because b.sx directly imports c.sx (one flat hop there, two from a
|
||||
// file that imports b.sx).
|
||||
//
|
||||
// Regression (Phase E4): before the bare-TYPE gate reached the reflection
|
||||
// type-arg and array-literal leaf sites, these 2-flat-hop references leaked
|
||||
// through the global `type_alias_map` / `findByName` / `type_bridge` lookup.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0765-modules-import-reflection-type-non-transitive/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("{}\n", size_of(Nums));
|
||||
print("{}\n", size_of(*COnly));
|
||||
xs := Nums.[1, 2];
|
||||
print("{} {}\n", xs[0], xs[1]);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#import "c.sx";
|
||||
|
||||
// b.sx directly imports c.sx, so it CAN name these types in reflection /
|
||||
// type-arg / array-literal positions — the type is one flat hop away here,
|
||||
// two from a file that imports b.sx.
|
||||
b_sizes :: () -> s64 {
|
||||
size_of(Nums) + size_of(*COnly)
|
||||
}
|
||||
|
||||
b_arr :: () -> s64 {
|
||||
xs := Nums.[1, 2];
|
||||
xs[0] + xs[1]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Nums :: [2]s64;
|
||||
|
||||
COnly :: struct { v: s64 = 0; }
|
||||
16
examples/0766-modules-reflection-type-direct-ok.sx
Normal file
16
examples/0766-modules-reflection-type-direct-ok.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// The pass side of 0765: a DIRECTLY imported module's types are bare-visible in
|
||||
// reflection / type-arg slots (`size_of(Nums)`, `size_of(*COnly)`) and in a
|
||||
// typed array-literal annotation (`Nums.[...]`) — single-hop visibility, so a
|
||||
// 1-flat-hop type resolves exactly as before the E4 gate. Confirms the gate
|
||||
// only rejects the 2-hop case (0765), never a directly-imported reference.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0766-modules-reflection-type-direct-ok/c.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("{}\n", size_of(Nums));
|
||||
print("{}\n", size_of(*COnly));
|
||||
xs := Nums.[1, 2];
|
||||
print("{} {}\n", xs[0], xs[1]);
|
||||
0
|
||||
}
|
||||
3
examples/0766-modules-reflection-type-direct-ok/c.sx
Normal file
3
examples/0766-modules-reflection-type-direct-ok/c.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
Nums :: [2]s64;
|
||||
|
||||
COnly :: struct { v: s64 = 0; }
|
||||
39
examples/0767-modules-ambiguous-bare-type-forms.sx
Normal file
39
examples/0767-modules-ambiguous-bare-type-forms.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
// Bare-TYPE references are NON-transitive AND ambiguity-checked at every site,
|
||||
// not just the nominal leaf annotation (0755). `main` flat-imports two modules
|
||||
// that each author a same-name `Thing` / `Box` / `Nums` and authors none itself,
|
||||
// so EACH of the following bare forms is a genuine collision the source cannot
|
||||
// disambiguate — and each must emit the LOUD "type ... is ambiguous" diagnostic
|
||||
// (consistent with the leaf, 0755) and poison the result, NEVER silently pick a
|
||||
// global `findByName` / `struct_template_map` author:
|
||||
//
|
||||
// - reflection / type-arg slot `size_of(Thing)`
|
||||
// - typed array/vector-literal `Nums.[1, 2]`
|
||||
// - parameterized generic head `Box(s64)`
|
||||
// - type-as-value `t : Type = Thing`
|
||||
// - type-category match arm `case Thing:`
|
||||
//
|
||||
// Regression (Phase E4 attempt-5): before the bare-type gate carried the full
|
||||
// source-aware author outcome, these non-leaf sites used a boolean leak-check
|
||||
// that dropped the AMBIGUOUS outcome — two direct flat same-name authors fell
|
||||
// through to a global pick (exit 0 / cascade) instead of the loud diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0767-modules-ambiguous-bare-type-forms/a.sx";
|
||||
#import "0767-modules-ambiguous-bare-type-forms/b.sx";
|
||||
|
||||
describe :: ($T: Type) -> s32 {
|
||||
r := if T == {
|
||||
case Thing: 1;
|
||||
else: 0;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
sz := size_of(Thing);
|
||||
xs := Nums.[1, 2];
|
||||
x : Box(s64) = .{ v = 3 };
|
||||
t : Type = Thing;
|
||||
d := describe(s64);
|
||||
0
|
||||
}
|
||||
6
examples/0767-modules-ambiguous-bare-type-forms/a.sx
Normal file
6
examples/0767-modules-ambiguous-bare-type-forms/a.sx
Normal file
@@ -0,0 +1,6 @@
|
||||
// One of two flat-imported authors of same-name types `Thing` / `Box` / `Nums`.
|
||||
// With both modules flat-visible from a file that authors none itself, every
|
||||
// bare reference to these names is genuinely ambiguous.
|
||||
Thing :: struct { a: s64; }
|
||||
Box :: struct($T: Type) { v: T; }
|
||||
Nums :: [2]s64;
|
||||
7
examples/0767-modules-ambiguous-bare-type-forms/b.sx
Normal file
7
examples/0767-modules-ambiguous-bare-type-forms/b.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// The second flat-imported author of same-name `Thing` / `Box` / `Nums`. The
|
||||
// distinct shapes (`Thing` a separate nominal identity, `Box` a separate generic
|
||||
// template, `Nums` aliased to a different element width) make each bare
|
||||
// reference a real collision the importing source cannot disambiguate.
|
||||
Thing :: struct { a: s64; }
|
||||
Box :: struct($T: Type) { v: T; }
|
||||
Nums :: [2]s32;
|
||||
44
examples/0768-modules-own-wins-nonleaf-bare-type.sx
Normal file
44
examples/0768-modules-own-wins-nonleaf-bare-type.sx
Normal file
@@ -0,0 +1,44 @@
|
||||
// Own-wins (0754 / issue 0105 case 3) holds at EVERY non-leaf bare-type site,
|
||||
// not just the nominal leaf annotation. `main` flat-imports `dep.sx` (which
|
||||
// authors a same-name `Widget { a, b }` = 16 bytes and `Nums :: [2]s32` = 8
|
||||
// bytes) AND authors its OWN `Widget { a }` = 8 bytes and `Nums :: [2]s64` = 16
|
||||
// bytes. Each bare reference below must resolve to MAIN's own author — the gate's
|
||||
// source-keyed `.resolved` author — NOT whichever same-name flat import a global
|
||||
// `findByName` / `struct_template_map` pick would return:
|
||||
//
|
||||
// - reflection / type-arg slot `size_of(Widget)` → 8 (own)
|
||||
// - typed array-literal head `Nums.[…]` / size_of → 16 (own [2]s64)
|
||||
// - type-as-value `t : Type = Widget`
|
||||
// - type-category match arm `case Widget:` → own identity
|
||||
//
|
||||
// Regression (Phase E4 attempt-6, finding #1): before the bare-type gate carried
|
||||
// the OWN author's source-keyed TypeId into the non-leaf sites, an own author
|
||||
// produced `.proceed` and these sites fell through to a global `findByName` — so
|
||||
// `size_of(Widget)` printed the IMPORTED type's 16 and `case Widget:` matched the
|
||||
// imported nominal identity (describe → 222). `dep_sizes` proves dep's distinct
|
||||
// types still resolve to THEIR own 16 + 8 = 24 inside dep.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0768-modules-own-wins-nonleaf-bare-type/dep.sx";
|
||||
|
||||
Widget :: struct { a: s64; }
|
||||
Nums :: [2]s64;
|
||||
|
||||
describe :: ($T: Type) -> s32 {
|
||||
r := if T == {
|
||||
case Widget: 111;
|
||||
else: 222;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
print("reflection={}\n", size_of(Widget));
|
||||
xs := Nums.[1, 2];
|
||||
print("array={}\n", size_of(Nums));
|
||||
t : Type = Widget;
|
||||
print("typeval_eq={}\n", t == Widget);
|
||||
print("match={}\n", describe(Widget));
|
||||
print("dep={}\n", dep_sizes());
|
||||
0
|
||||
}
|
||||
8
examples/0768-modules-own-wins-nonleaf-bare-type/dep.sx
Normal file
8
examples/0768-modules-own-wins-nonleaf-bare-type/dep.sx
Normal file
@@ -0,0 +1,8 @@
|
||||
// A flat-imported module authors its OWN `Widget { a, b }` (16 bytes) and
|
||||
// `Nums :: [2]s32` (8 bytes). The importing file (`main`) ALSO authors a
|
||||
// same-name `Widget { a }` (8 bytes) and `Nums :: [2]s64` (16 bytes) — its own
|
||||
// authors must win at EVERY bare-type site there (own-wins, 0754), while this
|
||||
// module's distinct types stay live via `dep_sizes`.
|
||||
Widget :: struct { a: s64; b: s64; }
|
||||
Nums :: [2]s32;
|
||||
dep_sizes :: () -> s64 { return size_of(Widget) + size_of(Nums); }
|
||||
21
examples/0769-modules-ambiguous-type-fn-head.sx
Normal file
21
examples/0769-modules-ambiguous-type-fn-head.sx
Normal file
@@ -0,0 +1,21 @@
|
||||
// A type-returning FUNCTION head (`Make(s64)` where `Make :: ($T) -> Type`) is
|
||||
// NON-transitive AND ambiguity-checked, exactly like the parameterized generic-
|
||||
// struct / protocol heads (0767) and the nominal leaf (0755). `main` flat-imports
|
||||
// two modules that each author a same-name `Make` type-fn with a different body
|
||||
// and authors none itself, so the bare `Make(s64)` head is a genuine collision —
|
||||
// it must emit the LOUD "type 'Make' is ambiguous" diagnostic and poison, NEVER
|
||||
// silently instantiate whichever single author `fn_ast_map` happens to hold.
|
||||
//
|
||||
// Regression (Phase E4 attempt-6, finding #2): before `headFnLeak` did bare-call
|
||||
// ambiguity selection it only checked `isNameVisible` (both authors ARE visible),
|
||||
// so two flat `Make` type-fns silently instantiated one author — `size_of(Make(s64))`
|
||||
// printed 16 (a.sx's two-field body) at exit 0 instead of the ambiguity diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0769-modules-ambiguous-type-fn-head/a.sx";
|
||||
#import "0769-modules-ambiguous-type-fn-head/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("size={}\n", size_of(Make(s64)));
|
||||
0
|
||||
}
|
||||
7
examples/0769-modules-ambiguous-type-fn-head/a.sx
Normal file
7
examples/0769-modules-ambiguous-type-fn-head/a.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// One of two flat-imported authors of a same-name type-returning function
|
||||
// `Make :: ($T) -> Type`. Its body returns a two-field struct; b.sx's returns a
|
||||
// one-field struct, so a bare `Make(s64)` head is a genuine collision the
|
||||
// importing source cannot disambiguate.
|
||||
Make :: ($T: Type) -> Type {
|
||||
return struct { x: T; y: T; };
|
||||
}
|
||||
5
examples/0769-modules-ambiguous-type-fn-head/b.sx
Normal file
5
examples/0769-modules-ambiguous-type-fn-head/b.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// The second flat-imported author of same-name type-fn `Make`. Its distinct
|
||||
// one-field body makes a bare `Make(s64)` head ambiguous against a.sx's.
|
||||
Make :: ($T: Type) -> Type {
|
||||
return struct { x: T; };
|
||||
}
|
||||
23
examples/0770-modules-type-fn-head-non-transitive.sx
Normal file
23
examples/0770-modules-type-fn-head-non-transitive.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// A type-returning FUNCTION head (`Make(s64)` where `Make :: ($T) -> Type`) is
|
||||
// NON-transitive even when a DIRECT flat import authors the same name as a
|
||||
// NON-function. `main` flat-imports `b.sx`; `b.sx` declares `Make :: 123` (a
|
||||
// value const, not a type-fn) AND flat-imports `c.sx`, whose `Make` IS the
|
||||
// type-returning function. The only TYPE-FN author of `Make` is two flat hops
|
||||
// away (main → b → c), so the bare `Make(s64)` head must emit the
|
||||
// "type 'Make' is not visible" diagnostic and poison — the visible 1-hop
|
||||
// `Make :: 123` const must NOT vouch for it.
|
||||
//
|
||||
// Regression (Phase E4 attempt-7, finding E4-type-fn-head-hidden-by-visible-
|
||||
// nonfn): before `headFnLeak` decided visibility from the ELIGIBLE FUNCTION
|
||||
// authors it used the module-scope NAME predicate (`isNameVisible`), which the
|
||||
// visible non-fn `Make :: 123` satisfied — so the global `fn_ast_map` type-fn
|
||||
// silently instantiated and `size_of(Make(s64))` printed 8 at exit 0 instead of
|
||||
// the visibility diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0770-modules-type-fn-head-non-transitive/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("size={}\n", size_of(Make(s64)));
|
||||
0
|
||||
}
|
||||
6
examples/0770-modules-type-fn-head-non-transitive/b.sx
Normal file
6
examples/0770-modules-type-fn-head-non-transitive/b.sx
Normal file
@@ -0,0 +1,6 @@
|
||||
// The directly-imported (1-hop) author of the NAME `Make` — but as a value
|
||||
// const, NOT a type-returning function. It flat-imports c.sx (where the real
|
||||
// `Make` type-fn lives, two hops from a file that imports b.sx). A same-name
|
||||
// non-function must not vouch for the 2-hop type-fn head.
|
||||
#import "c.sx";
|
||||
Make :: 123;
|
||||
5
examples/0770-modules-type-fn-head-non-transitive/c.sx
Normal file
5
examples/0770-modules-type-fn-head-non-transitive/c.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// The real type-returning function `Make`, two flat hops from a file that
|
||||
// imports b.sx. A file importing only b.sx must NOT see this head.
|
||||
Make :: ($T: Type) -> Type {
|
||||
return struct { x: T; };
|
||||
}
|
||||
25
examples/0771-modules-type-fn-head-ordinary-fn-no-vouch.sx
Normal file
25
examples/0771-modules-type-fn-head-ordinary-fn-no-vouch.sx
Normal file
@@ -0,0 +1,25 @@
|
||||
// A type-returning FUNCTION head (`Make(s64)` where `Make :: ($T) -> Type`) is
|
||||
// NON-transitive even when a DIRECT flat import authors the same name as an
|
||||
// ORDINARY (non-type-returning) function. `main` flat-imports `b.sx`; `b.sx`
|
||||
// declares `Make :: () -> s32` (a plain function, NOT a type-fn) AND flat-imports
|
||||
// `c.sx`, whose `Make` IS the type-returning function. The only TYPE-FN author of
|
||||
// `Make` is two flat hops away (main → b → c), so the bare `Make(s64)` head must
|
||||
// emit the "type 'Make' is not visible" diagnostic and poison — the visible 1-hop
|
||||
// ordinary `Make :: () -> s32` must NOT vouch for it.
|
||||
//
|
||||
// Regression (Phase E4 attempt-8, finding E4-type-fn-head-hidden-by-visible-
|
||||
// nontypefn): attempt-7's `headFnLeak` decided visibility from any FUNCTION
|
||||
// author (`fnDeclOfRaw != null`), so the visible ordinary `Make` function (which
|
||||
// CANNOT be the type head being instantiated) still vouched — the global
|
||||
// `fn_ast_map` type-fn silently instantiated and `size_of(Make(s64))` printed 8
|
||||
// at exit 0 instead of the visibility diagnostic. The fix narrows the author view
|
||||
// to TYPE-FUNCTIONS (`typeFnAuthor`: a `fn_decl` with ≥1 `$`-param), the same
|
||||
// discriminator every instantiation site uses to recognize a type-fn head.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0771-modules-type-fn-head-ordinary-fn-no-vouch/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("size={}\n", size_of(Make(s64)));
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// The directly-imported (1-hop) author of the NAME `Make` — but as an ORDINARY
|
||||
// function (`() -> s32`), NOT a type-returning function. It flat-imports c.sx
|
||||
// (where the real `Make` type-fn lives, two hops from a file that imports b.sx).
|
||||
// A same-name ordinary function must not vouch for the 2-hop type-fn head: it has
|
||||
// zero `$`-params, so it cannot be the type head `Make(s64)` is instantiating.
|
||||
#import "c.sx";
|
||||
Make :: () -> s32 { return 7; }
|
||||
@@ -0,0 +1,5 @@
|
||||
// The real type-returning function `Make`, two flat hops from a file that
|
||||
// imports b.sx. A file importing only b.sx must NOT see this head.
|
||||
Make :: ($T: Type) -> Type {
|
||||
return struct { x: T; };
|
||||
}
|
||||
29
examples/0772-modules-qualified-generic-head-author.sx
Normal file
29
examples/0772-modules-qualified-generic-head-author.sx
Normal file
@@ -0,0 +1,29 @@
|
||||
// A qualified generic type head `ns.Box(args)` must instantiate the template
|
||||
// AUTHORED by `ns`'s module — not the global same-name template that happened to
|
||||
// win the last-wins `struct_template_map`. `main` imports two namespaces that
|
||||
// each author a same-name generic `Box($T)` with a DIFFERENT layout (a: one
|
||||
// field, b: two fields). `a.Box(s64)` and `b.Box(s64)` must resolve to their OWN
|
||||
// module's template (sizes 8 and 16) and be DISTINCT types, so a field unique to
|
||||
// b's layout (`y`) is reachable only through `b.Box`.
|
||||
//
|
||||
// This is the ambiguity escape hatch made real: when a bare `Box(s64)` is
|
||||
// ambiguous (two flat same-name authors), the diagnostic tells the user to
|
||||
// "qualify the reference"; that advice only works if `ns.Box(..)` actually
|
||||
// selects ns's author.
|
||||
//
|
||||
// Regression (Phase E4): before qualified generic-head selection, the head was
|
||||
// stripped to the bare name and read from the global `struct_template_map`, so
|
||||
// `a.Box(s64)` and `b.Box(s64)` both instantiated the last-wins template (both
|
||||
// size 16) — the namespace qualifier was ignored.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0772-modules-qualified-generic-head-author/a.sx";
|
||||
b :: #import "0772-modules-qualified-generic-head-author/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
pa : a.Box(s64) = .{ x = 1 };
|
||||
pb : b.Box(s64) = .{ x = 10, y = 20 };
|
||||
print("a={} b={}\n", size_of(a.Box(s64)), size_of(b.Box(s64)));
|
||||
print("pa.x={} pb.x={} pb.y={}\n", pa.x, pb.x, pb.y);
|
||||
0
|
||||
}
|
||||
2
examples/0772-modules-qualified-generic-head-author/a.sx
Normal file
2
examples/0772-modules-qualified-generic-head-author/a.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// Author A's generic `Box` — one s64 field (size 8).
|
||||
Box :: struct($T: Type) { x: T; }
|
||||
3
examples/0772-modules-qualified-generic-head-author/b.sx
Normal file
3
examples/0772-modules-qualified-generic-head-author/b.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Author B's generic `Box` — two s64 fields (size 16). Same template NAME as
|
||||
// A's, different layout: the qualified head must select by namespace author.
|
||||
Box :: struct($T: Type) { x: T; y: T; }
|
||||
28
examples/0773-modules-qualified-generic-alias-author.sx
Normal file
28
examples/0773-modules-qualified-generic-alias-author.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// A type alias whose RHS is a qualified generic head `ns.Box(args)` must
|
||||
// instantiate the template AUTHORED by `ns`'s module — not the global same-name
|
||||
// template that won the last-wins `struct_template_map`. Two namespaces each
|
||||
// author a same-name generic `Box($T)` with a DIFFERENT layout (a: one field,
|
||||
// b: two fields). `ABox :: a.Box(s64)` and `BBox :: b.Box(s64)` must register
|
||||
// aliases over their OWN module's template (sizes 8 and 16) and stay DISTINCT,
|
||||
// so the field unique to b's layout (`y`) is reachable only through `BBox`.
|
||||
//
|
||||
// Regression (Phase E4): before the alias-registration path selected the
|
||||
// qualified author, the const-decl alias `.call` branch stripped the head to the
|
||||
// bare name and read the global `struct_template_map`, so `ABox` and `BBox` both
|
||||
// instantiated the last-wins template (both size 16). attempt-9 fixed the
|
||||
// annotation head sites; this pins the alias-registration sibling.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0773-modules-qualified-generic-alias-author/a.sx";
|
||||
b :: #import "0773-modules-qualified-generic-alias-author/b.sx";
|
||||
|
||||
ABox :: a.Box(s64);
|
||||
BBox :: b.Box(s64);
|
||||
|
||||
main :: () -> s32 {
|
||||
ab : ABox = .{ x = 1 };
|
||||
bb : BBox = .{ x = 10, y = 20 };
|
||||
print("alias a={} b={}\n", size_of(ABox), size_of(BBox));
|
||||
print("ab.x={} bb.x={} bb.y={}\n", ab.x, bb.x, bb.y);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Author A's generic `Box` — one s64 field (size 8).
|
||||
Box :: struct($T: Type) { x: T; }
|
||||
@@ -0,0 +1,3 @@
|
||||
// Author B's generic `Box` — two s64 fields (size 16). Same template NAME as
|
||||
// A's, different layout: the qualified alias head must select by namespace author.
|
||||
Box :: struct($T: Type) { x: T; y: T; }
|
||||
28
examples/0774-modules-bare-generic-head-visible-author.sx
Normal file
28
examples/0774-modules-bare-generic-head-visible-author.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// A BARE generic struct head (`Box(s64)`) and a BARE generic alias
|
||||
// (`ABox :: Box(s64)`) must instantiate the template authored by the single
|
||||
// bare-VISIBLE author — this file's own author or a DIRECT (1-hop) flat import —
|
||||
// NOT the global last-wins `struct_template_map`, which a NON-visible 2-flat-hop
|
||||
// same-name template can win.
|
||||
//
|
||||
// `b.sx` declares a one-field `Box($T)` (size 8) and itself flat-imports `c.sx`,
|
||||
// which declares a two-field `Box($T)` (size 16). This file flat-imports ONLY
|
||||
// `b.sx`, so `b.Box` is one flat hop away (visible) and `c.Box` is two hops away
|
||||
// (NOT bare-visible, mirrors 0764/0706). The bare head `Box(s64)` and the bare
|
||||
// alias `ABox :: Box(s64)` must both select `b.Box` (size 8).
|
||||
//
|
||||
// Regression (Phase E4 finding #1): before the bare head/alias consulted the
|
||||
// source-keyed visible author, both fell through the `.unregistered` gate arm to
|
||||
// the global last-wins template and instantiated the non-visible `c.Box`
|
||||
// (size 16). Fail-before printed `size=16 alias=16`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0774-modules-bare-generic-head-visible-author/b.sx";
|
||||
|
||||
ABox :: Box(s64);
|
||||
|
||||
main :: () -> s32 {
|
||||
x : Box(s64) = .{ x = 1 };
|
||||
a : ABox = .{ x = 2 };
|
||||
print("size={} alias={} x={} a={}\n", size_of(Box(s64)), size_of(ABox), x.x, a.x);
|
||||
0
|
||||
}
|
||||
11
examples/0774-modules-bare-generic-head-visible-author/b.sx
Normal file
11
examples/0774-modules-bare-generic-head-visible-author/b.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// The bare-VISIBLE author: a one-field generic `Box` (size 8). `b.sx` itself
|
||||
// flat-imports `c.sx`, so `b_make`'s `Box(s64)` resolves here (the head is one
|
||||
// flat hop away in this module) — but a file that imports b.sx reaches `c.Box`
|
||||
// only at two hops, so it must NOT win the bare head in the importer.
|
||||
Box :: struct($T: Type) { x: T; }
|
||||
|
||||
#import "c.sx";
|
||||
|
||||
b_make :: () -> Box(s64) {
|
||||
.{ x = 99 }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user