From 9ae6a6d661897782460eaebe6307ea0f6ca88531 Mon Sep 17 00:00:00 2001 From: Shea Leffler Date: Mon, 12 Apr 2021 16:49:01 -0700 Subject: [PATCH] As per discussion of issues #99 and #100 --- dialectic-compiler/src/target.rs | 16 +++--- dialectic-macro/src/lib.rs | 23 ++++---- dialectic/Cargo.toml | 2 +- dialectic/src/backend.rs | 93 ++++++++++++++++++++++++++------ dialectic/src/chan.rs | 80 +++++++++++++-------------- 5 files changed, 139 insertions(+), 75 deletions(-) diff --git a/dialectic-compiler/src/target.rs b/dialectic-compiler/src/target.rs index 556caa6e..6b97b874 100644 --- a/dialectic-compiler/src/target.rs +++ b/dialectic-compiler/src/target.rs @@ -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, (", carrier.to_token_stream())? + } None => write!(f, "Choose, (", count)?, } @@ -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, (", carrier.to_token_stream())? + } None => write!(f, "Offer, (", count)?, } @@ -169,8 +173,8 @@ impl Spanned { .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>) @@ -183,8 +187,8 @@ impl Spanned { .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>) diff --git a/dialectic-macro/src/lib.rs b/dialectic-macro/src/lib.rs index f3ba381e..5df1a720 100644 --- a/dialectic-macro/src/lib.rs +++ b/dialectic-macro/src/lib.rs @@ -436,7 +436,6 @@ impl Parse for Mutability { enum TransmitterBound { Send(Mutability, syn::Type), - Case(Mutability, Token![match], syn::Type), Choice(Token![match]), } @@ -462,10 +461,7 @@ impl Parse for TransmitterSpec { let match_token = input.parse::>()?; 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()?)), } } @@ -600,7 +596,7 @@ fn where_predicates_mut( /// # fn e() {} /// #[Transmitter(Tx for bool, i64, Vec)] /// # fn f() {} -/// #[Transmitter(Tx for bool, match Option, ref i64, ref mut Vec)] +/// #[Transmitter(Tx for match, bool, Option, ref i64, ref mut Vec)] /// # fn g() {} /// ``` /// @@ -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() {} /// ``` /// @@ -632,12 +628,14 @@ fn where_predicates_mut( /// fn f() /// 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, -/// Tx: TransmitCase, +/// Tx: Transmit, /// // ... /// {} /// ``` @@ -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 }), @@ -723,7 +718,7 @@ pub fn Transmitter( /// # fn a() {} /// #[Receiver(Rx for bool)] /// # fn b() {} -/// #[Receiver(Rx for bool, i64, Vec)] +/// #[Receiver(Rx for match, bool, i64, Vec)] /// # fn c() {} /// ``` /// @@ -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() {} /// ``` /// @@ -750,6 +745,8 @@ pub fn Transmitter( /// fn f() /// 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, /// Rx: Receive, diff --git a/dialectic/Cargo.toml b/dialectic/Cargo.toml index 03a4e3d0..205672c2 100644 --- a/dialectic/Cargo.toml +++ b/dialectic/Cargo.toml @@ -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 } diff --git a/dialectic/src/backend.rs b/dialectic/src/backend.rs index 40fe087d..6b2edce6 100644 --- a/dialectic/src/backend.rs +++ b/dialectic/src/backend.rs @@ -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`](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` bounds, the -/// [`Transmitter`](macro@crate::Transmitter) attribute macro can help you specify them more -/// succinctly. -pub trait TransmitCase: Transmitter +/// You do not need to ever implement `TransmitCase`. It has blanket implementations for all types +/// that matter. +pub trait TransmitCase: + Transmitter + sealed::TransmitCase 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: <>::Case as By<'a, C>>::Type, ) -> Pin> + Send + 'async_lifetime>> @@ -157,10 +150,35 @@ where 'a: 'async_lifetime; } -impl TransmitCase, 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`. +#[derive(Debug)] +pub struct CustomChoice(pub T); + +unsafe impl Match for CustomChoice { + type Range = T::Range; + + fn tag(&self) -> Option { + self.0.tag() + } +} + +impl, const N: usize> Case for CustomChoice { + 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 + TransmitCase, Val, N> for Tx { - fn send_case<'a, 'async_lifetime, const N: usize>( + fn send_case<'a, 'async_lifetime>( &'async_lifetime mut self, _message: < as Case>::Case as By<'a, Val>>::Type, ) -> Pin> + Send + 'async_lifetime>> @@ -176,6 +194,22 @@ impl TransmitCase, T: Match + Transmittable, const N: usize> + TransmitCase, Val, N> for Tx +{ + fn send_case<'a, 'async_lifetime>( + &'async_lifetime mut self, + message: < as Case>::Case as By<'a, Val>>::Type, + ) -> Pin> + Send + 'async_lifetime>> + where + CustomChoice: Case, + as Case>::Case: By<'a, Val>, + 'a: 'async_lifetime, + { + self.send( as Case>::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 @@ -259,3 +293,32 @@ where self.recv_choice::() } } + +impl ReceiveCase> for Rx +where + Rx: Receive + Send, +{ + fn recv_case<'async_lifetime>( + &'async_lifetime mut self, + ) -> Pin, Self::Error>> + Send + 'async_lifetime>> + { + Box::pin(async move { + let t = self.recv().await?; + Ok(CustomChoice(t)) + }) + } +} + +mod sealed { + use super::*; + + pub trait TransmitCase {} + + impl, T: Transmittable> TransmitCase, Val> for Tx {} + impl TransmitCase, Val> for Tx {} + + pub trait ReceiveCase {} + + impl, T> ReceiveCase> for Rx {} + impl ReceiveCase> for Rx {} +} diff --git a/dialectic/src/chan.rs b/dialectic/src/chan.rs index 2a019b50..8bff3ed9 100644 --- a/dialectic/src/chan.rs +++ b/dialectic/src/chan.rs @@ -376,51 +376,51 @@ where Number: ToUnary, Choices::AsList: Select< as ToUnary>::AsUnary>, as ToUnary>::AsUnary>>::Selected: Session, - Tx: TransmitCase, + Tx: TransmitCase, { - self.tx.as_mut().unwrap().send_case::(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( - mut self, - choice: &>::Case, - ) -> Result< - Chan< as ToUnary>::AsUnary>>::Selected, Tx, Rx>, - Tx::Error, - > - where - Carrier: Case, - Number: ToUnary, - Choices::AsList: Select< as ToUnary>::AsUnary>, - as ToUnary>::AsUnary>>::Selected: Session, - Tx: TransmitCase, - { - 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( + // mut self, + // choice: &>::Case, + // ) -> Result< + // Chan< as ToUnary>::AsUnary>>::Selected, Tx, Rx>, + // Tx::Error, + // > + // where + // Carrier: Case, + // Number: ToUnary, + // Choices::AsList: Select< as ToUnary>::AsUnary>, + // as ToUnary>::AsUnary>>::Selected: Session, + // Tx: TransmitCase, + // { + // 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 mutable - /// reference. Useful for custom carrier types. - pub async fn choose_mut( - mut self, - choice: &mut >::Case, - ) -> Result< - Chan< as ToUnary>::AsUnary>>::Selected, Tx, Rx>, - Tx::Error, - > - where - Carrier: Case, - Number: ToUnary, - Choices::AsList: Select< as ToUnary>::AsUnary>, - as ToUnary>::AsUnary>>::Selected: Session, - Tx: TransmitCase, - { - 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 mutable + // /// reference. Useful for custom carrier types. + // pub async fn choose_mut( + // mut self, + // choice: &mut >::Case, + // ) -> Result< + // Chan< as ToUnary>::AsUnary>>::Selected, Tx, Rx>, + // Tx::Error, + // > + // where + // Carrier: Case, + // Number: ToUnary, + // Choices::AsList: Select< as ToUnary>::AsUnary>, + // as ToUnary>::AsUnary>>::Selected: Session, + // Tx: TransmitCase, + // { + // self.tx.as_mut().unwrap().send_case::(choice).await?; + // Ok(self.unchecked_cast()) + // } } impl Chan