1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
//! The structs in this module enumerate the shapes of all expressible sessions. //! //! 💡 Generally speaking, users of Dialectic (that's you!) don't need to write these types //! directly; it's more readable to instead use the [`Session!`](macro@crate::Session) macro to //! specify a session type. //! //! The traits in this module are used to implement the session type system rules, and generally do //! not need to be referred to directly from code that uses this library. All of them are sealed, so //! that they can only ever apply to the session types defined in this crate. If you want to state //! the bound that a type is a session type, use the [`Session`](trait@crate::Session) trait rather //! than any of these bounds individually. use crate::unary::{LessThan, Unary, S, Z}; // Each construct in the session types language lives in its own module, along with the // implementation of its various typing rules. mod call; mod choose; mod r#continue; mod done; mod r#loop; mod offer; mod recv; mod send; mod split; pub use call::*; pub use choose::*; pub use done::*; pub use offer::*; pub use r#continue::*; pub use r#loop::*; pub use recv::*; pub use send::*; pub use split::*; /// Each session type has a [`HasDual::DualSession`], the type of the corresponding client on the /// other side of the channel. The sealed trait `HasDual` enumerates these types, and provides the /// dual of each. /// /// 💡 In general, you should prefer the [`Session`]() trait to the [`HasDual`] trait, since /// [`Session`] also ensures that a given type is a valid session type and provides other /// functionality. /// /// # Examples /// /// Here we define a `Client` and `Server` session type, which are duals of each other. This example /// illustrates every construct in the language of session types. /// /// ``` /// # use static_assertions::assert_type_eq_all; /// use dialectic::types::*; /// /// type Client = Loop<Offer<(Split<Call<Send<String, Done>, Done>, Recv<usize, Done>, Done>, Recv<bool, Continue<0>>)>>; /// type Server = Loop<Choose<(Split<Send<usize, Done>, Call<Recv<String, Done>, Done>, Done>, Send<bool, Continue<0>>)>>; /// /// assert_type_eq_all!(Client, <Server as HasDual>::DualSession); /// ``` /// /// [`Session`]: trait@crate::Session pub trait HasDual: sealed::IsSession + Sized + 'static { /// The dual to this session type, i.e. the session type required of the other end of the /// channel. type DualSession: HasDual<DualSession = Self>; } /// Each session type has a canonical [`Actionable::NextAction`], the session type which corresponds /// to the next thing to do on the channel. For most types, this is the same as `Self`, but for /// control constructs like [`Loop`], this corresponds to the inside of the [`Loop`]. /// /// 💡 In general, you should prefer the [`Session`] trait to the [`Actionable`] trait, since /// [`Session`] also ensures that a given type is a valid session type and provides other /// functionality. /// /// [`Session`]: trait@crate::Session pub trait Actionable: sealed::IsSession { /// The next actual channel action, which must be one of [`Send`], [`Recv`], [`Offer`], /// [`Choose`], [`Split`], [`Call`], or [`Done`]. /// /// The constraints on this associated type ensure that it is idemopotent: the `Action` and of /// an `Action` is the same as that `Action`. type NextAction: Actionable<NextAction = Self::NextAction>; } /// A session type is [`Scoped`] if none of its [`Continue`]s refer to outside of the /// [`Loop`]s which they are within. /// /// 💡 In general, you should prefer the [`Session`] trait to the [`Scoped`] trait, since /// [`Session`] also ensures that a given type is a valid session type and provides other /// functionality. /// /// [`Session`]: trait@crate::Session pub trait Scoped<N: Unary = Z>: sealed::IsSession {} /// In the [`Choose`] and [`Offer`] session types, we provide the ability to choose/offer a list of /// protocols. The sealed [`EachScoped`] trait ensures that every protocol in a type level list of /// protocols is [`Scoped`]. pub trait EachScoped<N: Unary = Z>: sealed::EachSession {} impl<N: Unary> EachScoped<N> for () {} impl<N: Unary, P: Scoped<N>, Ps: EachScoped<N>> EachScoped<N> for (P, Ps) {} /// In the [`Choose`] and [`Offer`] session types, we provide the ability to choose/offer a list of /// protocols. The sealed [`EachHasDual`] trait ensures that every protocol in a type level list of /// protocols [`HasDual`]. pub trait EachHasDual: sealed::EachSession + Sized + 'static where Self::Duals: EachHasDual<Duals = Self>, { /// The point-wise [`HasDual::DualSession`] of a type-level list of session types. type Duals; } impl EachHasDual for () { type Duals = (); } impl<P, Ps> EachHasDual for (P, Ps) where P: HasDual, Ps: EachHasDual, { type Duals = (P::DualSession, Ps::Duals); } /// Substitute `P` for every [`Continue`] referring to the outermost scope in the given session /// type. /// /// When entering a [`Loop`], all [`Continue`]s which refer to that [`Loop`] are unrolled by one /// loop iteration so that the session type remains valid. This trait implements that type-level /// operation. /// /// # Examples /// /// ``` /// use dialectic::types::*; /// use dialectic::prelude::*; /// # use static_assertions::assert_type_eq_all; /// /// assert_type_eq_all!( /// <Send<i64, Continue<0>> as Subst<Recv<(), Done>>>::Substituted, /// Send<i64, Recv<(), Done>>, /// ); /// ``` pub trait Subst<P, N: Unary = Z>: sealed::IsSession { /// The result of the substitution. type Substituted: 'static; } /// Substitute `P` for every [`Done`] in `Self`, thus concatenating the session `P` to `Self`. /// /// This does not require `P` to be a closed session type; it is reasonable to use open session /// types (those with [`Continue`]s that refer outside of themselves) as `P`. [`Then`] is careful to /// adjust the indices of such open types so that they refer correctly outside of `Self` even when /// `Self` contains [`Loop`]s. pub trait Then<P, N: Unary = Z>: sealed::IsSession { /// The combined type resulting from substituting `P` for [`Done`] in `Self`. type Combined: 'static; } /// For every "open" [`Continue`] (i.e. with an index larger than the number of [`Loop`]s that /// contain it), increment its index by `N`. /// /// This is used internally in the [`Then`] implementation for [`Done`]. pub trait Lift<N: Unary, Level: Unary = Z>: sealed::IsSession { /// The result of the lifting operation. type Lifted: 'static; } /// In the [`Choose`] and [`Offer`] session types, we provide the ability to choose/offer a list of /// protocols. The sealed [`EachSubst`] trait ensures that every protocol in a type level list of /// protocols can [`Subst`]. pub trait EachSubst<P, N: Unary = Z>: sealed::EachSession { /// The result of the substitution on every element of the list. type Substituted: 'static; } impl<N: Unary, Q> EachSubst<Q, N> for () { type Substituted = (); } impl<N: Unary, Q, P, Ps> EachSubst<Q, N> for (P, Ps) where P: Subst<Q, N>, Ps: EachSubst<Q, N>, { type Substituted = (P::Substituted, Ps::Substituted); } /// Analogously to [`EachSubst`], this trait allows iteration/mapping of the [`Then`] transform /// over a type level list. pub trait EachThen<P, N: Unary = Z>: sealed::EachSession { /// The result of the map. type Combined: 'static; } impl<N: Unary, Q> EachThen<Q, N> for () { type Combined = (); } impl<N: Unary, Q, P, Ps> EachThen<Q, N> for (P, Ps) where P: Then<Q, N>, Ps: EachThen<Q, N>, { type Combined = (P::Combined, Ps::Combined); } /// Analogously to [`EachSubst`], this trait allows iteration/mapping of the [`Then`] transform /// over a type level list. pub trait EachLift<N: Unary, Level: Unary = Z>: sealed::EachSession { /// The result of the map. type Lifted: 'static; } impl<N: Unary, Level: Unary> EachLift<N, Level> for () { type Lifted = (); } impl<N: Unary, Level: Unary, P, Ps> EachLift<N, Level> for (P, Ps) where P: Lift<N, Level>, Ps: EachLift<N, Level>, { type Lifted = (P::Lifted, Ps::Lifted); } /// Select by index from a type level list. /// /// This is used internally by the [`Choose`], [`Offer`], and [`Continue`] session types. /// /// # Examples /// /// ``` /// use static_assertions::{assert_impl_all, assert_not_impl_any}; /// use dialectic::types::*; /// use dialectic::unary::UnaryOf; /// /// type L = (UnaryOf<0>, (UnaryOf<1>, (UnaryOf<2>, ()))); /// /// assert_impl_all!(L: Select<UnaryOf<0>, Selected = UnaryOf<0>, Remainder = (UnaryOf<1>, (UnaryOf<2>, ()))>); /// assert_impl_all!(L: Select<UnaryOf<1>, Selected = UnaryOf<1>, Remainder = (UnaryOf<0>, (UnaryOf<2>, ()))>); /// assert_impl_all!(L: Select<UnaryOf<2>, Selected = UnaryOf<2>, Remainder = (UnaryOf<0>, (UnaryOf<1>, ()))>); /// /// assert_not_impl_any!(L: Select<UnaryOf<3>>); /// ``` pub trait Select<N: Unary>: sealed::Select<N> { /// The thing which is selected from this list by the index `N`. type Selected; /// The list with the selected thing removed. type Remainder; } impl<T, Rest> Select<Z> for (T, Rest) { type Selected = T; type Remainder = Rest; } impl<T, P, Rest, N: Unary> Select<S<N>> for (T, (P, Rest)) where (P, Rest): Select<N>, { type Selected = <(P, Rest) as Select<N>>::Selected; type Remainder = (T, <(P, Rest) as Select<N>>::Remainder); } mod sealed { use std::any::Any; use super::*; /// Seal the [`Session`] trait so only types defined in this crate can be session types. pub trait IsSession: Any {} /// Seal the [`EachSession`] trait so it can't be extended in weird ways. pub trait EachSession {} impl EachSession for () {} impl<T: IsSession, Ts: EachSession> EachSession for (T, Ts) {} /// Seal the [`Select`] trait so it can't be extended in weird ways. pub trait Select<N: Unary> {} impl<T, S> Select<Z> for (T, S) {} impl<T, P, Rest, N: Unary> Select<S<N>> for (T, (P, Rest)) where (P, Rest): Select<N> {} } /// Asserts that the specified type is a valid *closed* session type (that is, it does not /// `Continue` outside itself). #[cfg(test)] #[macro_export] macro_rules! assert_all_closed_sessions { () => {}; ($session:ty) => { assert_all_closed_sessions!($session,) }; ($session:ty, $($rest:tt)*) => { const _: fn() = || { fn assert_impl_all<T>() where T: $crate::Session, <T as Session>::Dual: $crate::Session, { } assert_impl_all::<$session>(); }; assert_all_closed_sessions!($($rest)*); }; } #[cfg(test)] mod tests { #[allow(unused_imports)] use super::*; // The session type `P` incorporates every construct in the session type language. This unit // test assures that even a complex nested session type is still a ZST. #[test] fn complex_session_zero_size() { type P = Loop< Loop< Choose<( Send<usize, Continue<0>>, Recv<String, Continue<0>>, Offer<( Send<bool, Continue<0>>, Continue<1>, Split<Send<isize, Continue<0>>, Recv<isize, Continue<0>>, Done>, )>, )>, >, >; assert_eq!(std::mem::size_of::<P>(), 0); } include!(concat!(env!("OUT_DIR"), "/valid_sessions.rs")); }