Skip to main content

asupersync/
session.rs

1//! Session types for protocol-safe communication.
2//!
3//! Session types encode communication protocols at the type level, ensuring
4//! protocol compliance at compile time. This module provides:
5//!
6//! - **Core protocol building blocks**: `Send`, `Recv`, `Choose`, `Offer`, `End`
7//! - **Duality**: Every session type has a dual — if one endpoint sends, the other receives
8//! - **Typed endpoints**: Communication channels that advance through protocol states
9//!
10//! # Protocol Example
11//!
12//! A simple request-response protocol:
13//!
14//! ```ignore
15//! // Client side: send a request, receive a response
16//! type ClientProtocol = Send<Request, Recv<Response, End>>;
17//!
18//! // Server side is the dual: receive a request, send a response
19//! type ServerProtocol = Dual<ClientProtocol>;
20//! // = Recv<Request, Send<Response, End>>
21//! ```
22//!
23//! # Design
24//!
25//! Session types are zero-sized marker types that exist only at compile time.
26//! They encode the protocol as a type-level state machine. Each communication
27//! operation consumes the current endpoint and returns one at the next state,
28//! ensuring protocol steps are followed in order and exactly once (affine use).
29//!
30//! # Compile-Time Protocol Compliance
31//!
32//! Protocol violations are type errors. The following shows correct usage:
33//!
34//! ```rust
35//! use asupersync::session::{Send, Recv, End, Session, Dual};
36//!
37//! // Dual of Send<T, End> is Recv<T, End>
38//! fn _check_duality() {
39//!     fn _assert_same<A, B>() where A: Session<Dual = B>, B: Session<Dual = A> {}
40//!     _assert_same::<Send<u32, End>, Recv<u32, End>>();
41//! }
42//! ```
43//!
44//! Attempting to call `send` on a `Recv` endpoint is a type error:
45//!
46//! ```compile_fail
47//! use asupersync::session::{Recv, End, channel};
48//!
49//! // ERROR: Endpoint<Recv<u32, End>> does not have a send() method
50//! async fn wrong_direction() {
51//!     let cx = asupersync::cx::Cx::for_testing();
52//!     type P = Recv<u32, End>;
53//!     let (ep, _peer) = channel::<P>();
54//!     ep.send(&cx, 42).await.unwrap();
55//! }
56//! ```
57//!
58//! Attempting to close an endpoint before the protocol completes is a type error:
59//!
60//! ```compile_fail
61//! use asupersync::session::{Send, End, channel};
62//!
63//! // ERROR: Endpoint<Send<u32, End>> does not have close()
64//! fn premature_close() {
65//!     type P = Send<u32, End>;
66//!     let (ep, _peer) = channel::<P>();
67//!     ep.close(); // Only Endpoint<End> has close()
68//! }
69//! ```
70//!
71//! Calling `recv` on a `Send` endpoint is a type error:
72//!
73//! ```compile_fail
74//! use asupersync::session::{Send, End, channel};
75//!
76//! // ERROR: Endpoint<Send<u32, End>> does not have recv()
77//! async fn recv_on_send() {
78//!     let cx = asupersync::cx::Cx::for_testing();
79//!     type P = Send<u32, End>;
80//!     let (ep, _peer) = channel::<P>();
81//!     ep.recv(&cx).await.unwrap();
82//! }
83//! ```
84
85use std::marker::PhantomData;
86
87// ---------- Core session type building blocks ----------
88
89/// A protocol step that sends a value of type `T`, then continues with `Next`.
90pub struct Send<T, Next: Session> {
91    _phantom: PhantomData<(T, Next)>,
92}
93
94/// A protocol step that receives a value of type `T`, then continues with `Next`.
95pub struct Recv<T, Next: Session> {
96    _phantom: PhantomData<(T, Next)>,
97}
98
99/// A protocol step where this endpoint chooses between two continuations.
100///
101/// The peer must offer both branches (see [`Offer`]).
102pub struct Choose<A: Session, B: Session> {
103    _phantom: PhantomData<(A, B)>,
104}
105
106/// A protocol step where this endpoint offers two continuations for the peer to choose.
107///
108/// The peer selects a branch (see [`Choose`]).
109pub struct Offer<A: Session, B: Session> {
110    _phantom: PhantomData<(A, B)>,
111}
112
113/// Protocol termination — no further communication.
114pub struct End;
115
116// ---------- Session trait ----------
117
118/// Marker trait for valid session types.
119///
120/// Every session type has a `Dual` — the complementary protocol that the
121/// other endpoint must follow. Duality is involutive: `Dual<Dual<S>> = S`.
122pub trait Session: std::marker::Send + 'static {
123    /// The dual (complementary) session type.
124    ///
125    /// Duality swaps Send↔Recv and Choose↔Offer, recursing into continuations.
126    type Dual: Session<Dual = Self>;
127}
128
129// ---------- Session implementations ----------
130
131impl Session for End {
132    type Dual = Self;
133}
134
135impl<T: std::marker::Send + 'static, Next: Session> Session for self::Send<T, Next> {
136    type Dual = Recv<T, Next::Dual>;
137}
138
139impl<T: std::marker::Send + 'static, Next: Session> Session for Recv<T, Next> {
140    type Dual = self::Send<T, Next::Dual>;
141}
142
143impl<A: Session, B: Session> Session for Choose<A, B> {
144    type Dual = Offer<A::Dual, B::Dual>;
145}
146
147impl<A: Session, B: Session> Session for Offer<A, B> {
148    type Dual = Choose<A::Dual, B::Dual>;
149}
150
151// ---------- Dual type alias ----------
152
153/// Computes the dual of a session type.
154///
155/// This is a convenience alias for `<S as Session>::Dual`.
156///
157/// # Examples
158///
159/// ```ignore
160/// type Client = Send<Request, Recv<Response, End>>;
161/// type Server = Dual<Client>;
162/// // Server = Recv<Request, Send<Response, End>>
163/// ```
164pub type Dual<S> = <S as Session>::Dual;
165
166// ---------- Typed endpoints ----------
167
168/// A typed endpoint at session state `S`.
169///
170/// Endpoints are affine: each operation consumes the endpoint and returns
171/// a new one at the next protocol state. Dropping an endpoint before `End`
172/// is a type error enforced by the protocol structure.
173///
174/// The underlying transport uses the crate's two-phase MPSC channels with
175/// `Box<dyn Any>` type erasure. Each protocol step sends/receives one value.
176pub struct Endpoint<S: Session> {
177    _session: PhantomData<S>,
178    /// Channel for sending data to the peer.
179    tx: crate::channel::mpsc::Sender<Box<dyn std::any::Any + std::marker::Send>>,
180    /// Channel for receiving data from the peer.
181    rx: crate::channel::mpsc::Receiver<Box<dyn std::any::Any + std::marker::Send>>,
182}
183
184/// Error returned when a session operation fails.
185#[derive(Debug)]
186pub enum SessionError {
187    /// The peer disconnected before the protocol completed.
188    Disconnected,
189    /// The received value did not match the expected type.
190    TypeMismatch,
191    /// The operation was cancelled.
192    Cancelled,
193}
194
195/// Creates a pair of dual session-typed endpoints.
196///
197/// Returns `(Endpoint<S>, Endpoint<Dual<S>>)` — the two sides of the protocol.
198/// The underlying channels have capacity 1 (session types are synchronous).
199///
200/// # Example
201///
202/// ```ignore
203/// type Client = Send<String, End>;
204/// let (client, server) = session::channel::<Client>();
205/// // client: Endpoint<Send<String, End>>
206/// // server: Endpoint<Recv<String, End>>
207/// ```
208#[must_use]
209pub fn channel<S: Session>() -> (Endpoint<S>, Endpoint<Dual<S>>) {
210    let (tx1, rx1) = crate::channel::mpsc::channel(1);
211    let (tx2, rx2) = crate::channel::mpsc::channel(1);
212
213    let ep1 = Endpoint {
214        _session: PhantomData,
215        tx: tx1,
216        rx: rx2,
217    };
218    let ep2 = Endpoint {
219        _session: PhantomData,
220        tx: tx2,
221        rx: rx1,
222    };
223
224    (ep1, ep2)
225}
226
227impl<T, Next> Endpoint<self::Send<T, Next>>
228where
229    T: std::marker::Send + 'static,
230    Next: Session,
231{
232    /// Send a value to the peer, advancing the protocol to the next state.
233    ///
234    /// Consumes this endpoint and returns a new one at state `Next`.
235    /// Uses the crate's two-phase send (reserve/commit) for cancel-safety.
236    pub async fn send(self, cx: &crate::cx::Cx, value: T) -> Result<Endpoint<Next>, SessionError> {
237        let Self { tx, rx, .. } = self;
238        let boxed: Box<dyn std::any::Any + std::marker::Send> = Box::new(value);
239        tx.send(cx, boxed)
240            .await
241            .map_err(|_| SessionError::Disconnected)?;
242        Ok(Endpoint {
243            _session: PhantomData,
244            tx,
245            rx,
246        })
247    }
248}
249
250impl<T, Next> Endpoint<Recv<T, Next>>
251where
252    T: std::marker::Send + 'static,
253    Next: Session,
254{
255    /// Receive a value from the peer, advancing the protocol to the next state.
256    ///
257    /// Consumes this endpoint and returns the value plus a new endpoint at state `Next`.
258    pub async fn recv(self, cx: &crate::cx::Cx) -> Result<(T, Endpoint<Next>), SessionError> {
259        let Self { tx, mut rx, .. } = self;
260        let boxed = rx.recv(cx).await.map_err(|e| match e {
261            crate::channel::mpsc::RecvError::Cancelled => SessionError::Cancelled,
262            crate::channel::mpsc::RecvError::Disconnected
263            | crate::channel::mpsc::RecvError::Empty => SessionError::Disconnected,
264        })?;
265        let value = boxed
266            .downcast::<T>()
267            .map_err(|_| SessionError::TypeMismatch)?;
268        Ok((
269            *value,
270            Endpoint {
271                _session: PhantomData,
272                tx,
273                rx,
274            },
275        ))
276    }
277}
278
279impl<A: Session, B: Session> Endpoint<Choose<A, B>> {
280    /// Choose the left branch of the protocol.
281    ///
282    /// Sends the choice to the peer and returns an endpoint at state `A`.
283    pub async fn choose_left(self, cx: &crate::cx::Cx) -> Result<Endpoint<A>, SessionError> {
284        let Self { tx, rx, .. } = self;
285        let boxed: Box<dyn std::any::Any + std::marker::Send> = Box::new(Branch::Left);
286        tx.send(cx, boxed)
287            .await
288            .map_err(|_| SessionError::Disconnected)?;
289        Ok(Endpoint {
290            _session: PhantomData,
291            tx,
292            rx,
293        })
294    }
295
296    /// Choose the right branch of the protocol.
297    ///
298    /// Sends the choice to the peer and returns an endpoint at state `B`.
299    pub async fn choose_right(self, cx: &crate::cx::Cx) -> Result<Endpoint<B>, SessionError> {
300        let Self { tx, rx, .. } = self;
301        let boxed: Box<dyn std::any::Any + std::marker::Send> = Box::new(Branch::Right);
302        tx.send(cx, boxed)
303            .await
304            .map_err(|_| SessionError::Disconnected)?;
305        Ok(Endpoint {
306            _session: PhantomData,
307            tx,
308            rx,
309        })
310    }
311}
312
313/// Result of an offer operation — the peer's chosen branch.
314pub enum Offered<A: Session, B: Session> {
315    /// Peer chose the left branch.
316    Left(Endpoint<A>),
317    /// Peer chose the right branch.
318    Right(Endpoint<B>),
319}
320
321impl<A: Session, B: Session> Endpoint<Offer<A, B>> {
322    /// Wait for the peer to choose a branch.
323    ///
324    /// Returns the chosen branch as an `Offered` enum.
325    pub async fn offer(self, cx: &crate::cx::Cx) -> Result<Offered<A, B>, SessionError> {
326        let Self { tx, mut rx, .. } = self;
327        let boxed = rx.recv(cx).await.map_err(|e| match e {
328            crate::channel::mpsc::RecvError::Cancelled => SessionError::Cancelled,
329            crate::channel::mpsc::RecvError::Disconnected
330            | crate::channel::mpsc::RecvError::Empty => SessionError::Disconnected,
331        })?;
332        let branch = boxed
333            .downcast::<Branch>()
334            .map_err(|_| SessionError::TypeMismatch)?;
335        match *branch {
336            Branch::Left => Ok(Offered::Left(Endpoint {
337                _session: PhantomData,
338                tx,
339                rx,
340            })),
341            Branch::Right => Ok(Offered::Right(Endpoint {
342                _session: PhantomData,
343                tx,
344                rx,
345            })),
346        }
347    }
348}
349
350impl Endpoint<End> {
351    /// Close a completed protocol endpoint.
352    ///
353    /// This is the only way to properly terminate a session. Consuming the
354    /// endpoint at the `End` state prevents protocol steps from being skipped.
355    pub fn close(self) {
356        // Endpoint is consumed; channels are dropped.
357    }
358}
359
360// ---------- Choice direction ----------
361
362/// Direction chosen by `Choose` — left or right branch.
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub enum Branch {
365    /// Select the left/first branch.
366    Left,
367    /// Select the right/second branch.
368    Right,
369}
370
371// ---------- Tests ----------
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    fn init_test(name: &str) {
378        crate::test_utils::init_test_logging();
379        crate::test_phase!(name);
380    }
381
382    // --- Compile-time duality checks via assert_type_eq ---
383
384    fn assert_dual<S: Session>()
385    where
386        S::Dual: Session<Dual = S>,
387    {
388        // If this compiles, duality is involutive for S
389    }
390
391    #[test]
392    fn duality_end() {
393        fn _check() -> Dual<End> {
394            End
395        }
396
397        init_test("duality_end");
398
399        assert_dual::<End>();
400        // Dual<End> = End
401
402        crate::test_complete!("duality_end");
403    }
404
405    #[test]
406    fn duality_send_recv() {
407        init_test("duality_send_recv");
408
409        // Dual<Send<T, End>> = Recv<T, End>
410        assert_dual::<Send<String, End>>();
411        assert_dual::<Recv<String, End>>();
412
413        // Dual<Send<u64, Recv<bool, End>>> = Recv<u64, Send<bool, End>>
414        assert_dual::<Send<u64, Recv<bool, End>>>();
415
416        crate::test_complete!("duality_send_recv");
417    }
418
419    #[test]
420    fn duality_choose_offer() {
421        init_test("duality_choose_offer");
422
423        // Dual<Choose<A, B>> = Offer<Dual<A>, Dual<B>>
424        assert_dual::<Choose<End, End>>();
425        assert_dual::<Offer<End, End>>();
426        assert_dual::<Choose<Send<u8, End>, Recv<u8, End>>>();
427
428        crate::test_complete!("duality_choose_offer");
429    }
430
431    #[test]
432    fn duality_is_involutive() {
433        // Dual<Dual<S>> = S for all S
434        // This is enforced by the Session trait bound: Dual: Session<Dual = Self>
435        // The fact that these compile proves involution:
436        fn _roundtrip_end(_: Dual<Dual<End>>) -> End {
437            End
438        }
439
440        fn _roundtrip_send(_: Dual<Dual<Send<u32, End>>>) -> Send<u32, End> {
441            Send {
442                _phantom: PhantomData,
443            }
444        }
445
446        init_test("duality_is_involutive");
447
448        crate::test_complete!("duality_is_involutive");
449    }
450
451    #[test]
452    fn duality_complex_protocol() {
453        // ATM-like protocol:
454        // Client: Send<Card, Recv<Pin, Choose<
455        //           Send<Amount, Recv<Cash, End>>,   -- withdraw
456        //           Recv<Balance, End>                -- check balance
457        //         >>>
458        type Card = u64;
459        type Pin = u32;
460        type Amount = u64;
461        type Cash = u64;
462        type Balance = u64;
463
464        type ClientProtocol =
465            Send<Card, Recv<Pin, Choose<Send<Amount, Recv<Cash, End>>, Recv<Balance, End>>>>;
466
467        // Server (dual):
468        // Recv<Card, Send<Pin, Offer<
469        //   Recv<Amount, Send<Cash, End>>,
470        //   Send<Balance, End>
471        // >>>
472        type ServerProtocol = Dual<ClientProtocol>;
473
474        // Verify the dual structure compiles correctly
475        fn _accept_server(_: ServerProtocol) {}
476
477        init_test("duality_complex_protocol");
478
479        assert_dual::<ClientProtocol>();
480        assert_dual::<ServerProtocol>();
481
482        crate::test_complete!("duality_complex_protocol");
483    }
484
485    #[test]
486    fn channel_creates_dual_endpoints() {
487        type P = Send<u32, Recv<bool, End>>;
488
489        init_test("channel_creates_dual_endpoints");
490        let (_client, _server) = channel::<P>();
491
492        // _client: Endpoint<Send<u32, Recv<bool, End>>>
493        // _server: Endpoint<Recv<u32, Send<bool, End>>>
494
495        crate::test_complete!("channel_creates_dual_endpoints");
496    }
497
498    #[test]
499    fn endpoint_close_at_end() {
500        init_test("endpoint_close_at_end");
501
502        let (ep1, ep2) = channel::<End>();
503        ep1.close();
504        ep2.close();
505
506        crate::test_complete!("endpoint_close_at_end");
507    }
508
509    #[test]
510    fn branch_enum() {
511        init_test("branch_enum");
512
513        let left = Branch::Left;
514        let right = Branch::Right;
515        assert_ne!(left, right);
516        assert_eq!(left, Branch::Left);
517        assert_eq!(right, Branch::Right);
518
519        crate::test_complete!("branch_enum");
520    }
521
522    // --- E2E protocol tests (lab runtime) ---
523
524    use std::sync::Arc;
525    use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
526
527    /// E2E: Simple request-response protocol over session-typed endpoints.
528    #[test]
529    fn session_send_recv_e2e() {
530        // Protocol: Send<u64, Recv<u64, End>>
531        type ClientP = Send<u64, Recv<u64, End>>;
532
533        init_test("session_send_recv_e2e");
534
535        let mut runtime = crate::lab::LabRuntime::new(crate::lab::LabConfig::default());
536        let region = runtime
537            .state
538            .create_root_region(crate::types::Budget::INFINITE);
539
540        let (client_ep, server_ep) = channel::<ClientP>();
541
542        let client_result = Arc::new(AtomicU64::new(0));
543        let server_result = Arc::new(AtomicU64::new(0));
544        let cr = client_result.clone();
545        let sr = server_result.clone();
546
547        // Client task: send 42, receive response
548        let (client_id, _) = runtime
549            .state
550            .create_task(region, crate::types::Budget::INFINITE, async move {
551                let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
552                let ep = client_ep.send(&cx, 42).await.expect("client send");
553                let (response, ep) = ep.recv(&cx).await.expect("client recv");
554                cr.store(response, Ordering::SeqCst);
555                ep.close();
556            })
557            .unwrap();
558
559        // Server task: receive request, send response (value * 2)
560        let (server_id, _) = runtime
561            .state
562            .create_task(region, crate::types::Budget::INFINITE, async move {
563                let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
564                let (request, ep) = server_ep.recv(&cx).await.expect("server recv");
565                sr.store(request, Ordering::SeqCst);
566                let ep = ep.send(&cx, request * 2).await.expect("server send");
567                ep.close();
568            })
569            .unwrap();
570
571        runtime.scheduler.lock().schedule(client_id, 0);
572        runtime.scheduler.lock().schedule(server_id, 0);
573        runtime.run_until_quiescent();
574
575        assert_eq!(
576            server_result.load(Ordering::SeqCst),
577            42,
578            "server received 42"
579        );
580        assert_eq!(
581            client_result.load(Ordering::SeqCst),
582            84,
583            "client received 84"
584        );
585
586        crate::test_complete!("session_send_recv_e2e");
587    }
588
589    /// E2E: Choose/Offer protocol — client chooses left branch.
590    #[test]
591    fn session_choose_offer_e2e() {
592        // Protocol: Choose<Send<u64, End>, Recv<u64, End>>
593        type ClientP = Choose<Send<u64, End>, Recv<u64, End>>;
594
595        init_test("session_choose_offer_e2e");
596
597        let mut runtime = crate::lab::LabRuntime::new(crate::lab::LabConfig::default());
598        let region = runtime
599            .state
600            .create_root_region(crate::types::Budget::INFINITE);
601
602        let (client_ep, server_ep) = channel::<ClientP>();
603
604        let left_taken = Arc::new(AtomicBool::new(false));
605        let value_sent = Arc::new(AtomicU64::new(0));
606        let lt = left_taken.clone();
607        let vs = value_sent.clone();
608
609        // Client: choose left branch, send a value
610        let (client_id, _) = runtime
611            .state
612            .create_task(region, crate::types::Budget::INFINITE, async move {
613                let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
614                let ep = client_ep.choose_left(&cx).await.expect("choose left");
615                let ep = ep.send(&cx, 99).await.expect("send on left");
616                ep.close();
617            })
618            .unwrap();
619
620        // Server: offer both branches, handle whichever the client picks
621        let (server_id, _) = runtime
622            .state
623            .create_task(region, crate::types::Budget::INFINITE, async move {
624                let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
625                match server_ep.offer(&cx).await.expect("offer") {
626                    Offered::Left(ep) => {
627                        lt.store(true, Ordering::SeqCst);
628                        let (val, ep) = ep.recv(&cx).await.expect("recv on left");
629                        vs.store(val, Ordering::SeqCst);
630                        ep.close();
631                    }
632                    Offered::Right(ep) => {
633                        // Server's right branch: Send<u64, End>
634                        let ep = ep.send(&cx, 0).await.unwrap();
635                        ep.close();
636                    }
637                }
638            })
639            .unwrap();
640
641        runtime.scheduler.lock().schedule(client_id, 0);
642        runtime.scheduler.lock().schedule(server_id, 0);
643        runtime.run_until_quiescent();
644
645        assert!(left_taken.load(Ordering::SeqCst), "server took left branch");
646        assert_eq!(value_sent.load(Ordering::SeqCst), 99, "server received 99");
647
648        crate::test_complete!("session_choose_offer_e2e");
649    }
650
651    /// E2E: Deterministic session execution — same seed, same result.
652    #[test]
653    fn session_deterministic() {
654        fn run_protocol(seed: u64) -> u64 {
655            type P = Send<u64, Recv<u64, End>>;
656
657            let config = crate::lab::LabConfig::new(seed);
658            let mut runtime = crate::lab::LabRuntime::new(config);
659            let region = runtime
660                .state
661                .create_root_region(crate::types::Budget::INFINITE);
662            let (client_ep, server_ep) = channel::<P>();
663
664            let result = Arc::new(AtomicU64::new(0));
665            let r = result.clone();
666
667            let (cid, _) = runtime
668                .state
669                .create_task(region, crate::types::Budget::INFINITE, async move {
670                    let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
671                    let ep = client_ep.send(&cx, 7).await.unwrap();
672                    let (val, ep) = ep.recv(&cx).await.unwrap();
673                    r.store(val, Ordering::SeqCst);
674                    ep.close();
675                })
676                .unwrap();
677
678            let (sid, _) = runtime
679                .state
680                .create_task(region, crate::types::Budget::INFINITE, async move {
681                    let cx: crate::cx::Cx = crate::cx::Cx::for_testing();
682                    let (v, ep) = server_ep.recv(&cx).await.unwrap();
683                    let ep = ep.send(&cx, v + 100).await.unwrap();
684                    ep.close();
685                })
686                .unwrap();
687
688            runtime.scheduler.lock().schedule(cid, 0);
689            runtime.scheduler.lock().schedule(sid, 0);
690            runtime.run_until_quiescent();
691
692            result.load(Ordering::SeqCst)
693        }
694
695        init_test("session_deterministic");
696
697        let r1 = run_protocol(0xCAFE);
698        let r2 = run_protocol(0xCAFE);
699        assert_eq!(r1, r2, "deterministic replay");
700        assert_eq!(r1, 107, "7 + 100 = 107");
701
702        crate::test_complete!("session_deterministic");
703    }
704
705    // Pure data-type tests (wave 36 – CyanBarn)
706
707    #[test]
708    fn session_error_debug() {
709        let e1 = SessionError::Disconnected;
710        let e2 = SessionError::TypeMismatch;
711        let e3 = SessionError::Cancelled;
712
713        let dbg1 = format!("{e1:?}");
714        let dbg2 = format!("{e2:?}");
715        let dbg3 = format!("{e3:?}");
716
717        assert!(dbg1.contains("Disconnected"));
718        assert!(dbg2.contains("TypeMismatch"));
719        assert!(dbg3.contains("Cancelled"));
720    }
721
722    #[test]
723    fn branch_debug_copy() {
724        let left = Branch::Left;
725        let right = Branch::Right;
726
727        let dbg_l = format!("{left:?}");
728        let dbg_r = format!("{right:?}");
729        assert!(dbg_l.contains("Left"));
730        assert!(dbg_r.contains("Right"));
731
732        // Copy semantics
733        let left2 = left;
734        assert_eq!(left, left2);
735
736        // Clone
737        let right2 = right;
738        assert_eq!(right, right2);
739    }
740}