transient_derive/
lib.rs

1//! Defines the [`Transient`][crate::Transient] derive macro that safely implements
2//! the [`Transient`][transient::tr::Transient] trait for any struct with at most 4
3//! lifetime parameters.
4use proc_macro::TokenStream;
5use proc_macro2::{Span, TokenStream as TokenStream2};
6use quote::{quote, ToTokens};
7use syn::spanned::Spanned;
8use syn::{parse_macro_input, parse_quote_spanned};
9use syn::{Data, DeriveInput, Generics, Ident, Lifetime};
10
11mod checks;
12mod options;
13mod utils;
14mod variance;
15
16use checks::ChecksModule;
17use options::TransientOptions;
18use utils::{extract_lifetimes, insert_static_predicates, TypeWithGenerics};
19use variance::{Variance, VarianceDeclarations, VarianceKind};
20
21// Maximum tuple length for which `Transience` is implemented
22const MAX_LIFETIMES: usize = 5;
23
24/// Derive macro that implements the  [`Transient`] trait for any struct.
25///
26/// Note that when this macro is applied to a `'static` struct with no lifetime
27/// parameters, it will instead implement the [`Static`] trait which results in
28/// a blanket impl of the `Transient` trait.
29///
30/// This macro is limited to structs satisfying the following conditions:
31/// - There must be at most 5 lifetime parameters. This is not a limitation of
32///   the derive macro, but in the tuple length for which the `Transience` trait
33///   is implemented.
34/// - There may be any number of type (or const) parameters, but the trait will
35///   only be implemented where `T: 'static` for each type parameter `T`.
36///
37/// # Customization
38///
39/// ## Variance
40/// By default, the [variance] of a deriving struct is assumed to be _invariant_
41/// respect to its lifetime parameter (if it has one), since this is the only
42/// type of variance that can be safely used for _all_ types without analyzing
43/// the behavior of its fields (which this macro does not attempt to do). When
44/// the added flexibility of _covariance_ or _contravariance_ is needed, the
45/// `covariant` and `contravariant` helper attributes can be used to override
46/// this default. When these attributes are used, a test module will be generated
47/// which defines a function for each lifetime that will fail to compile if the
48/// requested variance is not appropriate for the type.
49///
50/// One or more of these attributes may declared in the following forms:
51/// 1. `#[covariant]` or `#[contravariant]`
52/// 2. `#[covariant()]` or `#[contravariant()]`
53/// 3. `#[covariant(a)]` or `#[contravariant(a)]`
54/// 4. `#[covariant(a, b, c)]` or `#[contravariant(a, b, c)]`
55///
56/// Option 1 is a global declaration of the variance for ALL lifetimes, and must be
57/// the only declaration. Option 2 is a no-op, but still conflicts with the first
58/// option. Option 3 declares the variance for the type w.r.t. `'a`, and can coexist
59/// with other (non-global) declarations as long as there is no overlap. Option 4
60/// declares the variance w.r.t. each of the listed lifetimes, subject to the same
61/// rules as for option 3.
62///
63/// ## Crate path
64/// If it is desired that the `transient` crate's symbols be referenced from an
65/// alternate path in the generated impl, such as a re-export of the symbols in a
66/// downstream library, the `#[transient(crate = path::to::transient)]` attribute
67/// can be applied to the struct. By default, the path will be `::transient`.
68///
69/// # Failure modes
70/// This macro can fail for any of the following reasons:
71/// - The declared variance is not valid for the type. The compiler error generated
72///   in this case is not particularly helpful, but can be recognized by suggestions
73///   such as "help: consider adding the following bound: `'a: '__long`".
74/// - Requesting any variance for a type with no lifetime parameters
75/// - Requesting a variance for a lifetime that does not appear on the struct
76/// - Declaring a "global" variance in addition to a lifetime-specific variance
77/// - Providing more than one "variance" attribute for a lifetime parameter
78///
79/// # Example - struct with a type parameter and a lifetime parameter
80///
81/// Invocation:
82/// ```
83/// use transient::Transient;
84///
85/// #[derive(Debug, Clone, PartialEq, Eq, Transient)]
86/// struct S<'a, T> {
87///     value: &'a T
88/// }
89/// ```
90///
91/// Generated impl:
92/// ```
93/// # struct S<'a, T> {value: &'a T}
94/// unsafe impl<'a, T> ::transient::Transient for S<'a, T>
95/// where
96///     T: 'static,
97/// {
98///     type Static = S<'static, T>;
99///     type Transience = transient::Inv<'a>;
100/// }
101/// ```
102///
103/// # Example - struct with two lifetime parameters and a _covariance_ declaration
104///
105/// Invocation:
106/// ```
107/// use transient::Transient;
108///
109/// #[derive(Debug, Clone, PartialEq, Eq, Transient)]
110/// #[covariant(a)]
111/// struct S<'a, 'b> {
112///     name: &'a String,  // declared covariant
113///     values: &'b [i32], // no variance specified, defaults to invariant
114/// }
115/// # fn main() {}
116/// ```
117///
118/// Generated impl:
119/// ```
120/// # struct S<'a, 'b> {name: &'a String, values: &'b [i32]}
121/// unsafe impl<'a, 'b> ::transient::Transient for S<'a, 'b> {
122///     type Static = S<'static, 'static>;
123///     type Transience = (transient::Co<'a>, transient::Inv<'a>);
124/// }
125/// ```
126///
127/// This invocation also generates a test module, not shown above, including a
128/// function that will fail to compile if the declared variance is not correct
129/// for the type.
130///
131/// # Example - struct with a type parameter and `where` clause but no lifetimes
132///
133/// Invocation:
134/// ```
135/// use transient::Transient;
136///
137/// #[derive(Debug, Clone, PartialEq, Eq, Transient)]
138/// struct S<T> where T: Clone {
139///     value: T
140/// }
141/// ```
142///
143/// Generated impl:
144/// ```
145/// # struct S<T> where T: Clone {value: T}
146/// impl<T: 'static> transient::Static for S<T>
147/// where
148///     T: Clone + 'static,
149/// {}
150/// ```
151///
152/// [`Transient`]: ../transient/trait.Transient.html
153/// [`Static`]: ../transient/trait.Static.html
154/// [variance]: https://doc.rust-lang.org/nomicon/subtyping.html
155#[proc_macro_derive(Transient, attributes(transient, covariant, contravariant))]
156pub fn derive_transient(input: TokenStream) -> TokenStream {
157    let input = parse_macro_input!(input as DeriveInput);
158    match generate_impls(input) {
159        Ok(tokens) => tokens.into(),
160        Err(err) => err.into_compile_error().into(),
161    }
162}
163
164fn generate_impls(input: DeriveInput) -> Result<TokenStream2> {
165    if !matches!(input.data, Data::Struct(_)) {
166        return Err(Error::NotAStruct(input.ident.span()));
167    }
168    let options = TransientOptions::extract(&input.attrs)?;
169    let mut variance_decls = VarianceDeclarations::extract(&input.attrs)?;
170    let self_type = SelfType::new(input.ident, input.generics)?;
171    if self_type.is_static() {
172        variance_decls.ensure_empty()?;
173        Ok(StaticImpl(&self_type, &options).into_token_stream())
174    } else {
175        let static_type = self_type.get_static_type();
176        let transience = self_type.resolve_transience(variance_decls, &options)?;
177        let transient_impl = TransientImpl(&self_type, static_type, transience, &options);
178        let checks_module = ChecksModule::new(&transient_impl);
179        Ok(quote!(#transient_impl #checks_module))
180    }
181}
182
183/// Represents the `impl Static` block for a 'static type
184struct StaticImpl<'a>(&'a SelfType, &'a TransientOptions);
185
186impl<'a> ToTokens for StaticImpl<'a> {
187    fn to_tokens(&self, tokens: &mut TokenStream2) {
188        let StaticImpl(self_type, TransientOptions { krate }) = self;
189        let (impl_generics, _, where_clause) = self_type.generics.split_for_impl();
190        tokens.extend(quote!(
191            impl #impl_generics #krate::Static for #self_type #where_clause {}
192        ));
193    }
194}
195
196/// Represents the `impl Transient` block for a non-'static type
197struct TransientImpl<'a>(
198    &'a SelfType,
199    StaticType<'a>,
200    Transience<'a>,
201    &'a TransientOptions,
202);
203
204impl<'a> ToTokens for TransientImpl<'a> {
205    fn to_tokens(&self, tokens: &mut TokenStream2) {
206        let TransientImpl(self_type, static_type, transience, options) = self;
207        let krate = &options.krate;
208        let (impl_generics, _, where_clause) = self_type.generics.split_for_impl();
209        tokens.extend(quote!(
210            unsafe impl #impl_generics #krate::Transient for #self_type #where_clause {
211                type Static = #static_type;
212                type Transience = #transience;
213            }
214        ));
215    }
216}
217
218/// The target type implementing the `Static` or `Transient` trait
219struct SelfType {
220    name: Ident,
221    generics: Generics,
222    lifetimes: Vec<Lifetime>,
223}
224
225impl SelfType {
226    fn new(name: Ident, mut generics: Generics) -> Result<Self> {
227        insert_static_predicates(&mut generics);
228        let lifetimes = extract_lifetimes(&generics);
229        if lifetimes.len() > MAX_LIFETIMES {
230            let extra = lifetimes.into_iter().nth(MAX_LIFETIMES).unwrap();
231            return Err(Error::TooManyLifetimes(extra.span()));
232        }
233        Ok(SelfType { name, generics, lifetimes })
234    }
235    /// Query whether the type is 'static
236    fn is_static(&self) -> bool {
237        self.lifetimes.is_empty()
238    }
239    /// Get the `Static` associated type by replacing lifetimes with 'static
240    fn get_static_type(&self) -> StaticType<'_> {
241        let mut generics = self.generics.clone();
242        generics
243            .lifetimes_mut()
244            .for_each(|lt| *lt = parse_quote_spanned!(lt.span() => 'static));
245        StaticType { name: &self.name, generics }
246    }
247    /// Attempt to unify the lifetimes of the type and the variances declared in its
248    /// attributes to establish the variance with respect to each lifetime.
249    fn resolve_transience<'a>(
250        &'a self,
251        mut decls: VarianceDeclarations,
252        options: &'a TransientOptions,
253    ) -> Result<Transience<'a>> {
254        // pop a variance from the declarations for each lifetime or use the default
255        let variances = self
256            .lifetimes
257            .iter()
258            .map(|lt| decls.pop_variance(lt, options))
259            .collect::<Vec<_>>();
260        // check for remaining declarations that correspond to invalid lifetimes
261        decls.ensure_empty()?;
262        Ok(Transience(variances))
263    }
264}
265
266impl ToTokens for SelfType {
267    fn to_tokens(&self, tokens: &mut TokenStream2) {
268        let Self { name, generics, .. } = self;
269        let type_generics = generics.split_for_impl().1;
270        quote!(#name #type_generics).to_tokens(tokens);
271    }
272}
273
274/// The `Transient::Static` associated type
275type StaticType<'a> = TypeWithGenerics<'a>;
276
277/// The `Transient::Transience` associated type
278struct Transience<'a>(Vec<Variance<'a>>);
279
280impl<'a> ToTokens for Transience<'a> {
281    fn to_tokens(&self, tokens: &mut TokenStream2) {
282        let variances = &self.0;
283        if variances.len() == 1 {
284            variances[0].to_tokens(tokens);
285        } else {
286            quote!((#(#variances,)*)).to_tokens(tokens);
287        };
288    }
289}
290
291/// Collection of internal error conditions
292#[derive(Debug, thiserror::Error)]
293pub(crate) enum Error {
294    #[error(transparent)]
295    Syn(#[from] syn::Error),
296    #[error("Only `struct`'s are supported!")]
297    NotAStruct(Span),
298    #[error("At most {} lifetime parameters are allowed!", MAX_LIFETIMES)]
299    TooManyLifetimes(Span),
300    #[error("Duplicate variance specification! '{0}' replaced by '{1}'")]
301    DuplicateVariance(VarianceKind, VarianceKind),
302    #[error("Variance declared for an invalid lifetime `'{0}`!")]
303    UnexpectedLifetime(Ident),
304    #[error("Invalid option!")]
305    InvalidOption(Span),
306}
307
308pub(crate) type Result<T> = std::result::Result<T, Error>;
309
310impl Error {
311    fn into_compile_error(self) -> TokenStream2 {
312        syn::Error::from(self).into_compile_error()
313    }
314    fn span(&self) -> Span {
315        match self {
316            Self::Syn(err) => err.span(),
317            Self::DuplicateVariance(_, new) => new.span(),
318            Self::UnexpectedLifetime(lifetime) => lifetime.span(),
319            Self::NotAStruct(span) | Self::TooManyLifetimes(span) | Self::InvalidOption(span) => {
320                *span
321            }
322        }
323    }
324}
325
326impl From<Error> for syn::Error {
327    fn from(value: Error) -> Self {
328        match value {
329            Error::Syn(err) => err,
330            err => Self::new(err.span(), err.to_string()),
331        }
332    }
333}