iter 22b.2.1: add Constraint struct, Type::Forall.constraints

This commit is contained in:
2026-05-09 17:34:39 +02:00
parent e1d748d64e
commit b636b883a3
14 changed files with 94 additions and 20 deletions
+24 -3
View File
@@ -331,6 +331,21 @@ pub struct InstanceMethod {
pub body: Term,
}
/// Iter 22b.2: a class constraint on a polymorphic function (Decision
/// 11). `(class, type)` pair where `class` is a class name and `type`
/// is a `Type` expression — typically a single `Type::Var` (e.g.
/// `(Eq, a)` for `Eq a => ...`). Concrete-type constraints are legal
/// schema-wise but fired as `no-instance` at typecheck time if no
/// matching registry entry exists.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Constraint {
/// Class name.
pub class: String,
/// Type the class is applied to.
#[serde(rename = "type")]
pub type_: Type,
}
/// A constant (top-level binding to a value).
///
/// Differs from a zero-arg [`FnDef`] in that it is evaluated once and
@@ -588,6 +603,12 @@ pub enum Type {
/// names, instantiated fresh at each use site.
Forall {
vars: Vec<String>,
/// Iter 22b.2: class constraints quantified together with
/// `vars`. Empty for pre-22b.2 polymorphic types; serialised
/// with `skip_serializing_if = "Vec::is_empty"` so existing
/// canonical-JSON bytes stay bit-identical.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
constraints: Vec<Constraint>,
body: Box<Type>,
},
}
@@ -742,9 +763,9 @@ impl PartialEq for Type {
}
(Type::Var { name: a }, Type::Var { name: b }) => a == b,
(
Type::Forall { vars: a, body: ab },
Type::Forall { vars: b, body: bb },
) => a == b && ab == bb,
Type::Forall { vars: a, constraints: ac, body: ab },
Type::Forall { vars: b, constraints: bc, body: bb },
) => a == b && ac == bc && ab == bb,
_ => false,
}
}
+4 -1
View File
@@ -774,6 +774,7 @@ impl Desugarer {
} else {
Type::Forall {
vars: self.current_def_forall_vars.clone(),
constraints: vec![],
body: Box::new(inner_augmented_ty),
}
};
@@ -2307,6 +2308,7 @@ mod tests {
name: "rec_id".into(),
ty: Type::Forall {
vars: vec!["a".into()],
constraints: vec![],
body: Box::new(Type::Fn {
params: vec![Type::Var { name: "a".into() }],
ret: Box::new(Type::Var { name: "a".into() }),
@@ -2339,7 +2341,7 @@ mod tests {
);
// Lifted type must be Forall(a). Fn(Int, a) -> a (k + x capture).
match &lifted.ty {
Type::Forall { vars, body } => {
Type::Forall { vars, constraints: _, body } => {
assert_eq!(vars, &vec!["a".to_string()], "Forall vars must mirror enclosing");
match body.as_ref() {
Type::Fn { params, ret, .. } => {
@@ -2409,6 +2411,7 @@ mod tests {
name: "outer".into(),
ty: Type::Forall {
vars: vec!["a".into()],
constraints: vec![],
body: Box::new(Type::Fn {
params: vec![Type::Var { name: "a".into() }],
ret: Box::new(Type::Var { name: "a".into() }),
+30
View File
@@ -237,4 +237,34 @@ mod tests {
"ClassDef with elided optionals must hash identically to construction with explicit None"
);
}
/// Iter 22b.2: adding `constraints` to [`crate::ast::Type::Forall`]
/// must NOT alter canonical-JSON bytes of any pre-22b.2 polymorphic
/// type. The `#[serde(default, skip_serializing_if = "Vec::is_empty")]`
/// attribute on the field is what enforces this; if it is wrong,
/// every existing fixture's hash drifts and workspace dedup keys
/// based on the canonical bytes break.
///
/// Property: a `Type::Forall` constructed without specifying
/// `constraints` (i.e. empty vec) must serialise with no
/// `constraints` key in the canonical JSON.
#[test]
fn forall_without_constraints_hashes_bit_identical_to_pre_22b2() {
use crate::ast::Type;
let t = Type::Forall {
vars: vec!["a".into()],
constraints: vec![],
body: Box::new(Type::fn_implicit(
vec![Type::Var { name: "a".into() }],
Type::Var { name: "a".into() },
vec![],
)),
};
let bytes = crate::canonical::to_bytes(&t);
let s = std::str::from_utf8(&bytes).unwrap();
assert!(
!s.contains("constraints"),
"Type::Forall serialised must omit `constraints` when empty; got: {s}"
);
}
}
+1 -1
View File
@@ -154,7 +154,7 @@ pub fn type_to_string(t: &Type) -> String {
};
format!("({p}) -> {ret}{eff}", ret = type_to_string(ret))
}
Type::Forall { vars, body } => {
Type::Forall { vars, constraints: _, body } => {
format!("forall {}. {}", vars.join(" "), type_to_string(body))
}
}