Merge branch 'wt-stdlib-base'
Some checks failed
Build / build-linux (push) Has been cancelled
Build / build-windows (push) Has been cancelled

This commit is contained in:
agra
2026-06-09 23:07:21 +03:00
498 changed files with 7896 additions and 580 deletions

60
docs/fork-c/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Fork C — setup contract (S0)
Zero-legacy name-resolution redesign (S0→S6), replacing E6cK. 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` (AE6a 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 AE6 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 (AE6a merged + the 6 harvested baseline-green cases + FFI
12xx14xx + 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.

View 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` (AE6a 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 S1S2 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 `07950798`, `0823`, `0828`) | `bash tests/run_examples.sh` | **540** active markers |
| FFI corpus | `examples/12xx14xx` (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/12xx14xx`". 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 12xx14xx
> 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 (S1S2; 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 12xx14xx + 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).

View 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` (AE6a 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). AE6a 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/08110829` (19 trees) from `flow/stdlib/E6b @ af737b0` — error-set /
protocol / param-impl / route-all same-name ambiguity & own-wins surfaces;
- `examples/07950798` (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.

View File

@@ -0,0 +1,72 @@
# S0.3 — AE6 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 AE6 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 AE6
artifact is mapped to **REUSED** (with its Fork C home) or **DELETED/TRANSITIONAL**
(with the S3/S6 phase that removes it). AE6a stays merged; the transitional E6b src
is never merged (see `S0.2-…`).
## A. REUSED — AE6 work that becomes Fork C infrastructure
| AE6 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 E1E6a 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 AE6 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.

View File

@@ -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";

View 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
}

View File

@@ -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;
}

View 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;
}

View 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]);
}

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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; }

View File

@@ -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];

View File

@@ -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";

View File

@@ -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";

View File

@@ -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

View File

@@ -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,

View File

@@ -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";

View File

@@ -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
}

View File

@@ -0,0 +1 @@
DEP_LEN :: 3;

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
Secret :: struct {
x: s32;
y: s32;
}

View File

@@ -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 }
}

View File

@@ -0,0 +1,5 @@
Color :: enum {
red;
green;
blue;
}

View 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
}

View File

@@ -0,0 +1,2 @@
// A flat-visible VALUE/FUNCTION named `Secret` (not a type).
Secret :: () -> s32 { 0 }

View File

@@ -0,0 +1,4 @@
Secret :: struct {
x: s32;
y: s32;
}

View 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
}

View File

@@ -0,0 +1,4 @@
Secret :: struct {
x: s32;
y: s32;
}

View File

@@ -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
}

View File

@@ -0,0 +1 @@
Secret :: s32;

View 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
}

View File

@@ -0,0 +1,4 @@
Secret :: struct {
x: s32;
y: s32;
}

View File

@@ -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
}

View File

@@ -0,0 +1,5 @@
Box :: struct($T: Type) {
value: T;
}
Secret :: Box(s32);

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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
}

View File

@@ -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 }; }

View File

@@ -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 }; }

View 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
}

View 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 }; }

View 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 }; }

View 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
}

View 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 }; }

View 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
}

View File

@@ -0,0 +1,2 @@
// One of two flat-imported authors of a same-name `Thing`.
Thing :: struct { a: s64; }

View 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; }

View 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
}

View 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; }

View 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; }

View 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
}

View 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 }; }

View 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;
}

View 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
}

View 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; }

View 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;
}

View 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;
}

View 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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
}

View 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 }
}

View File

@@ -0,0 +1 @@
COnly :: struct { v: s64 = 0; }

View 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
}

View File

@@ -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 }
}

View File

@@ -0,0 +1 @@
Box :: struct($T: Type) { v: T; }

View File

@@ -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
}

View File

@@ -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]
}

View File

@@ -0,0 +1,3 @@
Nums :: [2]s64;
COnly :: struct { v: s64 = 0; }

View 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
}

View File

@@ -0,0 +1,3 @@
Nums :: [2]s64;
COnly :: struct { v: s64 = 0; }

View 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
}

View 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;

View 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;

View 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
}

View 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); }

View 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
}

View 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; };
}

View 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; };
}

View 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
}

View 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;

View 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; };
}

View 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
}

View File

@@ -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; }

View 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; };
}

View 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
}

View File

@@ -0,0 +1,2 @@
// Author A's generic `Box` — one s64 field (size 8).
Box :: struct($T: Type) { x: T; }

View 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; }

View 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
}

View File

@@ -0,0 +1,2 @@
// Author A's generic `Box` — one s64 field (size 8).
Box :: struct($T: Type) { x: T; }

View 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 alias head must select by namespace author.
Box :: struct($T: Type) { x: T; y: T; }

View 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
}

View 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