iter 22b.2.5: overriding-non-existent-method diagnostic

This commit is contained in:
2026-05-09 17:59:10 +02:00
parent 257b47625d
commit 634bd5eca3
3 changed files with 111 additions and 0 deletions
+17
View File
@@ -1029,6 +1029,23 @@ fn workspace_error_to_diagnostic(
"method": method,
})),
),
W::OverridingNonExistentMethod {
class,
type_repr,
method,
} => Some(
ailang_check::Diagnostic::error(
"overriding-non-existent-method",
format!(
"instance `{class} {type_repr}` provides body for method `{method}`, but class `{class}` does not declare it"
),
)
.with_ctx(serde_json::json!({
"class": class,
"type": type_repr,
"method": method,
})),
),
W::KindMismatch {
class,
param,
+56
View File
@@ -200,6 +200,18 @@ pub enum WorkspaceLoadError {
method: String,
},
/// Iter 22b.2: an instance specifies a body for a method name
/// that the corresponding class does not declare. Symmetric to
/// `MissingMethod` but in the opposite direction.
#[error(
"instance `{class} {type_repr}` provides body for method `{method}`, but class `{class}` does not declare it"
)]
OverridingNonExistentMethod {
class: String,
type_repr: String,
method: String,
},
/// Iter 22b.2: class-schema validation. The class parameter
/// appears in applied position (e.g. as the head of a
/// `Type::Con { name == param, args.len() > 0 }`) inside a method
@@ -420,6 +432,23 @@ fn build_registry(
});
}
}
// Symmetric to MissingMethod: an instance must
// not specify a body for a method name the class
// never declared. Decision 11 forbids ad-hoc
// additions to a class's method set at the
// instance site.
let declared: BTreeSet<&str> =
class_def.methods.iter().map(|m| m.name.as_str()).collect();
for inst_method in &inst.methods {
if !declared.contains(inst_method.name.as_str()) {
return Err(WorkspaceLoadError::OverridingNonExistentMethod {
class: inst.class.clone(),
type_repr: type_repr.clone(),
method: inst_method.name.clone(),
});
}
}
}
entries.insert(
@@ -890,6 +919,33 @@ mod tests {
}
}
/// Iter 22b.2: an instance that specifies a body for a method
/// name the class never declared must fire
/// `OverridingNonExistentMethod`. Symmetric counterpart to
/// `MissingMethod`: the latter fires when the class declares a
/// non-default method that the instance omits; this one fires
/// when the instance provides a body the class did not ask for.
/// Decision 11 forbids ad-hoc additions to a class's method set
/// at the instance site.
#[test]
fn instance_overriding_nonexistent_method_fires() {
let entry = std::path::PathBuf::from(
"../../examples/test_22b2_overriding_nonexistent.ail.json",
);
let err = load_workspace(&entry)
.expect_err("must fire overriding-non-existent-method");
match err {
WorkspaceLoadError::OverridingNonExistentMethod {
class, type_repr, method,
} => {
assert_eq!(class, "Eq");
assert_eq!(type_repr, "Int");
assert_eq!(method, "ne");
}
other => panic!("expected OverridingNonExistentMethod, got {other:?}"),
}
}
/// Iter 22b.2: a class method's `Type::Forall` whose `constraints`
/// reference a type variable that is neither bound by `Forall.vars`
/// nor equal to the class's `param` must fire
@@ -0,0 +1,38 @@
{
"schema": "ailang/v0",
"name": "test_22b2_overriding_nonexistent",
"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": "instance",
"class": "Eq",
"type": { "k": "con", "name": "Int" },
"methods": [
{
"name": "eq",
"body": { "t": "lit", "lit": { "kind": "bool", "value": true } }
},
{
"name": "ne",
"body": { "t": "lit", "lit": { "kind": "bool", "value": false } }
}
]
}
]
}