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:
@@ -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 } }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 } }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user