Skip to main content

trait_set/
lib.rs

1//! This crate provides support for [trait aliases][alias]: a feature
2//! that is already supported by the Rust compiler, but is [not stable][tracking_issue]
3//! yet.
4//!
5//! The idea is simple: combine a group of traits under a single name. The simplest
6//! example is:
7//!
8//! ```rust
9//! use trait_set::trait_set;
10//!
11//! trait_set! {
12//!     pub trait ThreadSafe = Send + Sync;
13//! }
14//! ```
15//!
16//! The [`trait_set`] macro displayed here is the main entity of the crate:
17//! it allows declaring multiple trait aliases, each of which 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
30use std::iter::FromIterator;
31
32use proc_macro::TokenStream;
33use proc_macro2::TokenStream as TokenStream2;
34use quote::quote;
35use syn::{
36    parse::{Error, Parse, ParseStream},
37    parse_macro_input,
38    punctuated::Punctuated,
39    spanned::Spanned,
40    Attribute, Generics, Ident, Meta, Result, Token, TypeTraitObject, Visibility,
41};
42
43/// Represents one trait alias.
44struct TraitSet {
45    attributes: Vec<Attribute>,
46    implementation_attributes: Vec<Attribute>,
47    visibility: Visibility,
48    _trait_token: Token![trait],
49    alias_name: Ident,
50    generics: Generics,
51    _eq_token: Token![=],
52    traits: TypeTraitObject,
53}
54
55impl TraitSet {
56    /// Selects configuration attributes that must also guard the generated
57    /// implementation. Other attributes describe the user-facing trait and
58    /// may not be valid on a trait implementation.
59    fn implementation_attributes(attributes: &[Attribute]) -> Result<Vec<Attribute>> {
60        attributes
61            .iter()
62            .map(|attribute| Self::configuration_meta(&attribute.meta))
63            .filter_map(Result::transpose)
64            .map(|meta| meta.map(|meta| syn::parse_quote!(#[#meta])))
65            .collect()
66    }
67
68    /// Retains only the item-existence part of an attribute. In particular,
69    /// `cfg_attr` can contain attributes such as `deprecated` that belong on
70    /// the trait but would be rejected on its blanket implementation.
71    fn configuration_meta(meta: &Meta) -> Result<Option<Meta>> {
72        if meta.path().is_ident("cfg") {
73            return Ok(Some(meta.clone()));
74        }
75        if !meta.path().is_ident("cfg_attr") {
76            return Ok(None);
77        }
78
79        let Meta::List(list) = meta else {
80            return Err(Error::new(meta.span(), "expected `cfg_attr(...)`"));
81        };
82        let arguments = list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
83        let mut arguments = arguments.iter();
84        let Some(predicate) = arguments.next() else {
85            return Err(Error::new(meta.span(), "missing `cfg_attr` predicate"));
86        };
87        let configuration_attributes = arguments
88            .map(Self::configuration_meta)
89            .collect::<Result<Vec<_>>>()?
90            .into_iter()
91            .flatten()
92            .collect::<Vec<_>>();
93
94        Ok(if configuration_attributes.is_empty() {
95            None
96        } else {
97            Some(syn::parse_quote! {
98                cfg_attr(#predicate, #(#configuration_attributes),*)
99            })
100        })
101    }
102
103    /// Renders a trait alias into a new trait with bounds set.
104    fn render(self) -> TokenStream2 {
105        // Generic and non-generic implementations have slightly different
106        // syntax, so it's simpler to process them individually rather than
107        // try to generalize the implementation.
108        if self.generics.params.is_empty() {
109            self.render_non_generic()
110        } else {
111            self.render_generic()
112        }
113    }
114
115    /// Renders the trait alias without generic parameters.
116    fn render_non_generic(self) -> TokenStream2 {
117        let attributes = self.attributes;
118        let implementation_attributes = self.implementation_attributes;
119        let visibility = self.visibility;
120        let alias_name = self.alias_name;
121        let bounds = self.traits.bounds;
122        quote! {
123            #(#attributes)*
124            #visibility trait #alias_name: #bounds {}
125
126            #(#implementation_attributes)*
127            impl<_INNER> #alias_name for _INNER where _INNER: #bounds {}
128        }
129    }
130
131    /// Renders the trait alias with generic parameters.
132    fn render_generic(self) -> TokenStream2 {
133        let attributes = self.attributes;
134        let implementation_attributes = self.implementation_attributes;
135        let visibility = self.visibility;
136        let alias_name = self.alias_name;
137        let bounds = self.traits.bounds;
138        let generics = self.generics;
139
140        // Syn owns the distinction between parameter declarations (`T: Send`,
141        // `const N: usize`) and their use as arguments (`T`, `N`). Besides
142        // preserving every supported generic form, `split_for_impl` removes
143        // defaults where Rust forbids them in an impl declaration.
144        let mut implementation_generics = generics.clone();
145        implementation_generics
146            .params
147            .push(syn::parse_quote!(__TRAIT_SET_INNER));
148        let (implementation_generics, _, _) = implementation_generics.split_for_impl();
149        let (_, alias_generics, _) = generics.split_for_impl();
150
151        quote! {
152            #(#attributes)*
153            #visibility trait #alias_name #generics: #bounds {}
154
155            #(#implementation_attributes)*
156            impl #implementation_generics #alias_name #alias_generics for __TRAIT_SET_INNER
157            where
158                __TRAIT_SET_INNER: #bounds
159            {}
160        }
161    }
162}
163
164impl Parse for TraitSet {
165    fn parse(input: ParseStream) -> Result<Self> {
166        let attributes = input.call(Attribute::parse_outer)?;
167        let implementation_attributes = Self::implementation_attributes(&attributes)?;
168        let result = TraitSet {
169            attributes,
170            implementation_attributes,
171            visibility: input.parse()?,
172            _trait_token: input.parse()?,
173            alias_name: input.parse()?,
174            generics: input.parse()?,
175            _eq_token: input.parse()?,
176            traits: input.parse()?,
177        };
178
179        if let Some(where_clause) = result.generics.where_clause {
180            return Err(Error::new(
181                where_clause.span(),
182                "Where clause is not allowed for trait alias",
183            ));
184        }
185        Ok(result)
186    }
187}
188
189/// Represents a sequence of trait aliases delimited by semicolons.
190struct ManyTraitSet {
191    entries: Punctuated<TraitSet, Token![;]>,
192}
193
194impl Parse for ManyTraitSet {
195    fn parse(input: ParseStream) -> Result<Self> {
196        Ok(ManyTraitSet {
197            entries: input.parse_terminated(TraitSet::parse, Token![;])?,
198        })
199    }
200}
201
202impl ManyTraitSet {
203    fn render(self) -> TokenStream2 {
204        TokenStream2::from_iter(self.entries.into_iter().map(|entry| entry.render()))
205    }
206}
207
208/// Creates an alias for a set of traits.
209///
210/// To demonstrate the idea, see the examples:
211///
212/// ```rust
213/// use trait_set::trait_set;
214///
215/// trait_set! {
216///     /// Doc comments are also supported, btw.
217///     pub trait ThreadSafe = Send + Sync;
218///     pub trait ThreadSafeIterator<T> = ThreadSafe + Iterator<Item = T>;
219///     pub trait ThreadSafeBytesIterator = ThreadSafeIterator<u8>;
220///     pub trait StaticDebug = 'static + std::fmt::Debug;
221/// }
222/// ```
223///
224/// This macro also supports [higher-rank trait bounds][hrtb]:
225///
226/// ```rust
227/// # pub trait Serializer {
228/// #     type Ok;
229/// #     type Error;
230/// #
231/// #     fn ok_value() -> Self::Ok;
232/// # }
233/// # pub trait Deserializer<'de> {
234/// #     type Error;
235/// # }
236/// #
237/// # pub trait Serialize {
238/// #     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239/// #     where
240/// #         S: Serializer;
241/// # }
242/// #
243/// # pub trait Deserialize<'de>: Sized {
244/// #     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245/// #     where
246/// #         D: Deserializer<'de>;
247/// # }
248/// #
249/// # impl Serializer for u8 {
250/// #     type Ok = ();
251/// #     type Error = ();
252/// #
253/// #     fn ok_value() -> Self::Ok {
254/// #         ()
255/// #     }
256/// # }
257/// #
258/// # impl<'de> Deserializer<'de> for u8 {
259/// #     type Error = ();
260/// # }
261/// #
262/// # impl Serialize for u8 {
263/// #     fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
264/// #     where
265/// #         S: Serializer
266/// #     {
267/// #         Ok(S::ok_value())
268/// #     }
269/// # }
270/// #
271/// # impl<'de> Deserialize<'de> for u8 {
272/// #     fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
273/// #     where
274/// #         D: Deserializer<'de>
275/// #         {
276/// #             Ok(0u8)
277/// #         }
278/// # }
279/// use trait_set::trait_set;
280///
281/// trait_set! {
282///     pub trait Serde = Serialize + for<'de> Deserialize<'de>;
283///     // Note that you can also use a lifetime as a generic parameter.
284///     pub trait SerdeLifetimeTemplate<'de> = Serialize + Deserialize<'de>;
285/// }
286/// ```
287///
288/// [hrtb]: https://doc.rust-lang.org/nomicon/hrtb.html
289#[proc_macro]
290pub fn trait_set(tokens: TokenStream) -> TokenStream {
291    let input = parse_macro_input!(tokens as ManyTraitSet);
292    input.render().into()
293}