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,
|
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
|
/// Iter 22b.1: extract the head-constructor name of a type, for the
|
||||||
/// "where is this type defined" lookup and for diagnostic-message
|
/// "where is this type defined" lookup and for diagnostic-message
|
||||||
/// rendering.
|
/// rendering.
|
||||||
@@ -2044,4 +2106,129 @@ mod tests {
|
|||||||
other => panic!("expected BareCrossModuleTypeRef, got {other:?}"),
|
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