replaceable_implementations/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(clippy::unwrap_used)]
3
4use counters::Counters;
5use proc_macro2::{Span, TokenStream};
6use quote::{ToTokens, quote};
7use std::env::VarError;
8use syn::{Ident, ItemImpl, Path};
9
10mod counters;
11
12static IMPLEMENTATION_COUNTERS: Counters = Counters::new();
13
14/// Due to orphan rules, we need to perform some setup.\
15/// This should be put in one spot that you know the path to.
16/// That could be the crate root, or a specific module that is passed into your macros. It is up to you.\
17/// The capacity is used to set the max number of implementations that can be replaced.
18#[must_use]
19pub fn setup(capacity: u32) -> TokenStream {
20    let mut output = quote! {
21        /// Self is the same type as T.
22        /// Used to bypass trivial bounds.
23        #[doc(hidden)]
24        pub trait Is<T> {}
25        impl<T> Is<T> for T {}
26    };
27
28    (0..capacity).for_each(|index| {
29        let ident = Ident::new(&format!("Switch{index}"), Span::call_site());
30        output.extend(quote! {
31            #[doc(hidden)]
32            pub struct #ident<T, const BOOL: bool>(core::marker::PhantomData<T>);
33        });
34    });
35    output
36}
37
38/// Provides an initial implementation.\
39/// Any other implementations will replace this one, no matter the execution order.
40/// 
41/// # Errors
42/// It will only error if there is something wrong with the `path_to_setup` or the `implementation`.
43pub fn initial_implementation(
44    path_to_setup: &Path,
45    implementation: ItemImpl,
46) -> Result<ItemImpl, syn::Error> {
47    make_replaceable(path_to_setup, 0, implementation)
48}
49
50/// Replaces the previous implementation marked by the `id` with a new implementation.\
51/// Returns the quantity of previous implementations as well as the function that will actually replace the implementation.
52/// 
53/// # Be Careful
54/// There is no guarantee on the execution order of procedural macros.\
55/// You must take care to make sure that your macros work in all execution orders.
56/// 
57/// # Errors
58/// It will only error if the crate name cannot be fetched from the environment variables.
59pub fn replace_implementation(
60    path_to_setup: &Path,
61    id: String,
62    has_initial_implementation: bool,
63) -> Result<(u16, impl FnOnce(ItemImpl) -> TokenStream), VarError> {
64    let previous_implementations =
65        IMPLEMENTATION_COUNTERS.fetch_add(id, has_initial_implementation.into())?;
66    let replace = move |implementation: ItemImpl| -> TokenStream {
67        let implementation =
68            match make_replaceable(path_to_setup, previous_implementations, implementation) {
69                Ok(implementation) => implementation,
70                Err(error) => return error.to_compile_error(),
71            };
72
73        // If there are no previous implementations, then we don't need to replace anything.
74        if previous_implementations == 0 {
75            implementation.to_token_stream()
76        } else {
77            let switch_previous = Ident::new(
78                &format!("Switch{}", previous_implementations - 1),
79                Span::call_site(),
80            );
81            quote! {
82                #implementation
83                impl<T> core::marker::Unpin for #path_to_setup::#switch_previous<T, false> {}
84            }
85        }
86    };
87    Ok((previous_implementations, replace))
88}
89
90fn make_replaceable(
91    path_to_setup: &Path,
92    previous_implementations: u16,
93    mut implementation: ItemImpl,
94) -> Result<ItemImpl, syn::Error> {
95    // Current is the previous value because the switch index starts from 0.
96    let switch_current = Ident::new(
97        &format!("Switch{previous_implementations}"),
98        Span::call_site(),
99    );
100
101    let kidnapped_type = &*implementation.self_ty;
102    let kidnapped = syn::parse2(quote! {
103        Kidnapped: #path_to_setup::Is<#kidnapped_type>
104    })?;
105
106    implementation
107        .generics
108        .params
109        .push(syn::GenericParam::Type(kidnapped));
110
111    *implementation.self_ty = syn::parse2(quote! {Kidnapped})?;
112
113    let predicate = syn::parse2(
114        quote! {#path_to_setup::#switch_current<Kidnapped, true>: core::marker::Unpin},
115    )?;
116    implementation
117        .generics
118        .make_where_clause()
119        .predicates
120        .push(predicate);
121
122    Ok(implementation)
123}