iter ct.1.3: validator rejects qualified class-name fields

This commit is contained in:
2026-05-11 01:08:50 +02:00
parent 3723495d8a
commit 9e2e84332f
+187
View File
@@ -824,6 +824,7 @@ pub(crate) fn validate_canonical_type_names(
type_name, mod_name, &local_types, &import_names,
)
})?;
check_class_name_fields(def, mod_name)?;
}
}
@@ -1146,6 +1147,67 @@ where
}
}
/// ct.1: reject any `.` in the four class-reference fields.
/// `ClassDef.name`, `InstanceDef.class`, `SuperclassRef.class`,
/// `Constraint.class`. Per spec §"Out of scope: Class names": class
/// names are NOT module-qualified in this milestone, so any
/// qualified form indicates a half-migrated file.
fn check_class_name_fields(
def: &Def,
owning_module: &str,
) -> Result<(), WorkspaceLoadError> {
fn fire(
owning_module: &str,
name: &str,
field: &'static str,
) -> WorkspaceLoadError {
WorkspaceLoadError::QualifiedClassName {
module: owning_module.to_string(),
name: name.to_string(),
field,
}
}
match def {
Def::Class(cd) => {
if cd.name.contains('.') {
return Err(fire(owning_module, &cd.name, "ClassDef.name"));
}
if let Some(sc) = &cd.superclass {
if sc.class.contains('.') {
return Err(fire(owning_module, &sc.class, "SuperclassRef.class"));
}
}
for m in &cd.methods {
if let Type::Forall { constraints, .. } = &m.ty {
for c in constraints {
if c.class.contains('.') {
return Err(fire(owning_module, &c.class, "Constraint.class"));
}
}
}
}
Ok(())
}
Def::Instance(id) => {
if id.class.contains('.') {
return Err(fire(owning_module, &id.class, "InstanceDef.class"));
}
Ok(())
}
Def::Fn(fd) => {
if let Type::Forall { constraints, .. } = &fd.ty {
for c in constraints {
if c.class.contains('.') {
return Err(fire(owning_module, &c.class, "Constraint.class"));
}
}
}
Ok(())
}
Def::Const(_) | Def::Type(_) => Ok(()),
}
}
/// Iter 22b.1: extract the head-constructor name of a type, for the
/// "where is this type defined" lookup and for diagnostic-message
/// rendering.
@@ -2044,4 +2106,129 @@ mod tests {
other => panic!("expected BareCrossModuleTypeRef, got {other:?}"),
}
}
/// ct.1: a qualified `ClassDef.name` (e.g. `other.Eq`) must fire
/// `QualifiedClassName`. Class names stay bare in this milestone.
#[test]
fn ct1_validator_rejects_qualified_classdef_name() {
let m: Module = serde_json::from_value(serde_json::json!({
"schema": crate::SCHEMA,
"name": "m",
"imports": [],
"defs": [{
"kind": "class",
"name": "other.MyEq",
"param": "a",
"methods": []
}],
})).unwrap();
let mut modules = BTreeMap::new();
modules.insert("m".to_string(), m);
let err = validate_canonical_type_names(&modules)
.expect_err("qualified ClassDef.name must be rejected");
match err {
WorkspaceLoadError::QualifiedClassName { module, name, field } => {
assert_eq!(module, "m");
assert_eq!(name, "other.MyEq");
assert_eq!(field, "ClassDef.name");
}
other => panic!("expected QualifiedClassName, got {other:?}"),
}
}
/// ct.1: a qualified `InstanceDef.class` must fire
/// `QualifiedClassName`.
#[test]
fn ct1_validator_rejects_qualified_instancedef_class() {
let m: Module = serde_json::from_value(serde_json::json!({
"schema": crate::SCHEMA,
"name": "m",
"imports": [],
"defs": [{
"kind": "instance",
"class": "other.MyEq",
"type": { "k": "con", "name": "Int" },
"methods": []
}],
})).unwrap();
let mut modules = BTreeMap::new();
modules.insert("m".to_string(), m);
let err = validate_canonical_type_names(&modules)
.expect_err("qualified InstanceDef.class must be rejected");
match err {
WorkspaceLoadError::QualifiedClassName { name, field, .. } => {
assert_eq!(name, "other.MyEq");
assert_eq!(field, "InstanceDef.class");
}
other => panic!("expected QualifiedClassName, got {other:?}"),
}
}
/// ct.1: a qualified `SuperclassRef.class` must fire
/// `QualifiedClassName`.
#[test]
fn ct1_validator_rejects_qualified_superclassref_class() {
let m: Module = serde_json::from_value(serde_json::json!({
"schema": crate::SCHEMA,
"name": "m",
"imports": [],
"defs": [{
"kind": "class",
"name": "MyOrd",
"param": "a",
"superclass": { "class": "other.MyEq", "type": "a" },
"methods": []
}],
})).unwrap();
let mut modules = BTreeMap::new();
modules.insert("m".to_string(), m);
let err = validate_canonical_type_names(&modules)
.expect_err("qualified SuperclassRef.class must be rejected");
match err {
WorkspaceLoadError::QualifiedClassName { name, field, .. } => {
assert_eq!(name, "other.MyEq");
assert_eq!(field, "SuperclassRef.class");
}
other => panic!("expected QualifiedClassName, got {other:?}"),
}
}
/// ct.1: a qualified `Constraint.class` (inside a `Type::Forall`)
/// must fire `QualifiedClassName`.
#[test]
fn ct1_validator_rejects_qualified_constraint_class() {
let m: Module = serde_json::from_value(serde_json::json!({
"schema": crate::SCHEMA,
"name": "m",
"imports": [],
"defs": [{
"kind": "fn",
"name": "f",
"type": {
"k": "forall",
"vars": ["a"],
"constraints": [
{ "class": "other.MyEq", "type": { "k": "var", "name": "a" } }
],
"body": {
"k": "fn", "params": [{ "k": "var", "name": "a" }],
"ret": { "k": "con", "name": "Unit" }, "effects": []
}
},
"params": ["x"],
"body": { "t": "lit", "lit": { "kind": "unit" } }
}],
})).unwrap();
let mut modules = BTreeMap::new();
modules.insert("m".to_string(), m);
let err = validate_canonical_type_names(&modules)
.expect_err("qualified Constraint.class must be rejected");
match err {
WorkspaceLoadError::QualifiedClassName { name, field, .. } => {
assert_eq!(name, "other.MyEq");
assert_eq!(field, "Constraint.class");
}
other => panic!("expected QualifiedClassName, got {other:?}"),
}
}
}