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}