iter ct.1.3: validator rejects qualified class-name fields
This commit is contained in:
@@ -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:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user