1use crate::{
2 attributes::{impl_custom_attribute_methods, CustomAttributes},
3 NamedField, UnnamedField,
4};
5use alloc::boxed::Box;
6use bevy_platform::collections::HashMap;
7use bevy_platform::sync::Arc;
8use core::slice::Iter;
9use thiserror::Error;
10
11#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
13pub enum VariantType {
14 Struct,
24 Tuple,
32 Unit,
40}
41
42#[derive(Debug, Error)]
44pub enum VariantInfoError {
45 #[error("variant type mismatch: expected {expected:?}, received {received:?}")]
49 TypeMismatch {
50 expected: VariantType,
52 received: VariantType,
54 },
55}
56
57#[derive(Clone, Debug)]
59pub enum VariantInfo {
60 Struct(StructVariantInfo),
70 Tuple(TupleVariantInfo),
78 Unit(UnitVariantInfo),
86}
87
88impl VariantInfo {
89 pub fn name(&self) -> &'static str {
91 match self {
92 Self::Struct(info) => info.name(),
93 Self::Tuple(info) => info.name(),
94 Self::Unit(info) => info.name(),
95 }
96 }
97
98 #[cfg(feature = "documentation")]
100 pub fn docs(&self) -> Option<&str> {
101 match self {
102 Self::Struct(info) => info.docs(),
103 Self::Tuple(info) => info.docs(),
104 Self::Unit(info) => info.docs(),
105 }
106 }
107
108 pub fn variant_type(&self) -> VariantType {
112 match self {
113 Self::Struct(_) => VariantType::Struct,
114 Self::Tuple(_) => VariantType::Tuple,
115 Self::Unit(_) => VariantType::Unit,
116 }
117 }
118
119 impl_custom_attribute_methods!(
120 self,
121 match self {
122 Self::Struct(info) => info.custom_attributes(),
123 Self::Tuple(info) => info.custom_attributes(),
124 Self::Unit(info) => info.custom_attributes(),
125 },
126 "variant"
127 );
128}
129
130macro_rules! impl_cast_method {
131 ($name:ident : $kind:ident => $info:ident) => {
132 #[doc = concat!("Attempts a cast to [`", stringify!($info), "`].")]
133 #[doc = concat!("\n\nReturns an error if `self` is not [`VariantInfo::", stringify!($kind), "`].")]
134 pub fn $name(&self) -> Result<&$info, VariantInfoError> {
135 match self {
136 Self::$kind(info) => Ok(info),
137 _ => Err(VariantInfoError::TypeMismatch {
138 expected: VariantType::$kind,
139 received: self.variant_type(),
140 }),
141 }
142 }
143 };
144}
145
146impl VariantInfo {
148 impl_cast_method!(as_struct_variant: Struct => StructVariantInfo);
149 impl_cast_method!(as_tuple_variant: Tuple => TupleVariantInfo);
150 impl_cast_method!(as_unit_variant: Unit => UnitVariantInfo);
151}
152
153#[derive(Clone, Debug)]
155pub struct StructVariantInfo {
156 name: &'static str,
157 fields: Box<[NamedField]>,
158 field_names: Box<[&'static str]>,
159 field_indices: HashMap<&'static str, usize>,
160 custom_attributes: Arc<CustomAttributes>,
161 #[cfg(feature = "documentation")]
162 docs: Option<&'static str>,
163}
164
165impl StructVariantInfo {
166 pub fn new(name: &'static str, fields: &[NamedField]) -> Self {
168 let field_indices = Self::collect_field_indices(fields);
169 let field_names = fields.iter().map(NamedField::name).collect();
170 Self {
171 name,
172 fields: fields.to_vec().into_boxed_slice(),
173 field_names,
174 field_indices,
175 custom_attributes: Arc::new(CustomAttributes::default()),
176 #[cfg(feature = "documentation")]
177 docs: None,
178 }
179 }
180
181 #[cfg(feature = "documentation")]
183 pub fn with_docs(self, docs: Option<&'static str>) -> Self {
184 Self { docs, ..self }
185 }
186
187 pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
189 Self {
190 custom_attributes: Arc::new(custom_attributes),
191 ..self
192 }
193 }
194
195 pub fn name(&self) -> &'static str {
197 self.name
198 }
199
200 pub fn field_names(&self) -> &[&'static str] {
202 &self.field_names
203 }
204
205 pub fn field(&self, name: &str) -> Option<&NamedField> {
207 self.field_indices
208 .get(name)
209 .map(|index| &self.fields[*index])
210 }
211
212 pub fn field_at(&self, index: usize) -> Option<&NamedField> {
214 self.fields.get(index)
215 }
216
217 pub fn index_of(&self, name: &str) -> Option<usize> {
219 self.field_indices.get(name).copied()
220 }
221
222 pub fn iter(&self) -> Iter<'_, NamedField> {
224 self.fields.iter()
225 }
226
227 pub fn field_len(&self) -> usize {
229 self.fields.len()
230 }
231
232 fn collect_field_indices(fields: &[NamedField]) -> HashMap<&'static str, usize> {
233 fields
234 .iter()
235 .enumerate()
236 .map(|(index, field)| (field.name(), index))
237 .collect()
238 }
239
240 #[cfg(feature = "documentation")]
242 pub fn docs(&self) -> Option<&'static str> {
243 self.docs
244 }
245
246 impl_custom_attribute_methods!(self.custom_attributes, "variant");
247}
248
249#[derive(Clone, Debug)]
251pub struct TupleVariantInfo {
252 name: &'static str,
253 fields: Box<[UnnamedField]>,
254 custom_attributes: Arc<CustomAttributes>,
255 #[cfg(feature = "documentation")]
256 docs: Option<&'static str>,
257}
258
259impl TupleVariantInfo {
260 pub fn new(name: &'static str, fields: &[UnnamedField]) -> Self {
262 Self {
263 name,
264 fields: fields.to_vec().into_boxed_slice(),
265 custom_attributes: Arc::new(CustomAttributes::default()),
266 #[cfg(feature = "documentation")]
267 docs: None,
268 }
269 }
270
271 #[cfg(feature = "documentation")]
273 pub fn with_docs(self, docs: Option<&'static str>) -> Self {
274 Self { docs, ..self }
275 }
276
277 pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
279 Self {
280 custom_attributes: Arc::new(custom_attributes),
281 ..self
282 }
283 }
284
285 pub fn name(&self) -> &'static str {
287 self.name
288 }
289
290 pub fn field_at(&self, index: usize) -> Option<&UnnamedField> {
292 self.fields.get(index)
293 }
294
295 pub fn iter(&self) -> Iter<'_, UnnamedField> {
297 self.fields.iter()
298 }
299
300 pub fn field_len(&self) -> usize {
302 self.fields.len()
303 }
304
305 #[cfg(feature = "documentation")]
307 pub fn docs(&self) -> Option<&'static str> {
308 self.docs
309 }
310
311 impl_custom_attribute_methods!(self.custom_attributes, "variant");
312}
313
314#[derive(Clone, Debug)]
316pub struct UnitVariantInfo {
317 name: &'static str,
318 custom_attributes: Arc<CustomAttributes>,
319 #[cfg(feature = "documentation")]
320 docs: Option<&'static str>,
321}
322
323impl UnitVariantInfo {
324 pub fn new(name: &'static str) -> Self {
326 Self {
327 name,
328 custom_attributes: Arc::new(CustomAttributes::default()),
329 #[cfg(feature = "documentation")]
330 docs: None,
331 }
332 }
333
334 #[cfg(feature = "documentation")]
336 pub fn with_docs(self, docs: Option<&'static str>) -> Self {
337 Self { docs, ..self }
338 }
339
340 pub fn with_custom_attributes(self, custom_attributes: CustomAttributes) -> Self {
342 Self {
343 custom_attributes: Arc::new(custom_attributes),
344 ..self
345 }
346 }
347
348 pub fn name(&self) -> &'static str {
350 self.name
351 }
352
353 #[cfg(feature = "documentation")]
355 pub fn docs(&self) -> Option<&'static str> {
356 self.docs
357 }
358
359 impl_custom_attribute_methods!(self.custom_attributes, "variant");
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::{Reflect, Typed};
366
367 #[test]
368 fn should_return_error_on_invalid_cast() {
369 #[derive(Reflect)]
370 enum Foo {
371 Bar,
372 }
373
374 let info = Foo::type_info().as_enum().unwrap();
375 let variant = info.variant_at(0).unwrap();
376 assert!(matches!(
377 variant.as_tuple_variant(),
378 Err(VariantInfoError::TypeMismatch {
379 expected: VariantType::Tuple,
380 received: VariantType::Unit
381 })
382 ));
383 }
384}