iter mq.3: retire MethodNameCollision + multi-class E2E + DESIGN.md sync

Closes the module-qualified-class-names milestone. Deletes the
workspace-load workaround; the two-libraries case now resolves via
type-driven dispatch at the call site with explicit qualifier as the
LLM-author's disambiguation tool. Resolves both mq.2 known-debt items
(active_declared_constraints plumbing, (class,method) re-key). New
synth warnings channel emits class-method-shadowed-by-fn at all three
fn-precedence branches per spec section Class-fn collisions. Three new
E2E fixtures + integration tests cover the ambiguous, qualified, and
class-fn-shadow trajectories. DESIGN.md class-names paragraph rewritten,
new section Method dispatch added. Roadmap P2 marked done, milestone-24
unblocked for re-brainstorm.

9/9 tasks. 545 tests green. bench/compile_check.py + cross_lang.py
exit 0; bench/check.py exit 1 (2 noise-class regressions, runtime
uncoupled to typecheck iter).
This commit is contained in:
2026-05-13 02:19:21 +02:00
parent 90075715d9
commit 99d3968656
23 changed files with 1399 additions and 399 deletions
+36
View File
@@ -0,0 +1,36 @@
{
"schema": "ailang/v0",
"name": "mq3_class_eq_vs_fn_eq",
"imports": [
{ "module": "mq3_class_eq_vs_fn_eq_classmod" },
{ "module": "mq3_class_eq_vs_fn_eq_fnmod" }
],
"defs": [
{
"kind": "fn",
"name": "main",
"doc": "mq.3.6 fixture (c): bare `myeq 1 2` in a workspace with `class MyEq` (in classmod) and `fn myeq` (in fnmod) both imported. Fn wins per lookup precedence; the typechecker emits `class-method-shadowed-by-fn` warning so the LLM-author sees the shadow.",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"body": {
"t": "do",
"op": "io/print_bool",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "myeq" },
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 1 } },
{ "t": "lit", "lit": { "kind": "int", "value": 2 } }
]
}
]
}
}
]
}
@@ -0,0 +1,47 @@
{
"schema": "ailang/v0",
"name": "mq3_class_eq_vs_fn_eq_classmod",
"imports": [],
"defs": [
{
"kind": "class",
"name": "MyEq",
"param": "a",
"methods": [
{
"name": "myeq",
"type": {
"k": "fn",
"params": [
{ "k": "var", "name": "a" },
{ "k": "var", "name": "a" }
],
"param_modes": ["borrow", "borrow"],
"ret": { "k": "con", "name": "Bool" },
"effects": []
}
}
]
},
{
"kind": "instance",
"class": "MyEq",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "myeq",
"body": {
"t": "lam",
"params": ["x", "y"],
"paramTypes": [
{ "k": "con", "name": "Int" },
{ "k": "con", "name": "Int" }
],
"retType": { "k": "con", "name": "Bool" },
"body": { "t": "lit", "lit": { "kind": "bool", "value": true } }
}
}
]
}
]
}
@@ -0,0 +1,24 @@
{
"schema": "ailang/v0",
"name": "mq3_class_eq_vs_fn_eq_fnmod",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "myeq",
"doc": "mq.3.6 fixture (c) helper: free fn `myeq` that shadows the class method `myeq` declared in `mq3_class_eq_vs_fn_eq_classmod`. Always returns false so the fn-wins precedence is observable at runtime if the e2e test ever runs the binary.",
"type": {
"k": "fn",
"params": [
{ "k": "con", "name": "Int" },
{ "k": "con", "name": "Int" }
],
"param_modes": ["borrow", "borrow"],
"ret": { "k": "con", "name": "Bool" },
"effects": []
},
"params": ["x", "y"],
"body": { "t": "lit", "lit": { "kind": "bool", "value": false } }
}
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"schema": "ailang/v0",
"name": "mq3_two_show_ambiguous",
"imports": [
{ "module": "mq3_two_show_ambiguous_a" },
{ "module": "mq3_two_show_ambiguous_b" }
],
"defs": [
{
"kind": "fn",
"name": "main",
"doc": "mq.3.6 fixture (a): bare `show 42` in a workspace where two Show classes (in modA and modB) each ship Show Int. Without an explicit qualifier the dispatch is genuinely ambiguous; the typechecker must fire `ambiguous-method-resolution` naming both candidate classes.",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"body": {
"t": "do",
"op": "io/print_str",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
]
}
}
]
}
@@ -0,0 +1,45 @@
{
"schema": "ailang/v0",
"name": "mq3_two_show_ambiguous_a",
"imports": [],
"defs": [
{
"kind": "class",
"name": "Show",
"param": "a",
"methods": [
{
"name": "show",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
"param_modes": ["borrow"],
"ret": { "k": "con", "name": "Str" },
"effects": []
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Int" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "int_to_str" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
}
]
}
@@ -0,0 +1,45 @@
{
"schema": "ailang/v0",
"name": "mq3_two_show_ambiguous_b",
"imports": [],
"defs": [
{
"kind": "class",
"name": "Show",
"param": "a",
"methods": [
{
"name": "show",
"type": {
"k": "fn",
"params": [{ "k": "var", "name": "a" }],
"param_modes": ["borrow"],
"ret": { "k": "con", "name": "Str" },
"effects": []
}
}
]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "show",
"body": {
"t": "lam",
"params": ["x"],
"paramTypes": [{ "k": "con", "name": "Int" }],
"retType": { "k": "con", "name": "Str" },
"body": {
"t": "app",
"fn": { "t": "var", "name": "int_to_str" },
"args": [{ "t": "var", "name": "x" }]
}
}
}
]
}
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"schema": "ailang/v0",
"name": "mq3_two_show_qualified",
"imports": [
{ "module": "mq3_two_show_ambiguous_a" },
{ "module": "mq3_two_show_ambiguous_b" }
],
"defs": [
{
"kind": "fn",
"name": "main",
"doc": "mq.3.6 fixture (b): explicit qualifier `mq3_two_show_ambiguous_a.Show.show 42` disambiguates the same workspace as fixture (a). Resolves cleanly to modA's class; typecheck passes.",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"body": {
"t": "do",
"op": "io/print_str",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "mq3_two_show_ambiguous_a.Show.show" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
]
}
}
]
}