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:
Doonv 2024-02-01 21:22:40 +02:00 committed by GitHub
parent d30fdda2c3
commit b1a2d342af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 546 additions and 242 deletions

View file

@ -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",
}
}
}

View 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,
}
}
}

View file

@ -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,
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)
]
);
}
}

View file

@ -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),
};
Some((self.access_following(token).map_err(err), offset))
}),
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> {