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;
1112use once_cell::sync::Lazy;
1314use std::collections::{HashMap, HashSet};
15use std::sync::Mutex;
1617use crate::enum_dispatch_item;
1819/// 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}
2526impl UniqueItemId {
27/// Convenience constructor for UniqueItemId.
28pub fn new(item_name: String, num_generics: usize) -> Self {
29Self {
30 item_name,
31 num_generics,
32 }
33 }
34}
3536// 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()));
4647/// Store a trait definition for future reference.
48pub fn cache_trait(item: syn::ItemTrait) {
49let num_generics = crate::supported_generics::num_supported_generics(&item.generics);
50let 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}
5657/// Store an enum definition for future reference.
58pub fn cache_enum_dispatch(item: enum_dispatch_item::EnumDispatchItem) {
59let num_generics = crate::supported_generics::num_supported_generics(&item.generics);
60let 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}
6667/// 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) {
69let uid = UniqueItemId::new(item.to_string(), num_generics);
70 ENUM_CONVERSION_IMPLS_DEFS.lock().unwrap().insert(uid);
71}
7273/// 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) {
81use std::collections::hash_map::Entry;
8283let (needed, cached) = (
84 UniqueItemId::new(needed.to_string(), needed_num_generics),
85 UniqueItemId::new(cached.to_string(), cached_num_generics),
86 );
87let mut deferred_links = DEFERRED_LINKS.lock().unwrap();
88if 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 }
96if 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}
102103/// 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> {
109let idents = match DEFERRED_LINKS
110 .lock()
111 .unwrap()
112 .remove_entry(&UniqueItemId::new(defname.to_string(), num_generic_args))
113 {
114Some((_, links)) => links,
115None => 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}
124125/// 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> {
131let idents = match DEFERRED_LINKS
132 .lock()
133 .unwrap()
134 .remove_entry(&UniqueItemId::new(defname.to_string(), num_generic_args))
135 {
136Some((_, links)) => links,
137None => 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}
146147/// 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}