iter 22b.4a.4.5: form-a printer+parser arms for Type::Forall constraints

This commit is contained in:
2026-05-09 22:23:10 +02:00
parent 5852833241
commit 2a2e6b9817
2 changed files with 94 additions and 5 deletions
+36 -2
View File
@@ -85,7 +85,7 @@
//! [`ailang_core::ast::Import::alias`].
use ailang_core::ast::{
Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, InstanceDef,
Arm, ClassDef, ClassMethod, Constraint, ConstDef, Ctor, Def, FnDef, Import, InstanceDef,
InstanceMethod, Literal, Module, ParamMode, Pattern, SuperclassRef, Suppress, Term,
Type, TypeDef,
};
@@ -1149,15 +1149,49 @@ impl<'a> Parser<'a> {
vars.push(self.expect_ident("type variable")?);
}
self.expect_rparen("forall vars")?;
// optional (constraints (constraint <Class> <type>)+ )
let constraints = if let Some("constraints") = self.peek_head_ident() {
self.parse_forall_constraints_clause()?
} else {
Vec::new()
};
let body = self.parse_type()?;
self.expect_rparen("forall-type")?;
Ok(Type::Forall {
vars,
constraints: vec![],
constraints,
body: Box::new(body),
})
}
fn parse_forall_constraints_clause(&mut self) -> Result<Vec<Constraint>, ParseError> {
self.expect_lparen("forall.constraints")?;
self.expect_keyword("constraints")?;
let mut out = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
out.push(self.parse_constraint()?);
}
if out.is_empty() {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "forall.constraints",
message: "(constraints …) clause must contain at least one (constraint …)".into(),
pos,
});
}
self.expect_rparen("forall.constraints")?;
Ok(out)
}
fn parse_constraint(&mut self) -> Result<Constraint, ParseError> {
self.expect_lparen("constraint")?;
self.expect_keyword("constraint")?;
let class = self.expect_ident("constraint class name")?;
let type_ = self.parse_type()?;
self.expect_rparen("constraint")?;
Ok(Constraint { class, type_ })
}
// ---- terms ----------------------------------------------------------
fn parse_term(&mut self) -> Result<Term, ParseError> {
+58 -3
View File
@@ -8,7 +8,7 @@
//! per level. Comments are NOT emitted.
use ailang_core::ast::{
Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, InstanceDef,
Arm, ClassDef, ClassMethod, Constraint, ConstDef, Ctor, Def, FnDef, Import, InstanceDef,
InstanceMethod, Literal, Module, ParamMode, Pattern, SuperclassRef, Suppress, Term,
Type, TypeDef,
};
@@ -336,6 +336,18 @@ fn write_fn_type_slot(out: &mut String, t: &Type, mode: ParamMode) {
}
}
/// Iter 22b.4a.4.5: print one `(constraint <Class> <type>)` pair, used
/// inside the optional `(constraints …)` clause of a `(forall …)`. The
/// clause itself is omitted entirely when `Type::Forall.constraints` is
/// empty so pre-22b.2 forall fixtures stay bit-identical.
fn write_constraint(out: &mut String, c: &Constraint) {
out.push_str("(constraint ");
out.push_str(&c.class);
out.push(' ');
write_type(out, &c.type_);
out.push(')');
}
fn write_type(out: &mut String, t: &Type) {
match t {
Type::Var { name } => out.push_str(name),
@@ -374,13 +386,22 @@ fn write_type(out: &mut String, t: &Type) {
}
out.push(')');
}
Type::Forall { vars, constraints: _, body } => {
Type::Forall { vars, constraints, body } => {
out.push_str("(forall (vars");
for v in vars {
out.push(' ');
out.push_str(v);
}
out.push_str(") ");
out.push(')');
if !constraints.is_empty() {
out.push_str(" (constraints");
for c in constraints {
out.push(' ');
write_constraint(out, c);
}
out.push(')');
}
out.push(' ');
write_type(out, body);
out.push(')');
}
@@ -658,6 +679,40 @@ mod tests {
round_trip(m, "class_with_superclass_and_default");
}
#[test]
fn print_then_parse_round_trip_forall_with_constraint() {
let m = Module {
schema: ailang_core::SCHEMA.to_string(),
name: "M".into(),
imports: vec![],
defs: vec![Def::Fn(ailang_core::ast::FnDef {
name: "f".into(),
ty: Type::Forall {
vars: vec!["a".into()],
constraints: vec![ailang_core::ast::Constraint {
class: "Show".into(),
type_: Type::Var { name: "a".into() },
}],
body: Box::new(Type::Fn {
params: vec![Type::Var { name: "a".into() }],
param_modes: vec![ParamMode::Implicit],
ret: Box::new(Type::Con {
name: "Str".into(),
args: vec![],
}),
ret_mode: ParamMode::Implicit,
effects: vec![],
}),
},
params: vec!["x".into()],
body: Term::Var { name: "x".into() },
doc: None,
suppress: vec![],
})],
};
round_trip(m, "forall_with_constraint");
}
#[test]
fn print_then_parse_round_trip_minimal_instance() {
let m = Module {