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"));
}