Skip to main content

oxilean_std/session_types/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8/// A global session type (multiparty protocol specification).
9#[derive(Debug, Clone)]
10pub enum GType {
11    /// `p → q : T; G` — p sends T to q, continues as G.
12    Comm {
13        /// Sender role.
14        sender: Role,
15        /// Receiver role.
16        receiver: Role,
17        /// Communicated type.
18        msg_ty: BaseType,
19        /// Continuation.
20        cont: Box<GType>,
21    },
22    /// `p → q { branch₁ : G₁, branch₂ : G₂ }` — p selects a branch to q.
23    Choice {
24        /// The role that makes the choice.
25        selector: Role,
26        /// The role that receives the choice.
27        receiver: Role,
28        /// Named branches (label → continuation).
29        branches: HashMap<String, GType>,
30    },
31    /// Protocol end.
32    End,
33    /// Recursive global type: `μX.G`.
34    Rec(String, Box<GType>),
35    /// Recursion variable.
36    Var(String),
37}
38impl GType {
39    /// Collect all roles participating in this global type.
40    pub fn participants(&self) -> HashSet<Role> {
41        let mut roles = HashSet::new();
42        self.collect_roles(&mut roles);
43        roles
44    }
45    fn collect_roles(&self, roles: &mut HashSet<Role>) {
46        match self {
47            GType::Comm {
48                sender,
49                receiver,
50                cont,
51                ..
52            } => {
53                roles.insert(sender.clone());
54                roles.insert(receiver.clone());
55                cont.collect_roles(roles);
56            }
57            GType::Choice {
58                selector,
59                receiver,
60                branches,
61            } => {
62                roles.insert(selector.clone());
63                roles.insert(receiver.clone());
64                for cont in branches.values() {
65                    cont.collect_roles(roles);
66                }
67            }
68            GType::End | GType::Var(_) => {}
69            GType::Rec(_, body) => body.collect_roles(roles),
70        }
71    }
72    /// Project the global type onto a specific role.
73    pub fn project(&self, role: &Role) -> LType {
74        match self {
75            GType::Comm {
76                sender,
77                receiver,
78                msg_ty,
79                cont,
80            } => {
81                let cont_proj = cont.project(role);
82                if sender == role {
83                    LType::Send(receiver.clone(), msg_ty.clone(), Box::new(cont_proj))
84                } else if receiver == role {
85                    LType::Recv(sender.clone(), msg_ty.clone(), Box::new(cont_proj))
86                } else {
87                    cont_proj
88                }
89            }
90            GType::Choice {
91                selector,
92                receiver,
93                branches,
94            } => {
95                if selector == role {
96                    let mut proj_branches: Vec<(String, LType)> = branches
97                        .iter()
98                        .map(|(lbl, g)| (lbl.clone(), g.project(role)))
99                        .collect();
100                    proj_branches.sort_by(|a, b| a.0.cmp(&b.0));
101                    LType::IChoice(receiver.clone(), proj_branches)
102                } else if receiver == role {
103                    let mut proj_branches: Vec<(String, LType)> = branches
104                        .iter()
105                        .map(|(lbl, g)| (lbl.clone(), g.project(role)))
106                        .collect();
107                    proj_branches.sort_by(|a, b| a.0.cmp(&b.0));
108                    LType::EChoice(selector.clone(), proj_branches)
109                } else {
110                    let projs: Vec<LType> = branches.values().map(|g| g.project(role)).collect();
111                    Self::merge_all(projs)
112                }
113            }
114            GType::End => LType::End,
115            GType::Rec(x, body) => LType::Rec(x.clone(), Box::new(body.project(role))),
116            GType::Var(x) => LType::Var(x.clone()),
117        }
118    }
119    fn merge_all(types: Vec<LType>) -> LType {
120        types
121            .into_iter()
122            .reduce(|a, b| if a == b { a } else { b })
123            .unwrap_or(LType::End)
124    }
125}
126/// An asynchronous session endpoint that buffers outgoing messages.
127///
128/// Unlike `SessionEndpoint`, sends do not block: they enqueue into a local
129/// outbox. The peer drains the outbox into its inbox when it is ready to
130/// receive, modelling asynchronous / buffered channels.
131pub struct AsyncSessionEndpoint {
132    /// The remaining (un-consumed) session type.
133    pub remaining: SType,
134    /// Outgoing messages waiting to be delivered.
135    outbox: VecDeque<Message>,
136    /// Incoming messages ready to be received.
137    inbox: VecDeque<Message>,
138}
139impl AsyncSessionEndpoint {
140    /// Create a new async endpoint with the given session type.
141    pub fn new(stype: SType) -> Self {
142        AsyncSessionEndpoint {
143            remaining: stype,
144            outbox: VecDeque::new(),
145            inbox: VecDeque::new(),
146        }
147    }
148    /// Asynchronously send a message (enqueue without blocking).
149    pub fn async_send(&mut self, msg: Message) -> Result<(), String> {
150        match &self.remaining.clone() {
151            SType::Send(_, cont) => {
152                self.remaining = *cont.clone();
153                self.outbox.push_back(msg);
154                Ok(())
155            }
156            other => Err(format!("AsyncSend: expected Send, got {}", other)),
157        }
158    }
159    /// Deliver all outgoing messages into the peer's inbox.
160    /// Returns the number of messages flushed.
161    pub fn flush_to(&mut self, peer: &mut AsyncSessionEndpoint) -> usize {
162        let count = self.outbox.len();
163        while let Some(msg) = self.outbox.pop_front() {
164            peer.inbox.push_back(msg);
165        }
166        count
167    }
168    /// Receive a message from the inbox (must have been delivered by the peer).
169    pub fn async_recv(&mut self) -> Result<Message, String> {
170        match &self.remaining.clone() {
171            SType::Recv(_, cont) => {
172                if let Some(msg) = self.inbox.pop_front() {
173                    self.remaining = *cont.clone();
174                    Ok(msg)
175                } else {
176                    Err("AsyncRecv: inbox empty — message not yet delivered".to_string())
177                }
178            }
179            other => Err(format!("AsyncRecv: expected Recv, got {}", other)),
180        }
181    }
182    /// Number of messages waiting in the outbox (not yet delivered).
183    pub fn outbox_len(&self) -> usize {
184        self.outbox.len()
185    }
186    /// Number of messages waiting in the inbox (delivered but not yet consumed).
187    pub fn inbox_len(&self) -> usize {
188        self.inbox.len()
189    }
190}
191/// A role in a multiparty session.
192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
193pub struct Role(pub String);
194impl Role {
195    /// Create a new role.
196    pub fn new(name: impl Into<String>) -> Self {
197        Role(name.into())
198    }
199}
200/// A builder for constructing session types fluently.
201pub struct ProtocolBuilder {
202    current: SType,
203}
204impl ProtocolBuilder {
205    /// Start with the `End` session.
206    pub fn end() -> Self {
207        ProtocolBuilder {
208            current: SType::End,
209        }
210    }
211    /// Prepend a send operation.
212    pub fn then_send(self, ty: BaseType) -> Self {
213        ProtocolBuilder {
214            current: SType::Send(Box::new(ty), Box::new(self.current)),
215        }
216    }
217    /// Prepend a receive operation.
218    pub fn then_recv(self, ty: BaseType) -> Self {
219        ProtocolBuilder {
220            current: SType::Recv(Box::new(ty), Box::new(self.current)),
221        }
222    }
223    /// Finalize and return the session type.
224    pub fn build(self) -> SType {
225        self.current
226    }
227}
228/// A binary session type.
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub enum SType {
231    /// `!T.S` — send T then continue as S.
232    Send(Box<BaseType>, Box<SType>),
233    /// `?T.S` — receive T then continue as S.
234    Recv(Box<BaseType>, Box<SType>),
235    /// Session termination.
236    End,
237    /// Internal choice: `S₁ ⊕ S₂`.
238    Choice(Box<SType>, Box<SType>),
239    /// External choice: `S₁ & S₂`.
240    Branch(Box<SType>, Box<SType>),
241    /// Recursive session type: `μX.S`.
242    Rec(String, Box<SType>),
243    /// Session type variable.
244    Var(String),
245}
246impl SType {
247    /// Compute the dual session type.
248    pub fn dual(&self) -> SType {
249        match self {
250            SType::Send(t, s) => SType::Recv(t.clone(), Box::new(s.dual())),
251            SType::Recv(t, s) => SType::Send(t.clone(), Box::new(s.dual())),
252            SType::End => SType::End,
253            SType::Choice(s1, s2) => SType::Branch(Box::new(s1.dual()), Box::new(s2.dual())),
254            SType::Branch(s1, s2) => SType::Choice(Box::new(s1.dual()), Box::new(s2.dual())),
255            SType::Rec(x, s) => SType::Rec(x.clone(), Box::new(s.dual())),
256            SType::Var(x) => SType::Var(x.clone()),
257        }
258    }
259    /// Unfold a recursive session type one step.
260    pub fn unfold(&self) -> SType {
261        match self {
262            SType::Rec(x, body) => {
263                let mut body = (**body).clone();
264                body.subst_var(x, self);
265                body
266            }
267            other => other.clone(),
268        }
269    }
270    /// Substitute variable `x` with `replacement` in `self`.
271    fn subst_var(&mut self, x: &str, replacement: &SType) {
272        match self {
273            SType::Send(_, s) | SType::Recv(_, s) => s.subst_var(x, replacement),
274            SType::End => {}
275            SType::Choice(s1, s2) | SType::Branch(s1, s2) => {
276                s1.subst_var(x, replacement);
277                s2.subst_var(x, replacement);
278            }
279            SType::Rec(y, s) => {
280                if y != x {
281                    s.subst_var(x, replacement);
282                }
283            }
284            SType::Var(y) => {
285                if y == x {
286                    *self = replacement.clone();
287                }
288            }
289        }
290    }
291    /// Check if this session type is `End`.
292    pub fn is_end(&self) -> bool {
293        matches!(self, SType::End)
294    }
295    /// Check if this is a send type.
296    pub fn is_send(&self) -> bool {
297        matches!(self, SType::Send(_, _))
298    }
299    /// Check if this is a receive type.
300    pub fn is_recv(&self) -> bool {
301        matches!(self, SType::Recv(_, _))
302    }
303}
304/// A base type that can be communicated over a session.
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub enum BaseType {
307    /// Natural numbers.
308    Nat,
309    /// Booleans.
310    Bool,
311    /// Strings.
312    Str,
313    /// Unit type.
314    Unit,
315    /// A named type.
316    Named(String),
317    /// Pair type.
318    Pair(Box<BaseType>, Box<BaseType>),
319    /// Sum type.
320    Sum(Box<BaseType>, Box<BaseType>),
321}
322/// A Markov-chain protocol simulator: makes probabilistic choices at each
323/// `Choice` node according to supplied weights.
324pub struct ProbSessionScheduler {
325    /// The per-choice weights.  Key = choice depth (simple approximation).
326    branches: Vec<ProbBranch>,
327}
328impl ProbSessionScheduler {
329    /// Create a new scheduler with the given branches.
330    pub fn new(branches: Vec<ProbBranch>) -> Self {
331        ProbSessionScheduler { branches }
332    }
333    /// Normalise the weights to proper probabilities that sum to 1.
334    pub fn probabilities(&self) -> Vec<f64> {
335        let total: f64 = self.branches.iter().map(|b| b.weight).sum();
336        if total == 0.0 {
337            return vec![0.0; self.branches.len()];
338        }
339        self.branches.iter().map(|b| b.weight / total).collect()
340    }
341    /// Sample a branch index deterministically by taking the one with the
342    /// highest weight (useful for testing without a random source).
343    pub fn greedy_choice(&self) -> Option<usize> {
344        self.branches
345            .iter()
346            .enumerate()
347            .max_by(|a, b| {
348                a.1.weight
349                    .partial_cmp(&b.1.weight)
350                    .unwrap_or(std::cmp::Ordering::Equal)
351            })
352            .map(|(idx, _)| idx)
353    }
354    /// Expected number of rounds: sum of (prob × cost) for each branch,
355    /// where cost is approximated as 1 for non-End, 0 for End.
356    pub fn expected_rounds(&self) -> f64 {
357        let probs = self.probabilities();
358        probs
359            .iter()
360            .zip(self.branches.iter())
361            .map(|(p, b)| {
362                let cost = if b.cont == SType::End { 0.0 } else { 1.0 };
363                p * cost
364            })
365            .sum()
366    }
367}
368/// A message that can be sent or received on a session channel.
369#[derive(Debug, Clone)]
370pub enum Message {
371    /// A natural number.
372    Nat(u64),
373    /// A boolean.
374    Bool(bool),
375    /// A string.
376    Str(String),
377    /// Unit.
378    Unit,
379    /// A left injection (choice/sum).
380    Left,
381    /// A right injection (choice/sum).
382    Right,
383}
384/// Behavioral subtype checker for session types.
385///
386/// Implements the standard coinductive subtype relation:
387/// - `Send T S₁ <: Send T S₂` when `S₁ <: S₂` (covariant send)
388/// - `Recv T S₁ <: Recv T S₂` when `S₁ <: S₂` (covariant receive)
389/// - `Choice S₁ S₂ <: Choice T₁ T₂` when `S₁ <: T₁` and `S₂ <: T₂`
390/// - `Branch S₁ S₂ <: Branch T₁ T₂` when `S₁ <: T₁` and `S₂ <: T₂`
391/// - `End <: End`
392pub struct SessionSubtypeChecker {
393    /// Pairs already decided (for coinductive checking of recursive types).
394    decided: HashSet<(String, String)>,
395}
396impl SessionSubtypeChecker {
397    /// Create a new subtype checker.
398    pub fn new() -> Self {
399        SessionSubtypeChecker {
400            decided: HashSet::new(),
401        }
402    }
403    /// Check whether `sub <: sup` holds under the behavioral subtype relation.
404    pub fn is_subtype(&mut self, sub: &SType, sup: &SType) -> bool {
405        let key = (format!("{}", sub), format!("{}", sup));
406        if self.decided.contains(&key) {
407            return true;
408        }
409        self.decided.insert(key);
410        match (sub, sup) {
411            (SType::End, SType::End) => true,
412            (SType::Send(t1, s1), SType::Send(t2, s2)) => t1 == t2 && self.is_subtype(s1, s2),
413            (SType::Recv(t1, s1), SType::Recv(t2, s2)) => t1 == t2 && self.is_subtype(s1, s2),
414            (SType::Choice(l1, r1), SType::Choice(l2, r2)) => {
415                self.is_subtype(l1, l2) && self.is_subtype(r1, r2)
416            }
417            (SType::Branch(l1, r1), SType::Branch(l2, r2)) => {
418                self.is_subtype(l1, l2) && self.is_subtype(r1, r2)
419            }
420            (SType::Rec(_, _), _) => self.is_subtype(&sub.unfold(), sup),
421            (_, SType::Rec(_, _)) => self.is_subtype(sub, &sup.unfold()),
422            _ => false,
423        }
424    }
425}
426/// An action step extracted from a global type during choreography execution.
427#[derive(Debug, Clone)]
428pub enum ChoreographyStep {
429    /// `sender` sends a message of type `msg_ty` to `receiver`.
430    Comm {
431        /// The sending role.
432        sender: String,
433        /// The receiving role.
434        receiver: String,
435        /// A description of the communicated type.
436        msg_ty: String,
437    },
438    /// A choice is made by `selector`, communicated to `receiver`.
439    Choice {
440        /// The role making the selection.
441        selector: String,
442        /// The role receiving the label.
443        receiver: String,
444        /// The selected branch label.
445        branch: String,
446    },
447    /// The protocol has ended.
448    End,
449}
450/// A choreography engine that steps through a `GType` and emits actions.
451pub struct ChoreographyEngine {
452    /// Sequence of steps emitted so far.
453    pub trace: Vec<ChoreographyStep>,
454}
455impl ChoreographyEngine {
456    /// Create a new engine with an empty trace.
457    pub fn new() -> Self {
458        ChoreographyEngine { trace: vec![] }
459    }
460    /// Execute a global type to completion, recording all communication steps.
461    /// Returns `Err` if a recursion variable is encountered without binding
462    /// (unfolding must be done before calling this method).
463    pub fn execute(&mut self, gtype: &GType) -> Result<(), String> {
464        match gtype {
465            GType::Comm {
466                sender,
467                receiver,
468                msg_ty,
469                cont,
470            } => {
471                self.trace.push(ChoreographyStep::Comm {
472                    sender: sender.0.clone(),
473                    receiver: receiver.0.clone(),
474                    msg_ty: format!("{}", msg_ty),
475                });
476                self.execute(cont)
477            }
478            GType::Choice {
479                selector,
480                receiver,
481                branches,
482            } => {
483                let mut sorted: Vec<(&String, &GType)> = branches.iter().collect();
484                sorted.sort_by_key(|(k, _)| k.as_str());
485                if let Some((label, cont)) = sorted.first() {
486                    self.trace.push(ChoreographyStep::Choice {
487                        selector: selector.0.clone(),
488                        receiver: receiver.0.clone(),
489                        branch: (*label).clone(),
490                    });
491                    self.execute(cont)
492                } else {
493                    Err("GType::Choice has no branches".to_string())
494                }
495            }
496            GType::End => {
497                self.trace.push(ChoreographyStep::End);
498                Ok(())
499            }
500            GType::Rec(_, body) => self.execute(body),
501            GType::Var(x) => Err(format!("Unresolved recursion variable: {}", x)),
502        }
503    }
504    /// Return the number of communication actions (excluding the final End).
505    pub fn comm_count(&self) -> usize {
506        self.trace
507            .iter()
508            .filter(|s| !matches!(s, ChoreographyStep::End))
509            .count()
510    }
511}
512/// A session channel endpoint, consuming the session type as it is used.
513///
514/// Note: In Rust we cannot enforce linearity at the type level without a
515/// linear type system; this struct instead tracks usage dynamically.
516pub struct SessionEndpoint {
517    /// The remaining session type (what is yet to be done).
518    pub remaining: SType,
519    /// The communication buffer (incoming messages).
520    buffer: VecDeque<Message>,
521    /// Whether the session has been closed.
522    closed: bool,
523}
524impl SessionEndpoint {
525    /// Create a new session endpoint with the given initial session type.
526    pub fn new(stype: SType) -> Self {
527        SessionEndpoint {
528            remaining: stype,
529            buffer: VecDeque::new(),
530            closed: false,
531        }
532    }
533    /// Send a message, advancing the session type.
534    pub fn send(&mut self, msg: Message) -> Result<(), String> {
535        match &self.remaining.clone() {
536            SType::Send(_, continuation) => {
537                self.remaining = *continuation.clone();
538                self.buffer.push_back(msg);
539                Ok(())
540            }
541            other => Err(format!("Expected Send, got {}", other)),
542        }
543    }
544    /// Receive a message from the buffer, advancing the session type.
545    pub fn recv(&mut self) -> Result<Message, String> {
546        match &self.remaining.clone() {
547            SType::Recv(_, continuation) => {
548                if let Some(msg) = self.buffer.pop_front() {
549                    self.remaining = *continuation.clone();
550                    Ok(msg)
551                } else {
552                    Err("No message available".to_string())
553                }
554            }
555            other => Err(format!("Expected Recv, got {}", other)),
556        }
557    }
558    /// Select the left branch of an internal choice.
559    pub fn select_left(&mut self) -> Result<(), String> {
560        match &self.remaining.clone() {
561            SType::Choice(left, _) => {
562                self.remaining = *left.clone();
563                Ok(())
564            }
565            other => Err(format!("Expected Choice, got {}", other)),
566        }
567    }
568    /// Select the right branch of an internal choice.
569    pub fn select_right(&mut self) -> Result<(), String> {
570        match &self.remaining.clone() {
571            SType::Choice(_, right) => {
572                self.remaining = *right.clone();
573                Ok(())
574            }
575            other => Err(format!("Expected Choice, got {}", other)),
576        }
577    }
578    /// Close the session.
579    pub fn close(&mut self) -> Result<(), String> {
580        if self.remaining == SType::End {
581            self.closed = true;
582            Ok(())
583        } else {
584            Err(format!("Expected End, got {}", self.remaining))
585        }
586    }
587    /// Check if the session is complete.
588    pub fn is_complete(&self) -> bool {
589        self.closed
590    }
591}
592/// A probabilistic session choice, picking a branch by weighted probability.
593///
594/// Each branch has a label, a weight (non-negative), and a continuation type.
595/// The scheduler normalises weights to obtain probabilities and samples a branch.
596#[allow(clippy::too_many_arguments)]
597pub struct ProbBranch {
598    /// Human-readable label for the branch.
599    pub label: String,
600    /// Relative weight (unnormalised); must be positive.
601    pub weight: f64,
602    /// Continuation session type if this branch is chosen.
603    pub cont: SType,
604}
605/// A simple graph-based deadlock freedom checker.
606///
607/// Represents the communication graph of a system of sessions and checks
608/// for cycles (which would indicate potential deadlocks).
609pub struct DeadlockChecker {
610    /// Edges: (channel_name, role_a, role_b) — role_a waits for role_b on channel.
611    wait_edges: Vec<(String, String, String)>,
612}
613impl DeadlockChecker {
614    /// Create a new deadlock checker.
615    pub fn new() -> Self {
616        DeadlockChecker { wait_edges: vec![] }
617    }
618    /// Add a wait edge: `waiter` waits for `provider` on `channel`.
619    pub fn add_wait(
620        &mut self,
621        channel: impl Into<String>,
622        waiter: impl Into<String>,
623        provider: impl Into<String>,
624    ) {
625        self.wait_edges
626            .push((channel.into(), waiter.into(), provider.into()));
627    }
628    /// Check if the wait graph is cycle-free (deadlock-free).
629    pub fn is_deadlock_free(&self) -> bool {
630        let mut adj: HashMap<&str, Vec<&str>> = HashMap::new();
631        for (_, waiter, provider) in &self.wait_edges {
632            adj.entry(waiter.as_str())
633                .or_default()
634                .push(provider.as_str());
635        }
636        let mut visited: HashSet<&str> = HashSet::new();
637        let mut in_stack: HashSet<&str> = HashSet::new();
638        let nodes: Vec<&str> = adj.keys().copied().collect();
639        for &node in &nodes {
640            if !visited.contains(node) && Self::has_cycle(node, &adj, &mut visited, &mut in_stack) {
641                return false;
642            }
643        }
644        true
645    }
646    fn has_cycle<'a>(
647        node: &'a str,
648        adj: &HashMap<&'a str, Vec<&'a str>>,
649        visited: &mut HashSet<&'a str>,
650        in_stack: &mut HashSet<&'a str>,
651    ) -> bool {
652        visited.insert(node);
653        in_stack.insert(node);
654        if let Some(neighbors) = adj.get(node) {
655            for &nb in neighbors {
656                if !visited.contains(nb) {
657                    if Self::has_cycle(nb, adj, visited, in_stack) {
658                        return true;
659                    }
660                } else if in_stack.contains(nb) {
661                    return true;
662                }
663            }
664        }
665        in_stack.remove(node);
666        false
667    }
668}
669/// A local session type (from one role's perspective).
670#[derive(Debug, Clone, PartialEq, Eq)]
671pub enum LType {
672    /// `!q(T).L` — send T to role q.
673    Send(Role, BaseType, Box<LType>),
674    /// `?p(T).L` — receive T from role p.
675    Recv(Role, BaseType, Box<LType>),
676    /// Internal choice: select one of the labeled continuations.
677    IChoice(Role, Vec<(String, LType)>),
678    /// External choice: offer labeled continuations to a role.
679    EChoice(Role, Vec<(String, LType)>),
680    /// Session end.
681    End,
682    /// Recursive local type.
683    Rec(String, Box<LType>),
684    /// Type variable.
685    Var(String),
686}
687/// A single session operation (for type checking).
688#[derive(Debug, Clone)]
689pub enum SessionOp {
690    /// Send a value of the given type.
691    Send(BaseType),
692    /// Receive a value of the given type.
693    Recv(BaseType),
694    /// Select the left branch.
695    SelectLeft,
696    /// Select the right branch.
697    SelectRight,
698    /// Close the session.
699    Close,
700}
701impl SessionOp {
702    /// Check this operation against the current session type, returning the continuation type.
703    pub fn check_step(&self, stype: SType) -> Result<SType, String> {
704        match (self, &stype) {
705            (SessionOp::Send(t), SType::Send(expected, cont)) => {
706                if t == expected.as_ref() {
707                    Ok(*cont.clone())
708                } else {
709                    Err(format!(
710                        "Type mismatch: sent {:?} but expected {:?}",
711                        t, expected
712                    ))
713                }
714            }
715            (SessionOp::Recv(t), SType::Recv(expected, cont)) => {
716                if t == expected.as_ref() {
717                    Ok(*cont.clone())
718                } else {
719                    Err(format!(
720                        "Type mismatch: recv {:?} but expected {:?}",
721                        t, expected
722                    ))
723                }
724            }
725            (SessionOp::SelectLeft, SType::Choice(left, _)) => Ok(*left.clone()),
726            (SessionOp::SelectRight, SType::Choice(_, right)) => Ok(*right.clone()),
727            (SessionOp::Close, SType::End) => Ok(SType::End),
728            _ => Err(format!(
729                "Operation {:?} incompatible with session type {}",
730                self, stype
731            )),
732        }
733    }
734}
735/// The result of a runtime monitor check.
736#[derive(Debug, Clone, PartialEq, Eq)]
737pub enum MonitorResult {
738    /// The communication matched the expected type.
739    Ok,
740    /// A cast was inserted (type did not match static annotation).
741    CastInserted(String),
742    /// A hard failure: the communication violates the protocol irreparably.
743    Failure(String),
744}
745/// A simple session type checker for binary sessions.
746pub struct SessionChecker {
747    /// Mapping from channel names to their session types.
748    channels: HashMap<String, SType>,
749}
750impl SessionChecker {
751    /// Create a new session type checker.
752    pub fn new() -> Self {
753        SessionChecker {
754            channels: HashMap::new(),
755        }
756    }
757    /// Register a channel with its session type.
758    pub fn register_channel(&mut self, name: impl Into<String>, stype: SType) {
759        self.channels.insert(name.into(), stype);
760    }
761    /// Check that a use pattern (sequence of operations) is consistent with
762    /// the declared session type of a channel.
763    pub fn check_usage(&self, channel: &str, ops: &[SessionOp]) -> Result<SType, String> {
764        let stype = self
765            .channels
766            .get(channel)
767            .ok_or_else(|| format!("Unknown channel: {}", channel))?;
768        let mut current = stype.clone();
769        for op in ops {
770            current = op.check_step(current)?;
771        }
772        Ok(current)
773    }
774}
775/// A runtime monitor for gradual session types.
776///
777/// Tracks the statically known portion of the session type and performs
778/// dynamic checks for the parts annotated with the dynamic type `?`.
779pub struct GradualSessionMonitor {
780    /// The expected session type (may contain `Var("?")` for dynamic parts).
781    expected: SType,
782    /// Violations logged during monitoring.
783    pub violations: Vec<String>,
784    /// Casts inserted during monitoring.
785    pub casts: Vec<String>,
786}
787impl GradualSessionMonitor {
788    /// Create a monitor for the given expected session type.
789    pub fn new(expected: SType) -> Self {
790        GradualSessionMonitor {
791            expected,
792            violations: vec![],
793            casts: vec![],
794        }
795    }
796    /// Check a send operation against the expected type.
797    /// Returns `Ok` if compatible, `CastInserted` if a cast bridges a mismatch,
798    /// or `Failure` if irreparably incompatible.
799    pub fn check_send(&mut self, actual_ty: &BaseType) -> MonitorResult {
800        match self.expected.clone() {
801            SType::Send(expected_ty, cont) => {
802                self.expected = *cont;
803                if actual_ty == expected_ty.as_ref() {
804                    MonitorResult::Ok
805                } else {
806                    let msg = format!("cast {:?} → {:?}", actual_ty, expected_ty);
807                    self.casts.push(msg.clone());
808                    MonitorResult::CastInserted(msg)
809                }
810            }
811            SType::Var(ref s) if s == "?" => {
812                let msg = format!("dynamic send {:?}", actual_ty);
813                self.casts.push(msg.clone());
814                MonitorResult::CastInserted(msg)
815            }
816            other => {
817                let msg = format!("expected Send, got {}", other);
818                self.violations.push(msg.clone());
819                MonitorResult::Failure(msg)
820            }
821        }
822    }
823    /// Check a receive operation against the expected type.
824    pub fn check_recv(&mut self, actual_ty: &BaseType) -> MonitorResult {
825        match self.expected.clone() {
826            SType::Recv(expected_ty, cont) => {
827                self.expected = *cont;
828                if actual_ty == expected_ty.as_ref() {
829                    MonitorResult::Ok
830                } else {
831                    let msg = format!("cast {:?} → {:?}", actual_ty, expected_ty);
832                    self.casts.push(msg.clone());
833                    MonitorResult::CastInserted(msg)
834                }
835            }
836            SType::Var(ref s) if s == "?" => {
837                let msg = format!("dynamic recv {:?}", actual_ty);
838                self.casts.push(msg.clone());
839                MonitorResult::CastInserted(msg)
840            }
841            other => {
842                let msg = format!("expected Recv, got {}", other);
843                self.violations.push(msg.clone());
844                MonitorResult::Failure(msg)
845            }
846        }
847    }
848    /// Return true iff no violations have been recorded.
849    pub fn is_safe(&self) -> bool {
850        self.violations.is_empty()
851    }
852}