truc/record/
type_resolver.rs

1//! Type resolution tools.
2
3use std::collections::{btree_map::Entry, BTreeMap};
4
5use serde::{Deserialize, Serialize};
6
7use crate::record::type_name::{truc_dynamic_type_name, truc_type_name};
8
9/// Type information (name, size and align) as given by the Rust compiler.
10#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
11pub struct TypeInfo {
12    /// The type name as it can be used in Rust code.
13    pub name: String,
14    /// The type size given by `std::mem::size_of()`.
15    pub size: usize,
16    /// The type size given by `std::mem::align_of()`.
17    pub align: usize,
18}
19
20/// Additional type information.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct DynamicTypeInfo {
23    /// Rust type information.
24    pub info: TypeInfo,
25    /// Indicates whether or not the type can be left safely uninitialized. Merely `Copy` types
26    /// can be left uninitialized, any other type cannot.
27    pub allow_uninit: bool,
28}
29
30/// Abstract type resolver trait.
31pub trait TypeResolver {
32    /// Gives the Rust type information for `T`.
33    fn type_info<T>(&self) -> TypeInfo;
34
35    /// Gives the dynamic type information `type_name`.
36    fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo;
37}
38
39impl<R> TypeResolver for &R
40where
41    R: TypeResolver,
42{
43    fn type_info<T>(&self) -> TypeInfo {
44        R::type_info::<T>(self)
45    }
46
47    fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo {
48        R::dynamic_type_info(self, type_name)
49    }
50}
51
52/// A type resolver that can give Rust type information only. Any call to
53/// [dynamic_type_info](HostTypeResolver::dynamic_type_info) will panic.
54pub struct HostTypeResolver;
55
56impl TypeResolver for HostTypeResolver {
57    /// Resolves the Rust type information by calling `std::mem::size_of()` and `std::mem::align_of()`.
58    fn type_info<T>(&self) -> TypeInfo {
59        TypeInfo {
60            name: truc_type_name::<T>(),
61            size: std::mem::size_of::<T>(),
62            align: std::mem::align_of::<T>(),
63        }
64    }
65
66    /// Panics!
67    fn dynamic_type_info(&self, _type_name: &str) -> DynamicTypeInfo {
68        unimplemented!("HostTypeResolver cannot resolve dynamic types")
69    }
70}
71
72macro_rules! add_type {
73    ($resolver:ident, $type:ty) => {
74        $resolver.add_type::<$type>();
75        $resolver.add_type::<Option<$type>>();
76    };
77    ($resolver:ident, $type:ty, allow_uninit) => {
78        $resolver.add_type_allow_uninit::<$type>();
79        $resolver.add_type_allow_uninit::<Option<$type>>();
80    };
81}
82macro_rules! add_type_and_arrays {
83    ($resolver:ident, $type:ty) => {
84        add_type!($resolver, $type);
85        add_type!($resolver, [$type; 1]);
86        add_type!($resolver, [$type; 2]);
87        add_type!($resolver, [$type; 3]);
88        add_type!($resolver, [$type; 4]);
89        add_type!($resolver, [$type; 5]);
90        add_type!($resolver, [$type; 6]);
91        add_type!($resolver, [$type; 7]);
92        add_type!($resolver, [$type; 8]);
93        add_type!($resolver, [$type; 9]);
94        add_type!($resolver, [$type; 10]);
95    };
96    ($resolver:ident, $type:ty, allow_uninit) => {
97        add_type!($resolver, $type, allow_uninit);
98        add_type!($resolver, [$type; 1], allow_uninit);
99        add_type!($resolver, [$type; 2], allow_uninit);
100        add_type!($resolver, [$type; 3], allow_uninit);
101        add_type!($resolver, [$type; 4], allow_uninit);
102        add_type!($resolver, [$type; 5], allow_uninit);
103        add_type!($resolver, [$type; 6], allow_uninit);
104        add_type!($resolver, [$type; 7], allow_uninit);
105        add_type!($resolver, [$type; 8], allow_uninit);
106        add_type!($resolver, [$type; 9], allow_uninit);
107        add_type!($resolver, [$type; 10], allow_uninit);
108    };
109}
110
111/// A type resolved that loads precomputed type information.
112///
113/// In addition to allowing a good level of customization, it is also very useful for
114/// cross-compilation:
115///
116/// * compute data by running the resolution on the target platform
117/// * serialize the data with `serde` to a file
118/// * deserialize the file in the project to be cross-compiled
119#[derive(Debug, From, Serialize, Deserialize)]
120pub struct StaticTypeResolver {
121    types: BTreeMap<String, DynamicTypeInfo>,
122}
123
124impl StaticTypeResolver {
125    /// Creates an empty resolver.
126    pub fn new() -> Self {
127        Self {
128            types: BTreeMap::new(),
129        }
130    }
131
132    /// Adds a single type information to the data.
133    pub fn add_type<T>(&mut self) {
134        let type_name = truc_type_name::<T>();
135        match self.types.entry(type_name.clone()) {
136            Entry::Vacant(vacant) => {
137                vacant.insert(DynamicTypeInfo {
138                    info: TypeInfo {
139                        name: type_name,
140                        size: std::mem::size_of::<T>(),
141                        align: std::mem::align_of::<T>(),
142                    },
143                    allow_uninit: false,
144                });
145            }
146            Entry::Occupied(occupied) => {
147                panic!(
148                    "Type {} is already defined with {:?}",
149                    type_name,
150                    occupied.get()
151                );
152            }
153        }
154    }
155
156    /// Adds a single `Copy` type information to the data.
157    pub fn add_type_allow_uninit<T>(&mut self)
158    where
159        T: Copy,
160    {
161        let type_name = truc_type_name::<T>();
162        match self.types.entry(type_name.clone()) {
163            Entry::Vacant(vacant) => {
164                vacant.insert(DynamicTypeInfo {
165                    info: TypeInfo {
166                        name: type_name,
167                        size: std::mem::size_of::<T>(),
168                        align: std::mem::align_of::<T>(),
169                    },
170                    allow_uninit: true,
171                });
172            }
173            Entry::Occupied(occupied) => {
174                panic!(
175                    "Type {} is already defined with {:?}",
176                    type_name,
177                    occupied.get()
178                );
179            }
180        }
181    }
182
183    /// Adds standard types to the data.
184    ///
185    /// It includes:
186    ///
187    /// * various types of integers and floating point numbers
188    /// * `String`
189    /// * `Box<str>`
190    /// * `Vec<()>` which is enough to support any kind of vector
191    pub fn add_std_types(&mut self) {
192        add_type_and_arrays!(self, u8, allow_uninit);
193        add_type_and_arrays!(self, u16, allow_uninit);
194        add_type_and_arrays!(self, u32, allow_uninit);
195        add_type_and_arrays!(self, u64, allow_uninit);
196        add_type_and_arrays!(self, u128, allow_uninit);
197        add_type_and_arrays!(self, usize, allow_uninit);
198
199        add_type_and_arrays!(self, i8, allow_uninit);
200        add_type_and_arrays!(self, i16, allow_uninit);
201        add_type_and_arrays!(self, i32, allow_uninit);
202        add_type_and_arrays!(self, i64, allow_uninit);
203        add_type_and_arrays!(self, i128, allow_uninit);
204        add_type_and_arrays!(self, isize, allow_uninit);
205
206        add_type_and_arrays!(self, f32, allow_uninit);
207        add_type_and_arrays!(self, f64, allow_uninit);
208
209        add_type_and_arrays!(self, char, allow_uninit);
210
211        add_type_and_arrays!(self, bool, allow_uninit);
212
213        add_type_and_arrays!(self, String);
214        add_type_and_arrays!(self, Box<str>);
215
216        add_type_and_arrays!(self, Vec<()>);
217    }
218
219    /// Adds `Uuid` types to the data.
220    #[cfg(feature = "uuid")]
221    pub fn add_uuid_types(&mut self) {
222        add_type_and_arrays!(self, uuid::Uuid, allow_uninit);
223    }
224
225    /// Adds all known types to the data.
226    ///
227    /// Basically all standard types plus feature gated types like `Uuid` types.
228    pub fn add_all_types(&mut self) {
229        self.add_std_types();
230        #[cfg(feature = "uuid")]
231        self.add_uuid_types();
232    }
233
234    /// Serialization to a `serde_json::Value`.
235    pub fn to_json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
236        serde_json::to_value(&self.types)
237    }
238
239    /// Serialization to a `String`.
240    pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
241        serde_json::to_string(&self.types)
242    }
243
244    /// Serialization to a `String` with pretty printing.
245    pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
246        serde_json::to_string_pretty(&self.types)
247    }
248}
249
250impl Default for StaticTypeResolver {
251    /// Creates an empty resolver.
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257impl TypeResolver for StaticTypeResolver {
258    /// Gives the Rust type information for `T` by looking up loaded data.
259    fn type_info<T>(&self) -> TypeInfo {
260        let type_name = truc_type_name::<T>();
261        self.types
262            .get(&type_name)
263            .unwrap_or_else(|| panic!("Could not resolve type {}", type_name))
264            .info
265            .clone()
266    }
267
268    /// Gives the dynamic type information  for `type_name` by looking up data.
269    fn dynamic_type_info(&self, type_name: &str) -> DynamicTypeInfo {
270        let type_name = truc_dynamic_type_name(type_name);
271        self.types
272            .get(&type_name)
273            .unwrap_or_else(|| panic!("Could not resolve type {}", type_name))
274            .clone()
275    }
276}
277
278#[cfg(test)]
279#[cfg_attr(coverage_nightly, coverage(off))]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_static_type_resolver() {
285        let mut type_infos = StaticTypeResolver::default();
286
287        type_infos.add_all_types();
288
289        let json = type_infos.to_json_value().unwrap();
290        type_infos.to_json_string().unwrap();
291        type_infos.to_json_string_pretty().unwrap();
292
293        let types = json.as_object().unwrap();
294
295        for t in [
296            "usize",
297            "isize",
298            "f32",
299            "f64",
300            "char",
301            "bool",
302            "String",
303            "Box < str >",
304            "Vec < () >",
305        ] {
306            assert!(types.contains_key(&t.to_owned()), "{}", t);
307            assert!(types.contains_key(&format!("[{} ; 2]", t)), "[{} ; 2]", t);
308            assert!(types.contains_key(&format!("[{} ; 10]", t)), "[{} ; 10]", t);
309        }
310
311        let name = assert_matches!(
312            type_infos.type_info::<usize>(),
313            TypeInfo {
314                name,
315                size: _,
316                align: _
317            } => name
318        );
319        assert_eq!(name, "usize");
320
321        let name = assert_matches!(
322            type_infos.dynamic_type_info("isize"),
323            DynamicTypeInfo {
324                info: TypeInfo {
325                    name,
326                    size: _,
327                    align: _
328                },
329                allow_uninit: true,
330            } => name
331        );
332        assert_eq!(name, "isize");
333
334        let name = assert_matches!(
335            type_infos.dynamic_type_info("String"),
336            DynamicTypeInfo {
337                info: TypeInfo {
338                    name,
339                    size: _,
340                    align: _
341                },
342                allow_uninit: false,
343            } => name
344        );
345        assert_eq!(name, "String");
346    }
347
348    #[test]
349    fn test_type_added_twice() {
350        let mut type_infos = StaticTypeResolver::default();
351
352        type_infos.add_type::<usize>();
353
354        let result = std::panic::catch_unwind(move || type_infos.add_type::<usize>());
355        assert!(result.is_err());
356    }
357
358    #[test]
359    fn test_type_added_twice_allow_uninit() {
360        let mut type_infos = StaticTypeResolver::default();
361
362        type_infos.add_type_allow_uninit::<usize>();
363
364        let result = std::panic::catch_unwind(move || type_infos.add_type_allow_uninit::<usize>());
365        assert!(result.is_err());
366    }
367}