Skip to main content

meta_language/translation/
output.rs

1//! Output and aborts as values, for the targets whose programs are pure.
2//!
3//! Rust and JavaScript print where the source prints and abort where it
4//! aborts; in Lean and Rocq a function that prints or may abort, directly or
5//! through a function it calls, takes the lines printed before it and returns
6//! them, with the lines it prints added in front, paired with its value, or,
7//! when it aborts, with the source's abort message instead of a value; main
8//! prints the lines each of its steps adds and stops with the message of the
9//! first abort. The rewriting is target-independent: it yields a portable-core
10//! program without `print` and `abort` nodes, machine-integer arithmetic or
11//! checked conversions, whose pairs are generated data types.
12//!
13//! Mirrors `js/src/translation/output.js`.
14
15use std::borrow::Cow;
16use std::collections::HashSet;
17
18mod settle;
19
20use super::Span;
21use super::aborts::{cast_message, overflow_message, zero_divisor_message};
22use super::diagnostics::{Result, unsupported};
23use super::ir::{
24    Case, Comparison, Ctor, DataDecl, Decl, Effect, Expr, Field, FnDecl, LitValue, Main, Node,
25    Param, Pattern, Program, Prop, TheoremDecl,
26};
27use super::surface::BinaryOp;
28use super::types::{BOOL, INT, NAT, STRING, Type, data};
29use settle::{aborts, division, fixed_max, fixed_min, representation, settle_program, total};
30/// A step of main is a `print` effect itself; what threads is a value that prints or may abort.
31fn effect_operand_any(effect: &Effect, test: &dyn Fn(&Expr) -> bool) -> bool {
32    match effect {
33        Effect::Let { value, .. } => test(value),
34        Effect::Assert { prop, .. } => prop_any(prop, test),
35        Effect::Print { expr, .. } | Effect::Output { expr, .. } => test(expr),
36        Effect::Unwrap { pair, .. } => test(pair),
37    }
38}
39
40/// The checked program with its output and aborts threaded through, or the
41/// program itself when nothing below main prints or aborts.
42///
43/// # Errors
44/// When an operand that prints or aborts sits in a compound assertion, or a
45/// theorem states a fact of a computation that may abort.
46pub fn thread_output(source: &Program) -> Result<Cow<'_, Program>> {
47    let program = settle_program(source);
48    let mut effectful = HashSet::new();
49    let mut grew = true;
50    while grew {
51        grew = false;
52        for entry in &program.declarations {
53            if let Decl::Fn(function) = entry
54                && !effectful.contains(&function.full_name)
55                && effects(&function.body, &effectful)
56            {
57                effectful.insert(function.full_name.clone());
58                grew = true;
59            }
60        }
61    }
62    let main_effects = program.main.as_ref().is_some_and(|main| {
63        main.effects
64            .iter()
65            .any(|effect| effect_operand_any(effect, &|e| effects(e, &effectful)))
66    });
67    if effectful.is_empty() && !main_effects {
68        return Ok(Cow::Owned(program));
69    }
70    let can_abort = program.declarations.iter().any(|entry| match entry {
71        Decl::Fn(function) => aborting(&function.body),
72        Decl::Theorem(theorem) => prop_any(&theorem.prop, &aborting),
73        Decl::Data(_) => false,
74    }) || program.main.as_ref().is_some_and(|main| {
75        main.effects
76            .iter()
77            .any(|effect| effect_operand_any(effect, &aborting))
78    });
79    Threader {
80        source: &program,
81        effectful,
82        can_abort,
83        pairs: Vec::new(),
84        count: 0,
85    }
86    .program()
87    .map(Cow::Owned)
88}
89
90fn effects(expr: &Expr, effectful: &HashSet<String>) -> bool {
91    match &expr.node {
92        Node::Print { .. } => true,
93        Node::Call { func, .. } if effectful.contains(func) => true,
94        _ if aborts(expr) => true,
95        _ => expr
96            .children()
97            .into_iter()
98            .any(|child| effects(child, effectful)),
99    }
100}
101
102fn aborting(expr: &Expr) -> bool {
103    aborts(expr) || expr.children().into_iter().any(aborting)
104}
105
106fn prop_any(prop: &Prop, test: &dyn Fn(&Expr) -> bool) -> bool {
107    match prop {
108        Prop::Forall { body, .. } => prop_any(body, test),
109        Prop::And { left, right } | Prop::Or { left, right } | Prop::Implies { left, right } => {
110            prop_any(left, test) || prop_any(right, test)
111        }
112        Prop::Not { arg } => prop_any(arg, test),
113        Prop::Bool { expr } => test(expr),
114        other => other
115            .comparison()
116            .is_some_and(|(_, comparison)| test(&comparison.left) || test(&comparison.right)),
117    }
118}
119
120fn literal(ty: Type, value: &impl ToString) -> Expr {
121    Expr::lit(ty, LitValue::Text(value.to_string()))
122}
123
124fn compare(op: BinaryOp, left: Expr, right: Expr) -> Expr {
125    let domain = representation(&left.ty);
126    Expr::new(
127        Node::Binary {
128            op,
129            left: Box::new(left),
130            right: Box::new(right),
131            domain: Some(domain),
132            semantics: None,
133            rounding: None,
134            by_zero: None,
135        },
136        BOOL,
137    )
138}
139
140fn logical(op: BinaryOp, left: Expr, right: Expr) -> Expr {
141    Expr::new(
142        Node::Binary {
143            op,
144            left: Box::new(left),
145            right: Box::new(right),
146            domain: None,
147            semantics: None,
148            rounding: None,
149            by_zero: None,
150        },
151        BOOL,
152    )
153}
154
155fn let_in(name: String, value: Expr, body: Expr) -> Expr {
156    let ty = body.ty.clone();
157    Expr::new(
158        Node::Let {
159            name,
160            value: Box::new(value),
161            body: Box::new(body),
162        },
163        ty,
164    )
165}
166
167const fn out_nil() -> Expr {
168    Expr::new(Node::OutNil, Type::Output)
169}
170
171/// A pair taken apart by a match: `match scrutinee with mk out value => …`.
172struct Frame {
173    scrutinee: Expr,
174    data: String,
175    out: String,
176    value: String,
177}
178
179struct Threader<'p> {
180    source: &'p Program,
181    effectful: HashSet<String>,
182    can_abort: bool,
183    /// The generated pairs with the keys of their value types, in order.
184    pairs: Vec<(String, DataDecl)>,
185    count: usize,
186}
187
188impl Threader<'_> {
189    fn program(mut self) -> Result<Program> {
190        let mut declarations = Vec::new();
191        for entry in &self.source.declarations {
192            let entry = match entry {
193                Decl::Fn(function) if self.effectful.contains(&function.full_name) => {
194                    let out = "ml_out";
195                    let mut params = function.params.clone();
196                    params.push(Param {
197                        name: out.to_owned(),
198                        ty: Type::Output,
199                        guard: None,
200                        default_value: None,
201                    });
202                    let ret = self.pair_type(&function.ret);
203                    let body = self.io(&function.body, Expr::var(out, Type::Output))?;
204                    Decl::Fn(FnDecl {
205                        params,
206                        ret,
207                        body,
208                        ..function.clone()
209                    })
210                }
211                Decl::Theorem(theorem) if self.prop_effects(&theorem.prop) => {
212                    Decl::Theorem(TheoremDecl {
213                        prop: self.pure_prop(&theorem.prop)?,
214                        ..theorem.clone()
215                    })
216                }
217                other => other.clone(),
218            };
219            declarations.push(entry);
220        }
221        let main = match &self.source.main {
222            Some(main) => {
223                let mut effects = Vec::new();
224                for effect in &main.effects {
225                    effects.extend(self.effect(effect)?);
226                }
227                Some(Main {
228                    effects,
229                    ..main.clone()
230                })
231            }
232            None => None,
233        };
234        // The pairs come first; a pair of a data type follows it in the emitted order.
235        let mut all: Vec<Decl> = self
236            .pairs
237            .into_iter()
238            .map(|(_, entry)| Decl::Data(entry))
239            .collect();
240        all.extend(declarations);
241        Ok(Program {
242            declarations: all,
243            main,
244            output_threaded: true,
245            aborts_threaded: self.can_abort,
246            ..self.source.clone()
247        })
248    }
249
250    fn effects(&self, expr: &Expr) -> bool {
251        effects(expr, &self.effectful)
252    }
253
254    fn prop_effects(&self, prop: &Prop) -> bool {
255        prop_any(prop, &|e| effects(e, &self.effectful))
256    }
257
258    fn fresh(&mut self, prefix: &str) -> String {
259        self.count += 1;
260        format!("ml_{prefix}{}", self.count)
261    }
262
263    /// The generated data type pairing the lines printed so far with a value
264    /// of `source`, or, when the program may abort, with the message it
265    /// aborted with. A machine integer pairs as its representation, which its
266    /// value is.
267    fn pair(&mut self, source: &Type) -> String {
268        let ty = representation(source);
269        let key = ty.key();
270        if let Some((_, entry)) = self.pairs.iter().find(|(candidate, _)| *candidate == key) {
271            return entry.full_name.clone();
272        }
273        let name = format!("ml_io{}", self.pairs.len() + 1);
274        let mut ctors = vec![Ctor {
275            name: format!("{name}_mk"),
276            fields: vec![
277                Field {
278                    name: "output".to_owned(),
279                    ty: Type::Output,
280                },
281                Field {
282                    name: "value".to_owned(),
283                    ty,
284                },
285            ],
286        }];
287        if self.can_abort {
288            ctors.push(Ctor {
289                name: format!("{name}_abort"),
290                fields: vec![
291                    Field {
292                        name: "output".to_owned(),
293                        ty: Type::Output,
294                    },
295                    Field {
296                        name: "message".to_owned(),
297                        ty: STRING,
298                    },
299                ],
300            });
301        }
302        self.pairs.push((
303            key,
304            DataDecl {
305                name: name.clone(),
306                ctors,
307                span: None,
308                full_name: name.clone(),
309                module_path: Vec::new(),
310                generated: true,
311                output: true,
312            },
313        ));
314        name
315    }
316
317    fn pair_type(&mut self, ty: &Type) -> Type {
318        data(self.pair(ty))
319    }
320
321    /// The value paired with the lines `out`; `ty` is the value's source type
322    /// when its node computes on the representation.
323    fn make_as(&mut self, out: Expr, value: Expr, ty: &Type) -> Expr {
324        let name = self.pair(ty);
325        Expr::new(
326            Node::Ctor {
327                ctor: format!("{name}_mk"),
328                data: name.clone(),
329                args: vec![out, value],
330            },
331            data(name),
332        )
333    }
334
335    fn make(&mut self, out: Expr, value: Expr) -> Expr {
336        let ty = value.ty.clone();
337        self.make_as(out, value, &ty)
338    }
339
340    /// The abort with `message` after the lines `out`, as a pair of a `ty` value.
341    fn fail(&mut self, out: Expr, message: Expr, ty: &Type) -> Expr {
342        let name = self.pair(ty);
343        Expr::new(
344            Node::Ctor {
345                ctor: format!("{name}_abort"),
346                data: name.clone(),
347                args: vec![out, message],
348            },
349            data(name),
350        )
351    }
352
353    fn reraise(&self, out: Expr, message: Expr, pair_type: &Type) -> Expr {
354        let entry = match pair_type {
355            Type::Data { name } => self
356                .pairs
357                .iter()
358                .find(|(_, entry)| entry.full_name == *name)
359                .map(|(_, entry)| entry),
360            _ => None,
361        };
362        let entry = entry.expect("an abort passes on only as a pair");
363        Expr::new(
364            Node::Ctor {
365                data: entry.full_name.clone(),
366                ctor: format!("{}_abort", entry.full_name),
367                args: vec![out, message],
368            },
369            pair_type.clone(),
370        )
371    }
372
373    /// Takes apart `pair`, a pair of lines and a value of `ty`.
374    fn open(&mut self, scrutinee: Expr, ty: &Type) -> Frame {
375        let data = self.pair(ty);
376        let out = self.fresh("o");
377        let value = self.fresh("v");
378        Frame {
379            scrutinee,
380            data,
381            out,
382            value,
383        }
384    }
385
386    /// `match pair with mk o v => inner | abort o m => failure(o, m)`; an
387    /// abort passes on as the abort of the pair `inner` is unless `failure`
388    /// says otherwise.
389    fn close_one(
390        &mut self,
391        frame: Frame,
392        inner: Expr,
393        failure: Option<&dyn Fn(Expr, Expr) -> Expr>,
394    ) -> Expr {
395        let ty = inner.ty.clone();
396        let mut cases = vec![Case {
397            pattern: Pattern::Ctor {
398                data: frame.data.clone(),
399                ctor: format!("{}_mk", frame.data),
400                binds: vec![Some(frame.out), Some(frame.value)],
401            },
402            body: inner,
403        }];
404        if self.can_abort {
405            let lines = self.fresh("o");
406            let message = self.fresh("m");
407            let out = Expr::var(lines.clone(), Type::Output);
408            let text = Expr::var(message.clone(), STRING);
409            let handled = match failure {
410                Some(failure) => failure(out, text),
411                None => self.reraise(out, text, &ty),
412            };
413            cases.push(Case {
414                pattern: Pattern::Ctor {
415                    data: frame.data.clone(),
416                    ctor: format!("{}_abort", frame.data),
417                    binds: vec![Some(lines), Some(message)],
418                },
419                body: handled,
420            });
421        }
422        Expr::new(
423            Node::Match {
424                scrutinee: Box::new(frame.scrutinee),
425                cases,
426            },
427            ty,
428        )
429    }
430
431    /// `inner` under the matches of `frames`, innermost last.
432    fn close(&mut self, frames: Vec<Frame>, inner: Expr) -> Expr {
433        frames
434            .into_iter()
435            .rev()
436            .fold(inner, |inner, frame| self.close_one(frame, inner, None))
437    }
438
439    /// The operands run in order after the lines `out`: the matches taking
440    /// apart the pairs of those that print or may abort, the lines after them
441    /// all, and their values; a value that neither prints nor aborts is used
442    /// as it is.
443    fn bind_all(&mut self, list: &[&Expr], mut out: Expr) -> Result<(Vec<Frame>, Expr, Vec<Expr>)> {
444        let mut frames = Vec::new();
445        let mut values = Vec::new();
446        for &operand in list {
447            if self.effects(operand) {
448                let pair = self.io(operand, out)?;
449                let frame = self.open(pair, &operand.ty);
450                out = Expr::var(frame.out.clone(), Type::Output);
451                values.push(Expr::var(frame.value.clone(), operand.ty.clone()));
452                frames.push(frame);
453            } else {
454                values.push(operand.clone());
455            }
456        }
457        Ok((frames, out, values))
458    }
459
460    fn bind(&mut self, operand: &Expr, out: Expr) -> Result<(Vec<Frame>, Expr, Expr)> {
461        let (frames, out, mut values) = self.bind_all(&[operand], out)?;
462        let value = values.pop().expect("one operand");
463        Ok((frames, out, value))
464    }
465
466    /// `e` run after the lines `out`, a variable: its value paired with the
467    /// lines printed by then, or its abort.
468    fn io(&mut self, e: &Expr, out: Expr) -> Result<Expr> {
469        if !self.effects(e) {
470            return Ok(self.make(out, e.clone()));
471        }
472        let ty = self.pair_type(&e.ty);
473        let rebuilt = |node: Node| Expr {
474            node,
475            ty: ty.clone(),
476            span: e.span,
477        };
478        match &e.node {
479            Node::Print { text, body } => {
480                let (frames, before, text) = self.bind(text, out)?;
481                let after = self.fresh("o");
482                let value = Expr::new(
483                    Node::OutCons {
484                        head: Box::new(text),
485                        tail: Box::new(before),
486                    },
487                    Type::Output,
488                );
489                let body = self.io(body, Expr::var(after.clone(), Type::Output))?;
490                let inner = Expr::new(
491                    Node::Let {
492                        name: after,
493                        value: Box::new(value),
494                        body: Box::new(body),
495                    },
496                    ty,
497                );
498                Ok(self.close(frames, inner))
499            }
500            Node::Abort { message } => Ok(self.fail(out, literal(STRING, &message), &e.ty)),
501            Node::Call { func, args } => {
502                let operands: Vec<&Expr> = args.iter().collect();
503                let (frames, after, mut args) = self.bind_all(&operands, out)?;
504                let inner = if self.effectful.contains(func) {
505                    args.push(after);
506                    rebuilt(Node::Call {
507                        func: func.clone(),
508                        args,
509                    })
510                } else {
511                    let call = Expr {
512                        node: Node::Call {
513                            func: func.clone(),
514                            args,
515                        },
516                        ty: e.ty.clone(),
517                        span: e.span,
518                    };
519                    self.make(after, call)
520                };
521                Ok(self.close(frames, inner))
522            }
523            Node::If {
524                cond,
525                then,
526                otherwise,
527            } => {
528                let (frames, after, cond) = self.bind(cond, out)?;
529                let then = self.io(then, after.clone())?;
530                let otherwise = self.io(otherwise, after)?;
531                let inner = rebuilt(Node::If {
532                    cond: Box::new(cond),
533                    then: Box::new(then),
534                    otherwise: Box::new(otherwise),
535                });
536                Ok(self.close(frames, inner))
537            }
538            Node::Let { name, value, body } => {
539                let (frames, after, value) = self.bind(value, out)?;
540                let body = self.io(body, after)?;
541                let inner = rebuilt(Node::Let {
542                    name: name.clone(),
543                    value: Box::new(value),
544                    body: Box::new(body),
545                });
546                Ok(self.close(frames, inner))
547            }
548            Node::Match { scrutinee, cases } => {
549                let (frames, after, scrutinee) = self.bind(scrutinee, out)?;
550                let mut threaded = Vec::new();
551                for kase in cases {
552                    threaded.push(Case {
553                        pattern: kase.pattern.clone(),
554                        body: self.io(&kase.body, after.clone())?,
555                    });
556                }
557                let inner = rebuilt(Node::Match {
558                    scrutinee: Box::new(scrutinee),
559                    cases: threaded,
560                });
561                Ok(self.close(frames, inner))
562            }
563            // The right operand of `&&` and `||` runs only when the left one does not decide.
564            Node::Binary {
565                op: op @ (BinaryOp::And | BinaryOp::Or),
566                left,
567                right,
568                ..
569            } => {
570                let decided = Expr::lit(BOOL, LitValue::Bool(*op == BinaryOp::Or));
571                let (then, otherwise) = if *op == BinaryOp::And {
572                    ((**right).clone(), decided)
573                } else {
574                    (decided, (**right).clone())
575                };
576                let test = Expr::new(
577                    Node::If {
578                        cond: left.clone(),
579                        then: Box::new(then),
580                        otherwise: Box::new(otherwise),
581                    },
582                    BOOL,
583                );
584                self.io(&test, out)
585            }
586            // Every other node runs its operands in order, then computes from their values.
587            _ => {
588                let operands = e.children();
589                let (frames, after, values) = self.bind_all(&operands, out)?;
590                let mut values = values.into_iter();
591                let copy = e.map_children(&mut |_| values.next().expect("an operand value"));
592                let inner = self.complete(after, copy);
593                Ok(self.close(frames, inner))
594            }
595        }
596    }
597
598    /// A node whose operands are values, after the lines `out`: when it may
599    /// abort, the test of the source's abort condition, its abort message, and
600    /// otherwise its value computed on the representation, where it cannot abort.
601    fn complete(&mut self, out: Expr, e: Expr) -> Expr {
602        if !aborts(&e) {
603            return self.make(out, e);
604        }
605        let mut operands: Vec<(String, Expr)> = Vec::new();
606        let mut name = |this: &mut Self, value: Expr| -> Expr {
607            if matches!(value.node, Node::Var { .. } | Node::Lit { .. }) {
608                return value;
609            }
610            let bound = this.fresh("a");
611            let ty = value.ty.clone();
612            operands.push((bound.clone(), value));
613            Expr::var(bound, ty)
614        };
615        let source = e.ty.clone();
616        let guard = |this: &mut Self, cond: Expr, message: &str, rest: Expr| -> Expr {
617            let ty = rest.ty.clone();
618            let then = this.fail(out.clone(), literal(STRING, &message), &source);
619            Expr::new(
620                Node::If {
621                    cond: Box::new(cond),
622                    then: Box::new(then),
623                    otherwise: Box::new(rest),
624                },
625                ty,
626            )
627        };
628        let Expr { node, ty, span } = e;
629        let result = match node {
630            Node::Cast {
631                arg,
632                from,
633                to,
634                flavor,
635                message,
636                order,
637            } => {
638                let arg = name(self, *arg);
639                let zero = literal(representation(&arg.ty), &0);
640                let cond = compare(BinaryOp::Lt, arg.clone(), zero);
641                let text = cast_message(message.as_deref()).to_owned();
642                let node = total(Expr {
643                    node: Node::Cast {
644                        arg: Box::new(arg),
645                        from,
646                        to,
647                        flavor,
648                        message,
649                        order,
650                    },
651                    ty: ty.clone(),
652                    span,
653                });
654                let rest = self.make_as(out.clone(), node, &ty);
655                guard(self, cond, &text, rest)
656            }
657            Node::Unary { op, arg, semantics } => {
658                let arg = name(self, *arg);
659                let cond = compare(BinaryOp::Eq, arg.clone(), literal(INT, &fixed_min(&ty)));
660                let node = total(Expr {
661                    node: Node::Unary {
662                        op,
663                        arg: Box::new(arg),
664                        semantics,
665                    },
666                    ty: ty.clone(),
667                    span,
668                });
669                let rest = self.make_as(out.clone(), node, &ty);
670                guard(self, cond, overflow_message("neg"), rest)
671            }
672            Node::Binary {
673                op,
674                left,
675                right,
676                domain,
677                semantics,
678                rounding,
679                by_zero,
680            } => {
681                let left = name(self, *left);
682                let right = name(self, *right);
683                let node = total(Expr {
684                    node: Node::Binary {
685                        op,
686                        left: Box::new(left.clone()),
687                        right: Box::new(right.clone()),
688                        domain,
689                        semantics,
690                        rounding,
691                        by_zero,
692                    },
693                    ty: ty.clone(),
694                    span,
695                });
696                let rep = representation(&ty);
697                if division(op) {
698                    let mut rest = self.make_as(out.clone(), node, &ty);
699                    if matches!(ty, Type::Fixed { signed: true, .. }) {
700                        let overflow = logical(
701                            BinaryOp::And,
702                            compare(BinaryOp::Eq, left, literal(rep.clone(), &fixed_min(&ty))),
703                            compare(BinaryOp::Eq, right.clone(), literal(rep.clone(), &-1)),
704                        );
705                        rest = guard(self, overflow, overflow_message(op.name()), rest);
706                    }
707                    let zero = compare(BinaryOp::Eq, right, literal(rep, &0));
708                    guard(self, zero, zero_divisor_message(op.name(), &ty), rest)
709                } else if op == BinaryOp::Sub && rep == NAT {
710                    let cond = compare(BinaryOp::Lt, left, right);
711                    let rest = self.make_as(out.clone(), node, &ty);
712                    guard(self, cond, overflow_message("sub"), rest)
713                } else {
714                    // The exact result, range-checked before it is the machine integer's value.
715                    let wide = self.fresh("w");
716                    let value = Expr::var(wide.clone(), rep.clone());
717                    let above = compare(
718                        BinaryOp::Gt,
719                        value.clone(),
720                        literal(rep.clone(), &fixed_max(&ty)),
721                    );
722                    let outside = if rep == NAT {
723                        above
724                    } else {
725                        let below =
726                            compare(BinaryOp::Lt, value.clone(), literal(rep, &fixed_min(&ty)));
727                        logical(BinaryOp::Or, below, above)
728                    };
729                    let rest = self.make_as(out.clone(), value, &ty);
730                    let body = guard(self, outside, overflow_message(op.name()), rest);
731                    let pair = self.pair_type(&ty);
732                    Expr::new(
733                        Node::Let {
734                            name: wide,
735                            value: Box::new(node),
736                            body: Box::new(body),
737                        },
738                        pair,
739                    )
740                }
741            }
742            Node::Abort { .. } => unreachable!("an abort runs as an effect"),
743            other => unreachable!("{} does not abort", other.kind()),
744        };
745        operands
746            .into_iter()
747            .rev()
748            .fold(result, |body, (bound, value)| let_in(bound, value, body))
749    }
750
751    /// The value of `e` alone, for a theorem, which states facts of values.
752    fn value(&mut self, e: &Expr, span: Option<Span>) -> Result<Expr> {
753        if self.can_abort {
754            return Err(unsupported(
755                "a theorem over a computation that may abort",
756                "a theorem states a fact of a value, which an abort does not have",
757                span,
758            ));
759        }
760        let pair = self.io(e, out_nil())?;
761        let frame = self.open(pair, &e.ty);
762        let value = Expr::var(frame.value.clone(), e.ty.clone());
763        Ok(self.close(vec![frame], value))
764    }
765
766    fn pure_expr(&mut self, e: &Expr) -> Result<Expr> {
767        if self.effects(e) {
768            self.value(e, None)
769        } else {
770            Ok(e.clone())
771        }
772    }
773
774    fn pure_prop(&mut self, prop: &Prop) -> Result<Prop> {
775        Ok(match prop {
776            Prop::Forall { binders, body } => Prop::Forall {
777                binders: binders.clone(),
778                body: Box::new(self.pure_prop(body)?),
779            },
780            Prop::And { left, right } => Prop::And {
781                left: Box::new(self.pure_prop(left)?),
782                right: Box::new(self.pure_prop(right)?),
783            },
784            Prop::Or { left, right } => Prop::Or {
785                left: Box::new(self.pure_prop(left)?),
786                right: Box::new(self.pure_prop(right)?),
787            },
788            Prop::Implies { left, right } => Prop::Implies {
789                left: Box::new(self.pure_prop(left)?),
790                right: Box::new(self.pure_prop(right)?),
791            },
792            Prop::Not { arg } => Prop::Not {
793                arg: Box::new(self.pure_prop(arg)?),
794            },
795            Prop::Bool { expr } => Prop::Bool {
796                expr: self.pure_expr(expr)?,
797            },
798            other => {
799                let (op, comparison) = other.comparison().expect("a comparison");
800                let left = self.pure_expr(&comparison.left)?;
801                let right = self.pure_expr(&comparison.right)?;
802                Prop::compare(
803                    op,
804                    Comparison {
805                        left,
806                        right,
807                        ..comparison.clone()
808                    },
809                )
810            }
811        })
812    }
813
814    /// A main step whose value prints or may abort: the pair of its value and
815    /// the lines it prints, a step printing those lines, a step that stops
816    /// main with the message of an abort, and the step itself on the value.
817    fn effect(&mut self, effect: &Effect) -> Result<Vec<Effect>> {
818        Ok(match effect {
819            Effect::Let {
820                name,
821                value,
822                constant,
823                span,
824            } if self.effects(value) => {
825                let (mut steps, value) = self.run(value)?;
826                steps.push(Effect::Let {
827                    name: name.clone(),
828                    value,
829                    constant: *constant,
830                    span: *span,
831                });
832                steps
833            }
834            Effect::Print { expr, span } if self.effects(expr) => {
835                let (mut steps, expr) = self.run(expr)?;
836                steps.push(Effect::Print { expr, span: *span });
837                steps
838            }
839            Effect::Assert { prop, span } if self.prop_effects(prop) => {
840                let mut steps = Vec::new();
841                let prop = self.hoist(prop, &mut steps, *span)?;
842                steps.push(Effect::Assert { prop, span: *span });
843                steps
844            }
845            other => vec![other.clone()],
846        })
847    }
848
849    fn run(&mut self, e: &Expr) -> Result<(Vec<Effect>, Expr)> {
850        let name = self.fresh("run");
851        let pair = Expr::var(name.clone(), self.pair_type(&e.ty));
852        let value = self.io(e, out_nil())?;
853        let lines = self.open(pair.clone(), &e.ty);
854        let lines_var = Expr::var(lines.out.clone(), Type::Output);
855        let lines = self.close_one(lines, lines_var, Some(&|out, _| out));
856        let mut steps = vec![
857            Effect::Let {
858                name,
859                value,
860                constant: false,
861                span: e.span,
862            },
863            Effect::Output {
864                expr: lines,
865                span: e.span,
866            },
867        ];
868        if !self.can_abort {
869            let result = self.open(pair, &e.ty);
870            let result_var = Expr::var(result.value.clone(), e.ty.clone());
871            let result = self.close(vec![result], result_var);
872            return Ok((steps, result));
873        }
874        // `unwrap` binds the value of a pair, or stops main with its abort message.
875        let value = self.fresh("val");
876        let data = self.pair(&e.ty);
877        steps.push(Effect::Unwrap {
878            name: value.clone(),
879            pair,
880            ctors: vec![format!("{data}_mk"), format!("{data}_abort")],
881            data,
882            ty: e.ty.clone(),
883            span: e.span,
884        });
885        Ok((steps, Expr::var(value, e.ty.clone())))
886    }
887
888    fn lift(&mut self, e: &Expr, steps: &mut Vec<Effect>) -> Result<Expr> {
889        if !self.effects(e) {
890            return Ok(e.clone());
891        }
892        let (run, value) = self.run(e)?;
893        steps.extend(run);
894        Ok(value)
895    }
896
897    /// An assertion's operands that print or may abort run before it, in order, as steps of main.
898    fn hoist(&mut self, prop: &Prop, steps: &mut Vec<Effect>, span: Option<Span>) -> Result<Prop> {
899        Ok(match prop {
900            Prop::Bool { expr } => Prop::Bool {
901                expr: self.lift(expr, steps)?,
902            },
903            Prop::Not { arg } => Prop::Not {
904                arg: Box::new(self.hoist(arg, steps, span)?),
905            },
906            // The left operand always runs, first; the right one only when the left does not decide the assertion.
907            Prop::And { left, right } if !self.prop_effects(right) => Prop::And {
908                left: Box::new(self.hoist(left, steps, span)?),
909                right: right.clone(),
910            },
911            Prop::Or { left, right } if !self.prop_effects(right) => Prop::Or {
912                left: Box::new(self.hoist(left, steps, span)?),
913                right: right.clone(),
914            },
915            Prop::And { .. } | Prop::Or { .. } => {
916                return Err(unsupported(
917                    "output or an abort in a compound assertion",
918                    "an operand that prints or aborts after the first runs only when the assertion evaluates it; assert on a value computed before",
919                    span,
920                ));
921            }
922            Prop::Implies { .. } | Prop::Forall { .. } => {
923                return Err(unsupported(
924                    "output or an abort in a compound assertion",
925                    "an operand that prints or aborts runs only when the assertion evaluates it; assert on a value computed before",
926                    span,
927                ));
928            }
929            other => {
930                let (op, comparison) = other.comparison().expect("a comparison");
931                let left = self.lift(&comparison.left, steps)?;
932                let right = self.lift(&comparison.right, steps)?;
933                Prop::compare(
934                    op,
935                    Comparison {
936                        left,
937                        right,
938                        ..comparison.clone()
939                    },
940                )
941            }
942        })
943    }
944}