diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index 70e75f6..c58a560 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -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 )+ ) + 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, 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 { + 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 { diff --git a/crates/ailang-surface/src/print.rs b/crates/ailang-surface/src/print.rs index 032ae43..6c66389 100644 --- a/crates/ailang-surface/src/print.rs +++ b/crates/ailang-surface/src/print.rs @@ -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 )` 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 {