1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use syn::{Generics, parse_quote, Token, Type};
use syn::punctuated::Punctuated;

pub trait ToPhantom {
    /// Creates a [`PhantomData`] based on `Self`.
    ///
    /// The `PhantomData` will be `Send` and `Sync`.
    ///
    /// The resulting `Type` will look something like:
    ///
    /// ```
    /// # use std::marker::PhantomData;
    /// # fn demo<'a, 'b, T, U>() {
    /// # let _phantom:
    /// PhantomData<(fn(&'a (), &'b ()) -> (T, U))>
    /// # ;
    /// # }
    /// ```
    ///
    /// [`PhantomData`]: core::marker::PhantomData
    fn to_phantom(&self) -> Type;
}

impl ToPhantom for Generics {
    fn to_phantom(&self) -> Type {
        let lifetimes = self.lifetimes().map(|param| {
            &param.lifetime
        }).collect::<Vec<_>>();

        let types = self.type_params().map(|param| {
            &param.ident
        }).collect::<Punctuated<_, Token![,]>>();

        parse_quote!(::core::marker::PhantomData<fn(#(&#lifetimes ()),*) -> (#types)>)
    }
}

#[cfg(test)]
mod tests {
    use quote::ToTokens;
    use syn::{Generics, parse_quote};
    use crate::ToPhantom;

    #[test]
    fn maps_empty_generics() {
        let generics: Generics = parse_quote!(<>);
        let phantom = generics.to_phantom();
        let output = phantom.to_token_stream().to_string();
        assert_eq!(":: core :: marker :: PhantomData < fn () -> () >", output);
    }

    #[test]
    fn maps_lifetimes_generics() {
        let generics: Generics = parse_quote!(<'a, 'b>);
        let phantom = generics.to_phantom();
        let output = phantom.to_token_stream().to_string();
        assert_eq!(":: core :: marker :: PhantomData < fn (& 'a () , & 'b ()) -> () >", output);
    }

    #[test]
    fn maps_type_generics() {
        let generics: Generics = parse_quote!(<T, U>);
        let phantom = generics.to_phantom();
        let output = phantom.to_token_stream().to_string();
        assert_eq!(":: core :: marker :: PhantomData < fn () -> (T , U) >", output);
    }

    #[test]
    fn maps_const_type_generics() {
        let generics: Generics = parse_quote!(<const T: usize, const U: usize>);
        let phantom = generics.to_phantom();
        let output = phantom.to_token_stream().to_string();
        assert_eq!(":: core :: marker :: PhantomData < fn () -> () >", output);
    }

    #[test]
    fn maps_all_generics() {
        let generics: Generics = parse_quote!(<'a, 'b: 'a, T: Default, const N: usize>);
        let phantom = generics.to_phantom();
        let output = phantom.to_token_stream().to_string();
        assert_eq!(":: core :: marker :: PhantomData < fn (& 'a () , & 'b ()) -> (T) >", output);
    }
}