iter 22b.2.10: fix — positive green-path test, drop dead let _ = ws

Adds the missing positive counterpart to the no-instance test:
`show 42` with `instance Show Int` PRESENT must typecheck green.
This guards against registry-keying or threading bugs that would
otherwise sneak past the negative-only test (an empty registry at
typecheck time, or a key shape mismatch, would silently fire
no-instance even when the instance exists).

Also drops the dead `let _ = ws;` and its stale comment in
`check_in_workspace` — the line above already consumes
`ws.registry`, so the explicit drop is redundant. The remaining
comment is folded into the registry-threading comment so the
"registry is the only thing threaded" rationale is preserved.
This commit is contained in:
2026-05-09 19:13:32 +02:00
parent 3600e82ec2
commit 47bec74b26
3 changed files with 67 additions and 5 deletions
+23
View File
@@ -235,3 +235,26 @@ fn fn_calling_class_method_at_type_without_instance_fires_no_instance() {
diagnostics.iter().map(|d| &d.code).collect::<Vec<_>>()
);
}
/// Property protected: the positive counterpart to `no-instance` — when a
/// monomorphic fn calls a class method at a fully-concrete type AND a
/// matching `instance C T` exists in the workspace, the call typechecks
/// green. The concrete-residual path must look up `(class, type_hash(T))`
/// in `env.workspace_registry` and find a hit, so no diagnostic fires.
/// This guards against registry-keying or threading bugs that would
/// otherwise sneak past the negative-only test: a registry that is empty
/// at typecheck time, or keyed under the wrong shape, would silently fire
/// `no-instance` even when the instance is present. Fixture: `main` calls
/// `show 42` with `instance Show Int` declared in the same workspace.
#[test]
fn fn_calling_class_method_at_type_with_instance_typechecks_green() {
let entry = examples_dir().join("test_22b2_instance_present.ail.json");
let ws = ailang_core::workspace::load_workspace(&entry)
.expect("workspace loads");
let diagnostics = ailang_check::check_workspace(&ws);
assert!(
diagnostics.is_empty(),
"expected no diagnostics — instance Show Int is present, registry lookup must hit; got: {:?}",
diagnostics
);
}
+4 -5
View File
@@ -1177,12 +1177,11 @@ fn check_in_workspace(
env.class_superclasses = class_superclasses;
// Iter 22b.2 (Task 10): thread the workspace-load-time instance
// registry into the env so `check_fn`'s concrete-residual path can
// look up `(class, type_hash)` keys for the no-instance check.
// look up `(class, type_hash)` keys for the no-instance check. The
// registry is the only field of `ws` consumed at typecheck time;
// cross-module name resolution uses `module_globals` / `module_types`
// built earlier in this function.
env.workspace_registry = ws.registry.clone();
// Workspace isn't otherwise needed in the env; cross-module lookup
// uses only `module_globals` / `module_types`. The ws reference is
// preserved in case cross-module ADTs are added later.
let _ = ws;
for def in &m.defs {
if let Err(e) = check_def(def, &env) {
@@ -0,0 +1,40 @@
{
"schema": "ailang/v0",
"name": "test_22b2_instance_present",
"imports": [],
"defs": [
{
"kind": "class", "name": "Show", "param": "a",
"methods": [{
"name": "show",
"type": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Str" }, "effects": []
}
}]
},
{
"kind": "instance",
"class": "Show",
"type": { "k": "con", "name": "Int" },
"methods": [{
"name": "show",
"body": { "t": "lit", "lit": { "kind": "str", "value": "n" } }
}]
},
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn", "params": [], "ret": { "k": "con", "name": "Str" },
"effects": []
},
"params": [],
"body": {
"t": "app",
"fn": { "t": "var", "name": "show" },
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 42 } }]
}
}
]
}