iter ct.1.5a-followup: forall-constraints recursion + docstring + drop iteration-tag comments
Three Minor items from the ct.1.5a quality review: 1. normalize_type_for_registry now recurses into Type::Forall.constraints[].type_, symmetric to walk_type. Closes a latent gap: a Forall with bare cross-module constraint types reaching the registry would have produced a key disagreeing with the qualified form. RED test: ct1_5a_normalize_recurses_into_forall_constraints. 2. Documented the bare-name limitation on Registry::type_def_module: two modules each defining 'type Foo' collide at insert; the proper fix (re-key as (owning_module, bare_name)) is out of ct.1's scope. 3. Removed the four iter-tag explanatory paragraphs at the normalize_type_for_lookup call sites (check/lib.rs check_fn, mono.rs three sites). Replaced each with a one-line pointer to the helper's docstring per project comment policy.
This commit is contained in:
@@ -1538,11 +1538,7 @@ fn check_fn(f: &FnDef, env: &Env) -> Result<()> {
|
||||
// → no-instance. The hash uses `canonical::type_hash`, the
|
||||
// same key shape `workspace::build_registry` writes with, so
|
||||
// representation-equal types match deterministically.
|
||||
// ct.1.5a: route `r_ty` through
|
||||
// `Registry::normalize_type_for_lookup` so a bare
|
||||
// `Type::Con` produced for an instance whose head-type lives
|
||||
// in another module rewrites to the qualified form the
|
||||
// registry actually keyed on.
|
||||
// normalize to match Registry::normalize_type_for_lookup contract
|
||||
let r_ty_norm = env.workspace_registry.normalize_type_for_lookup(&r_ty);
|
||||
let key = (
|
||||
r.class.clone(),
|
||||
|
||||
@@ -115,12 +115,7 @@ pub fn monomorphise_workspace(ws: &Workspace) -> Result<Workspace> {
|
||||
// never mutates the registry.
|
||||
for t in &new {
|
||||
let key = mono_target_key(t);
|
||||
// ct.1.5a: route `t.type_` through
|
||||
// `Registry::normalize_type_for_lookup` so a bare
|
||||
// `Type::Con` produced for an instance whose head-type lives
|
||||
// in another module rewrites to the qualified form the
|
||||
// registry actually keyed on. Symmetric to the
|
||||
// `collect_targets_*` lookups above.
|
||||
// normalize to match Registry::normalize_type_for_lookup contract
|
||||
let t_ty_norm = ws_owned.registry.normalize_type_for_lookup(&t.type_);
|
||||
let registry_key =
|
||||
(t.class.clone(), ailang_core::canonical::type_hash(&t_ty_norm));
|
||||
@@ -521,9 +516,7 @@ pub fn collect_mono_targets(
|
||||
if !crate::is_fully_concrete(&r_ty) {
|
||||
continue;
|
||||
}
|
||||
// ct.1.5a: normalise `r_ty` to its always-qualified canonical
|
||||
// form before hashing — symmetric to the write side in
|
||||
// `workspace::build_registry`.
|
||||
// normalize to match Registry::normalize_type_for_lookup contract
|
||||
let r_ty_norm = env.workspace_registry.normalize_type_for_lookup(&r_ty);
|
||||
let key = (r.class.clone(), ailang_core::canonical::type_hash(&r_ty_norm));
|
||||
let entry = match env.workspace_registry.entries.get(&key) {
|
||||
@@ -879,9 +872,7 @@ pub(crate) fn collect_residuals_ordered(
|
||||
out.push(None);
|
||||
continue;
|
||||
}
|
||||
// ct.1.5a: normalise `r_ty` to its always-qualified canonical
|
||||
// form before hashing — symmetric to the write side in
|
||||
// `workspace::build_registry`.
|
||||
// normalize to match Registry::normalize_type_for_lookup contract
|
||||
let r_ty_norm = env.workspace_registry.normalize_type_for_lookup(&r_ty);
|
||||
let key = (r.class.clone(), ailang_core::canonical::type_hash(&r_ty_norm));
|
||||
let entry = match env.workspace_registry.entries.get(&key) {
|
||||
|
||||
@@ -92,6 +92,17 @@ pub struct Registry {
|
||||
/// before computing the registry key. Primitives are not present.
|
||||
/// Populated in [`build_registry`] from the same scan that builds
|
||||
/// the entry map.
|
||||
///
|
||||
/// Note: keyed by bare type name only. If two modules each define a
|
||||
/// type with the same bare name (e.g. `type Foo` in both `M` and
|
||||
/// `N`), the second `insert` overwrites the first, and
|
||||
/// [`Self::normalize_type_for_lookup`] would collapse `M.Foo` and
|
||||
/// `N.Foo` to whichever module wins the race. Acceptable for the
|
||||
/// current corpus (all in-tree fixtures use distinct bare type
|
||||
/// names across modules); revisit if a future workspace breaks the
|
||||
/// assumption. Proper fix is to re-key as
|
||||
/// `(owning_module, bare_name) -> defining_module` and thread the
|
||||
/// calling module through every consumer site — out of ct.1's scope.
|
||||
pub type_def_module: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -864,7 +875,13 @@ fn normalize_type_for_registry(
|
||||
},
|
||||
Type::Forall { vars, constraints, body } => Type::Forall {
|
||||
vars: vars.clone(),
|
||||
constraints: constraints.clone(),
|
||||
constraints: constraints
|
||||
.iter()
|
||||
.map(|c| crate::ast::Constraint {
|
||||
class: c.class.clone(),
|
||||
type_: normalize_type_for_registry(&c.type_, type_def_module),
|
||||
})
|
||||
.collect(),
|
||||
body: Box::new(normalize_type_for_registry(body, type_def_module)),
|
||||
},
|
||||
Type::Var { name } => Type::Var { name: name.clone() },
|
||||
@@ -2430,6 +2447,60 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// ct.1.5a follow-up: `normalize_type_for_registry` must recurse into
|
||||
/// `Type::Forall.constraints[].type_` just like it does into
|
||||
/// `Type::Forall.body`. A bare `Type::Con` nested inside a Forall
|
||||
/// constraint that lives in a different module must be rewritten to
|
||||
/// its qualified form, otherwise the registry key for a constrained
|
||||
/// polymorphic type would disagree between bare-local and
|
||||
/// qualified-cross-module call sites — the same divergence
|
||||
/// `ct1_registry_duplicate_detection_survives_mixed_canonical_form`
|
||||
/// guards against at the outer level.
|
||||
#[test]
|
||||
fn ct1_5a_normalize_recurses_into_forall_constraints() {
|
||||
let mut type_def_module: BTreeMap<String, String> = BTreeMap::new();
|
||||
type_def_module.insert("MyInt".to_string(), "other".to_string());
|
||||
|
||||
// Forall a. (TShow MyInt) => a -> a
|
||||
// The constraint's type carries a bare `MyInt` that should be
|
||||
// rewritten to `other.MyInt`.
|
||||
let input = Type::Forall {
|
||||
vars: vec!["a".to_string()],
|
||||
constraints: vec![crate::ast::Constraint {
|
||||
class: "TShow".to_string(),
|
||||
type_: Type::Con {
|
||||
name: "MyInt".to_string(),
|
||||
args: vec![],
|
||||
},
|
||||
}],
|
||||
body: Box::new(Type::Fn {
|
||||
params: vec![Type::Var { name: "a".to_string() }],
|
||||
param_modes: vec![],
|
||||
ret: Box::new(Type::Var { name: "a".to_string() }),
|
||||
ret_mode: Default::default(),
|
||||
effects: vec![],
|
||||
}),
|
||||
};
|
||||
|
||||
let out = normalize_type_for_registry(&input, &type_def_module);
|
||||
|
||||
match out {
|
||||
Type::Forall { constraints, .. } => {
|
||||
assert_eq!(constraints.len(), 1);
|
||||
match &constraints[0].type_ {
|
||||
Type::Con { name, .. } => {
|
||||
assert_eq!(
|
||||
name, "other.MyInt",
|
||||
"constraint type was not normalised; got {name}"
|
||||
);
|
||||
}
|
||||
other => panic!("expected Type::Con, got {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("expected Type::Forall, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// ct.1: a qualified `Constraint.class` nested inside a
|
||||
/// `ClassDef.methods[].ty.Forall.constraints` must fire
|
||||
/// `QualifiedClassName`. Distinct from the `Def::Fn` site: the
|
||||
|
||||
Reference in New Issue
Block a user