Skip to content

Commit

Permalink
As per discussion of issues #99 and #100
Browse files Browse the repository at this point in the history
  • Loading branch information
sdleffler committed Jun 2, 2021
1 parent bf5546a commit 9ae6a6d
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 75 deletions.
16 changes: 10 additions & 6 deletions dialectic-compiler/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ impl fmt::Display for Target {
let count = cs.len();

match carrier_type {
Some(carrier) => write!(f, "Choose<{}, (", carrier.to_token_stream())?,
Some(carrier) => {
write!(f, "Choose<CustomChoice<{}>, (", carrier.to_token_stream())?
}
None => write!(f, "Choose<Choice<{}>, (", count)?,
}

Expand All @@ -89,7 +91,9 @@ impl fmt::Display for Target {
let count = cs.len();

match carrier_type {
Some(carrier) => write!(f, "Offer<{}, (", carrier.to_token_stream())?,
Some(carrier) => {
write!(f, "Offer<CustomChoice<{}>, (", carrier.to_token_stream())?
}
None => write!(f, "Offer<Choice<{}>, (", count)?,
}

Expand Down Expand Up @@ -169,8 +173,8 @@ impl Spanned<Target> {
.to_tokens(tokens);
}
Choose(carrier_type, cs) => {
let carrier = match carrier_type {
Some(ty) => ty.clone(),
let carrier: syn::Type = match carrier_type {
Some(ty) => parse_quote!(#dialectic_crate::backend::CustomChoice<#ty>),
None => {
let n = cs.len();
parse_quote!(#dialectic_crate::backend::Choice<#n>)
Expand All @@ -183,8 +187,8 @@ impl Spanned<Target> {
.to_tokens(tokens)
}
Offer(carrier_type, cs) => {
let carrier = match carrier_type {
Some(ty) => ty.clone(),
let carrier: syn::Type = match carrier_type {
Some(ty) => parse_quote!(#dialectic_crate::backend::CustomChoice<#ty>),
None => {
let n = cs.len();
parse_quote!(#dialectic_crate::backend::Choice<#n>)
Expand Down
23 changes: 10 additions & 13 deletions dialectic-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,6 @@ impl Parse for Mutability {

enum TransmitterBound {
Send(Mutability, syn::Type),
Case(Mutability, Token![match], syn::Type),
Choice(Token![match]),
}

Expand All @@ -462,10 +461,7 @@ impl Parse for TransmitterSpec {
let match_token = input.parse::<Option<Token![match]>>()?;

match match_token {
Some(token) if input.peek(Token![,]) || input.is_empty() => {
Ok(TransmitterBound::Choice(token))
}
Some(token) => Ok(TransmitterBound::Case(mutability, token, input.parse()?)),
Some(token) => Ok(TransmitterBound::Choice(token)),
None => Ok(TransmitterBound::Send(mutability, input.parse()?)),
}
}
Expand Down Expand Up @@ -600,7 +596,7 @@ fn where_predicates_mut(
/// # fn e<Tx>() {}
/// #[Transmitter(Tx for bool, i64, Vec<String>)]
/// # fn f<Tx>() {}
/// #[Transmitter(Tx for bool, match Option<String>, ref i64, ref mut Vec<String>)]
/// #[Transmitter(Tx for match, bool, Option<String>, ref i64, ref mut Vec<String>)]
/// # fn g<Tx>() {}
/// ```
///
Expand All @@ -613,7 +609,7 @@ fn where_predicates_mut(
/// or `mut`), and types `T1`, `T2`, `...`, the invocation:
///
/// ```ignore
/// #[Transmitter(Tx for C1? T1, C2? match T2, ...)]
/// #[Transmitter(Tx for (match,)? C1? T1, C2? T2, ...)]
/// fn f<Tx>() {}
/// ```
///
Expand All @@ -632,12 +628,14 @@ fn where_predicates_mut(
/// fn f<Tx>()
/// where
/// Tx: Transmitter + Send + 'static,
/// // If `match` is specified, a `TransmitChoice` bound is emitted:
/// Tx: TransmitChoice,
/// // For each of the types `T1`, `T2`, ...
/// // If the convention is unspecified, `C` is left unspecified;
/// // otherwise, we translate into `call_by` conventions using
/// // `move` => `Val`, `ref` => `Ref`, and `mut` => `Mut`
/// Tx: Transmit<T1, C1>,
/// Tx: TransmitCase<T2, C2>,
/// Tx: Transmit<T2, C2>,
/// // ...
/// {}
/// ```
Expand All @@ -664,9 +662,6 @@ pub fn Transmitter(
TransmitterBound::Send(mutability, ty) => predicates.push(syn::parse_quote! {
#name: #dialectic_path::backend::Transmit<#ty, #mutability>
}),
TransmitterBound::Case(mutability, _, ty) => predicates.push(syn::parse_quote! {
#name: #dialectic_path::backend::TransmitCase<#ty, #mutability>
}),
TransmitterBound::Choice(_) => predicates.push(syn::parse_quote! {
#name: #dialectic_path::backend::TransmitChoice
}),
Expand Down Expand Up @@ -723,7 +718,7 @@ pub fn Transmitter(
/// # fn a<Rx>() {}
/// #[Receiver(Rx for bool)]
/// # fn b<Rx>() {}
/// #[Receiver(Rx for bool, i64, Vec<String>)]
/// #[Receiver(Rx for match, bool, i64, Vec<String>)]
/// # fn c<Rx>() {}
/// ```
///
Expand All @@ -735,7 +730,7 @@ pub fn Transmitter(
/// For a transmitter type `Rx`, and types `T1`, `T2`, `...`, the invocation:
///
/// ```ignore
/// #[Receiver(Rx for T1, T2, ...)]
/// #[Receiver(Rx for (match,)? T1, T2, ...)]
/// fn f<Rx>() {}
/// ```
///
Expand All @@ -750,6 +745,8 @@ pub fn Transmitter(
/// fn f<Rx>()
/// where
/// Rx: Receiver + Send + 'static,
/// // If `match` is present in the list, a `ReceiveChoice` bound is generated:
/// Rx: ReceiveChoice,
/// // For each of the types `T1`, `T2`, ...
/// Rx: Receive<T1>,
/// Rx: Receive<T2>,
Expand Down
2 changes: 1 addition & 1 deletion dialectic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ readme = "../README.md"

[dependencies]
thiserror = "1"
call-by = { version = "^0.2.2" }
call-by = { version = "^0.2.2", path = "../../call-by" }
vesta = { version = "0.1" }
tokio = { version = "1", optional = true }
tokio-util = { version = "0.6", features = ["codec"], optional = true }
Expand Down
93 changes: 78 additions & 15 deletions dialectic/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,15 @@ pub trait TransmitChoice: Transmitter {
/// [`vesta`](https://docs.rs/vesta) crate and its `Match` and `Case` traits; implementation of
/// these traits does not need to be done by hand as Vesta provides a derive macro for them.
///
/// If you are writing a backend for a new protocol, you almost certainly do not need to implement
/// [`TransmitCase`] for your backend, and you can rely on an implementation of [`TransmitChoice`].
/// If you are writing a backend for an existing protocol which you are reimplementing using
/// Dialectic, however, then [`TransmitCase`] is necessary to allow you to branch on a custom type.
/// Without it, you would be limited to [`Choice<N>`](Choice), which is represented as a single byte
/// and is insufficient to represent "choosing" operations in existing protocols.
///
/// If you're writing a function and need a lot of different `TransmitCase<T, C>` bounds, the
/// [`Transmitter`](macro@crate::Transmitter) attribute macro can help you specify them more
/// succinctly.
pub trait TransmitCase<T: ?Sized, C: Convention = Val>: Transmitter
/// You do not need to ever implement `TransmitCase`. It has blanket implementations for all types
/// that matter.
pub trait TransmitCase<T: ?Sized, C: Convention, const N: usize>:
Transmitter + sealed::TransmitCase<T, C>
where
T: Transmittable + Match,
{
/// Send a "case" of a [`Match`]-able type.
fn send_case<'a, 'async_lifetime, const N: usize>(
fn send_case<'a, 'async_lifetime>(
&'async_lifetime mut self,
message: <<T as Case<N>>::Case as By<'a, C>>::Type,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_lifetime>>
Expand All @@ -157,10 +150,35 @@ where
'a: 'async_lifetime;
}

impl<Tx: Transmitter + TransmitChoice, const LENGTH: usize> TransmitCase<Choice<LENGTH>, Val>
for Tx
/// This is a wrapper type which disambiguates, at the type level, "custom" choice types, and makes
/// sure Rust sees them as a different type from `Choice<N>`.
#[derive(Debug)]
pub struct CustomChoice<T>(pub T);

unsafe impl<T: Match> Match for CustomChoice<T> {
type Range = T::Range;

fn tag(&self) -> Option<usize> {
self.0.tag()
}
}

impl<T: Match + Case<N>, const N: usize> Case<N> for CustomChoice<T> {
type Case = T::Case;

unsafe fn case(this: Self) -> Self::Case {
T::case(this.0)
}

fn uncase(case: Self::Case) -> Self {
CustomChoice(T::uncase(case))
}
}

impl<Tx: Transmitter + TransmitChoice, const LENGTH: usize, const N: usize>
TransmitCase<Choice<LENGTH>, Val, N> for Tx
{
fn send_case<'a, 'async_lifetime, const N: usize>(
fn send_case<'a, 'async_lifetime>(
&'async_lifetime mut self,
_message: <<Choice<LENGTH> as Case<N>>::Case as By<'a, Val>>::Type,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_lifetime>>
Expand All @@ -176,6 +194,22 @@ impl<Tx: Transmitter + TransmitChoice, const LENGTH: usize> TransmitCase<Choice<
}
}

impl<Tx: Transmitter + Transmit<T>, T: Match + Transmittable, const N: usize>
TransmitCase<CustomChoice<T>, Val, N> for Tx
{
fn send_case<'a, 'async_lifetime>(
&'async_lifetime mut self,
message: <<CustomChoice<T> as Case<N>>::Case as By<'a, Val>>::Type,
) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send + 'async_lifetime>>
where
CustomChoice<T>: Case<N>,
<CustomChoice<T> as Case<N>>::Case: By<'a, Val>,
'a: 'async_lifetime,
{
self.send(<CustomChoice<T> as Case<N>>::uncase(call_by::coerce_move(message)).0)
}
}

/// A backend transport used for receiving (i.e. the `Rx` parameter of [`Chan`](crate::Chan)) must
/// implement [`Receiver`], which specifies what type of errors it might return. This is a
/// super-trait of [`Receive`] and [`ReceiveCase`], which are the traits that are actually needed to
Expand Down Expand Up @@ -259,3 +293,32 @@ where
self.recv_choice::<LENGTH>()
}
}

impl<Rx: Receiver, T: Match> ReceiveCase<CustomChoice<T>> for Rx
where
Rx: Receive<T> + Send,
{
fn recv_case<'async_lifetime>(
&'async_lifetime mut self,
) -> Pin<Box<dyn Future<Output = Result<CustomChoice<T>, Self::Error>> + Send + 'async_lifetime>>
{
Box::pin(async move {
let t = self.recv().await?;
Ok(CustomChoice(t))
})
}
}

mod sealed {
use super::*;

pub trait TransmitCase<T: ?Sized, C> {}

impl<Tx: Transmitter + Transmit<T>, T: Transmittable> TransmitCase<CustomChoice<T>, Val> for Tx {}
impl<Tx: Transmitter + TransmitChoice, const N: usize> TransmitCase<Choice<N>, Val> for Tx {}

pub trait ReceiveCase<T: ?Sized> {}

impl<Rx: Receiver + Receive<T>, T> ReceiveCase<CustomChoice<T>> for Rx {}
impl<Rx: Receiver + ReceiveChoice, const N: usize> ReceiveCase<Choice<N>> for Rx {}
}
80 changes: 40 additions & 40 deletions dialectic/src/chan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,51 +376,51 @@ where
Number<N>: ToUnary,
Choices::AsList: Select<<Number<N> as ToUnary>::AsUnary>,
<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected: Session,
Tx: TransmitCase<Carrier>,
Tx: TransmitCase<Carrier, Val, N>,
{
self.tx.as_mut().unwrap().send_case::<N>(choice).await?;
self.tx.as_mut().unwrap().send_case(choice).await?;
Ok(self.unchecked_cast())
}

/// Identical to [`Chan::choose`], but allows you to send the carrier's case value by reference.
/// Useful for custom carrier types.
pub async fn choose_ref<const N: usize>(
mut self,
choice: &<Carrier as Case<N>>::Case,
) -> Result<
Chan<<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected, Tx, Rx>,
Tx::Error,
>
where
Carrier: Case<N>,
Number<N>: ToUnary,
Choices::AsList: Select<<Number<N> as ToUnary>::AsUnary>,
<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected: Session,
Tx: TransmitCase<Carrier, Ref>,
{
self.tx.as_mut().unwrap().send_case::<N>(choice).await?;
Ok(self.unchecked_cast())
}
// /// Identical to [`Chan::choose`], but allows you to send the carrier's case value by reference.
// /// Useful for custom carrier types.
// pub async fn choose_ref<const N: usize>(
// mut self,
// choice: &<Carrier as Case<N>>::Case,
// ) -> Result<
// Chan<<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected, Tx, Rx>,
// Tx::Error,
// >
// where
// Carrier: Case<N>,
// Number<N>: ToUnary,
// Choices::AsList: Select<<Number<N> as ToUnary>::AsUnary>,
// <Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected: Session,
// Tx: TransmitCase<Carrier, Ref>,
// {
// self.tx.as_mut().unwrap().send_case::<N>(choice).await?;
// Ok(self.unchecked_cast())
// }

/// Identical to [`Chan::choose`], but allows you to send the carrier's case value by mutable
/// reference. Useful for custom carrier types.
pub async fn choose_mut<const N: usize>(
mut self,
choice: &mut <Carrier as Case<N>>::Case,
) -> Result<
Chan<<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected, Tx, Rx>,
Tx::Error,
>
where
Carrier: Case<N>,
Number<N>: ToUnary,
Choices::AsList: Select<<Number<N> as ToUnary>::AsUnary>,
<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected: Session,
Tx: TransmitCase<Carrier, Mut>,
{
self.tx.as_mut().unwrap().send_case::<N>(choice).await?;
Ok(self.unchecked_cast())
}
// /// Identical to [`Chan::choose`], but allows you to send the carrier's case value by mutable
// /// reference. Useful for custom carrier types.
// pub async fn choose_mut<const N: usize>(
// mut self,
// choice: &mut <Carrier as Case<N>>::Case,
// ) -> Result<
// Chan<<Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected, Tx, Rx>,
// Tx::Error,
// >
// where
// Carrier: Case<N>,
// Number<N>: ToUnary,
// Choices::AsList: Select<<Number<N> as ToUnary>::AsUnary>,
// <Choices::AsList as Select<<Number<N> as ToUnary>::AsUnary>>::Selected: Session,
// Tx: TransmitCase<Carrier, Mut>,
// {
// self.tx.as_mut().unwrap().send_case::<N>(choice).await?;
// Ok(self.unchecked_cast())
// }
}

impl<Tx, Rx, S, Choices, Carrier> Chan<S, Tx, Rx>
Expand Down

0 comments on commit 9ae6a6d

Please sign in to comment.