Skip to main content

simplicity/node/
commit.rs

1// SPDX-License-Identifier: CC0-1.0
2
3use crate::dag::{DagLike, MaxSharing, NoSharing, PostOrderIterItem};
4use crate::jet::Jet;
5use crate::types::arrow::{Arrow, FinalArrow};
6use crate::{encode, types, Value};
7use crate::{Amr, BitIter, BitWriter, Cmr, DecodeError, Ihr, Imr};
8
9use super::{
10    Construct, ConstructData, ConstructNode, Constructible, Converter, Inner, Marker, NoDisconnect,
11    NoWitness, Node, Redeem, RedeemNode,
12};
13
14use std::io;
15use std::sync::Arc;
16
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
18pub struct Commit {
19    /// Makes the type non-constructible.
20    never: std::convert::Infallible,
21}
22
23impl Marker for Commit {
24    type CachedData = Arc<CommitData>;
25    type Witness = NoWitness;
26    type Disconnect = NoDisconnect;
27    type SharingId = Ihr;
28
29    fn compute_sharing_id(_: Cmr, cached_data: &Arc<CommitData>) -> Option<Ihr> {
30        cached_data.ihr
31    }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Hash)]
35pub struct CommitData {
36    /// The source and target types of the node
37    arrow: FinalArrow,
38    /// The IMR of the node if it exists. This is distinct from its IHR; it is
39    /// used during computation of the IHR.
40    imr: Option<Imr>,
41    /// The AMR of the node if it exists, meaning, if it is not (an ancestor of)
42    /// a witness or disconnect node.
43    amr: Option<Amr>,
44    /// The IHR of the node if it exists, meaning, if it is not (an ancestor of)
45    /// a witness or disconnect node.
46    ihr: Option<Ihr>,
47}
48
49impl CommitData {
50    /// Accessor for the node's arrow
51    pub fn arrow(&self) -> &FinalArrow {
52        &self.arrow
53    }
54
55    /// Accessor for the node's IHR, if known
56    pub fn ihr(&self) -> Option<Ihr> {
57        self.ihr
58    }
59
60    /// Helper function to compute a cached AMR
61    fn incomplete_amr(
62        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
63        arrow: &FinalArrow,
64    ) -> Option<Amr> {
65        match inner {
66            Inner::Iden => Some(Amr::iden(arrow)),
67            Inner::Unit => Some(Amr::unit(arrow)),
68            Inner::InjL(child) => child.amr.map(|amr| Amr::injl(arrow, amr)),
69            Inner::InjR(child) => child.amr.map(|amr| Amr::injr(arrow, amr)),
70            Inner::Take(child) => child.amr.map(|amr| Amr::take(arrow, amr)),
71            Inner::Drop(child) => child.amr.map(|amr| Amr::drop(arrow, amr)),
72            Inner::Comp(left, right) => left
73                .amr
74                .zip(right.amr)
75                .map(|(a, b)| Amr::comp(arrow, &left.arrow, a, b)),
76            Inner::Case(left, right) => {
77                left.amr.zip(right.amr).map(|(a, b)| Amr::case(arrow, a, b))
78            }
79            Inner::AssertL(left, r_cmr) => left
80                .amr
81                .map(|l_amr| Amr::assertl(arrow, l_amr, r_cmr.into())),
82            Inner::AssertR(l_cmr, right) => right
83                .amr
84                .map(|r_amr| Amr::assertr(arrow, l_cmr.into(), r_amr)),
85            Inner::Pair(left, right) => left
86                .amr
87                .zip(right.amr)
88                .map(|(a, b)| Amr::pair(arrow, &left.arrow, &right.arrow, a, b)),
89            Inner::Disconnect(..) => None,
90            Inner::Witness(..) => None,
91            Inner::Fail(entropy) => Some(Amr::fail(entropy)),
92            Inner::Jet(jet) => Some(Amr::jet(jet.as_ref())),
93            Inner::Word(ref val) => Some(Amr::const_word(val)),
94        }
95    }
96
97    /// Helper function to compute a cached first-pass IHR
98    fn imr(inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>) -> Option<Imr> {
99        match inner {
100            Inner::Iden => Some(Imr::iden()),
101            Inner::Unit => Some(Imr::unit()),
102            Inner::InjL(child) => child.imr.map(Imr::injl),
103            Inner::InjR(child) => child.imr.map(Imr::injr),
104            Inner::Take(child) => child.imr.map(Imr::take),
105            Inner::Drop(child) => child.imr.map(Imr::drop),
106            Inner::Comp(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::comp(a, b)),
107            Inner::Case(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::case(a, b)),
108            Inner::AssertL(left, r_cmr) => left.imr.map(|l_ihr| Imr::case(l_ihr, r_cmr.into())),
109            Inner::AssertR(l_cmr, right) => right.imr.map(|r_ihr| Imr::case(l_cmr.into(), r_ihr)),
110            Inner::Pair(left, right) => left.imr.zip(right.imr).map(|(a, b)| Imr::pair(a, b)),
111            Inner::Disconnect(..) => None,
112            Inner::Witness(..) => None,
113            Inner::Fail(entropy) => Some(Imr::fail(entropy)),
114            Inner::Jet(jet) => Some(Imr::jet(jet.as_ref())),
115            Inner::Word(ref val) => Some(Imr::const_word(val)),
116        }
117    }
118
119    pub fn new(
120        arrow: &Arrow,
121        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
122    ) -> Result<Self, types::Error> {
123        let final_arrow = arrow.finalize()?;
124        let imr = Self::imr(inner.clone());
125        let amr = Self::incomplete_amr(inner, &final_arrow);
126        Ok(CommitData {
127            imr,
128            amr,
129            ihr: imr.map(|ihr| Ihr::from_imr(ihr, &final_arrow)),
130            arrow: final_arrow,
131        })
132    }
133
134    pub fn from_final(
135        arrow: FinalArrow,
136        inner: Inner<&Arc<Self>, &NoDisconnect, &NoWitness>,
137    ) -> Self {
138        let imr = Self::imr(inner.clone());
139        let amr = Self::incomplete_amr(inner, &arrow);
140        CommitData {
141            imr,
142            amr,
143            ihr: imr.map(|ihr| Ihr::from_imr(ihr, &arrow)),
144            arrow,
145        }
146    }
147}
148
149pub type CommitNode = Node<Commit>;
150
151impl CommitNode {
152    /// Accessor for the node's arrow
153    pub fn arrow(&self) -> &FinalArrow {
154        &self.data.arrow
155    }
156
157    /// Accessor for the node's AMR, if known
158    pub fn amr(&self) -> Option<Amr> {
159        self.data.amr
160    }
161
162    /// Accessor for the node's IHR, if known
163    pub fn ihr(&self) -> Option<Ihr> {
164        self.data.ihr
165    }
166
167    /// Finalizes a DAG, by iterating through through it without sharing, attaching
168    /// witnesses, and hiding branches.
169    ///
170    /// This is a thin wrapper around [`Node::convert`] which fixes a few types to make
171    /// it easier to use.
172    pub fn finalize<C: Converter<Commit, Redeem>>(
173        &self,
174        converter: &mut C,
175    ) -> Result<Arc<RedeemNode>, C::Error> {
176        self.convert::<NoSharing, Redeem, _>(converter)
177    }
178
179    /// Convert a [`CommitNode`] back to a [`ConstructNode`] by redoing type inference
180    pub fn unfinalize_types<'brand>(
181        &self,
182        inference_context: &types::Context<'brand>,
183    ) -> Result<Arc<ConstructNode<'brand>>, types::Error> {
184        struct UnfinalizeTypes<'a, 'brand> {
185            inference_context: &'a types::Context<'brand>,
186        }
187
188        impl<'brand> Converter<Commit, Construct<'brand>> for UnfinalizeTypes<'_, 'brand> {
189            type Error = types::Error;
190            fn convert_witness(
191                &mut self,
192                _: &PostOrderIterItem<&CommitNode>,
193                _: &NoWitness,
194            ) -> Result<Option<Value>, Self::Error> {
195                Ok(None)
196            }
197
198            fn convert_disconnect(
199                &mut self,
200                _: &PostOrderIterItem<&CommitNode>,
201                _: Option<&Arc<ConstructNode<'brand>>>,
202                _: &NoDisconnect,
203            ) -> Result<Option<Arc<ConstructNode<'brand>>>, Self::Error> {
204                Ok(None)
205            }
206
207            fn convert_data(
208                &mut self,
209                _: &PostOrderIterItem<&CommitNode>,
210                inner: Inner<
211                    &Arc<ConstructNode<'brand>>,
212                    &Option<Arc<ConstructNode<'brand>>>,
213                    &Option<Value>,
214                >,
215            ) -> Result<ConstructData<'brand>, Self::Error> {
216                let inner = inner
217                    .map(|node| node.arrow())
218                    .map_disconnect(|maybe_node| maybe_node.as_ref().map(|node| node.arrow()));
219                let inner = inner.disconnect_as_ref(); // lol sigh rust
220                Ok(ConstructData::new(Arrow::from_inner(
221                    self.inference_context,
222                    inner,
223                )?))
224            }
225        }
226
227        self.convert::<MaxSharing<Commit>, _, _>(&mut UnfinalizeTypes { inference_context })
228    }
229
230    /// Decode a Simplicity program from bits, without witness data.
231    ///
232    /// # Usage
233    ///
234    /// Use this method only if the serialization **does not** include the witness data.
235    /// This means, the program simply has no witness during commitment,
236    /// or the witness is provided by other means.
237    ///
238    /// If the serialization contains the witness data, then use [`RedeemNode::decode()`].
239    pub fn decode<I: Iterator<Item = u8>, J: Jet>(
240        bits: BitIter<I>,
241    ) -> Result<Arc<Self>, DecodeError> {
242        use crate::decode;
243
244        // 1. Decode program with out witnesses.
245        let program = types::Context::with_context(|ctx| {
246            let construct =
247                crate::ConstructNode::decode::<I, J>(&ctx, bits).map_err(DecodeError::Decode)?;
248            construct.finalize_types().map_err(DecodeError::Type)
249        })?;
250        // 2. Do sharing check, using incomplete IHRs
251        if program.as_ref().is_shared_as::<MaxSharing<Commit>>() {
252            Ok(program)
253        } else {
254            Err(DecodeError::Decode(decode::Error::SharingNotMaximal))
255        }
256    }
257
258    #[cfg(feature = "base64")]
259    #[allow(clippy::should_implement_trait)] // returns Arc<Self>
260    pub fn from_str(s: &str) -> Result<Arc<Self>, crate::ParseError> {
261        use crate::base64::engine::general_purpose;
262        use crate::base64::Engine as _;
263
264        let v = general_purpose::STANDARD
265            .decode(s)
266            .map_err(crate::ParseError::Base64)?;
267        let iter = crate::BitIter::new(v.into_iter());
268        Self::decode::<_, crate::jet::Core>(iter).map_err(crate::ParseError::Decode)
269    }
270
271    /// Encode a Simplicity expression to bits without any witness data
272    #[deprecated(since = "0.5.0", note = "use Self::encode_without_witness instead")]
273    pub fn encode(&self, w: &mut BitWriter<&mut dyn io::Write>) -> io::Result<usize> {
274        let program_bits = encode::encode_program(self, w)?;
275        w.flush_all()?;
276        Ok(program_bits)
277    }
278
279    /// Encode a Simplicity program to a vector of bytes, without any witness data.
280    #[deprecated(since = "0.5.0", note = "use Self::to_vec_without_witness instead")]
281    pub fn encode_to_vec(&self) -> Vec<u8> {
282        let mut program = Vec::<u8>::new();
283        self.encode_without_witness(&mut program)
284            .expect("write to vector never fails");
285        debug_assert!(!program.is_empty());
286
287        program
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    use hex::DisplayHex;
296    use std::fmt;
297
298    use crate::{decode::Error, jet::Core};
299    #[cfg(feature = "human_encoding")]
300    use crate::{human_encoding::Forest, jet::CoreEnv, node::SimpleFinalizer, BitMachine, Value};
301
302    #[cfg(feature = "human_encoding")]
303    #[cfg_attr(not(feature = "base64"), allow(unused_variables))]
304    #[track_caller]
305    fn assert_program_deserializable<J: Jet>(
306        prog_str: &str,
307        prog_bytes: &[u8],
308        cmr_str: &str,
309        b64_str: &str,
310    ) -> Arc<CommitNode> {
311        let forest = match Forest::parse::<J>(prog_str) {
312            Ok(forest) => forest,
313            Err(e) => panic!("Failed to parse program `{}`: {}", prog_str, e),
314        };
315        assert_eq!(
316            forest.roots().len(),
317            1,
318            "program `{}` has multiple roots",
319            prog_str
320        );
321        let main = match forest.roots().get("main") {
322            Some(root) => root,
323            None => panic!("Program `{}` has no main", prog_str),
324        };
325
326        let prog_hex = prog_bytes.as_hex();
327        let main_bytes = main.to_vec_without_witness();
328        assert_eq!(
329            prog_bytes,
330            main_bytes,
331            "Program string `{}` encoded to {} (expected {})",
332            prog_str,
333            main_bytes.as_hex(),
334            prog_hex,
335        );
336
337        let iter = BitIter::from(prog_bytes);
338        let prog = match CommitNode::decode::<_, J>(iter) {
339            Ok(prog) => prog,
340            Err(e) => panic!("program {} failed: {}", prog_hex, e),
341        };
342
343        assert_eq!(
344            prog.cmr().to_string(),
345            cmr_str,
346            "CMR mismatch (got {} expected {}) for program {}",
347            prog.cmr(),
348            cmr_str,
349            prog_hex,
350        );
351
352        let reser_sink = prog.to_vec_without_witness();
353        assert_eq!(
354            prog_bytes,
355            &reser_sink[..],
356            "program {} reserialized as {}",
357            prog_hex,
358            reser_sink.as_hex(),
359        );
360
361        #[cfg(feature = "base64")]
362        {
363            assert_eq!(prog.to_string(), b64_str);
364            assert_eq!(prog.display().program().to_string(), b64_str);
365            assert_eq!(prog, CommitNode::from_str(b64_str).unwrap());
366        }
367
368        prog
369    }
370
371    #[track_caller]
372    fn assert_program_not_deserializable<J: Jet>(prog: &[u8], err: &dyn fmt::Display) {
373        let prog_hex = prog.as_hex();
374        let err_str = err.to_string();
375
376        let iter = BitIter::from(prog);
377        match CommitNode::decode::<_, J>(iter) {
378            Ok(prog) => panic!(
379                "Program {} succeded (expected error {}). Program parsed as:\n{:?}",
380                prog_hex, err, prog
381            ),
382            Err(e) if e.to_string() == err_str => {} // ok
383            Err(e) => panic!(
384                "Program {} failed with error {} (expected error {})",
385                prog_hex, e, err
386            ),
387        };
388    }
389
390    #[test]
391    fn canonical_order() {
392        // "main = comp unit iden", but with the iden serialized before the unit
393        // To obtain this test vector I temporarily swapped `get_left` and `get_right`
394        // in the implementation of `PostOrderIter`
395        assert_program_not_deserializable::<Core>(&[0xa8, 0x48, 0x10], &Error::NotInCanonicalOrder);
396
397        // "main = iden", but prefixed by some unused nodes, the first of which is also iden.
398        assert_program_not_deserializable::<Core>(
399            &[0xc1, 0x00, 0x06, 0x20],
400            &Error::NotInCanonicalOrder,
401        );
402    }
403
404    #[test]
405    fn hidden_node() {
406        // main = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
407        #[rustfmt::skip]
408        let hidden = [
409            0x36, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
410            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
411            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
412            0x7e, 0xf5, 0x6d, 0xf7, 0x7e, 0xf5, 0x6d, 0xf7,
413            78,
414        ];
415        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
416
417        // main = comp witness hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
418        let hidden = [
419            0xae, 0xdb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb,
420            0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xfb, 0xd5, 0xb7,
421            0xdd, 0xfb, 0xd5, 0xb7, 0xdd, 0xe0, 0x80,
422        ];
423        assert_program_not_deserializable::<Core>(&hidden, &Error::HiddenNode);
424    }
425
426    #[test]
427    fn case_both_children_hidden() {
428        // h1 = hidden deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
429        // main = case h1 h1
430        #[rustfmt::skip]
431        let hidden = [
432            0x8d, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
433            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
434            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
435            0xdf, 0xbd, 0x5b, 0x7d, 0xdf, 0xbd, 0x5b, 0x7d,
436            0xde, 0x10,
437        ];
438        assert_program_not_deserializable::<Core>(&hidden, &Error::BothChildrenHidden);
439    }
440
441    #[test]
442    fn unshared_hidden() {
443        // This program has a repeated hidden node, but all other sharing is correct
444        // and the order is canonical, etc.
445        #[rustfmt::skip]
446        let hidden = [
447            0xd6, 0xe9, 0x62, 0x56, 0x62, 0xc9, 0x38, 0x8a,
448            0x44, 0x31, 0x85, 0xee, 0xc2, 0x2b, 0x91, 0x48,
449            0x87, 0xe1, 0xfd, 0x18, 0x57, 0xc2, 0x8c, 0x4a,
450            0x28, 0x44, 0x2f, 0xa8, 0x61, 0x5c, 0xa7, 0x6e,
451            0x8c, 0xf9, 0x80, 0xc2, 0x18, 0x95, 0x98, 0xb2,
452            0x4e, 0x22, 0x91, 0x0c, 0x61, 0x7b, 0xb0, 0x8a,
453            0xe4, 0x52, 0x21, 0xf8, 0x7f, 0x46, 0x15, 0xf0,
454            0xa3, 0x12, 0x8a, 0x11, 0x0b, 0xea, 0x18, 0x57,
455            0x29, 0xdb, 0xa3, 0x3e, 0x60, 0x30, 0x2c, 0x00,
456            0xd0, 0x48, 0x20,
457        ];
458        assert_program_not_deserializable::<Core>(&hidden, &Error::SharingNotMaximal);
459    }
460
461    #[test]
462    #[cfg(feature = "human_encoding")]
463    fn shared_witnesses() {
464        assert_program_deserializable::<Core>(
465            "main := witness",
466            &[0x38],
467            "a0fc8debd6796917c86b77aded82e6c61649889ae8f2ed65b57b41aa9d90e375",
468            "OA==",
469        );
470
471        #[rustfmt::skip]
472        let bad_diff1s = vec![
473            // Above program, but with both witness nodes shared (note they have
474            // the same type and CMR)
475            vec![
476                0xda, 0xe2, 0x39, 0xa3, 0x10, 0x42, 0x0e, 0x05,
477                0x71, 0x88, 0xa3, 0x6d, 0xc4, 0x11, 0x80, 0x80
478            ],
479            // Same program but with each `witness` replaced by `comp iden witness`, which
480            // is semantically the same but buries the offending witness nodes a bit to
481            // trip up naive sharing logic.
482            vec![
483                0xde, 0x87, 0x04, 0x08, 0xe6, 0x8c, 0x41, 0x08,
484                0x38, 0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46,
485                0x02, 0x00,
486            ],
487        ];
488        for bad_diff1 in bad_diff1s {
489            assert_program_not_deserializable::<Core>(&bad_diff1, &Error::SharingNotMaximal);
490        }
491
492        #[rustfmt::skip]
493        let diff1s = vec![
494            (
495                // Sharing corrected
496                "
497                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
498                    wit1 := witness : 1 -> 2^32
499                    wit2 := witness : 1 -> 2^32
500
501                    wit_diff := comp (comp (pair wit1 wit2) jet_subtract_32) (drop iden) : 1 -> 2^32
502                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
503                    main := comp diff_is_one jet_verify                                  : 1 -> 1
504                ",
505                vec![
506                    0xdc, 0xee, 0x28, 0xe6, 0x8c, 0x41, 0x08, 0x38,
507                    0x15, 0xc6, 0x22, 0x8d, 0xb7, 0x10, 0x46, 0x02,
508                    0x00,
509                ],
510                // CMR not checked against C code, since C won't give us any data without witnesses
511                "e7661630013b789c535146485c72001cad7eea92d04bc4135dc9c2d9e48093b7",
512                "3O4o5oxBCDgVxiKNtxBGAgA=",
513            ),
514            // Same program but with each `witness` replaced by `comp iden witness`.
515            (
516                "
517                    -- Program which demands two 32-bit witnesses, the first one == the second + 1
518                    wit1 := witness : 1 -> 2^32
519                    wit2 := witness : 1 -> 2^32
520                    compwit1 := comp iden wit1
521                    compwit2 := comp iden wit2
522
523                    wit_diff := comp (comp (pair compwit1 compwit2) jet_subtract_32) (drop iden)
524                    diff_is_one := comp (pair wit_diff jet_one_32) jet_eq_32             : 1 -> 2
525                    main := comp diff_is_one jet_verify                                  : 1 -> 1
526                ",
527                vec![
528                    0xe0, 0x28, 0x70, 0x43, 0x83, 0x00, 0xab, 0x9a,
529                    0x31, 0x04, 0x20, 0xe0, 0x57, 0x18, 0x8a, 0x36,
530                    0xdc, 0x41, 0x18, 0x08,
531                ],
532                // CMR not checked against C code, since C won't give us any data without witnesses
533                "774105cb723e00ab3ac48452aa2dd3744999605393f0343eedc8c6387aa5b3c8",
534                "4ChwQ4MAq5oxBCDgVxiKNtxBGAg=",
535            )
536        ];
537
538        for (prog_str, diff1, cmr, b64) in diff1s {
539            let diff1_prog = crate::node::commit::tests::assert_program_deserializable::<Core>(
540                prog_str, &diff1, cmr, b64,
541            );
542
543            // Attempt to finalize, providing 32-bit witnesses 0, 1, ..., and then
544            // counting how many were consumed afterward.
545            let mut counter = 0..100;
546            let witness_iter = (&mut counter).rev().map(Value::u32);
547            let diff1_final = diff1_prog
548                .finalize(&mut SimpleFinalizer::new(witness_iter))
549                .unwrap();
550            assert_eq!(counter, 0..98);
551
552            // Execute the program to confirm that it worked
553            let mut mac =
554                BitMachine::for_program(&diff1_final).expect("program has reasonable bounds");
555            mac.exec(&diff1_final, &CoreEnv::new()).unwrap();
556        }
557    }
558
559    #[test]
560    fn extra_nodes() {
561        // main = comp unit unit # but with an extra unconnected `unit` stuck on the beginning
562        // I created this unit test by hand
563        assert_program_not_deserializable::<Core>(&[0xa9, 0x48, 0x00], &Error::NotInCanonicalOrder);
564    }
565
566    #[test]
567    #[cfg(feature = "human_encoding")]
568    fn regression_177() {
569        // `case (drop iden) iden` from upstream occurs-check test. Has an infinitely sized
570        // input type. Will fail trying to unify the input type with the unit type before
571        // doing the occurs check, putting an infinitely-sized type into the error variant.
572        //
573        // The human-readable encoding will keep going and then also hit the occurs check.
574        //
575        // Check that both error types can be generated and printed in finite space/time.
576        let bad_prog = "
577            id := iden
578            main := case (drop id) id
579        ";
580        match Forest::parse::<Core>(bad_prog) {
581            Ok(_) => panic!("program should have failed"),
582            Err(set) => {
583                let mut errs_happened = (false, false);
584                for err in set.iter() {
585                    match err {
586                        crate::human_encoding::Error::TypeCheck(e @ types::Error::Bind { .. }) => {
587                            errs_happened.0 = true;
588                            e.to_string();
589                        }
590                        crate::human_encoding::Error::TypeCheck(
591                            e @ types::Error::OccursCheck { .. },
592                        ) => {
593                            errs_happened.1 = true;
594                            e.to_string();
595                        }
596                        x => panic!("unexpected error {x:?}"),
597                    }
598                }
599                assert_eq!(errs_happened, (true, true));
600            }
601        };
602    }
603}