dialectic_macro/lib.rs
1#![forbid(broken_intra_doc_links)]
2
3extern crate proc_macro;
4use proc_macro2::TokenStream;
5use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
6use syn::{
7 braced, parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated,
8 spanned::Spanned, Arm, Ident, LitInt, Pat, Token,
9};
10
11/**
12The `Session!` macro compiles a small domain-specific language for describing session types into
13the types themselves. It should be invoked in type position.
14
15The `Session!` macro language looks a lot like Rust code: sequential instructions are separated
16by semicolons, blocks are delimited with curly braces, etc.
17
18The language defines several keywords, each of which corresponding to the session type of the
19same name (except for `break`, which is translated by the macro into other constructs):
20
21- [`send` and `recv`](#the-send-and-recv-keywords)
22- [`offer` and `choose`](#the-offer-and-choose-keywords)
23- [`loop`, `break`, and `continue`](#the-loop-break-and-continue-keywords)
24- [`call`](#the-call-keyword)
25- [`split`](#the-split-keyword)
26
27Additionally, [arbitrary session types can be used inline by name](#external-session-types), so
28specifications can be composed.
29
30# Examples
31
32In these examples, each example of a session type defined using `Session!` is paired with its
33equivalent session type written out using the session type constructors in
34[`types`](https://docs.rs/dialectic/latest/dialectic/types.index.html).
35
36You can find further examples of the use of `Session!` and its meaning throughout this reference
37documentation, the [`tutorial`](https://docs.rs/dialectic/latest/dialectic/tutorial.index.html), and
38[in the Dialectic crate's examples](https://github.com/boltlabs-inc/dialectic/tree/main/dialectic/examples).
39
40In the below examples, all code blocks import:
41
42```
43use static_assertions::assert_type_eq_all as type_eq;
44use dialectic::prelude::*;
45
46// Normally you don't need to import these, because they are only useful
47// when writing session types directly, not when using the `Session!` macro:
48use dialectic::types::*;
49```
50
51## The `send` and `recv` keywords
52
53These keywords take the type to be sent or received as an argument, with no parentheses. See
54also: the [`Send`] type and [`send`] method, and the [`Recv`] type and [`recv`] method.
55
56```
57# use static_assertions::assert_type_eq_all as type_eq;
58# use dialectic::prelude::*;
59# use dialectic::types::*;
60#
61type_eq!(
62 Session! { send bool },
63 Send<bool, Done>,
64);
65
66type_eq!(
67 Session! { recv bool },
68 Recv<bool, Done>,
69);
70```
71
72## The `offer` and `choose` keywords
73
74Using `offer` and `choose` matches the syntax of the [`offer!`] macro, with each
75numerically-labeled branch listed in order, corresponding to the session type to be used in the
76case of each potential choice.
77
78See also: the [`Choose`] type and the [`choose`] method, and the [`Offer`] type
79and the [`offer!`] macro.
80
81```
82# use static_assertions::assert_type_eq_all as type_eq;
83# use dialectic::prelude::*;
84# use dialectic::types::*;
85#
86type_eq!(
87 Session! { offer { 0 => {}, 1 => {} } },
88 Offer<(Done, Done)>
89);
90
91type_eq!(
92 Session! { choose { 0 => {}, 1 => {} } },
93 Choose<(Done, Done)>
94);
95```
96
97## The `loop`, `break`, and `continue` keywords
98
99The syntax of `loop`, `break` and `continue` are identical to in Rust syntax, including optional
100named labels.
101
102Just like Rust's `loop` keyword, but *unlike* Dialectic's [`Loop`] type, when control flow
103reaches the end of a `loop` in a `Session!`, it automatically returns to the head of the loop.
104To exit a loop, you must use `break`.
105
106```
107# use static_assertions::assert_type_eq_all as type_eq;
108# use dialectic::prelude::*;
109# use dialectic::types::*;
110#
111type_eq!(
112 Session! { loop { break } },
113 Loop<Done>
114);
115
116type_eq!(
117 Session! { loop { send (); } },
118 Session! { loop { send (); continue; } },
119 Loop<Send<(), Continue<0>>>,
120);
121
122type_eq!(
123 Session! {
124 'outer: loop {
125 loop {
126 break 'outer;
127 }
128 }
129 },
130 Loop<Loop<Done>>
131);
132
133type_eq!(
134 Session! {
135 'outer: loop {
136 send ();
137 loop {
138 continue 'outer;
139 }
140 }
141 },
142 Loop<Send<(), Loop<Continue<1>>>>
143);
144```
145
146## The `call` keyword
147
148The `call` keyword either precedes a named session type, or a block. It corresponds to the
149[`Call`] session type and the [`call`] method.
150
151```
152# use static_assertions::assert_type_eq_all as type_eq;
153# use dialectic::prelude::*;
154# use dialectic::types::*;
155#
156type P = Session! { send i64 };
157type Q = Session! { call P };
158
159type_eq!(Q, Call<Send<i64, Done>, Done>);
160
161type R = Session! {
162 loop {
163 choose {
164 0 => {
165 send i64;
166 call { continue };
167 recv i64;
168 },
169 1 => break,
170 }
171 }
172};
173
174type_eq!(R, Loop<Choose<(Send<i64, Call<Continue<0>, Recv<i64, Continue<0>>>>, Done)>>);
175```
176
177## The `split` keyword
178
179The `split` keyword precedes a block with two clauses, indicating two concurrent sessions to be
180run, one (marked by `->`) which can only transmit information, and the other (marked by `<-`)
181which can only receive information. It corresponds to the [`Split`] session type and the [`split`]
182method.
183
184```
185# use static_assertions::assert_type_eq_all as type_eq;
186# use dialectic::prelude::*;
187# use dialectic::types::*;
188#
189type P = Session! {
190 split {
191 -> send i64,
192 <- recv bool,
193 };
194 send String;
195};
196
197type_eq!(P, Split<Send<i64, Done>, Recv<bool, Done>, Send<String, Done>>);
198```
199
200## External session types
201
202Arbitrary session types defined outside the macro invocation can be spliced in as an individual
203statement, or even used as generic parameters.
204
205```
206# use static_assertions::assert_type_eq_all as type_eq;
207# use dialectic::prelude::*;
208# use dialectic::types::*;
209#
210type Parity = Session! { send i64; recv bool };
211type Twice<T> = Session! { T; T };
212type Protocol = Session! {
213 loop {
214 choose {
215 0 => Twice<Parity>,
216 1 => break,
217 }
218 }
219};
220
221type_eq!(
222 Protocol,
223 Loop<Choose<(Send<i64, Recv<bool, Send<i64, Recv<bool, Continue<0>>>>>, Done)>>,
224);
225```
226
227[`Send`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Send.html
228[`Recv`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Recv.html
229[`Offer`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Offer.html
230[`Choose`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Choose.html
231[`Loop`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Loop.html
232[`Continue`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Continue.html
233[`Done`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Done.html
234[`Call`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Call.html
235[`Split`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Split.html
236[`send`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html#method.send
237[`recv`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html#method.recv
238[`offer!`]: https://docs.rs/dialectic/latest/dialectic/macro.offer.html
239[`choose`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html#method.choose
240[`call`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html#method.call
241[`split`]: https://docs.rs/dialectic/latest/dialectic/struct.Chan.html#method.split
242*/
243#[proc_macro]
244#[allow(non_snake_case)]
245pub fn Session(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
246 let result = parse_macro_input!(input as dialectic_compiler::Invocation).compile();
247
248 match result {
249 Ok(compiled) => compiled.to_token_stream().into(),
250 Err(error) => {
251 let compile_errors = error.to_compile_error();
252 let quoted = quote! { [(); { #compile_errors 0 }] };
253 quoted.into()
254 }
255 }
256}
257
258/// The `offer!` macro offers a set of different protocols, allowing the other side of the channel
259/// to choose with which one to proceed.
260///
261/// Each invocation of the `offer!` macro on some `Chan<S, Tx, Rx>` returns `Result<_, Rx::Error>`,
262/// which will be an error if the receiving transport end `Rx` failed to receive the choice made by
263/// the other party. Otherwise, it returns whatever type is returned by each of its branches (which
264/// means that each branch must have the same type).
265///
266/// You must specify exactly as many branches as there are options in the type of the [`Offer`] to
267/// which this expression corresponds, and they must be in the same order as the choices are in the
268/// tuple [`Offer`]ed. In the body of each branch, the identifier for the channel is rebound to have
269/// the session type corresponding to that branch, so you can proceed with its session.
270///
271/// # Examples
272///
273/// ```
274/// use dialectic::prelude::*;
275/// use dialectic_tokio_mpsc as mpsc;
276///
277/// # #[tokio::main]
278/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
279/// type GiveOrTake = Session! {
280/// choose {
281/// 0 => send i64,
282/// 1 => recv String,
283/// }
284/// };
285///
286/// let (c1, c2) = GiveOrTake::channel(|| mpsc::channel(1));
287///
288/// // Spawn a thread to offer a choice
289/// let t1 = tokio::spawn(async move {
290/// offer!(in c2 {
291/// 0 => { c2.recv().await?; },
292/// 1 => { c2.send("Hello!".to_string()).await?; },
293/// })?;
294/// Ok::<_, mpsc::Error>(())
295/// });
296///
297/// // Choose to send an integer
298/// c1.choose::<0>().await?.send(42).await?;
299///
300/// // Wait for the offering thread to finish
301/// t1.await??;
302/// # Ok(())
303/// # }
304/// ```
305///
306/// [`Offer`]: https://docs.rs/dialectic/latest/dialectic/types/struct.Offer.html
307#[proc_macro]
308pub fn offer(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
309 let result = parse_macro_input!(input as OfferInvocation).compile();
310 match result {
311 Ok(output) => output.to_token_stream().into(),
312 Err(error) => error.to_compile_error().into(),
313 }
314}
315
316/// The surface syntax of the `offer!` macro, as captured by `syn`'s parsing machinery.
317struct OfferInvocation {
318 /// The identifier of the channel to be offered upon.
319 chan: syn::Ident,
320 /// The syntactic branches of the invocation: this could be invalid, because [`Arm`] contains
321 /// many variants for its patterns which are not legitimate in our context.
322 branches: Vec<Arm>,
323}
324
325/// The output information generated by the `offer!` macro, as required to quote it back into a
326/// generated [`TokenStream`].
327struct OfferOutput {
328 /// The identifier of the channel to be offered upon.
329 chan: syn::Ident,
330 /// The branches, in order, to be emitted for the `match` statement generated by the macro.
331 branches: Vec<syn::Expr>,
332}
333
334impl Parse for OfferInvocation {
335 fn parse(input: ParseStream) -> syn::Result<Self> {
336 let _ = input.parse::<Token![in]>()?;
337 let chan = input.parse::<Ident>()?;
338 let content;
339 let _ = braced!(content in input);
340 let mut branches = Vec::new();
341 while !content.is_empty() {
342 branches.push(content.call(Arm::parse)?);
343 }
344 Ok(OfferInvocation { chan, branches })
345 }
346}
347
348impl ToTokens for OfferOutput {
349 fn to_tokens(&self, tokens: &mut TokenStream) {
350 let OfferOutput { chan, branches } = self;
351
352 // Find the path necessary to refer to types in the dialectic crate.
353 let dialectic_crate = dialectic_compiler::dialectic_path();
354
355 // Create an iterator of token streams, one for each branch of the generated `match`
356 let arms = branches.iter().enumerate().map(|(choice, body)| {
357 let byte = choice as u8;
358 quote! {
359 #byte => {
360 let #chan = #dialectic_crate::Branches::case::<#choice>(#chan);
361 let #chan = match #chan {
362 Ok(chan) => chan,
363 Err(_) => unreachable!("malformed generated code from `offer!` macro: mismatch between variant and choice: this is a bug!"),
364 };
365 #body
366 }
367 }
368 });
369
370 // Merge all the branches into one `match` statement that branches precisely once, on the
371 // discriminant of the `Branches` structure
372 tokens.append_all(quote! {
373 {
374 match #dialectic_crate::Chan::offer( #chan ).await {
375 Ok(#chan) => Ok(match {
376 let choice: u8 = std::convert::Into::into(dialectic::Branches::choice(& #chan));
377 choice
378 } {
379 #(#arms),*
380 _ => unreachable!("malformed code generated from `offer!` macro: impossible variant encountered: this is a bug!"),
381 }),
382 Err(error) => Err(error),
383 }
384 }
385 })
386 }
387}
388
389impl OfferInvocation {
390 /// Compile an [`OfferInvocation`] to an [`OfferOutput`], or return an error if it was not a
391 /// valid macro invocation.
392 fn compile(self) -> Result<OfferOutput, syn::Error> {
393 // Validate the structure, collecting all errors
394 let mut errors: Vec<syn::Error> = Vec::new();
395 for (choice, arm) in self.branches.iter().enumerate() {
396 if choice > 256 {
397 let message = format!("at most 256 arms (labeled `0` through `255` in ascending order) are permitted in the `offer!` macro; this arm is number {}", choice);
398 errors.push(syn::Error::new(arm.span(), message));
399 }
400 match &arm.pat {
401 Pat::Lit(pat_lit) => {
402 let lit_int = match &*pat_lit.expr {
403 syn::Expr::Lit(syn::ExprLit {
404 lit: syn::Lit::Int(lit_int),
405 ..
406 }) => lit_int,
407 _ => {
408 let message = "expected a usize literal";
409 errors.push(syn::Error::new(pat_lit.expr.span(), message));
410 continue;
411 }
412 };
413
414 match lit_int.base10_parse::<usize>() {
415 Ok(n) if n == choice => {}
416 Ok(_) => {
417 let message = format!("expected the `usize` literal `{}` for this arm of the `offer!` macro (note: arms must be in ascending order)", choice);
418 errors.push(syn::Error::new(pat_lit.span(), message));
419 }
420 Err(e) => {
421 let message = format!("could not parse literal: `{}`", e);
422 errors.push(syn::Error::new(pat_lit.span(), message));
423 }
424 }
425 }
426 _ => {
427 let message = format!("expected the `usize` literal `{}` for this arm (note: arms must be in ascending order)", choice);
428 errors.push(syn::Error::new(arm.pat.span(), message))
429 }
430 }
431 }
432
433 // Either collect the valid expressions, one for each branch, or return the errors
434 match errors.pop() {
435 None => Ok(OfferOutput {
436 chan: self.chan,
437 branches: self
438 .branches
439 .into_iter()
440 .map(|Arm { body, .. }| *body)
441 .collect(),
442 }),
443 Some(mut error) => {
444 for e in errors.drain(..) {
445 error.combine(e)
446 }
447 Err(error)
448 }
449 }
450 }
451}
452
453enum Mutability {
454 Val,
455 Ref(Token![ref]),
456 Mut(Token![ref], Token![mut]),
457}
458
459impl ToTokens for Mutability {
460 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
461 use Mutability::*;
462 let dialectic_path = dialectic_compiler::dialectic_path();
463 stream.extend(match self {
464 Val => quote!(#dialectic_path::backend::Val),
465 Ref(t) => quote_spanned!(t.span()=> #dialectic_path::backend::Ref),
466 Mut(t1, t2) => {
467 quote_spanned!(t1.span().join(t2.span()).unwrap_or_else(|| t2.span())=> #dialectic_path::backend::Mut)
468 },
469 })
470 }
471}
472
473impl Parse for Mutability {
474 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
475 let lookahead = input.lookahead1();
476 let mutability = if lookahead.peek(Token![ref]) {
477 let ref_token = input.parse()?;
478 if input.peek(Token![mut]) {
479 Mutability::Mut(ref_token, input.parse()?)
480 } else {
481 Mutability::Ref(ref_token)
482 }
483 } else {
484 Mutability::Val
485 };
486
487 Ok(mutability)
488 }
489}
490
491struct TransmitterSpec {
492 name: syn::Type,
493 types: Punctuated<(Mutability, syn::Type), Token![,]>,
494}
495
496struct ReceiverSpec {
497 name: syn::Type,
498 types: Punctuated<syn::Type, Token![,]>,
499}
500
501impl Parse for TransmitterSpec {
502 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
503 fn parse_convention_type_pair(
504 input: ParseStream,
505 ) -> Result<(Mutability, syn::Type), syn::Error> {
506 Ok((input.parse()?, input.parse()?))
507 }
508
509 let name: syn::Type = input.parse()?;
510 if input.is_empty() {
511 Ok(TransmitterSpec {
512 name,
513 types: Punctuated::new(),
514 })
515 } else {
516 let _for: Token![for] = input.parse()?;
517 let types = if input.is_empty() {
518 Punctuated::new()
519 } else {
520 input.parse_terminated(parse_convention_type_pair)?
521 };
522 Ok(TransmitterSpec { name, types })
523 }
524 }
525}
526
527impl Parse for ReceiverSpec {
528 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
529 let name: syn::Type = input.parse()?;
530 if input.is_empty() {
531 Ok(ReceiverSpec {
532 name,
533 types: Punctuated::new(),
534 })
535 } else {
536 let _for: Token![for] = input.parse()?;
537 let types = if input.is_empty() {
538 Punctuated::new()
539 } else {
540 input.parse_terminated(syn::Type::parse)?
541 };
542 Ok(ReceiverSpec { name, types })
543 }
544 }
545}
546
547/// Get a mutable reference to the `where` predicates of an item, if the item supports `where`
548/// predicates. Creates empty `where` clause if none exists yet.
549fn where_predicates_mut(
550 item: &mut syn::Item,
551) -> Option<&mut Punctuated<syn::WherePredicate, Token![,]>> {
552 use syn::Item::*;
553 let span = item.span();
554 let maybe_where = match item {
555 Fn(i) => &mut i.sig.generics.where_clause,
556 Enum(i) => &mut i.generics.where_clause,
557 Impl(i) => &mut i.generics.where_clause,
558 Struct(i) => &mut i.generics.where_clause,
559 Trait(i) => &mut i.generics.where_clause,
560 TraitAlias(i) => &mut i.generics.where_clause,
561 Type(i) => &mut i.generics.where_clause,
562 Union(i) => &mut i.generics.where_clause,
563 _ => return None,
564 };
565 if let Some(where_clause) = maybe_where {
566 Some(&mut where_clause.predicates)
567 } else {
568 let clause = syn::WhereClause {
569 where_token: syn::token::Where { span },
570 predicates: Punctuated::new(),
571 };
572 *maybe_where = Some(clause);
573 Some(&mut maybe_where.as_mut().unwrap().predicates)
574 }
575}
576
577/// In situations where the transmitting backend for a channel is generic, explicitly writing down
578/// all the trait bounds necessary to implement a protocol for that parameterized backend can be a
579/// lot of boilerplate. The `Transmitter` attribute macro abbreviates these bounds by modifying the
580/// `where` clause of the item to which it is attached.
581///
582/// This macro may be attached to any item capable of supporting a `where`-clause: an `enum`
583/// definition, `fn` item (top level or in a trait definition or implementation), `impl` block,
584/// `struct` definition, `trait` definition, `type` synonym, or `union` definition.
585///
586/// # Examples
587///
588/// ```
589/// use dialectic::prelude::*;
590///
591/// #[Transmitter(Tx for bool, i64)]
592/// async fn foo<Tx, Rx>(
593/// chan: Chan<Session!{ send bool; send i64 }, Tx, Rx>
594/// ) -> Result<(), Tx::Error>
595/// where
596/// Rx: Send + 'static,
597/// {
598/// let chan = chan.send(true).await?;
599/// let chan = chan.send(42).await?;
600/// chan.close();
601/// Ok(())
602/// }
603/// ```
604///
605/// # Syntax
606///
607/// The `Transmitter` attribute takes the name of the transmitter for which the bounds will be
608/// generated, an optional *calling convention* (one of `move`, `ref`, or `mut`), and, optionally,
609/// the keyword `for` followed by a comma-separated list of types.
610///
611/// Some example invocations:
612///
613/// ```
614/// # use dialectic::prelude::*;
615/// #[Transmitter(Tx)]
616/// # fn a<Tx>() {}
617/// #[Transmitter(Tx)]
618/// # fn b<Tx>() {}
619/// #[Transmitter(Tx for bool)]
620/// # fn c<Tx>() {}
621/// #[Transmitter(Tx for ref bool)]
622/// # fn d<Tx>() {}
623/// #[Transmitter(Tx for ref mut bool)]
624/// # fn e<Tx>() {}
625/// #[Transmitter(Tx for bool, i64, Vec<String>)]
626/// # fn f<Tx>() {}
627/// #[Transmitter(Tx for bool, ref i64, ref mut Vec<String>)]
628/// # fn g<Tx>() {}
629/// ```
630///
631/// # Expansion
632///
633/// The attribute adds extra bounds to the `where`-clause of the item to which it is attached (if
634/// the item does not have a `where-clause, one will be created containing the bounds).
635///
636/// For a transmitter type `Tx`, optional calling conventions `CN?` (none, or one of `move`, `ref`,
637/// or `mut`), and types `T1`, `T2`, `...`, the invocation:
638///
639/// ```ignore
640/// #[Transmitter(Tx for C1? T1, C2? T2, ...)]
641/// fn f<Tx>() {}
642/// ```
643///
644/// ...translates to the following bounds:
645///
646/// ```
647/// use dialectic::prelude::*;
648///
649/// # type C1 = Ref;
650/// # type C2 = Ref;
651/// # struct T1;
652/// # struct T2;
653/// #
654/// fn f<Tx>()
655/// where
656/// Tx: Transmitter + Send + 'static,
657/// // For each of the types `T1`, `T2`, ...
658/// // If the convention is unspecified, `C` is left unspecified;
659/// // otherwise, we translate into `call_by` conventions using
660/// // `move` => `Val`, `ref` => `Ref`, and `mut` => `Mut`
661/// Tx: Transmit<T1, C1>,
662/// Tx: Transmit<T2, C2>,
663/// // ...
664/// {}
665/// ```
666#[allow(non_snake_case)]
667#[proc_macro_attribute]
668pub fn Transmitter(
669 params: proc_macro::TokenStream,
670 input: proc_macro::TokenStream,
671) -> proc_macro::TokenStream {
672 let TransmitterSpec { name, types } = parse_macro_input!(params as TransmitterSpec);
673 let dialectic_path = dialectic_compiler::dialectic_path();
674 let mut item = parse_macro_input!(input as syn::Item);
675 if let Some(predicates) = where_predicates_mut(&mut item) {
676 predicates.push(syn::parse_quote! {
677 #name: ::std::marker::Send
678 + #dialectic_path::backend::Transmitter
679 + 'static
680 });
681 for (mutability, ty) in types {
682 predicates.push(syn::parse_quote! {
683 #name: #dialectic_path::backend::Transmit<#ty, #mutability>
684 });
685 }
686 item.into_token_stream().into()
687 } else {
688 let message = "unexpected kind of item for `Transmitter` attribute: expecting `enum`, `fn`, `impl`, `struct`, `trait`, `type`, or `union`";
689 syn::Error::new(item.span(), message)
690 .into_compile_error()
691 .into_token_stream()
692 .into()
693 }
694}
695
696/// In situations where the transmitting backend for a channel is generic, explicitly writing down
697/// all the trait bounds necessary to implement a protocol for that parameterized backend can be a
698/// lot of boilerplate. The `Receiver` attribute macro abbreviates these bounds by modifying the
699/// `where` clause of the item to which it is attached.
700///
701/// This macro may be attached to any item capable of supporting a `where`-clause: an `enum`
702/// definition, `fn` item (top level or in a trait definition or implementation), `impl` block,
703/// `struct` definition, `trait` definition, `type` synonym, or `union` definition.
704///
705/// # Examples
706///
707/// ```
708/// use dialectic::prelude::*;
709///
710/// #[Receiver(Rx for bool, i64)]
711/// async fn foo<Tx, Rx>(
712/// chan: Chan<Session!{ recv bool; recv i64 }, Tx, Rx>
713/// ) -> Result<(), Rx::Error>
714/// where
715/// Tx: Send + 'static,
716/// {
717/// let (_, chan) = chan.recv().await?;
718/// let (_, chan) = chan.recv().await?;
719/// chan.close();
720/// Ok(())
721/// }
722/// ```
723///
724/// # Syntax
725///
726/// The `Receiver` attribute takes the name of the receiver for which the bounds will be generated,
727/// and, optionally, the keyword `for` followed by a comma-separated list of types.
728///
729/// Some example invocations:
730///
731/// ```
732/// # use dialectic::prelude::*;
733/// #[Receiver(Rx)]
734/// # fn a<Rx>() {}
735/// #[Receiver(Rx for bool)]
736/// # fn b<Rx>() {}
737/// #[Receiver(Rx for bool, i64, Vec<String>)]
738/// # fn c<Rx>() {}
739/// ```
740///
741/// # Expansion
742///
743/// The attribute adds extra bounds to the `where`-clause of the item to which it is attached (if
744/// the item does not have a `where-clause, one will be created containing the bounds).
745///
746/// For a transmitter type `Rx`, and types `T1`, `T2`, `...`, the invocation:
747///
748/// ```ignore
749/// #[Receiver(Rx for T1, T2, ...)]
750/// fn f<Rx>() {}
751/// ```
752///
753/// ...translates to the following bounds:
754///
755/// ```
756/// use dialectic::prelude::*;
757///
758/// # struct T1;
759/// # struct T2;
760/// #
761/// fn f<Rx>()
762/// where
763/// Rx: Receiver + Send + 'static,
764/// // For each of the types `T1`, `T2`, ...
765/// Rx: Receive<T1>,
766/// Rx: Receive<T2>,
767/// // ...
768/// {}
769/// ```
770#[allow(non_snake_case)]
771#[proc_macro_attribute]
772pub fn Receiver(
773 params: proc_macro::TokenStream,
774 input: proc_macro::TokenStream,
775) -> proc_macro::TokenStream {
776 let dialectic_path = dialectic_compiler::dialectic_path();
777 let ReceiverSpec { name, types } = parse_macro_input!(params as ReceiverSpec);
778 let mut item = parse_macro_input!(input as syn::Item);
779 if let Some(predicates) = where_predicates_mut(&mut item) {
780 predicates.push(syn::parse_quote! {
781 #name: ::std::marker::Send
782 + #dialectic_path::backend::Receiver
783 + 'static
784 });
785 for ty in types {
786 predicates.push(syn::parse_quote! {
787 #name: #dialectic_path::backend::Receive<#ty>
788 });
789 }
790 item.into_token_stream().into()
791 } else {
792 let message = "unexpected kind of item for `Receiver` attribute: expecting `enum`, `fn`, `impl`, `struct`, `trait`, `type`, or `union`";
793 syn::Error::new(item.span(), message)
794 .into_compile_error()
795 .into_token_stream()
796 .into()
797 }
798}
799
800/// **Internal implementation detail:** This proc macro generates implementations of `Tuple` and
801/// `AsList` for tuples up to the arity passed in as the argument.
802#[proc_macro]
803pub fn impl_tuples(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
804 let arity_limit = parse_macro_input!(input as LitInt)
805 .base10_parse::<usize>()
806 .unwrap();
807
808 let idents = (0..=arity_limit)
809 .map(|i| format_ident!("T{}", i))
810 .collect::<Vec<_>>();
811 let mut impls = TokenStream::new();
812
813 for i in 0..=arity_limit {
814 let ident_slice = &idents[..i];
815
816 let typarams = if ident_slice.is_empty() {
817 quote!()
818 } else {
819 quote!(<#(#ident_slice,)*>)
820 };
821
822 let as_tuple = quote!((#(#ident_slice,)*));
823 let as_list = ident_slice
824 .iter()
825 .rev()
826 .fold(quote!(()), |acc, ident| quote!((#ident, #acc)));
827
828 let current_impl = quote! {
829 impl #typarams Tuple for #as_tuple {
830 type AsList = #as_list;
831 }
832
833 impl #typarams List for #as_list {
834 type AsTuple = #as_tuple;
835 }
836 };
837
838 current_impl.to_tokens(&mut impls);
839 }
840
841 impls.into()
842}
843
844/// **Internal implementation detail:** This proc macro generates trait implementations of `ToUnary`
845/// and `ToConstant` which convert type-level constants into unary representation, and vice versa.
846/// It will generate up to the maximum number specified as the argument.
847#[proc_macro]
848pub fn generate_unary_conversion_impls(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
849 let arity_limit = parse_macro_input!(input as LitInt)
850 .base10_parse::<usize>()
851 .unwrap();
852
853 let impls = (0..=arity_limit).scan(quote!(Z), |state, i| {
854 let tokens = quote! {
855 impl ToUnary for Number<#i> {
856 type AsUnary = #state;
857 }
858
859 impl ToConstant for #state {
860 type AsConstant = Number<#i>;
861 }
862 };
863
864 *state = quote!(S<#state>);
865
866 Some(tokens)
867 });
868
869 quote!(#(#impls)*).into()
870}