diff --git a/docs/specs/2026-05-10-canonical-type-names.md b/docs/specs/2026-05-10-canonical-type-names.md index 334011d..9ad6ccc 100644 --- a/docs/specs/2026-05-10-canonical-type-names.md +++ b/docs/specs/2026-05-10-canonical-type-names.md @@ -85,7 +85,7 @@ The milestone decomposes into four iterations. | Iter | Scope | |------|-------| -| **ct.1** | `.ail.json` schema validator at `load_workspace` (the rule above) + one-time migration. The migration is targeted: a workspace-walker tool (small Rust binary or test-driven script under `crates/ail/src/bin/`) reads every `.ail.json` under `examples/`, finds bare cross-module Type::Con names (i.e. bare names that don't resolve to a local type def in the same file), looks up the qualified form via the import graph, rewrites in place. Files with only intra-module refs are unchanged. Single bulk commit for the rewritten fixtures. | +| **ct.1** | `.ail.json` schema validator at `load_workspace` (the rule above) + one-time migration. The migration is targeted: a workspace-walker tool (small Rust binary or test-driven script under `crates/ail/src/bin/`) reads every `.ail.json` under `examples/`, finds bare cross-module Type::Con names (i.e. bare names that don't resolve to a local type def in the same file), looks up the qualified form via the import graph, rewrites in place. Files with only intra-module refs are unchanged. Single bulk commit for the rewritten fixtures. The validator also rejects *qualified* class-name fields (`InstanceDef.class`, `SuperclassRef.class`, `Constraint.class`) — those stay bare per the Out-of-scope note. | | **ct.2** | Remove obsolete typechecker mechanisms that papered over bare cross-module refs: `Pattern::Ctor`'s imports-fallback (iter 15a, ~`crates/ailang-check/src/lib.rs:2486-2521`) and `Term::Ctor`'s synth imports-fallback (iter 23.1.3, ~lib.rs:1979-2025). Both become unreachable because bare-non-primitive type-name reaches them only when local (direct lookup succeeds) — never as a stale cross-module ref. **`qualify_local_types` (iter 15a, ~lib.rs:1848) STAYS** — it correctly handles the cross-boundary translation when a cross-module fn signature is pulled into the current module's typecheck context. Under the new rule it becomes more prominent (called uniformly at every cross-module fn-lookup boundary, where today it runs only at the Term::Var prefix-qualified path); this iter audits its call sites for consistency. | | **ct.3** | Remove obsolete codegen + mono mechanisms: `lookup_ctor_by_type`'s imports-fallback (iter 23.1.4) and `apply_per_module_ctor_index_overlay` in mono (iter 23.2 fix `84dcc46`). The codegen `lower_workspace_inner` implicit-prelude import_map entry (iter 23.2.4 fix `be882c4`) STAYS — fn-name resolution for mono symbols, not Type::Con names. | | **ct.4** | DESIGN.md amendments + JOURNAL migration entry + re-baseline of hash-pinned regression tests that touched cross-module-ref fixtures + IR snapshot refresh (where affected). Form-B prose round-trip test extended to cover the qualifier-trim-on-print behaviour for intra-module refs. | @@ -261,12 +261,41 @@ many ambiguity cases are caught earlier by the validator. local-to-this-file, qualified = explicit cross-module). No imports-fallback needed in the pattern path. -- **Class names** (`class Eq`, `instance Eq Int`, `class Ord extends Eq`, - `Constraint.class`): same conceptual shape as Type::Con but separate - namespace, separate uniqueness check (`MethodNameCollision`). Whether - class names should also follow the same module-scoping rule is a - separate spec question deferred to a future milestone. This spec - touches Type::Con only. +- **Class names** (`ClassDef.name`, `InstanceDef.class`, + `SuperclassRef.class`, `Constraint.class`): NOT migrated to the + module-scoped canonical form in this milestone. The asymmetry to + Type::Con is intentional and rests on a real semantic difference: + + - For types, no workspace-global uniqueness rule exists. Two modules + may independently declare `type Foo`. Bare cross-module references + are therefore genuinely ambiguous; the iter-23.3 bug is the + concrete instance of that ambiguity surfacing in unification. + + - For classes, workspace-global uniqueness is enforced one level + deeper: `WorkspaceLoadError::MethodNameCollision` (iter 22b.2, + `workspace.rs:472`) rejects two classes that share any method + name. Two `class Eq`s with the conventional `eq` method cannot + coexist; `class_by_name: BTreeMap` (workspace- + flat) is consistent with that invariant. Empirically no + `examples/` fixture today has a bare cross-module class + reference that resolves ambiguously — the cross-module fixtures + that exist (`test_22b3_xmod_e2e_main`) call methods bare and rely + on the method→class→instance chain, never on a direct class-name + reference. + + Three class-reference fields (`InstanceDef.class`, + `SuperclassRef.class`, `Constraint.class`) gain a *defensive* + validator check in ct.1: reject qualified forms (containing `'.'`) + explicitly, since the schema does not currently model qualified + class names. This prevents half-migrated files from silently + loading. + + If AILang ever revisits workspace-global method-name uniqueness + (e.g. introduces module-qualified method names so two libraries + can each declare `class Eq` with its own `eq`), the symmetry to + Type::Con returns and class names will need the same module-scoped + treatment. That is a separate DESIGN-level milestone and is not + this spec's scope. - **Surface notation in `.ailx`**: bare local names continue to be the LLM-author convention. Prose parser/printer bridge — no