iter 22b.2.7: missing-superclass-instance diagnostic

This commit is contained in:
2026-05-09 18:18:12 +02:00
parent b252f837af
commit a546c5851c
3 changed files with 118 additions and 0 deletions
+17
View File
@@ -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,
})),
),
}
}
+62
View File
@@ -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:?}"),
}
}
}
@@ -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 } }
}]
}
]
}