spec: canonical type names — name MethodNameCollision as the workaround it is

This commit is contained in:
2026-05-11 00:33:57 +02:00
parent b840c340d4
commit 33932cb6e6
+44 -29
View File
@@ -263,39 +263,54 @@ many ambiguity cases are caught earlier by the validator.
- **Class names** (`ClassDef.name`, `InstanceDef.class`, - **Class names** (`ClassDef.name`, `InstanceDef.class`,
`SuperclassRef.class`, `Constraint.class`): NOT migrated to the `SuperclassRef.class`, `Constraint.class`): NOT migrated to the
module-scoped canonical form in this milestone. The asymmetry to module-scoped canonical form in this milestone — but we name the
Type::Con is intentional and rests on a real semantic difference: reason honestly rather than dressing it as "different semantics".
- For types, no workspace-global uniqueness rule exists. Two modules The asymmetry is structural, not semantic. AILang's class-method
may independently declare `type Foo`. Bare cross-module references resolution today (`ModuleGlobals::class_methods: IndexMap<String,
are therefore genuinely ambiguous; the iter-23.3 bug is the ClassMethodEntry>`, `crates/ailang-check/src/lib.rs:983`, consumed
concrete instance of that ambiguity surfacing in unification. by `mono::rewrite_class_method_calls`,
`crates/ailang-check/src/mono.rs:657-665`) is **name-driven**: the
call `eq x y` is resolved by looking up `"eq"` and getting back
exactly one `ClassMethodEntry` carrying its `class_name` and
`defining_module`. The class is reverse-inferred from the method.
- For classes, workspace-global uniqueness is enforced one level That single-entry guarantee is what `WorkspaceLoadError::MethodNameCollision`
deeper: `WorkspaceLoadError::MethodNameCollision` (iter 22b.2, (iter 22b.2, `workspace.rs:472`) enforces at load time. The
`workspace.rs:472`) rejects two classes that share any method causal chain is:
name. Two `class Eq`s with the conventional `eq` method cannot
coexist; `class_by_name: BTreeMap<String, &ClassDef>` (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`, 1. Class names are bare in the AST.
`SuperclassRef.class`, `Constraint.class`) gain a *defensive* 2. → Class lookup is `BTreeMap<String, &ClassDef>` (workspace-flat,
validator check in ct.1: reject qualified forms (containing `'.'`) bare-keyed).
explicitly, since the schema does not currently model qualified 3. → For method resolution to be tractable from a bare `Term::Var
class names. This prevents half-migrated files from silently { name: "eq" }`, the method name must resolve to a unique class.
loading. 4. → `MethodNameCollision` enforces uniqueness as a load-time
invariant.
If AILang ever revisits workspace-global method-name uniqueness In other words: `MethodNameCollision` is the load-time constraint
(e.g. introduces module-qualified method names so two libraries that makes bare class names viable. Qualifying class names in the
can each declare `class Eq` with its own `eq`), the symmetry to AST would not pay off in isolation — it would force step 3 to be
Type::Con returns and class names will need the same module-scoped replaced with type-driven dispatch (look up `eq` candidates by
treatment. That is a separate DESIGN-level milestone and is not argument type, Haskell-style), which is a Designfeld of its own
this spec's scope. with implications for inference, ambiguity rules, and diagnostic
shape. That is out of scope here; this milestone is canonical
type names, not a typeclass-dispatch overhaul.
What this spec DOES do for class-reference fields
(`InstanceDef.class`, `SuperclassRef.class`, `Constraint.class`):
ct.1's validator rejects *qualified* forms (containing `'.'`) on
those fields. The schema does not currently model qualified class
names; the rejection prevents half-migrated files from silently
loading. `ClassDef.name` itself remains bare (the file-top
`"name"` is the module context, same as `TypeDef.name`).
Forward note: if AILang ever revisits name-driven method
dispatch — e.g. to allow two libraries to each declare `class
Eq` with its own `eq` — the symmetry returns and class names
will then need the same module-scoped treatment, along with
type-driven method resolution. That is a separate DESIGN-level
milestone and is named here so the workaround is visible rather
than buried.
- **Surface notation in `.ailx`**: bare local names continue to be - **Surface notation in `.ailx`**: bare local names continue to be
the LLM-author convention. Prose parser/printer bridge — no the LLM-author convention. Prose parser/printer bridge — no