1use std::fmt::{self, Display, Formatter};
4use std::ptr::{from_mut, from_ref};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct DowncastTypeKey(&'static str);
13
14impl DowncastTypeKey {
15 #[must_use]
21 pub const fn new(key: &'static str) -> Self {
22 assert!(!key.is_empty(), "downcast type keys cannot be empty");
23 Self(key)
24 }
25
26 #[must_use]
28 pub const fn as_str(self) -> &'static str {
29 self.0
30 }
31}
32
33impl Display for DowncastTypeKey {
34 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
35 formatter.write_str(self.0)
36 }
37}
38
39pub unsafe trait DowncastType: 'static {
56 const TYPE_KEY: DowncastTypeKey;
58}
59
60macro_rules! impl_steel_downcast_type {
61 ($type:ty, $key:literal) => {
62 unsafe impl DowncastType for $type {
65 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new($key);
66 }
67 };
68}
69
70impl_steel_downcast_type!((), "steel:rust_primitive/unit");
71impl_steel_downcast_type!(bool, "steel:rust_primitive/bool");
72impl_steel_downcast_type!(i32, "steel:rust_primitive/i32");
73impl_steel_downcast_type!(f32, "steel:rust_primitive/f32");
74impl_steel_downcast_type!(crate::Identifier, "steel:value/identifier");
75impl_steel_downcast_type!(text_components::TextComponent, "steel:value/text_component");
76
77mod private {
78 pub trait Sealed {}
79
80 impl<T: super::DowncastType> Sealed for T {}
81}
82
83pub trait ErasedType: private::Sealed {
88 #[doc(hidden)]
90 fn downcast_type_key(&self) -> DowncastTypeKey;
91
92 #[doc(hidden)]
94 fn downcast_data(&self) -> *const ();
95
96 #[doc(hidden)]
98 fn downcast_data_mut(&mut self) -> *mut ();
99}
100
101impl<T: DowncastType> ErasedType for T {
102 fn downcast_type_key(&self) -> DowncastTypeKey {
103 T::TYPE_KEY
104 }
105
106 fn downcast_data(&self) -> *const () {
107 from_ref(self).cast()
108 }
109
110 fn downcast_data_mut(&mut self) -> *mut () {
111 from_mut(self).cast()
112 }
113}
114
115pub trait Downcast: ErasedType {
117 #[must_use]
119 fn is<T: DowncastType>(&self) -> bool {
120 self.downcast_type_key() == T::TYPE_KEY
121 }
122
123 #[must_use]
125 fn downcast_ref<T: DowncastType>(&self) -> Option<&T> {
126 if !self.is::<T>() {
127 return None;
128 }
129
130 Some(unsafe { &*self.downcast_data().cast::<T>() })
134 }
135
136 #[must_use]
138 fn downcast_mut<T: DowncastType>(&mut self) -> Option<&mut T> {
139 if !self.is::<T>() {
140 return None;
141 }
142
143 Some(unsafe { &mut *self.downcast_data_mut().cast::<T>() })
147 }
148}
149
150impl<T: ErasedType + ?Sized> Downcast for T {}
151
152#[cfg(test)]
153mod tests {
154 use super::{Downcast as _, DowncastType, DowncastTypeKey, ErasedType};
155
156 struct First(u32);
157 struct Second;
158
159 unsafe impl DowncastType for First {
162 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:test/downcast/first");
163 }
164
165 unsafe impl DowncastType for Second {
168 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:test/downcast/second");
169 }
170
171 #[test]
172 fn downcasts_matching_shared_reference() {
173 let value = First(7);
174 let erased: &dyn ErasedType = &value;
175
176 assert!(erased.is::<First>());
177 assert!(!erased.is::<Second>());
178 assert_eq!(erased.downcast_ref::<First>().map(|first| first.0), Some(7));
179 assert!(erased.downcast_ref::<Second>().is_none());
180 }
181
182 #[test]
183 fn downcasts_matching_mutable_reference() {
184 let mut value = First(7);
185 let erased: &mut dyn ErasedType = &mut value;
186 let Some(first) = erased.downcast_mut::<First>() else {
187 panic!("matching type key should downcast");
188 };
189 first.0 = 11;
190
191 assert_eq!(value.0, 11);
192 }
193}