mono_macro/
lib.rs

1//! Mono macro
2//! ==================
3//!
4//! This crate provides the `#[mono]` macro to force a generic function to be monomorphizied with given types.
5//!
6//! Pair with `share-generics` mode in rustc, this can result less code, for details see <https://github.com/rust-lang/rust/pull/48779>.
7//!
8//! ```toml
9//! [dependencies]
10//! mono-macro = "1.0"
11//! ```
12//!
13//! <br>
14//!
15//! ## Usage
16//!
17//! Since we are monomorphizing ourselves, you are required to spell out the static dispatch manually:
18//!
19//! In a bare function case,
20//! ```rust
21//! use mono_macro::mono;
22//! #[mono(T = i32, U = i64)]
23//! fn func<T, U>(t: T, u: U) {}
24//! ```
25//!
26//! it will be expanded to:
27//! ```rust
28//! pub const _: *const () = (&func::<i32, i64>) as *const _ as _;
29//! fn func<T, U>(t: T, u: U) {}
30//! ```
31//!
32//! For more complicated case, use `mono_macro!` instead:
33//! ```rust
34//! use mono_macro::mono_macro;
35//! trait Tr<T> {
36//!     fn foo(&self, _t: T) {}
37//! }
38//!
39//! struct Foo<'a> {
40//!     t: &'a str,
41//! }
42//!
43//! impl<'a, T> Tr<T> for Foo<'a> {
44//!     fn foo(&self, _t: T) {}
45//! }
46//!
47//! mono_macro!(<Foo<'static> as Tr<i32>>::foo);
48//! ```
49//!
50//! this will expand to:
51//! ```rust
52//! trait Tr<T> {
53//!     fn foo(&self, _t: T) {}
54//! }
55//!
56//! struct Foo<'a> {
57//!     t: &'a str,
58//! }
59//!
60//! impl<'a, T> Tr<T> for Foo<'a> {
61//!     fn foo(&self, _t: T) {}
62//! }
63//!
64//! pub const _: *const () = (&<Foo<'static> as Tr<i32>>::foo) as *const _ as _;
65//! ```
66
67use proc_macro::TokenStream;
68use quote::quote;
69use syn::parse::Parse;
70use syn::parse::ParseStream;
71use syn::parse_macro_input;
72use syn::punctuated::Punctuated;
73use syn::spanned::Spanned;
74use syn::GenericParam;
75use syn::Ident;
76use syn::ItemFn;
77use syn::Lifetime;
78use syn::Token;
79use syn::TypePath;
80
81/// Apply this macro on a generic function will cast the **bare function** pointer with given type into pointer, which forces this function to be monomorphized.
82///
83/// # Example
84/// ```rust,no_run
85/// use mono_macro::mono;
86/// #[mono(T = i32)]
87/// fn foo<T>(t: T) {}
88/// ```
89/// expands to:
90/// ```rust,no_run
91/// pub const _: *const () = &foo::<i32> as *const _ as _;
92/// fn foo<T>(t: T) {}
93/// ```
94///
95#[proc_macro_attribute]
96pub fn mono(attr: TokenStream, func: TokenStream) -> TokenStream {
97    let mono_attr = parse_macro_input!(attr as TypeEqs);
98
99    let input = func.clone();
100    let fn_sig = parse_macro_input!(input as ItemFn).sig;
101    let fn_span = fn_sig.span();
102    let func_ident = fn_sig.ident.clone();
103
104    let mut params = vec![];
105    for g in fn_sig.generics.params.into_iter() {
106        if let Some(t) = mono_attr
107            .eqs
108            .iter()
109            .find(|eq| match (&g, &eq.type_or_lifetime) {
110                // (GenericParam::Lifetime(ld), TypeOrLifetime::Lifetime(l)) => &ld.lifetime == l,
111                (GenericParam::Type(t1), TypeOrLifetime::Type(t2)) => &t1.ident == t2,
112                (_, _) => false,
113            })
114        {
115            params.push(t.param.clone());
116        } else if matches!(g, GenericParam::Type(_)) {
117            let err = syn::Error::new(fn_span, "all the type parameters should be spelled out")
118                .into_compile_error()
119                .into();
120            return err;
121        }
122    }
123
124    let mut expand = TokenStream::from(quote! {
125        pub const _: *const () = (&#func_ident::<#(#params,)*>) as *const _ as _;
126    });
127
128    expand.extend(func);
129    expand
130}
131
132/// Force monomorphizing on a path of function, for the complex functions like impl methods of generic types.
133/// For example,
134/// ```rust,no_run
135/// use mono_macro::mono_macro;
136/// struct Foo<T>(T);
137/// trait Trait<K> {
138///     fn method(&self, k: K);
139/// }
140/// impl<T, K> Trait<K> for Foo<T> {
141///     fn method(&self, k: K) {}
142/// }
143///
144/// mono_macro!(<Foo<i32> as Trait<u8>>::method);
145/// ```
146///
147/// this will expand to:
148/// ```rust,no_run
149/// use mono_macro::mono_macro;
150/// struct Foo<T>(T);
151/// trait Trait<K> {
152///     fn method(&self, k: K);
153/// }
154/// impl<T, K> Trait<K> for Foo<T> {
155///     fn method(&self, k: K) {}
156/// }
157/// pub const _: *const () = (&<Foo<i32> as Trait<u8>>::method) as *const _ as _;
158/// ```
159#[proc_macro]
160pub fn mono_macro(input: TokenStream) -> TokenStream {
161    let path = parse_macro_input!(input as TypePath);
162    TokenStream::from(quote! {
163        pub const _: *const () = (&#path) as *const _ as _;
164    })
165}
166
167// T = i32, U = i64
168struct TypeEqs {
169    eqs: Punctuated<TypeEqTo, Token![,]>,
170}
171
172impl Parse for TypeEqs {
173    fn parse(input: ParseStream) -> syn::Result<Self> {
174        Ok(TypeEqs {
175            eqs: { input.parse_terminated(TypeEqTo::parse)? },
176        })
177    }
178}
179
180// T = i32
181struct TypeEqTo {
182    type_or_lifetime: TypeOrLifetime,
183    #[allow(dead_code)]
184    eq_token: Token![=],
185    param: TypeOrLifetime,
186}
187
188impl Parse for TypeEqTo {
189    fn parse(input: ParseStream) -> syn::Result<Self> {
190        Ok(TypeEqTo {
191            type_or_lifetime: input.parse()?,
192            eq_token: input.parse()?,
193            param: input.parse()?,
194        })
195    }
196}
197
198#[derive(Clone, Debug)]
199enum TypeOrLifetime {
200    Type(Ident),
201    Lifetime(syn::Lifetime),
202}
203
204impl Parse for TypeOrLifetime {
205    fn parse(input: ParseStream) -> syn::Result<Self> {
206        let lookahead = input.lookahead1();
207        if lookahead.peek(Lifetime) {
208            input.parse().map(TypeOrLifetime::Lifetime)
209        } else if lookahead.peek(Ident) {
210            input.parse().map(TypeOrLifetime::Type)
211        } else {
212            Err(lookahead.error())
213        }
214    }
215}
216
217extern crate proc_macro2;
218use proc_macro2::TokenStream as TokenStream2;
219impl quote::ToTokens for TypeOrLifetime {
220    fn to_tokens(&self, tokens: &mut TokenStream2) {
221        match self {
222            TypeOrLifetime::Lifetime(l) => l.to_tokens(tokens),
223            TypeOrLifetime::Type(t) => t.to_tokens(tokens),
224        }
225    }
226}