Skip to main content

trait_set/
lib.rs

1//! This crate provide support for [trait aliases][alias]: a feature
2//! that is already supported by Rust compiler, but is [not stable][tracking_issue]
3//! yet.
4//!
5//! The idea is simple: combine group of traits under a single name. The simplest
6//! example will be:
7//!
8//! ```rust
9//! use trait_set::trait_set;
10//!
11//! trait_set! {
12//!     pub trait ThreadSafe = Send + Sync;
13//! }
14//! ```
15//!
16//! Macro [`trait_set`] displayed here is the main entity of the crate:
17//! it allows declaring multiple trait aliases, each of them is represented
18//! as
19//!
20//! ```text
21//! [visibility] trait [AliasName][<generics>] = [Element1] + [Element2] + ... + [ElementN];
22//! ```
23//!
24//! For more details, see the [`trait_set`] macro documentation.
25//!
26//! [alias]: https://doc.rust-lang.org/unstable-book/language-features/trait-alias.html
27//! [tracking_issue]: https://github.com/rust-lang/rust/issues/41517
28//! [`trait_set`]: macro.trait_set.html
29
30extern crate proc_macro;
31
32use std::iter::FromIterator;
33
34use proc_macro::TokenStream;
35use proc_macro2::TokenStream as TokenStream2;
36use quote::quote;
37use syn::{
38    parse::{Error, Parse, ParseStream},
39    parse_macro_input,
40    punctuated::Punctuated,
41    spanned::Spanned,
42    Attribute, Expr, GenericParam, Generics, Ident, Lit, Meta, MetaNameValue, Result, Token,
43    TypeTraitObject, Visibility,
44};
45
46/// Represents one trait alias.
47struct TraitSet {
48    doc_comment: Option<String>,
49    visibility: Visibility,
50    _trait_token: Token![trait],
51    alias_name: Ident,
52    generics: Generics,
53    _eq_token: Token![=],
54    traits: TypeTraitObject,
55}
56
57impl TraitSet {
58    /// Attempts to parse doc-comments from the trait attributes
59    /// and returns the results as a single string.
60    /// If multiple doc-comments were provided (e.g. with `///` and `#[doc]`),
61    /// they will be joined with a newline.
62    fn parse_doc(attrs: &[Attribute]) -> Result<Option<String>> {
63        let mut out = String::new();
64
65        for attr in attrs {
66            // Check whether current attribute is `#[doc = "..."]`.
67            if let Meta::NameValue(MetaNameValue {
68                path,
69                value: Expr::Lit(lit),
70                ..
71            }) = &attr.meta
72            {
73                if let Some(path_ident) = path.get_ident() {
74                    if path_ident == "doc" {
75                        if let Lit::Str(doc_comment) = &lit.lit {
76                            out += &doc_comment.value();
77                            // Newlines are not included in the literal value,
78                            // so we have to add them manually.
79                            out.push('\n');
80                        }
81                    }
82                }
83            }
84        }
85
86        Ok(if !out.is_empty() { Some(out) } else { None })
87    }
88
89    /// Renders trait alias into a new trait with bounds set.
90    fn render(self) -> TokenStream2 {
91        // Generic and non-generic implementation have slightly different
92        // syntax, so it's simpler to process them individually rather than
93        // try to generalize implementation.
94        if self.generics.params.is_empty() {
95            self.render_non_generic()
96        } else {
97            self.render_generic()
98        }
99    }
100
101    /// Renders the trait alias without generic parameters.
102    fn render_non_generic(self) -> TokenStream2 {
103        let visibility = self.visibility;
104        let alias_name = self.alias_name;
105        let bounds = self.traits.bounds;
106        let doc_comment = self.doc_comment.map(|val| quote! { #[doc = #val] });
107        quote! {
108            #doc_comment
109            #visibility trait #alias_name: #bounds {}
110
111            impl<_INNER> #alias_name for _INNER where _INNER: #bounds {}
112        }
113    }
114
115    /// Renders the trait alias with generic parameters.
116    fn render_generic(self) -> TokenStream2 {
117        let visibility = self.visibility;
118        let alias_name = self.alias_name;
119        let bounds = self.traits.bounds;
120        let doc_comment = self.doc_comment.map(|val| quote! { #[doc = #val] });
121
122        // We differentiate `generics` and `bound_generics` because in the
123        // `impl<X> Trait<Y>` block there must be no trait bounds in the `<Y>` part,
124        // they must go into `<X>` part only.
125        // E.g. `impl<X: Send, _INNER> Trait<X> for _INNER`.
126        let mut unbound_generics = self.generics.clone();
127        for param in unbound_generics.params.iter_mut() {
128            if let GenericParam::Type(ty) = param {
129                if !ty.bounds.is_empty() {
130                    ty.bounds.clear();
131                }
132            }
133        }
134        let unbound_generics = unbound_generics.params;
135        let bound_generics = self.generics.params;
136
137        // Note that it's important for `_INNER` to go *after* user-defined
138        // generics, because generics can contain lifetimes, and lifetimes
139        // should always go first.
140        quote! {
141            #doc_comment
142            #visibility trait #alias_name<#bound_generics>: #bounds {}
143
144            impl<#bound_generics, _INNER> #alias_name<#unbound_generics> for _INNER where _INNER: #bounds {}
145        }
146    }
147}
148
149impl Parse for TraitSet {
150    fn parse(input: ParseStream) -> Result<Self> {
151        let attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
152        let result = TraitSet {
153            doc_comment: Self::parse_doc(&attrs)?,
154            visibility: input.parse()?,
155            _trait_token: input.parse()?,
156            alias_name: input.parse()?,
157            generics: input.parse()?,
158            _eq_token: input.parse()?,
159            traits: input.parse()?,
160        };
161
162        if let Some(where_clause) = result.generics.where_clause {
163            return Err(Error::new(
164                where_clause.span(),
165                "Where clause is not allowed for trait alias",
166            ));
167        }
168        Ok(result)
169    }
170}
171
172/// Represents a sequence of trait aliases delimited by semicolon.
173struct ManyTraitSet {
174    entries: Punctuated<TraitSet, Token![;]>,
175}
176
177impl Parse for ManyTraitSet {
178    fn parse(input: ParseStream) -> Result<Self> {
179        Ok(ManyTraitSet {
180            entries: input.parse_terminated(TraitSet::parse, Token![;])?,
181        })
182    }
183}
184
185impl ManyTraitSet {
186    fn render(self) -> TokenStream2 {
187        TokenStream2::from_iter(self.entries.into_iter().map(|entry| entry.render()))
188    }
189}
190
191/// Creates an alias for set of traits.
192///
193/// To demonstrate the idea, see the examples:
194///
195/// ```rust
196/// use trait_set::trait_set;
197///
198/// trait_set! {
199///     /// Doc-comments are also supported btw.
200///     pub trait ThreadSafe = Send + Sync;
201///     pub trait ThreadSafeIterator<T> = ThreadSafe + Iterator<Item = T>;
202///     pub trait ThreadSafeBytesIterator = ThreadSafeIterator<u8>;
203///     pub trait StaticDebug = 'static + std::fmt::Debug;
204/// }
205///```
206///
207/// This macro also supports [higher-rank trait bound][hrtb]:
208///
209/// ```rust
210/// # pub trait Serializer {
211/// #     type Ok;
212/// #     type Error;
213/// #
214/// #     fn ok_value() -> Self::Ok;
215/// # }
216/// # pub trait Deserializer<'de> {
217/// #     type Error;
218/// # }
219/// #
220/// # pub trait Serialize {
221/// #     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
222/// #     where
223/// #         S: Serializer;
224/// # }
225/// #
226/// # pub trait Deserialize<'de>: Sized {
227/// #     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
228/// #     where
229/// #         D: Deserializer<'de>;
230/// # }
231/// #
232/// # impl Serializer for u8 {
233/// #     type Ok = ();
234/// #     type Error = ();
235/// #
236/// #     fn ok_value() -> Self::Ok {
237/// #         ()
238/// #     }
239/// # }
240/// #
241/// # impl<'de> Deserializer<'de> for u8 {
242/// #     type Error = ();
243/// # }
244/// #
245/// # impl Serialize for u8 {
246/// #     fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
247/// #     where
248/// #         S: Serializer
249/// #     {
250/// #         Ok(S::ok_value())
251/// #     }
252/// # }
253/// #
254/// # impl<'de> Deserialize<'de> for u8 {
255/// #     fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
256/// #     where
257/// #         D: Deserializer<'de>
258/// #         {
259/// #             Ok(0u8)
260/// #         }
261/// # }
262/// use trait_set::trait_set;
263///
264/// trait_set!{
265///     pub trait Serde = Serialize + for<'de> Deserialize<'de>;
266///     // Note that you can also use lifetimes as a generic parameter.
267///     pub trait SerdeLifetimeTemplate<'de> = Serialize + Deserialize<'de>;
268/// }
269/// ```
270///
271/// [hrtb]: https://doc.rust-lang.org/nomicon/hrtb.html
272#[proc_macro]
273pub fn trait_set(tokens: TokenStream) -> TokenStream {
274    let input = parse_macro_input!(tokens as ManyTraitSet);
275    input.render().into()
276}