From a546c5851c90b5edd6db4ea24dc9728a0bb4bdd5 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 18:18:12 +0200 Subject: [PATCH] iter 22b.2.7: missing-superclass-instance diagnostic --- crates/ail/src/main.rs | 17 +++++ crates/ailang-core/src/workspace.rs | 62 +++++++++++++++++++ ..._22b2_missing_superclass_instance.ail.json | 39 ++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 examples/test_22b2_missing_superclass_instance.ail.json diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 26385f2..cbd2c2c 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -1123,6 +1123,23 @@ fn workspace_error_to_diagnostic( "second_origin": second_origin, })), ), + W::MissingSuperclassInstance { + class, + superclass, + type_repr, + } => Some( + ailang_check::Diagnostic::error( + "missing-superclass-instance", + format!( + "instance `{class} {type_repr}` requires superclass instance `{superclass} {type_repr}`, but none was found" + ), + ) + .with_ctx(serde_json::json!({ + "class": class, + "superclass": superclass, + "type": type_repr, + })), + ), } } diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index 6eaf627..0029ac2 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -269,6 +269,19 @@ pub enum WorkspaceLoadError { first_origin: String, second_origin: String, }, + + /// Iter 22b.2: an instance `C T` was declared, but `C`'s + /// superclass `S` does not have an instance for the same type + /// `T`. Decision 11 single-superclass model requires `instance S + /// T` to exist whenever `instance C T` exists. + #[error( + "instance `{class} {type_repr}` requires superclass instance `{superclass} {type_repr}`, but none was found" + )] + MissingSuperclassInstance { + class: String, + superclass: String, + type_repr: String, + }, } /// Hash over the canonical bytes of a complete module. @@ -557,6 +570,29 @@ fn build_registry( } } + // Iter 22b.2: superclass-instance completeness. For every entry, + // walk the class's superclass chain and require an entry for each + // step at the same type-hash. + for (key, entry) in entries.iter() { + let (class_name, type_hash) = key; + let mut current = class_by_name.get(class_name.as_str()).copied(); + while let Some(c) = current { + if let Some(sc) = &c.superclass { + let sc_key = (sc.class.clone(), type_hash.clone()); + if !entries.contains_key(&sc_key) { + return Err(WorkspaceLoadError::MissingSuperclassInstance { + class: class_name.clone(), + superclass: sc.class.clone(), + type_repr: type_head_name(&entry.instance.type_), + }); + } + current = class_by_name.get(sc.class.as_str()).copied(); + } else { + break; + } + } + } + Ok(Registry { entries }) } @@ -1134,4 +1170,30 @@ mod tests { other => panic!("expected MethodNameCollision, got {other:?}"), } } + + /// Iter 22b.2: an instance `C T` whose class `C` declares a + /// superclass `S` requires that `instance S T` also exist in the + /// workspace. The fixture declares `class Eq a`, `class Ord a + /// extends Eq a`, and `instance Ord Int` — but no `instance Eq + /// Int` — so registry build must fire + /// `MissingSuperclassInstance`. Decision 11 single-superclass + /// model requires `instance S T` whenever `instance C T` exists. + #[test] + fn instance_without_superclass_instance_fires() { + let entry = std::path::PathBuf::from( + "../../examples/test_22b2_missing_superclass_instance.ail.json", + ); + let err = load_workspace(&entry) + .expect_err("must fire missing-superclass-instance"); + match err { + WorkspaceLoadError::MissingSuperclassInstance { + class, superclass, type_repr, + } => { + assert_eq!(class, "Ord"); + assert_eq!(superclass, "Eq"); + assert_eq!(type_repr, "Int"); + } + other => panic!("expected MissingSuperclassInstance, got {other:?}"), + } + } } diff --git a/examples/test_22b2_missing_superclass_instance.ail.json b/examples/test_22b2_missing_superclass_instance.ail.json new file mode 100644 index 0000000..cfa87ff --- /dev/null +++ b/examples/test_22b2_missing_superclass_instance.ail.json @@ -0,0 +1,39 @@ +{ + "schema": "ailang/v0", + "name": "test_22b2_missing_superclass_instance", + "imports": [], + "defs": [ + { + "kind": "class", "name": "Eq", "param": "a", + "methods": [{ + "name": "eq", + "type": { + "k": "fn", + "params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }], + "ret": { "k": "con", "name": "Bool" }, "effects": [] + } + }] + }, + { + "kind": "class", "name": "Ord", "param": "a", + "superclass": { "class": "Eq", "type": "a" }, + "methods": [{ + "name": "lt", + "type": { + "k": "fn", + "params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }], + "ret": { "k": "con", "name": "Bool" }, "effects": [] + } + }] + }, + { + "kind": "instance", + "class": "Ord", + "type": { "k": "con", "name": "Int" }, + "methods": [{ + "name": "lt", + "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } + }] + } + ] +}