iter 22b.2.3: invalid-superclass-param class-schema diagnostic

This commit is contained in:
2026-05-09 17:50:29 +02:00
parent 928deb9d74
commit 4b84e781d4
3 changed files with 111 additions and 0 deletions
+19
View File
@@ -1049,6 +1049,25 @@ fn workspace_error_to_diagnostic(
"defining_module": defining_module,
})),
),
W::InvalidSuperclassParam {
class,
superclass,
expected_param,
got_type,
} => Some(
ailang_check::Diagnostic::error(
"invalid-superclass-param",
format!(
"class `{class}` declares superclass `{superclass} {got_type}`, but its own parameter is `{expected_param}` — superclass `type` must equal class `param`"
),
)
.with_ctx(serde_json::json!({
"class": class,
"superclass": superclass,
"expected_param": expected_param,
"got_type": got_type,
})),
),
}
}
+50
View File
@@ -215,6 +215,21 @@ pub enum WorkspaceLoadError {
method: String,
defining_module: String,
},
/// Iter 22b.2: class-schema validation. A class's `superclass.type`
/// does not equal its own `param`. Decision 11 single-superclass
/// model requires the superclass to be applied to the same param
/// (e.g. `class Ord a extends Eq a`, not `extends Eq b`).
#[error(
"class `{class}` declares superclass `{superclass} {got_type}`, but its own parameter is `{expected_param}` \
— superclass `type` must equal class `param`"
)]
InvalidSuperclassParam {
class: String,
superclass: String,
expected_param: String,
got_type: String,
},
}
/// Hash over the canonical bytes of a complete module.
@@ -425,6 +440,16 @@ fn validate_classdefs(
defining_module: mod_name.clone(),
})?;
}
if let Some(sc) = &c.superclass {
if sc.type_ != c.param {
return Err(WorkspaceLoadError::InvalidSuperclassParam {
class: c.name.clone(),
superclass: sc.class.clone(),
expected_param: c.param.clone(),
got_type: sc.type_.clone(),
});
}
}
}
}
}
@@ -806,4 +831,29 @@ mod tests {
other => panic!("expected KindMismatch, got {other:?}"),
}
}
/// Iter 22b.2: a class whose `superclass.type` differs from its
/// own `param` (e.g. `class Ord a extends Eq b`) must fire
/// `InvalidSuperclassParam`. Decision 11 axis 1 ("single
/// superclass, applied to the same param") makes the only legal
/// shape `extends Eq a` when the parent class has `param: "a"`.
#[test]
fn superclass_with_wrong_param_fires_invalid_superclass_param() {
let entry = std::path::PathBuf::from(
"../../examples/test_22b2_invalid_superclass_param.ail.json",
);
let err = load_workspace(&entry)
.expect_err("must fire invalid-superclass-param");
match err {
WorkspaceLoadError::InvalidSuperclassParam {
class, superclass, expected_param, got_type,
} => {
assert_eq!(class, "Ord");
assert_eq!(superclass, "Eq");
assert_eq!(expected_param, "a");
assert_eq!(got_type, "b");
}
other => panic!("expected InvalidSuperclassParam, got {other:?}"),
}
}
}
@@ -0,0 +1,42 @@
{
"schema": "ailang/v0",
"name": "test_22b2_invalid_superclass_param",
"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": "b" },
"methods": [
{
"name": "lt",
"type": {
"k": "fn",
"params": [
{ "k": "var", "name": "a" },
{ "k": "var", "name": "a" }
],
"ret": { "k": "con", "name": "Bool" },
"effects": []
}
}
]
}
]
}