diff --git a/crates/bevy_reflect/src/path/access.rs b/crates/bevy_reflect/src/path/access.rs index c0a141fcba..5d40b3b1f2 100644 --- a/crates/bevy_reflect/src/path/access.rs +++ b/crates/bevy_reflect/src/path/access.rs @@ -6,7 +6,7 @@ use core::fmt; use super::error::AccessErrorKind; use crate::{AccessError, PartialReflect, ReflectKind, ReflectMut, ReflectRef, VariantType}; -type InnerResult = Result; +type InnerResult<'a, T> = Result>; /// A singular element access within a path. /// Multiple accesses can be combined into a [`ParsedPath`](super::ParsedPath). @@ -60,49 +60,94 @@ impl<'a> Access<'a> { .map_err(|err| err.with_access(self.clone(), offset)) } + #[inline] + fn try_parse_field_as_index( + base: impl Fn() -> ReflectKind, + field: Cow<'a, str>, + ) -> Result> { + field + .parse() + .map_err(|_| AccessErrorKind::UnsupportedAccess { + base: base(), + access: Access::Field(field), + }) + } + fn element_inner<'r>( &self, base: &'r dyn PartialReflect, - ) -> InnerResult> { + ) -> InnerResult<'a, Option<&'r dyn PartialReflect>> { use ReflectRef::*; let invalid_variant = |expected, actual| AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual }; - match (self, base.reflect_ref()) { - (Self::Field(field), Struct(struct_ref)) => Ok(struct_ref.field(field.as_ref())), - (Self::Field(field), Enum(enum_ref)) => match enum_ref.variant_type() { + match (base.reflect_ref(), self) { + // Struct + (Struct(struct_ref), Access::Field(field)) => Ok(struct_ref.field(field.as_ref())), + (Struct(struct_ref), &Access::FieldIndex(index)) => Ok(struct_ref.field_at(index)), + // Tuple Struct + (TupleStruct(struct_ref), Access::Field(field)) => Ok(struct_ref.field( + Self::try_parse_field_as_index(|| struct_ref.reflect_kind(), field.clone())?, + )), + ( + TupleStruct(struct_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(struct_ref.field(index)), + // Tuple + (Tuple(tuple_ref), Access::Field(field)) => Ok(tuple_ref.field( + Self::try_parse_field_as_index(|| tuple_ref.reflect_kind(), field.clone())?, + )), + ( + Tuple(tuple_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(tuple_ref.field(index)), + // List + (List(list_ref), Access::Field(field)) => Ok(list_ref.get( + Self::try_parse_field_as_index(|| list_ref.reflect_kind(), field.clone())?, + )), + ( + List(list_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(list_ref.get(index)), + // Array + (Array(array_ref), Access::Field(field)) => Ok(array_ref.get( + Self::try_parse_field_as_index(|| array_ref.reflect_kind(), field.clone())?, + )), + ( + Array(array_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(array_ref.get(index)), + // Map + (Map(map_ref), Access::Field(field)) => Ok(map_ref.get(&field.clone().into_owned())), + ( + Map(map_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(map_ref.get(&index)), + // Set + (Set(set_ref), Access::Field(field)) => Ok(set_ref.get(&field.clone().into_owned())), + ( + Set(set_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(set_ref.get(&index)), + // Enum + (Enum(enum_ref), Access::Field(field)) => match enum_ref.variant_type() { VariantType::Struct => Ok(enum_ref.field(field.as_ref())), actual => Err(invalid_variant(VariantType::Struct, actual)), }, - (&Self::FieldIndex(index), Struct(struct_ref)) => Ok(struct_ref.field_at(index)), - (&Self::FieldIndex(index), Enum(enum_ref)) => match enum_ref.variant_type() { + (Enum(enum_ref), &Access::FieldIndex(index)) => match enum_ref.variant_type() { VariantType::Struct => Ok(enum_ref.field_at(index)), actual => Err(invalid_variant(VariantType::Struct, actual)), }, - (Self::Field(_) | Self::FieldIndex(_), actual) => { - Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::Struct, - actual: actual.into(), - }) + (Enum(enum_ref), &Access::TupleIndex(index) | &Access::ListIndex(index)) => { + match enum_ref.variant_type() { + VariantType::Tuple => Ok(enum_ref.field_at(index)), + actual => Err(invalid_variant(VariantType::Tuple, actual)), + } } - - (&Self::TupleIndex(index), TupleStruct(tuple)) => Ok(tuple.field(index)), - (&Self::TupleIndex(index), Tuple(tuple)) => Ok(tuple.field(index)), - (&Self::TupleIndex(index), Enum(enum_ref)) => match enum_ref.variant_type() { - VariantType::Tuple => Ok(enum_ref.field_at(index)), - actual => Err(invalid_variant(VariantType::Tuple, actual)), - }, - (Self::TupleIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::Tuple, - actual: actual.into(), - }), - - (&Self::ListIndex(index), List(list)) => Ok(list.get(index)), - (&Self::ListIndex(index), Array(list)) => Ok(list.get(index)), - (Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::List, - actual: actual.into(), + (other, access) => Err(AccessErrorKind::UnsupportedAccess { + base: other.kind(), + access: access.clone().into_owned(), }), } } @@ -116,52 +161,90 @@ impl<'a> Access<'a> { self.element_inner_mut(base) .and_then(|maybe| maybe.ok_or(AccessErrorKind::MissingField(kind))) - .map_err(|err| err.with_access(self.clone(), offset)) + .map_err(move |err| err.with_access(self.clone(), offset)) } fn element_inner_mut<'r>( &self, base: &'r mut dyn PartialReflect, - ) -> InnerResult> { + ) -> InnerResult<'a, Option<&'r mut dyn PartialReflect>> { use ReflectMut::*; let invalid_variant = |expected, actual| AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual }; - match (self, base.reflect_mut()) { - (Self::Field(field), Struct(struct_mut)) => Ok(struct_mut.field_mut(field.as_ref())), - (Self::Field(field), Enum(enum_mut)) => match enum_mut.variant_type() { - VariantType::Struct => Ok(enum_mut.field_mut(field.as_ref())), - actual => Err(invalid_variant(VariantType::Struct, actual)), - }, - (&Self::FieldIndex(index), Struct(struct_mut)) => Ok(struct_mut.field_at_mut(index)), - (&Self::FieldIndex(index), Enum(enum_mut)) => match enum_mut.variant_type() { - VariantType::Struct => Ok(enum_mut.field_at_mut(index)), - actual => Err(invalid_variant(VariantType::Struct, actual)), - }, - (Self::Field(_) | Self::FieldIndex(_), actual) => { - Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::Struct, - actual: actual.into(), - }) + match (base.reflect_mut(), self) { + // Struct + (Struct(struct_ref), Access::Field(field)) => Ok(struct_ref.field_mut(field.as_ref())), + ( + Struct(struct_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(struct_ref.field_at_mut(index)), + // Tuple Struct + (TupleStruct(struct_ref), Access::Field(field)) => Ok(struct_ref.field_mut( + Self::try_parse_field_as_index(|| struct_ref.reflect_kind(), field.clone())?, + )), + ( + TupleStruct(struct_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(struct_ref.field_mut(index)), + // Tuple + (Tuple(tuple_ref), Access::Field(field)) => Ok(tuple_ref.field_mut( + Self::try_parse_field_as_index(|| tuple_ref.reflect_kind(), field.clone())?, + )), + ( + Tuple(tuple_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(tuple_ref.field_mut(index)), + // List + (List(list_ref), Access::Field(field)) => Ok(list_ref.get_mut( + Self::try_parse_field_as_index(|| list_ref.reflect_kind(), field.clone())?, + )), + ( + List(list_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(list_ref.get_mut(index)), + // Array + (Array(array_ref), Access::Field(field)) => Ok(array_ref.get_mut( + Self::try_parse_field_as_index(|| array_ref.reflect_kind(), field.clone())?, + )), + ( + Array(array_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(array_ref.get_mut(index)), + // Map + (Map(map_ref), Access::Field(field)) => { + Ok(map_ref.get_mut(&field.clone().into_owned())) } - - (&Self::TupleIndex(index), TupleStruct(tuple)) => Ok(tuple.field_mut(index)), - (&Self::TupleIndex(index), Tuple(tuple)) => Ok(tuple.field_mut(index)), - (&Self::TupleIndex(index), Enum(enum_mut)) => match enum_mut.variant_type() { - VariantType::Tuple => Ok(enum_mut.field_at_mut(index)), - actual => Err(invalid_variant(VariantType::Tuple, actual)), + ( + Map(map_ref), + &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + ) => Ok(map_ref.get_mut(&index)), + // Set - no get_mut + // (Set(set_ref), Access::Field(field)) => Ok(set_ref.get(&field.clone().into_owned())), + // ( + // Set(set_ref), + // &Access::FieldIndex(index) | &Access::TupleIndex(index) | &Access::ListIndex(index), + // ) => Ok(set_ref.get(&index)), + // Enum + // Enum + (Enum(enum_ref), Access::Field(field)) => match enum_ref.variant_type() { + VariantType::Struct => Ok(enum_ref.field_mut(field.as_ref())), + actual => Err(invalid_variant(VariantType::Struct, actual)), }, - (Self::TupleIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::Tuple, - actual: actual.into(), - }), - - (&Self::ListIndex(index), List(list)) => Ok(list.get_mut(index)), - (&Self::ListIndex(index), Array(list)) => Ok(list.get_mut(index)), - (Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes { - expected: ReflectKind::List, - actual: actual.into(), + (Enum(enum_ref), &Access::FieldIndex(index)) => match enum_ref.variant_type() { + VariantType::Struct => Ok(enum_ref.field_at_mut(index)), + actual => Err(invalid_variant(VariantType::Struct, actual)), + }, + (Enum(enum_ref), &Access::TupleIndex(index) | &Access::ListIndex(index)) => { + match enum_ref.variant_type() { + VariantType::Tuple => Ok(enum_ref.field_at_mut(index)), + actual => Err(invalid_variant(VariantType::Tuple, actual)), + } + } + (other, access) => Err(AccessErrorKind::UnsupportedAccess { + base: other.kind(), + access: access.clone().into_owned(), }), } } diff --git a/crates/bevy_reflect/src/path/error.rs b/crates/bevy_reflect/src/path/error.rs index 00188a4cc3..06075acfcb 100644 --- a/crates/bevy_reflect/src/path/error.rs +++ b/crates/bevy_reflect/src/path/error.rs @@ -5,20 +5,11 @@ use crate::{ReflectKind, VariantType}; /// The kind of [`AccessError`], along with some kind-specific information. #[derive(Debug, PartialEq, Eq, Clone)] -pub enum AccessErrorKind { +pub enum AccessErrorKind<'a> { /// An error that occurs when a certain type doesn't /// contain the value referenced by the [`Access`]. MissingField(ReflectKind), - /// An error that occurs when using an [`Access`] on the wrong type. - /// (i.e. a [`ListIndex`](Access::ListIndex) on a struct, or a [`TupleIndex`](Access::TupleIndex) on a list) - IncompatibleTypes { - /// The [`ReflectKind`] that was expected based on the [`Access`]. - expected: ReflectKind, - /// The actual [`ReflectKind`] that was found. - actual: ReflectKind, - }, - /// An error that occurs when using an [`Access`] on the wrong enum variant. /// (i.e. a [`ListIndex`](Access::ListIndex) on a struct variant, or a [`TupleIndex`](Access::TupleIndex) on a unit variant) IncompatibleEnumVariantTypes { @@ -27,10 +18,25 @@ pub enum AccessErrorKind { /// The actual [`VariantType`] that was found. actual: VariantType, }, + + /// Occurs when an [`Access`] is attempted on a type that does not support it. + UnsupportedAccess { + /// The [`ReflectKind`] that was attempted to be accessed. + base: ReflectKind, + /// The [`Access`] that was attempted. + access: Access<'a>, + }, } -impl AccessErrorKind { - pub(super) fn with_access(self, access: Access, offset: Option) -> AccessError { +impl AccessErrorKind<'_> { + pub(super) fn with_access<'a>( + self, + access: Access<'a>, + offset: Option, + ) -> AccessError<'a> + where + Self: 'a, + { AccessError { kind: self, access, @@ -52,7 +58,7 @@ impl AccessErrorKind { /// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct AccessError<'a> { - pub(super) kind: AccessErrorKind, + pub(super) kind: AccessErrorKind<'a>, pub(super) access: Access<'a>, pub(super) offset: Option, } @@ -113,11 +119,11 @@ impl fmt::Display for AccessError<'_> { access.display_value() ) } - } - AccessErrorKind::IncompatibleTypes { expected, actual } => write!( + }, + AccessErrorKind::UnsupportedAccess { base, access } => write!( f, - "Expected {} access to access a {expected}, found a {actual} instead.", - access.kind() + "Tried to access {base} with {} access: '{access}'", + access.kind(), ), AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual } => write!( f, diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index f0434686ee..a8417b3f2d 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -520,6 +520,7 @@ impl core::ops::IndexMut for ParsedPath { mod tests { use super::*; use crate::*; + use alloc::string::String; use alloc::vec; #[derive(Reflect, PartialEq, Debug)] @@ -533,6 +534,8 @@ mod tests { struct_variant: F, array: [i32; 3], tuple: (bool, f32), + string_map: std::collections::HashMap, + usize_map: std::collections::HashMap, } #[derive(Reflect, PartialEq, Debug)] @@ -573,6 +576,10 @@ mod tests { struct_variant: F::Şķràźÿ { 東京: 'm' }, array: [86, 75, 309], tuple: (true, 1.23), + string_map: vec![("foo".into(), "bar".into()), ("baz".into(), "qux".into())] + .into_iter() + .collect(), + usize_map: vec![(0, 1), (1, 2), (2, 3)].into_iter().collect(), } } @@ -589,14 +596,19 @@ mod tests { type StaticError = ReflectPathError<'static>; - fn invalid_access( - offset: usize, - actual: ReflectKind, - expected: ReflectKind, - access: &'static str, - ) -> StaticError { + fn invalid_access(offset: usize, actual: ReflectKind, access: &'static str) -> StaticError { ReflectPathError::InvalidAccess(AccessError { - kind: AccessErrorKind::IncompatibleTypes { actual, expected }, + kind: AccessErrorKind::UnsupportedAccess { + base: actual, + access: ParsedPath::parse_static(access) + .unwrap() + .0 + .into_iter() + .last() + .unwrap() + .access + .clone(), + }, access: ParsedPath::parse_static(access).unwrap()[1].access.clone(), offset: Some(offset), }) @@ -755,6 +767,8 @@ mod tests { assert_eq!(*a.path::("x.łørđ.mосква").unwrap(), 3.14); assert_eq!(*a.path::("y[1].mосква").unwrap(), 2.0); assert_eq!(*a.path::("z.0.1").unwrap(), 42); + assert_eq!(*a.path::("z.0[1]").unwrap(), 42); + assert_eq!(*a.path::("z[0][1]").unwrap(), 42); assert_eq!(*a.path::("x#0").unwrap(), 10); assert_eq!(*a.path::("x#1#0").unwrap(), 3.14); @@ -764,6 +778,7 @@ mod tests { assert_eq!(*a.path::("struct_variant#0").unwrap(), 'm'); assert_eq!(*a.path::("array[2]").unwrap(), 309); + assert_eq!(*a.path::("array.2").unwrap(), 309); assert_eq!(*a.path::("tuple.1").unwrap(), 1.23); *a.path_mut::("tuple.1").unwrap() = 3.21; @@ -775,6 +790,23 @@ mod tests { *a.path_mut::("tuple_variant.0").unwrap() = 1337; assert_eq!(a.tuple_variant, F::Tuple(1337, 321)); + assert_eq!( + *a.path::("string_map.foo").unwrap(), + String::from("bar") + ); + assert_eq!( + *a.path::("string_map.baz").unwrap(), + String::from("qux") + ); + + assert_eq!(*a.path::("usize_map[0]").unwrap(), 1); + assert_eq!(*a.path::("usize_map[1]").unwrap(), 2); + assert_eq!(*a.path::("usize_map[2]").unwrap(), 3); + + assert_eq!(*a.path::("usize_map.0").unwrap(), 1); + assert_eq!(*a.path::("usize_map.1").unwrap(), 2); + assert_eq!(*a.path::("usize_map.2").unwrap(), 3); + assert_eq!( a.reflect_path("x.notreal").err().unwrap(), ReflectPathError::InvalidAccess(AccessError { @@ -799,11 +831,11 @@ mod tests { ); assert_eq!( a.reflect_path("x[0]").err().unwrap(), - invalid_access(2, ReflectKind::Struct, ReflectKind::List, "x[0]") + invalid_access(2, ReflectKind::Struct, "x[0]") ); assert_eq!( a.reflect_path("y.x").err().unwrap(), - invalid_access(2, ReflectKind::List, ReflectKind::Struct, "y.x") + invalid_access(2, ReflectKind::List, "y.x") ); }