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}