mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 23:24:44 +00:00
Add the ability to manually create ParsedPaths (+ cleanup) (#11029)
# Objective I'm working on a developer console plugin, and I wanted to get a field/index of a struct/list/tuple. My command parser already parses member expressions and all that, so I wanted to construct a `ParsedPath` manually, but it's all private. ## Solution Make the internals of `ParsedPath` public and add documentation for everything, and I changed the boxed slice inside `ParsedPath` to a vector for more flexibility. I also did a bunch of code cleanup. Improving documentation, error messages, code, type names, etc. --- ## Changelog - Added the ability to manually create `ParsedPath`s from their elements, without the need of string parsing. - Improved `ReflectPath` error handling. ## Migration Guide - `bevy::reflect::AccessError` has been refactored. That should be it I think, everything else that was changed was private before this PR. --------- Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
This commit is contained in:
parent
d30fdda2c3
commit
b1a2d342af
4 changed files with 546 additions and 242 deletions
|
@ -1,115 +1,25 @@
|
|||
//! Representation for individual element accesses within a path.
|
||||
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use super::{AccessError, ReflectPathError};
|
||||
use crate::{Reflect, ReflectMut, ReflectRef, VariantType};
|
||||
use thiserror::Error;
|
||||
use super::error::{AccessErrorKind, TypeKind};
|
||||
use crate::{AccessError, Reflect, ReflectMut, ReflectRef, VariantType};
|
||||
|
||||
type InnerResult<T> = Result<Option<T>, Error<'static>>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub(super) enum Error<'a> {
|
||||
#[error(
|
||||
"the current {ty} doesn't have the {} {}",
|
||||
access.kind(),
|
||||
access.display_value(),
|
||||
)]
|
||||
Access { ty: TypeShape, access: Access<'a> },
|
||||
|
||||
#[error("invalid type shape: expected {expected} but found a reflect {actual}")]
|
||||
Type {
|
||||
expected: TypeShape,
|
||||
actual: TypeShape,
|
||||
},
|
||||
|
||||
#[error("invalid enum access: expected {expected} variant but found {actual} variant")]
|
||||
Enum {
|
||||
expected: TypeShape,
|
||||
actual: TypeShape,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Error<'a> {
|
||||
fn with_offset(self, offset: usize) -> ReflectPathError<'a> {
|
||||
let error = AccessError(self);
|
||||
ReflectPathError::InvalidAccess { offset, error }
|
||||
}
|
||||
|
||||
fn access(ty: TypeShape, access: Access<'a>) -> Self {
|
||||
Self::Access { ty, access }
|
||||
}
|
||||
}
|
||||
impl Error<'static> {
|
||||
fn bad_enum_variant(expected: TypeShape, actual: impl Into<TypeShape>) -> Self {
|
||||
let actual = actual.into();
|
||||
Error::Enum { expected, actual }
|
||||
}
|
||||
fn bad_type(expected: TypeShape, actual: impl Into<TypeShape>) -> Self {
|
||||
let actual = actual.into();
|
||||
Error::Type { expected, actual }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum TypeShape {
|
||||
Struct,
|
||||
TupleStruct,
|
||||
Tuple,
|
||||
List,
|
||||
Array,
|
||||
Map,
|
||||
Enum,
|
||||
Value,
|
||||
Unit,
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeShape {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
TypeShape::Struct => "struct",
|
||||
TypeShape::TupleStruct => "tuple struct",
|
||||
TypeShape::Tuple => "tuple",
|
||||
TypeShape::List => "list",
|
||||
TypeShape::Array => "array",
|
||||
TypeShape::Map => "map",
|
||||
TypeShape::Enum => "enum",
|
||||
TypeShape::Value => "value",
|
||||
TypeShape::Unit => "unit",
|
||||
};
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
impl<'a> From<ReflectRef<'a>> for TypeShape {
|
||||
fn from(value: ReflectRef<'a>) -> Self {
|
||||
match value {
|
||||
ReflectRef::Struct(_) => TypeShape::Struct,
|
||||
ReflectRef::TupleStruct(_) => TypeShape::TupleStruct,
|
||||
ReflectRef::Tuple(_) => TypeShape::Tuple,
|
||||
ReflectRef::List(_) => TypeShape::List,
|
||||
ReflectRef::Array(_) => TypeShape::Array,
|
||||
ReflectRef::Map(_) => TypeShape::Map,
|
||||
ReflectRef::Enum(_) => TypeShape::Enum,
|
||||
ReflectRef::Value(_) => TypeShape::Value,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<VariantType> for TypeShape {
|
||||
fn from(value: VariantType) -> Self {
|
||||
match value {
|
||||
VariantType::Struct => TypeShape::Struct,
|
||||
VariantType::Tuple => TypeShape::Tuple,
|
||||
VariantType::Unit => TypeShape::Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
type InnerResult<T> = Result<T, AccessErrorKind>;
|
||||
|
||||
/// A singular element access within a path.
|
||||
/// Multiple accesses can be combined into a [`ParsedPath`](super::ParsedPath).
|
||||
///
|
||||
/// Can be applied to a `dyn Reflect` to get a reference to the targeted element.
|
||||
/// Can be applied to a [`dyn Reflect`](Reflect) to get a reference to the targeted element.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) enum Access<'a> {
|
||||
pub enum Access<'a> {
|
||||
/// A name-based field access on a struct.
|
||||
Field(Cow<'a, str>),
|
||||
/// A index-based field access on a struct.
|
||||
FieldIndex(usize),
|
||||
/// An index-based access on a tuple.
|
||||
TupleIndex(usize),
|
||||
/// An index-based access on a list.
|
||||
ListIndex(usize),
|
||||
}
|
||||
|
||||
|
@ -125,101 +35,146 @@ impl fmt::Display for Access<'_> {
|
|||
}
|
||||
|
||||
impl<'a> Access<'a> {
|
||||
pub(super) fn into_owned(self) -> Access<'static> {
|
||||
/// Converts this into an "owned" value.
|
||||
///
|
||||
/// If the [`Access`] is of variant [`Field`](Access::Field),
|
||||
/// the field's [`Cow<str>`] will be converted to it's owned
|
||||
/// counterpart, which doesn't require a reference.
|
||||
pub fn into_owned(self) -> Access<'static> {
|
||||
match self {
|
||||
Self::Field(value) => Access::Field(value.to_string().into()),
|
||||
Self::Field(value) => Access::Field(Cow::Owned(value.into_owned())),
|
||||
Self::FieldIndex(value) => Access::FieldIndex(value),
|
||||
Self::TupleIndex(value) => Access::TupleIndex(value),
|
||||
Self::ListIndex(value) => Access::ListIndex(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn display_value(&self) -> &dyn fmt::Display {
|
||||
match self {
|
||||
Self::Field(value) => value,
|
||||
Self::FieldIndex(value) | Self::TupleIndex(value) | Self::ListIndex(value) => value,
|
||||
}
|
||||
}
|
||||
fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Field(_) => "field",
|
||||
Self::FieldIndex(_) => "field index",
|
||||
Self::TupleIndex(_) | Self::ListIndex(_) => "index",
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn element<'r>(
|
||||
&self,
|
||||
base: &'r dyn Reflect,
|
||||
offset: usize,
|
||||
) -> Result<&'r dyn Reflect, ReflectPathError<'a>> {
|
||||
let ty = base.reflect_ref().into();
|
||||
offset: Option<usize>,
|
||||
) -> Result<&'r dyn Reflect, AccessError<'a>> {
|
||||
self.element_inner(base)
|
||||
.and_then(|maybe| maybe.ok_or(Error::access(ty, self.clone())))
|
||||
.map_err(|err| err.with_offset(offset))
|
||||
.and_then(|opt| opt.ok_or(AccessErrorKind::MissingField(base.into())))
|
||||
.map_err(|err| err.with_access(self.clone(), offset))
|
||||
}
|
||||
|
||||
fn element_inner<'r>(&self, base: &'r dyn Reflect) -> InnerResult<&'r dyn Reflect> {
|
||||
fn element_inner<'r>(&self, base: &'r dyn Reflect) -> InnerResult<Option<&'r dyn Reflect>> {
|
||||
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() {
|
||||
VariantType::Struct => Ok(enum_ref.field(field.as_ref())),
|
||||
actual => Err(Error::bad_enum_variant(TypeShape::Struct, actual)),
|
||||
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() {
|
||||
VariantType::Struct => Ok(enum_ref.field_at(index)),
|
||||
actual => Err(Error::bad_enum_variant(TypeShape::Struct, actual)),
|
||||
actual => Err(invalid_variant(VariantType::Struct, actual)),
|
||||
},
|
||||
(Self::Field(_) | Self::FieldIndex(_), actual) => {
|
||||
Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::Struct,
|
||||
actual: actual.into(),
|
||||
})
|
||||
}
|
||||
|
||||
(&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(Error::bad_enum_variant(TypeShape::Tuple, actual)),
|
||||
actual => Err(invalid_variant(VariantType::Tuple, actual)),
|
||||
},
|
||||
(Self::TupleIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::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(Error::bad_type(TypeShape::List, actual)),
|
||||
(_, actual) => Err(Error::bad_type(TypeShape::Struct, actual)),
|
||||
(Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::List,
|
||||
actual: actual.into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn element_mut<'r>(
|
||||
&self,
|
||||
base: &'r mut dyn Reflect,
|
||||
offset: usize,
|
||||
) -> Result<&'r mut dyn Reflect, ReflectPathError<'a>> {
|
||||
let ty = base.reflect_ref().into();
|
||||
offset: Option<usize>,
|
||||
) -> Result<&'r mut dyn Reflect, AccessError<'a>> {
|
||||
let kind = base.into();
|
||||
|
||||
self.element_inner_mut(base)
|
||||
.and_then(|maybe| maybe.ok_or(Error::access(ty, self.clone())))
|
||||
.map_err(|err| err.with_offset(offset))
|
||||
.and_then(|maybe| maybe.ok_or(AccessErrorKind::MissingField(kind)))
|
||||
.map_err(|err| err.with_access(self.clone(), offset))
|
||||
}
|
||||
|
||||
fn element_inner_mut<'r>(&self, base: &'r mut dyn Reflect) -> InnerResult<&'r mut dyn Reflect> {
|
||||
fn element_inner_mut<'r>(
|
||||
&self,
|
||||
base: &'r mut dyn Reflect,
|
||||
) -> InnerResult<Option<&'r mut dyn Reflect>> {
|
||||
use ReflectMut::*;
|
||||
let base_shape: TypeShape = base.reflect_ref().into();
|
||||
|
||||
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(Error::bad_enum_variant(TypeShape::Struct, actual)),
|
||||
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(Error::bad_enum_variant(TypeShape::Struct, actual)),
|
||||
actual => Err(invalid_variant(VariantType::Struct, actual)),
|
||||
},
|
||||
(Self::Field(_) | Self::FieldIndex(_), actual) => {
|
||||
Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::Struct,
|
||||
actual: actual.into(),
|
||||
})
|
||||
}
|
||||
|
||||
(&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(Error::bad_enum_variant(TypeShape::Tuple, actual)),
|
||||
actual => Err(invalid_variant(VariantType::Tuple, actual)),
|
||||
},
|
||||
(Self::TupleIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::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(_), _) => Err(Error::bad_type(TypeShape::List, base_shape)),
|
||||
(_, _) => Err(Error::bad_type(TypeShape::Struct, base_shape)),
|
||||
(Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||
expected: TypeKind::List,
|
||||
actual: actual.into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to this [`Access`]'s inner value as a [`&dyn Display`](fmt::Display).
|
||||
pub fn display_value(&self) -> &dyn fmt::Display {
|
||||
match self {
|
||||
Self::Field(value) => value,
|
||||
Self::FieldIndex(value) | Self::TupleIndex(value) | Self::ListIndex(value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Field(_) => "field",
|
||||
Self::FieldIndex(_) => "field index",
|
||||
Self::TupleIndex(_) | Self::ListIndex(_) => "index",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
207
crates/bevy_reflect/src/path/error.rs
Normal file
207
crates/bevy_reflect/src/path/error.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
use std::fmt;
|
||||
|
||||
use super::Access;
|
||||
use crate::{Reflect, ReflectMut, ReflectRef, VariantType};
|
||||
|
||||
/// The kind of [`AccessError`], along with some kind-specific information.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum AccessErrorKind {
|
||||
/// An error that occurs when a certain type doesn't
|
||||
/// contain the value referenced by the [`Access`].
|
||||
MissingField(TypeKind),
|
||||
|
||||
/// 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 [`TypeKind`] that was expected based on the [`Access`].
|
||||
expected: TypeKind,
|
||||
/// The actual [`TypeKind`] that was found.
|
||||
actual: TypeKind,
|
||||
},
|
||||
|
||||
/// 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 {
|
||||
/// The [`VariantType`] that was expected based on the [`Access`].
|
||||
expected: VariantType,
|
||||
/// The actual [`VariantType`] that was found.
|
||||
actual: VariantType,
|
||||
},
|
||||
}
|
||||
|
||||
impl AccessErrorKind {
|
||||
pub(super) fn with_access(self, access: Access, offset: Option<usize>) -> AccessError {
|
||||
AccessError {
|
||||
kind: self,
|
||||
access,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error originating from an [`Access`] of an element within a type.
|
||||
///
|
||||
/// Use the `Display` impl of this type to get information on the error.
|
||||
///
|
||||
/// Some sample messages:
|
||||
///
|
||||
/// ```text
|
||||
/// Error accessing element with `.alpha` access (offset 14): The struct accessed doesn't have an "alpha" field
|
||||
/// Error accessing element with '[0]' access: Expected index access to access a list, found a struct instead.
|
||||
/// Error accessing element with '.4' access: Expected variant index access to access a Tuple variant, found a Unit variant instead.
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccessError<'a> {
|
||||
pub(super) kind: AccessErrorKind,
|
||||
pub(super) access: Access<'a>,
|
||||
pub(super) offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> AccessError<'a> {
|
||||
/// Returns the kind of [`AccessError`].
|
||||
pub const fn kind(&self) -> &AccessErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
/// The returns the [`Access`] that this [`AccessError`] occured in.
|
||||
pub const fn access(&self) -> &Access {
|
||||
&self.access
|
||||
}
|
||||
|
||||
/// If the [`Access`] was created with a parser or an offset was manually provided,
|
||||
/// returns the offset of the [`Access`] in it's path string.
|
||||
pub const fn offset(&self) -> Option<&usize> {
|
||||
self.offset.as_ref()
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for AccessError<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let AccessError {
|
||||
kind,
|
||||
access,
|
||||
offset,
|
||||
} = self;
|
||||
|
||||
write!(f, "Error accessing element with `{access}` access")?;
|
||||
if let Some(offset) = offset {
|
||||
write!(f, "(offset {offset})")?;
|
||||
}
|
||||
write!(f, ": ")?;
|
||||
|
||||
match kind {
|
||||
AccessErrorKind::MissingField(type_accessed) => {
|
||||
match access {
|
||||
Access::Field(field) => write!(
|
||||
f,
|
||||
"The {type_accessed} accessed doesn't have {} `{}` field",
|
||||
if let Some("a" | "e" | "i" | "o" | "u") = field.get(0..1) {
|
||||
"an"
|
||||
} else {
|
||||
"a"
|
||||
},
|
||||
access.display_value()
|
||||
),
|
||||
Access::FieldIndex(_) => write!(
|
||||
f,
|
||||
"The {type_accessed} accessed doesn't have field index `{}`",
|
||||
access.display_value(),
|
||||
),
|
||||
Access::TupleIndex(_) | Access::ListIndex(_) => write!(
|
||||
f,
|
||||
"The {type_accessed} accessed doesn't have index `{}`",
|
||||
access.display_value()
|
||||
)
|
||||
}
|
||||
}
|
||||
AccessErrorKind::IncompatibleTypes { expected, actual } => write!(
|
||||
f,
|
||||
"Expected {} access to access a {expected}, found a {actual} instead.",
|
||||
access.kind()
|
||||
),
|
||||
AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual } => write!(
|
||||
f,
|
||||
"Expected variant {} access to access a {expected:?} variant, found a {actual:?} variant instead.",
|
||||
access.kind()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for AccessError<'_> {}
|
||||
|
||||
/// The kind of the type trying to be accessed.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[allow(missing_docs /* Variants are self-explanatory */)]
|
||||
pub enum TypeKind {
|
||||
Struct,
|
||||
TupleStruct,
|
||||
Tuple,
|
||||
List,
|
||||
Array,
|
||||
Map,
|
||||
Enum,
|
||||
Value,
|
||||
Unit,
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TypeKind::Struct => f.pad("struct"),
|
||||
TypeKind::TupleStruct => f.pad("tuple struct"),
|
||||
TypeKind::Tuple => f.pad("tuple"),
|
||||
TypeKind::List => f.pad("list"),
|
||||
TypeKind::Array => f.pad("array"),
|
||||
TypeKind::Map => f.pad("map"),
|
||||
TypeKind::Enum => f.pad("enum"),
|
||||
TypeKind::Value => f.pad("value"),
|
||||
TypeKind::Unit => f.pad("unit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ReflectRef<'_>> for TypeKind {
|
||||
fn from(value: ReflectRef) -> Self {
|
||||
match value {
|
||||
ReflectRef::Struct(_) => TypeKind::Struct,
|
||||
ReflectRef::TupleStruct(_) => TypeKind::TupleStruct,
|
||||
ReflectRef::Tuple(_) => TypeKind::Tuple,
|
||||
ReflectRef::List(_) => TypeKind::List,
|
||||
ReflectRef::Array(_) => TypeKind::Array,
|
||||
ReflectRef::Map(_) => TypeKind::Map,
|
||||
ReflectRef::Enum(_) => TypeKind::Enum,
|
||||
ReflectRef::Value(_) => TypeKind::Value,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&dyn Reflect> for TypeKind {
|
||||
fn from(value: &dyn Reflect) -> Self {
|
||||
value.reflect_ref().into()
|
||||
}
|
||||
}
|
||||
impl From<ReflectMut<'_>> for TypeKind {
|
||||
fn from(value: ReflectMut) -> Self {
|
||||
match value {
|
||||
ReflectMut::Struct(_) => TypeKind::Struct,
|
||||
ReflectMut::TupleStruct(_) => TypeKind::TupleStruct,
|
||||
ReflectMut::Tuple(_) => TypeKind::Tuple,
|
||||
ReflectMut::List(_) => TypeKind::List,
|
||||
ReflectMut::Array(_) => TypeKind::Array,
|
||||
ReflectMut::Map(_) => TypeKind::Map,
|
||||
ReflectMut::Enum(_) => TypeKind::Enum,
|
||||
ReflectMut::Value(_) => TypeKind::Value,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&mut dyn Reflect> for TypeKind {
|
||||
fn from(value: &mut dyn Reflect) -> Self {
|
||||
value.reflect_ref().into()
|
||||
}
|
||||
}
|
||||
impl From<VariantType> for TypeKind {
|
||||
fn from(value: VariantType) -> Self {
|
||||
match value {
|
||||
VariantType::Struct => TypeKind::Struct,
|
||||
VariantType::Tuple => TypeKind::Tuple,
|
||||
VariantType::Unit => TypeKind::Unit,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +1,49 @@
|
|||
mod access;
|
||||
mod parse;
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::fmt;
|
||||
pub mod access;
|
||||
pub use access::*;
|
||||
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
mod parse;
|
||||
pub use parse::ParseError;
|
||||
use parse::PathParser;
|
||||
|
||||
use crate::Reflect;
|
||||
use access::Access;
|
||||
use parse::PathParser;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use parse::ParseError;
|
||||
|
||||
type PathResult<'a, T> = Result<T, ReflectPathError<'a>>;
|
||||
|
||||
/// An error specific to accessing a field/index on a `Reflect`.
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct AccessError<'a>(access::Error<'a>);
|
||||
|
||||
/// An error returned from a failed path string query.
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
pub enum ReflectPathError<'a> {
|
||||
#[error("at {offset} in path specification: {error}")]
|
||||
InvalidAccess {
|
||||
/// Position in the path string.
|
||||
offset: usize,
|
||||
error: AccessError<'a>,
|
||||
},
|
||||
/// An error caused by trying to access a path that's not able to be accessed,
|
||||
/// see [`AccessError`] for details.
|
||||
#[error(transparent)]
|
||||
InvalidAccess(AccessError<'a>),
|
||||
|
||||
#[error("failed to downcast to the path result to the given type")]
|
||||
/// An error that occurs when a type cannot downcast to a given type.
|
||||
#[error("Can't downcast result of access to the given type")]
|
||||
InvalidDowncast,
|
||||
|
||||
#[error("at {offset} in '{path}': {error}")]
|
||||
/// An error caused by an invalid path string that couldn't be parsed.
|
||||
#[error("Encounted an error at offset {offset} while parsing `{path}`: {error}")]
|
||||
ParseError {
|
||||
/// Position in `path`.
|
||||
offset: usize,
|
||||
/// The path that the error occured in.
|
||||
path: &'a str,
|
||||
/// The underlying error.
|
||||
error: ParseError<'a>,
|
||||
},
|
||||
}
|
||||
impl<'a> From<AccessError<'a>> for ReflectPathError<'a> {
|
||||
fn from(value: AccessError<'a>) -> Self {
|
||||
Self::InvalidAccess(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can be interpreted as a reflection path in [`GetPath`].
|
||||
pub trait ReflectPath<'a>: Sized {
|
||||
|
@ -75,13 +81,14 @@ pub trait ReflectPath<'a>: Sized {
|
|||
impl<'a> ReflectPath<'a> for &'a str {
|
||||
fn reflect_element(self, mut root: &dyn Reflect) -> PathResult<'a, &dyn Reflect> {
|
||||
for (access, offset) in PathParser::new(self) {
|
||||
root = access?.element(root, offset)?;
|
||||
let a = access?;
|
||||
root = a.element(root, Some(offset))?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
fn reflect_element_mut(self, mut root: &mut dyn Reflect) -> PathResult<'a, &mut dyn Reflect> {
|
||||
for (access, offset) in PathParser::new(self) {
|
||||
root = access?.element_mut(root, offset)?;
|
||||
root = access?.element_mut(root, Some(offset))?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
|
@ -271,22 +278,80 @@ pub trait GetPath: Reflect {
|
|||
// Implement `GetPath` for `dyn Reflect`
|
||||
impl<T: Reflect + ?Sized> GetPath for T {}
|
||||
|
||||
/// An [`Access`] combined with an `offset` for more helpful error reporting.
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
pub struct OffsetAccess {
|
||||
/// The [`Access`] itself.
|
||||
pub access: Access<'static>,
|
||||
/// A character offset in the string the path was parsed from.
|
||||
pub offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<Access<'static>> for OffsetAccess {
|
||||
fn from(access: Access<'static>) -> Self {
|
||||
OffsetAccess {
|
||||
access,
|
||||
offset: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pre-parsed path to an element within a type.
|
||||
///
|
||||
/// This struct can be constructed manually from its [`Access`]es or with
|
||||
/// the [parse](ParsedPath::parse) method.
|
||||
///
|
||||
/// This struct may be used like [`GetPath`] but removes the cost of parsing the path
|
||||
/// string at each element access.
|
||||
///
|
||||
/// It's recommended to use this in place of `GetPath` when the path string is
|
||||
/// It's recommended to use this in place of [`GetPath`] when the path string is
|
||||
/// unlikely to be changed and will be accessed repeatedly.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// Parsing a [`&'static str`](str):
|
||||
/// ```
|
||||
/// # use bevy_reflect::ParsedPath;
|
||||
/// let my_static_string: &'static str = "bar#0.1[2].0";
|
||||
/// // Breakdown:
|
||||
/// // "bar" - Access struct field named "bar"
|
||||
/// // "#0" - Access struct field at index 0
|
||||
/// // ".1" - Access tuple struct field at index 1
|
||||
/// // "[2]" - Access list element at index 2
|
||||
/// // ".0" - Access tuple variant field at index 0
|
||||
/// let my_path = ParsedPath::parse_static(my_static_string);
|
||||
/// ```
|
||||
/// Parsing a non-static [`&str`](str):
|
||||
/// ```
|
||||
/// # use bevy_reflect::ParsedPath;
|
||||
/// let my_string = String::from("bar#0.1[2].0");
|
||||
/// // Breakdown:
|
||||
/// // "bar" - Access struct field named "bar"
|
||||
/// // "#0" - Access struct field at index 0
|
||||
/// // ".1" - Access tuple struct field at index 1
|
||||
/// // "[2]" - Access list element at index 2
|
||||
/// // ".0" - Access tuple variant field at index 0
|
||||
/// let my_path = ParsedPath::parse(&my_string);
|
||||
/// ```
|
||||
/// Manually constructing a [`ParsedPath`]:
|
||||
/// ```
|
||||
/// # use std::borrow::Cow;
|
||||
/// # use bevy_reflect::access::Access;
|
||||
/// # use bevy_reflect::ParsedPath;
|
||||
/// let path_elements = [
|
||||
/// Access::Field(Cow::Borrowed("bar")),
|
||||
/// Access::FieldIndex(0),
|
||||
/// Access::TupleIndex(1),
|
||||
/// Access::ListIndex(2),
|
||||
/// Access::TupleIndex(1),
|
||||
/// ];
|
||||
/// let my_path = ParsedPath::from(path_elements);
|
||||
/// ```
|
||||
///
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
pub struct ParsedPath(
|
||||
/// This is the boxed slice of pre-parsed accesses.
|
||||
///
|
||||
/// Each item in the slice contains the access along with the character
|
||||
/// index of the start of the access within the parsed path string.
|
||||
///
|
||||
/// The index is mainly used for more helpful error reporting.
|
||||
Box<[(Access<'static>, usize)]>,
|
||||
/// This is a vector of pre-parsed [`OffsetAccess`]es.
|
||||
pub Vec<OffsetAccess>,
|
||||
);
|
||||
|
||||
impl ParsedPath {
|
||||
|
@ -338,9 +403,12 @@ impl ParsedPath {
|
|||
pub fn parse(string: &str) -> PathResult<Self> {
|
||||
let mut parts = Vec::new();
|
||||
for (access, offset) in PathParser::new(string) {
|
||||
parts.push((access?.into_owned(), offset));
|
||||
parts.push(OffsetAccess {
|
||||
access: access?.into_owned(),
|
||||
offset: Some(offset),
|
||||
});
|
||||
}
|
||||
Ok(Self(parts.into_boxed_slice()))
|
||||
Ok(Self(parts))
|
||||
}
|
||||
|
||||
/// Similar to [`Self::parse`] but only works on `&'static str`
|
||||
|
@ -348,41 +416,84 @@ impl ParsedPath {
|
|||
pub fn parse_static(string: &'static str) -> PathResult<Self> {
|
||||
let mut parts = Vec::new();
|
||||
for (access, offset) in PathParser::new(string) {
|
||||
parts.push((access?, offset));
|
||||
parts.push(OffsetAccess {
|
||||
access: access?,
|
||||
offset: Some(offset),
|
||||
});
|
||||
}
|
||||
Ok(Self(parts.into_boxed_slice()))
|
||||
Ok(Self(parts))
|
||||
}
|
||||
}
|
||||
impl<'a> ReflectPath<'a> for &'a ParsedPath {
|
||||
fn reflect_element(self, mut root: &dyn Reflect) -> PathResult<'a, &dyn Reflect> {
|
||||
for (access, offset) in &*self.0 {
|
||||
for OffsetAccess { access, offset } in &self.0 {
|
||||
root = access.element(root, *offset)?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
fn reflect_element_mut(self, mut root: &mut dyn Reflect) -> PathResult<'a, &mut dyn Reflect> {
|
||||
for (access, offset) in &*self.0 {
|
||||
for OffsetAccess { access, offset } in &self.0 {
|
||||
root = access.element_mut(root, *offset)?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
}
|
||||
impl From<Vec<OffsetAccess>> for ParsedPath {
|
||||
fn from(value: Vec<OffsetAccess>) -> Self {
|
||||
ParsedPath(value)
|
||||
}
|
||||
}
|
||||
impl<const N: usize> From<[OffsetAccess; N]> for ParsedPath {
|
||||
fn from(value: [OffsetAccess; N]) -> Self {
|
||||
ParsedPath(value.to_vec())
|
||||
}
|
||||
}
|
||||
impl From<Vec<Access<'static>>> for ParsedPath {
|
||||
fn from(value: Vec<Access<'static>>) -> Self {
|
||||
ParsedPath(
|
||||
value
|
||||
.into_iter()
|
||||
.map(|access| OffsetAccess {
|
||||
access,
|
||||
offset: None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const N: usize> From<[Access<'static>; N]> for ParsedPath {
|
||||
fn from(value: [Access<'static>; N]) -> Self {
|
||||
value.to_vec().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParsedPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (access, _) in self.0.iter() {
|
||||
for OffsetAccess { access, .. } in &self.0 {
|
||||
write!(f, "{access}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl std::ops::Index<usize> for ParsedPath {
|
||||
type Output = OffsetAccess;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
impl std::ops::IndexMut<usize> for ParsedPath {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as bevy_reflect;
|
||||
use crate::*;
|
||||
use access::TypeShape;
|
||||
use error::{AccessErrorKind, TypeKind};
|
||||
|
||||
#[derive(Reflect)]
|
||||
struct A {
|
||||
|
@ -438,61 +549,79 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn offset(access: Access<'static>, offset: usize) -> OffsetAccess {
|
||||
OffsetAccess {
|
||||
access,
|
||||
offset: Some(offset),
|
||||
}
|
||||
}
|
||||
|
||||
fn access_field(field: &'static str) -> Access {
|
||||
Access::Field(field.into())
|
||||
}
|
||||
|
||||
type StaticError = ReflectPathError<'static>;
|
||||
|
||||
fn invalid_access(offset: usize, actual: TypeShape, expected: TypeShape) -> StaticError {
|
||||
let error = AccessError(access::Error::Type { actual, expected });
|
||||
ReflectPathError::InvalidAccess { offset, error }
|
||||
fn invalid_access(
|
||||
offset: usize,
|
||||
actual: TypeKind,
|
||||
expected: TypeKind,
|
||||
access: &'static str,
|
||||
) -> StaticError {
|
||||
ReflectPathError::InvalidAccess(AccessError {
|
||||
kind: AccessErrorKind::IncompatibleTypes { actual, expected },
|
||||
access: ParsedPath::parse_static(access).unwrap()[1].access.clone(),
|
||||
offset: Some(offset),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsed_path_parse() {
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("w").unwrap().0,
|
||||
&[(access_field("w"), 1)]
|
||||
ParsedPath::parse("w").unwrap().0,
|
||||
&[offset(access_field("w"), 1)]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("x.foo").unwrap().0,
|
||||
&[(access_field("x"), 1), (access_field("foo"), 2)]
|
||||
ParsedPath::parse("x.foo").unwrap().0,
|
||||
&[offset(access_field("x"), 1), offset(access_field("foo"), 2)]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("x.łørđ.mосква").unwrap().0,
|
||||
ParsedPath::parse("x.łørđ.mосква").unwrap().0,
|
||||
&[
|
||||
(access_field("x"), 1),
|
||||
(access_field("łørđ"), 2),
|
||||
(access_field("mосква"), 10)
|
||||
offset(access_field("x"), 1),
|
||||
offset(access_field("łørđ"), 2),
|
||||
offset(access_field("mосква"), 10)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("y[1].mосква").unwrap().0,
|
||||
ParsedPath::parse("y[1].mосква").unwrap().0,
|
||||
&[
|
||||
(access_field("y"), 1),
|
||||
(Access::ListIndex(1), 2),
|
||||
(access_field("mосква"), 5)
|
||||
offset(access_field("y"), 1),
|
||||
offset(Access::ListIndex(1), 2),
|
||||
offset(access_field("mосква"), 5)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("z.0.1").unwrap().0,
|
||||
ParsedPath::parse("z.0.1").unwrap().0,
|
||||
&[
|
||||
(access_field("z"), 1),
|
||||
(Access::TupleIndex(0), 2),
|
||||
(Access::TupleIndex(1), 4),
|
||||
offset(access_field("z"), 1),
|
||||
offset(Access::TupleIndex(0), 2),
|
||||
offset(Access::TupleIndex(1), 4),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("x#0").unwrap().0,
|
||||
&[(access_field("x"), 1), (Access::FieldIndex(0), 2)]
|
||||
ParsedPath::parse("x#0").unwrap().0,
|
||||
&[
|
||||
offset(access_field("x"), 1),
|
||||
offset(Access::FieldIndex(0), 2)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("x#0#1").unwrap().0,
|
||||
ParsedPath::parse("x#0#1").unwrap().0,
|
||||
&[
|
||||
(access_field("x"), 1),
|
||||
(Access::FieldIndex(0), 2),
|
||||
(Access::FieldIndex(1), 4)
|
||||
offset(access_field("x"), 1),
|
||||
offset(Access::FieldIndex(0), 2),
|
||||
offset(Access::FieldIndex(1), 4)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -605,52 +734,59 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
a.reflect_path("x.notreal").err().unwrap(),
|
||||
ReflectPathError::InvalidAccess {
|
||||
offset: 2,
|
||||
error: AccessError(access::Error::Access {
|
||||
ty: TypeShape::Struct,
|
||||
access: access_field("notreal"),
|
||||
}),
|
||||
}
|
||||
ReflectPathError::InvalidAccess(AccessError {
|
||||
kind: AccessErrorKind::MissingField(TypeKind::Struct),
|
||||
access: access_field("notreal"),
|
||||
offset: Some(2),
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
a.reflect_path("unit_variant.0").err().unwrap(),
|
||||
ReflectPathError::InvalidAccess {
|
||||
offset: 13,
|
||||
error: AccessError(access::Error::Enum {
|
||||
actual: TypeShape::Unit,
|
||||
expected: TypeShape::Tuple
|
||||
}),
|
||||
}
|
||||
ReflectPathError::InvalidAccess(AccessError {
|
||||
kind: AccessErrorKind::IncompatibleEnumVariantTypes {
|
||||
actual: VariantType::Unit,
|
||||
expected: VariantType::Tuple,
|
||||
},
|
||||
access: ParsedPath::parse_static("unit_variant.0").unwrap()[1]
|
||||
.access
|
||||
.clone(),
|
||||
offset: Some(13),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
a.reflect_path("x[0]").err().unwrap(),
|
||||
invalid_access(2, TypeShape::Struct, TypeShape::List)
|
||||
invalid_access(2, TypeKind::Struct, TypeKind::List, "x[0]")
|
||||
);
|
||||
assert_eq!(
|
||||
a.reflect_path("y.x").err().unwrap(),
|
||||
invalid_access(2, TypeShape::List, TypeShape::Struct)
|
||||
invalid_access(2, TypeKind::List, TypeKind::Struct, "y.x")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept_leading_tokens() {
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse(".w").unwrap().0,
|
||||
&[(access_field("w"), 1)]
|
||||
ParsedPath::parse(".w").unwrap().0,
|
||||
&[offset(access_field("w"), 1)]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("#0.foo").unwrap().0,
|
||||
&[(Access::FieldIndex(0), 1), (access_field("foo"), 3)]
|
||||
ParsedPath::parse("#0.foo").unwrap().0,
|
||||
&[
|
||||
offset(Access::FieldIndex(0), 1),
|
||||
offset(access_field("foo"), 3)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse(".5").unwrap().0,
|
||||
&[(Access::TupleIndex(5), 1)]
|
||||
ParsedPath::parse(".5").unwrap().0,
|
||||
&[offset(Access::TupleIndex(5), 1)]
|
||||
);
|
||||
assert_eq!(
|
||||
&*ParsedPath::parse("[0].łørđ").unwrap().0,
|
||||
&[(Access::ListIndex(0), 1), (access_field("łørđ"), 4)]
|
||||
ParsedPath::parse("[0].łørđ").unwrap().0,
|
||||
&[
|
||||
offset(Access::ListIndex(0), 1),
|
||||
offset(access_field("łørđ"), 4)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use std::{fmt, num::ParseIntError, str::from_utf8_unchecked};
|
||||
use std::{
|
||||
fmt::{self, Write},
|
||||
num::ParseIntError,
|
||||
str::from_utf8_unchecked,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -102,12 +106,15 @@ impl<'a> Iterator for PathParser<'a> {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let token = self.next_token()?;
|
||||
let offset = self.offset();
|
||||
let err = |error| ReflectPathError::ParseError {
|
||||
Some((
|
||||
self.access_following(token)
|
||||
.map_err(|error| ReflectPathError::ParseError {
|
||||
offset,
|
||||
path: self.path,
|
||||
error: ParseError(error),
|
||||
}),
|
||||
offset,
|
||||
path: self.path,
|
||||
error: ParseError(error),
|
||||
};
|
||||
Some((self.access_following(token).map_err(err), offset))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,14 +148,13 @@ enum Token<'a> {
|
|||
}
|
||||
impl fmt::Display for Token<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let text = match self {
|
||||
Token::Dot => ".",
|
||||
Token::Pound => "#",
|
||||
Token::OpenBracket => "[",
|
||||
Token::CloseBracket => "]",
|
||||
Token::Ident(ident) => ident.0,
|
||||
};
|
||||
f.write_str(text)
|
||||
match self {
|
||||
Token::Dot => f.write_char('.'),
|
||||
Token::Pound => f.write_char('#'),
|
||||
Token::OpenBracket => f.write_char('['),
|
||||
Token::CloseBracket => f.write_char(']'),
|
||||
Token::Ident(ident) => f.write_str(ident.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Token<'a> {
|
||||
|
|
Loading…
Reference in a new issue