yash_env/
any.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2025 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Types for storing arbitrary data in the environment
18//!
19//! This module provides [`DataSet`] for storing arbitrary data in [`Env`].
20//! It internally uses [`Any`] to store data of arbitrary types.
21//!
22//! [`Env`]: crate::Env
23
24use dyn_clone::DynClone;
25use std::any::{Any, TypeId};
26use std::collections::HashMap;
27use std::fmt::Debug;
28
29/// Trait for data stored in [`DataSet`]
30trait Data: Any + DynClone {}
31
32dyn_clone::clone_trait_object!(Data);
33
34impl<T: Clone + 'static> Data for T {}
35
36/// Entry in the [`DataSet`]
37#[derive(Clone)]
38struct Entry(Box<dyn Data>);
39
40impl std::fmt::Debug for Entry {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("Entry").finish_non_exhaustive()
43    }
44}
45
46/// Collection of arbitrary data
47///
48/// This struct is used to store arbitrary data in the environment.
49/// Data stored in this struct are identified by their [`TypeId`], so you cannot
50/// store multiple data instances of the same type.
51#[derive(Clone, Debug, Default)]
52pub struct DataSet {
53    inner: HashMap<TypeId, Entry>,
54}
55
56impl DataSet {
57    /// Creates a new empty `DataSet`.
58    #[must_use]
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Inserts a new data into the `DataSet`.
64    ///
65    /// If data of the same type is already stored in `self`, it is replaced.
66    /// Returns the old data if it exists.
67    pub fn insert<T: Clone + 'static>(&mut self, data: Box<T>) -> Option<Box<T>> {
68        self.inner
69            .insert(TypeId::of::<T>(), Entry(data))
70            .map(|old| (old.0 as Box<dyn Any>).downcast().unwrap())
71    }
72
73    /// Obtains a reference to the data of the specified type.
74    #[must_use]
75    pub fn get<T: 'static>(&self) -> Option<&T> {
76        self.inner
77            .get(&TypeId::of::<T>())
78            .map(|entry| (&*entry.0 as &dyn Any).downcast_ref().unwrap())
79    }
80
81    /// Obtains a mutable reference to the data of the specified type.
82    #[must_use]
83    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
84        self.inner
85            .get_mut(&TypeId::of::<T>())
86            .map(|entry| (&mut *entry.0 as &mut dyn Any).downcast_mut().unwrap())
87    }
88
89    /// Obtains a reference to the data of the specified type, or inserts a new
90    /// data if it does not exist.
91    ///
92    /// If the data does not exist, the data is created by calling the provided
93    /// closure and inserted into the `DataSet`, and a reference to the data is
94    /// returned. If the data already exists, a reference to the existing data
95    /// is returned and the closure is not called.
96    pub fn get_or_insert_with<T, F>(&mut self, f: F) -> &mut T
97    where
98        T: Clone + 'static,
99        F: FnOnce() -> Box<T>,
100    {
101        let entry = self
102            .inner
103            .entry(TypeId::of::<T>())
104            .or_insert_with(|| Entry(f()));
105        (&mut *entry.0 as &mut dyn Any).downcast_mut().unwrap()
106    }
107
108    /// Removes the data of the specified type from the `DataSet`.
109    ///
110    /// Returns the data if it exists.
111    pub fn remove<T: 'static>(&mut self) -> Option<Box<T>> {
112        self.inner
113            .remove(&TypeId::of::<T>())
114            .map(|entry| (entry.0 as Box<dyn Any>).downcast().unwrap())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn insert_and_get() {
124        let mut data_set = DataSet::new();
125        let old = data_set.insert(Box::new(42i32));
126        assert_eq!(old, None);
127        assert_eq!(data_set.get::<i32>(), Some(&42));
128    }
129
130    #[test]
131    fn insert_again() {
132        let mut data_set = DataSet::new();
133        data_set.insert(Box::new(42i32));
134        let old = data_set.insert(Box::new(43i32));
135        assert_eq!(old, Some(Box::new(42)));
136        assert_eq!(data_set.get::<i32>(), Some(&43));
137    }
138
139    #[test]
140    fn get_mut() {
141        let mut data_set = DataSet::new();
142        data_set.insert(Box::new(42i32));
143        let data = data_set.get_mut::<i32>().unwrap();
144        assert_eq!(data, &42);
145        *data = 43;
146        assert_eq!(data_set.get::<i32>(), Some(&43));
147    }
148
149    #[test]
150    fn get_or_insert_with() {
151        let mut data_set = DataSet::new();
152        let data = data_set.get_or_insert_with(|| Box::new(0i8));
153        assert_eq!(data, &0);
154        *data = 1;
155        let data = data_set.get_or_insert_with::<i8, _>(|| unreachable!());
156        assert_eq!(data, &1);
157    }
158
159    #[test]
160    fn remove_existing() {
161        let mut data_set = DataSet::new();
162        data_set.insert(Box::new(42i32));
163        let data = data_set.remove::<i32>().unwrap();
164        assert_eq!(*data, 42);
165    }
166
167    #[test]
168    fn remove_nonexisting() {
169        let mut data_set = DataSet::new();
170        let data = data_set.remove::<i32>();
171        assert_eq!(data, None);
172    }
173
174    #[test]
175    fn clone() {
176        let mut data_set = DataSet::new();
177        data_set.insert::<i32>(Box::new(42));
178        let clone = data_set.clone();
179        assert_eq!(clone.get::<i32>(), Some(&42));
180    }
181}