enum_dispatch/
cache.rs

1//! Procedural macros don't offer a good way to store information between macro invocations.  In
2//! addition, all syntax-related structures implement `!Send` and `!Sync`, making it impossible to
3//! keep them in any sort of static storage. This module uses some workarounds to add that
4//! functionality.
5//!
6//! Fortunately, `TokenStream`s can be converted to and from `String`s, which can be stored
7//! statically. Unfortunately, doing so strips any related `Span` information, preventing error
8//! messages from being as informative as they could be. For now, it seems this is the best option
9//! available.
10use quote::ToTokens;
11
12use once_cell::sync::Lazy;
13
14use std::collections::{HashMap, HashSet};
15use std::sync::Mutex;
16
17use crate::enum_dispatch_item;
18
19/// Uniquely identifies a trait or an enum. This is based on its name and number of arguments.
20#[derive(PartialEq, Eq, Hash, Clone)]
21struct UniqueItemId {
22    item_name: String,
23    num_generics: usize,
24}
25
26impl UniqueItemId {
27    /// Convenience constructor for UniqueItemId.
28    pub fn new(item_name: String, num_generics: usize) -> Self {
29        Self {
30            item_name,
31            num_generics,
32        }
33    }
34}
35
36// Magical storage for trait definitions so that they can be used when parsing other syntax
37// structures.
38static TRAIT_DEFS: Lazy<Mutex<HashMap<UniqueItemId, String>>> =
39    Lazy::new(|| Mutex::new(HashMap::new()));
40static ENUM_DEFS: Lazy<Mutex<HashMap<UniqueItemId, String>>> =
41    Lazy::new(|| Mutex::new(HashMap::new()));
42static DEFERRED_LINKS: Lazy<Mutex<HashMap<UniqueItemId, Vec<UniqueItemId>>>> =
43    Lazy::new(|| Mutex::new(HashMap::new()));
44static ENUM_CONVERSION_IMPLS_DEFS: Lazy<Mutex<HashSet<UniqueItemId>>> =
45    Lazy::new(|| Mutex::new(HashSet::new()));
46
47/// Store a trait definition for future reference.
48pub fn cache_trait(item: syn::ItemTrait) {
49    let num_generics = crate::supported_generics::num_supported_generics(&item.generics);
50    let uid = UniqueItemId::new(item.ident.to_string(), num_generics);
51    TRAIT_DEFS
52        .lock()
53        .unwrap()
54        .insert(uid, item.into_token_stream().to_string());
55}
56
57/// Store an enum definition for future reference.
58pub fn cache_enum_dispatch(item: enum_dispatch_item::EnumDispatchItem) {
59    let num_generics = crate::supported_generics::num_supported_generics(&item.generics);
60    let uid = UniqueItemId::new(item.ident.to_string(), num_generics);
61    ENUM_DEFS
62        .lock()
63        .unwrap()
64        .insert(uid, item.into_token_stream().to_string());
65}
66
67/// Store whether a From/TryInto definition has been defined once for an enum.
68pub fn cache_enum_conversion_impls_defined(item: syn::Ident, num_generics: usize) {
69    let uid = UniqueItemId::new(item.to_string(), num_generics);
70    ENUM_CONVERSION_IMPLS_DEFS.lock().unwrap().insert(uid);
71}
72
73/// Cache a "link" to be fulfilled once the needed definition is also cached.
74///
75/// The number of generic arguments is also cached and must be equal in order to fulfill a link,
76/// however the actual generic arguments themselves may have different names.
77pub fn defer_link(
78    (needed, needed_num_generics): (&::proc_macro2::Ident, usize),
79    (cached, cached_num_generics): (&::proc_macro2::Ident, usize),
80) {
81    use std::collections::hash_map::Entry;
82
83    let (needed, cached) = (
84        UniqueItemId::new(needed.to_string(), needed_num_generics),
85        UniqueItemId::new(cached.to_string(), cached_num_generics),
86    );
87    let mut deferred_links = DEFERRED_LINKS.lock().unwrap();
88    if deferred_links.contains_key(&needed) {
89        deferred_links
90            .get_mut(&needed)
91            .unwrap()
92            .push(cached.to_owned());
93    } else {
94        deferred_links.insert(needed.to_owned(), vec![cached.to_owned()]);
95    }
96    if let Entry::Vacant(e) = deferred_links.entry(cached.clone()) {
97        e.insert(vec![needed]);
98    } else {
99        deferred_links.get_mut(&cached).unwrap().push(needed);
100    }
101}
102
103/// Returns a list of all of the trait definitions that were previously linked to the supplied enum
104/// name.
105pub fn fulfilled_by_enum(
106    defname: &::proc_macro2::Ident,
107    num_generic_args: usize,
108) -> Vec<syn::ItemTrait> {
109    let idents = match DEFERRED_LINKS
110        .lock()
111        .unwrap()
112        .remove_entry(&UniqueItemId::new(defname.to_string(), num_generic_args))
113    {
114        Some((_, links)) => links,
115        None => vec![],
116    };
117    idents
118        .iter()
119        .filter_map(
120            |ident_string| TRAIT_DEFS.lock().unwrap().get(ident_string).map(|entry| syn::parse(entry.parse().unwrap()).unwrap())
121        )
122        .collect()
123}
124
125/// Returns a list of all of the enum definitions that were previously linked to the supplied trait
126/// name.
127pub fn fulfilled_by_trait(
128    defname: &::proc_macro2::Ident,
129    num_generic_args: usize,
130) -> Vec<enum_dispatch_item::EnumDispatchItem> {
131    let idents = match DEFERRED_LINKS
132        .lock()
133        .unwrap()
134        .remove_entry(&UniqueItemId::new(defname.to_string(), num_generic_args))
135    {
136        Some((_, links)) => links,
137        None => vec![],
138    };
139    idents
140        .iter()
141        .filter_map(
142            |ident_string| ENUM_DEFS.lock().unwrap().get(ident_string).map(|entry| syn::parse(entry.parse().unwrap()).unwrap())
143        )
144        .collect()
145}
146
147/// Returns true if From/TryInto was already defined for this enum
148pub fn conversion_impls_def_by_enum(item: &syn::Ident, num_generics: usize) -> bool {
149    ENUM_CONVERSION_IMPLS_DEFS
150        .lock()
151        .unwrap()
152        .contains(&UniqueItemId::new(item.to_string(), num_generics))
153}