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
@ -1,115 +1,25 @@
|
|||||||
|
//! Representation for individual element accesses within a path.
|
||||||
|
|
||||||
use std::{borrow::Cow, fmt};
|
use std::{borrow::Cow, fmt};
|
||||||
|
|
||||||
use super::{AccessError, ReflectPathError};
|
use super::error::{AccessErrorKind, TypeKind};
|
||||||
use crate::{Reflect, ReflectMut, ReflectRef, VariantType};
|
use crate::{AccessError, Reflect, ReflectMut, ReflectRef, VariantType};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
type InnerResult<T> = Result<Option<T>, Error<'static>>;
|
type InnerResult<T> = Result<T, AccessErrorKind>;
|
||||||
|
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A singular element access within a path.
|
/// 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)]
|
#[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>),
|
Field(Cow<'a, str>),
|
||||||
|
/// A index-based field access on a struct.
|
||||||
FieldIndex(usize),
|
FieldIndex(usize),
|
||||||
|
/// An index-based access on a tuple.
|
||||||
TupleIndex(usize),
|
TupleIndex(usize),
|
||||||
|
/// An index-based access on a list.
|
||||||
ListIndex(usize),
|
ListIndex(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,101 +35,146 @@ impl fmt::Display for Access<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Access<'a> {
|
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 {
|
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::FieldIndex(value) => Access::FieldIndex(value),
|
||||||
Self::TupleIndex(value) => Access::TupleIndex(value),
|
Self::TupleIndex(value) => Access::TupleIndex(value),
|
||||||
Self::ListIndex(value) => Access::ListIndex(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>(
|
pub(super) fn element<'r>(
|
||||||
&self,
|
&self,
|
||||||
base: &'r dyn Reflect,
|
base: &'r dyn Reflect,
|
||||||
offset: usize,
|
offset: Option<usize>,
|
||||||
) -> Result<&'r dyn Reflect, ReflectPathError<'a>> {
|
) -> Result<&'r dyn Reflect, AccessError<'a>> {
|
||||||
let ty = base.reflect_ref().into();
|
|
||||||
self.element_inner(base)
|
self.element_inner(base)
|
||||||
.and_then(|maybe| maybe.ok_or(Error::access(ty, self.clone())))
|
.and_then(|opt| opt.ok_or(AccessErrorKind::MissingField(base.into())))
|
||||||
.map_err(|err| err.with_offset(offset))
|
.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::*;
|
use ReflectRef::*;
|
||||||
|
|
||||||
|
let invalid_variant =
|
||||||
|
|expected, actual| AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual };
|
||||||
|
|
||||||
match (self, base.reflect_ref()) {
|
match (self, base.reflect_ref()) {
|
||||||
(Self::Field(field), Struct(struct_ref)) => Ok(struct_ref.field(field.as_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() {
|
(Self::Field(field), Enum(enum_ref)) => match enum_ref.variant_type() {
|
||||||
VariantType::Struct => Ok(enum_ref.field(field.as_ref())),
|
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), Struct(struct_ref)) => Ok(struct_ref.field_at(index)),
|
||||||
(&Self::FieldIndex(index), Enum(enum_ref)) => match enum_ref.variant_type() {
|
(&Self::FieldIndex(index), Enum(enum_ref)) => match enum_ref.variant_type() {
|
||||||
VariantType::Struct => Ok(enum_ref.field_at(index)),
|
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), TupleStruct(tuple)) => Ok(tuple.field(index)),
|
||||||
(&Self::TupleIndex(index), Tuple(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() {
|
(&Self::TupleIndex(index), Enum(enum_ref)) => match enum_ref.variant_type() {
|
||||||
VariantType::Tuple => Ok(enum_ref.field_at(index)),
|
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), List(list)) => Ok(list.get(index)),
|
||||||
(&Self::ListIndex(index), Array(list)) => Ok(list.get(index)),
|
(&Self::ListIndex(index), Array(list)) => Ok(list.get(index)),
|
||||||
(&Self::ListIndex(_), actual) => Err(Error::bad_type(TypeShape::List, actual)),
|
(Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||||
(_, actual) => Err(Error::bad_type(TypeShape::Struct, actual)),
|
expected: TypeKind::List,
|
||||||
|
actual: actual.into(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn element_mut<'r>(
|
pub(super) fn element_mut<'r>(
|
||||||
&self,
|
&self,
|
||||||
base: &'r mut dyn Reflect,
|
base: &'r mut dyn Reflect,
|
||||||
offset: usize,
|
offset: Option<usize>,
|
||||||
) -> Result<&'r mut dyn Reflect, ReflectPathError<'a>> {
|
) -> Result<&'r mut dyn Reflect, AccessError<'a>> {
|
||||||
let ty = base.reflect_ref().into();
|
let kind = base.into();
|
||||||
|
|
||||||
self.element_inner_mut(base)
|
self.element_inner_mut(base)
|
||||||
.and_then(|maybe| maybe.ok_or(Error::access(ty, self.clone())))
|
.and_then(|maybe| maybe.ok_or(AccessErrorKind::MissingField(kind)))
|
||||||
.map_err(|err| err.with_offset(offset))
|
.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::*;
|
use ReflectMut::*;
|
||||||
let base_shape: TypeShape = base.reflect_ref().into();
|
|
||||||
|
let invalid_variant =
|
||||||
|
|expected, actual| AccessErrorKind::IncompatibleEnumVariantTypes { expected, actual };
|
||||||
|
|
||||||
match (self, base.reflect_mut()) {
|
match (self, base.reflect_mut()) {
|
||||||
(Self::Field(field), Struct(struct_mut)) => Ok(struct_mut.field_mut(field.as_ref())),
|
(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() {
|
(Self::Field(field), Enum(enum_mut)) => match enum_mut.variant_type() {
|
||||||
VariantType::Struct => Ok(enum_mut.field_mut(field.as_ref())),
|
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), Struct(struct_mut)) => Ok(struct_mut.field_at_mut(index)),
|
||||||
(&Self::FieldIndex(index), Enum(enum_mut)) => match enum_mut.variant_type() {
|
(&Self::FieldIndex(index), Enum(enum_mut)) => match enum_mut.variant_type() {
|
||||||
VariantType::Struct => Ok(enum_mut.field_at_mut(index)),
|
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), TupleStruct(tuple)) => Ok(tuple.field_mut(index)),
|
||||||
(&Self::TupleIndex(index), Tuple(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() {
|
(&Self::TupleIndex(index), Enum(enum_mut)) => match enum_mut.variant_type() {
|
||||||
VariantType::Tuple => Ok(enum_mut.field_at_mut(index)),
|
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), List(list)) => Ok(list.get_mut(index)),
|
||||||
(&Self::ListIndex(index), Array(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)),
|
(Self::ListIndex(_), actual) => Err(AccessErrorKind::IncompatibleTypes {
|
||||||
(_, _) => Err(Error::bad_type(TypeShape::Struct, base_shape)),
|
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;
|
#![warn(missing_docs)]
|
||||||
mod parse;
|
|
||||||
|
|
||||||
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 crate::Reflect;
|
||||||
use access::Access;
|
use std::fmt;
|
||||||
use parse::PathParser;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use parse::ParseError;
|
|
||||||
|
|
||||||
type PathResult<'a, T> = Result<T, ReflectPathError<'a>>;
|
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.
|
/// An error returned from a failed path string query.
|
||||||
#[derive(Debug, PartialEq, Eq, Error)]
|
#[derive(Debug, PartialEq, Eq, Error)]
|
||||||
pub enum ReflectPathError<'a> {
|
pub enum ReflectPathError<'a> {
|
||||||
#[error("at {offset} in path specification: {error}")]
|
/// An error caused by trying to access a path that's not able to be accessed,
|
||||||
InvalidAccess {
|
/// see [`AccessError`] for details.
|
||||||
/// Position in the path string.
|
#[error(transparent)]
|
||||||
offset: usize,
|
InvalidAccess(AccessError<'a>),
|
||||||
error: 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,
|
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 {
|
ParseError {
|
||||||
/// Position in `path`.
|
/// Position in `path`.
|
||||||
offset: usize,
|
offset: usize,
|
||||||
|
/// The path that the error occured in.
|
||||||
path: &'a str,
|
path: &'a str,
|
||||||
|
/// The underlying error.
|
||||||
error: ParseError<'a>,
|
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`].
|
/// Something that can be interpreted as a reflection path in [`GetPath`].
|
||||||
pub trait ReflectPath<'a>: Sized {
|
pub trait ReflectPath<'a>: Sized {
|
||||||
@ -75,13 +81,14 @@ pub trait ReflectPath<'a>: Sized {
|
|||||||
impl<'a> ReflectPath<'a> for &'a str {
|
impl<'a> ReflectPath<'a> for &'a str {
|
||||||
fn reflect_element(self, mut root: &dyn Reflect) -> PathResult<'a, &dyn Reflect> {
|
fn reflect_element(self, mut root: &dyn Reflect) -> PathResult<'a, &dyn Reflect> {
|
||||||
for (access, offset) in PathParser::new(self) {
|
for (access, offset) in PathParser::new(self) {
|
||||||
root = access?.element(root, offset)?;
|
let a = access?;
|
||||||
|
root = a.element(root, Some(offset))?;
|
||||||
}
|
}
|
||||||
Ok(root)
|
Ok(root)
|
||||||
}
|
}
|
||||||
fn reflect_element_mut(self, mut root: &mut dyn Reflect) -> PathResult<'a, &mut dyn Reflect> {
|
fn reflect_element_mut(self, mut root: &mut dyn Reflect) -> PathResult<'a, &mut dyn Reflect> {
|
||||||
for (access, offset) in PathParser::new(self) {
|
for (access, offset) in PathParser::new(self) {
|
||||||
root = access?.element_mut(root, offset)?;
|
root = access?.element_mut(root, Some(offset))?;
|
||||||
}
|
}
|
||||||
Ok(root)
|
Ok(root)
|
||||||
}
|
}
|
||||||
@ -271,22 +278,80 @@ pub trait GetPath: Reflect {
|
|||||||
// Implement `GetPath` for `dyn Reflect`
|
// Implement `GetPath` for `dyn Reflect`
|
||||||
impl<T: Reflect + ?Sized> GetPath for T {}
|
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.
|
/// 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
|
/// This struct may be used like [`GetPath`] but removes the cost of parsing the path
|
||||||
/// string at each element access.
|
/// 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.
|
/// 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)]
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
pub struct ParsedPath(
|
pub struct ParsedPath(
|
||||||
/// This is the boxed slice of pre-parsed accesses.
|
/// This is a vector of pre-parsed [`OffsetAccess`]es.
|
||||||
///
|
pub Vec<OffsetAccess>,
|
||||||
/// 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)]>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ParsedPath {
|
impl ParsedPath {
|
||||||
@ -338,9 +403,12 @@ impl ParsedPath {
|
|||||||
pub fn parse(string: &str) -> PathResult<Self> {
|
pub fn parse(string: &str) -> PathResult<Self> {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for (access, offset) in PathParser::new(string) {
|
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`
|
/// 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> {
|
pub fn parse_static(string: &'static str) -> PathResult<Self> {
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for (access, offset) in PathParser::new(string) {
|
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 {
|
impl<'a> ReflectPath<'a> for &'a ParsedPath {
|
||||||
fn reflect_element(self, mut root: &dyn Reflect) -> PathResult<'a, &dyn Reflect> {
|
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)?;
|
root = access.element(root, *offset)?;
|
||||||
}
|
}
|
||||||
Ok(root)
|
Ok(root)
|
||||||
}
|
}
|
||||||
fn reflect_element_mut(self, mut root: &mut dyn Reflect) -> PathResult<'a, &mut dyn Reflect> {
|
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)?;
|
root = access.element_mut(root, *offset)?;
|
||||||
}
|
}
|
||||||
Ok(root)
|
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 {
|
impl fmt::Display for ParsedPath {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
for (access, _) in self.0.iter() {
|
for OffsetAccess { access, .. } in &self.0 {
|
||||||
write!(f, "{access}")?;
|
write!(f, "{access}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate as bevy_reflect;
|
use crate as bevy_reflect;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use access::TypeShape;
|
use error::{AccessErrorKind, TypeKind};
|
||||||
|
|
||||||
#[derive(Reflect)]
|
#[derive(Reflect)]
|
||||||
struct A {
|
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 {
|
fn access_field(field: &'static str) -> Access {
|
||||||
Access::Field(field.into())
|
Access::Field(field.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaticError = ReflectPathError<'static>;
|
type StaticError = ReflectPathError<'static>;
|
||||||
|
|
||||||
fn invalid_access(offset: usize, actual: TypeShape, expected: TypeShape) -> StaticError {
|
fn invalid_access(
|
||||||
let error = AccessError(access::Error::Type { actual, expected });
|
offset: usize,
|
||||||
ReflectPathError::InvalidAccess { offset, error }
|
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]
|
#[test]
|
||||||
fn parsed_path_parse() {
|
fn parsed_path_parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("w").unwrap().0,
|
ParsedPath::parse("w").unwrap().0,
|
||||||
&[(access_field("w"), 1)]
|
&[offset(access_field("w"), 1)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("x.foo").unwrap().0,
|
ParsedPath::parse("x.foo").unwrap().0,
|
||||||
&[(access_field("x"), 1), (access_field("foo"), 2)]
|
&[offset(access_field("x"), 1), offset(access_field("foo"), 2)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("x.łørđ.mосква").unwrap().0,
|
ParsedPath::parse("x.łørđ.mосква").unwrap().0,
|
||||||
&[
|
&[
|
||||||
(access_field("x"), 1),
|
offset(access_field("x"), 1),
|
||||||
(access_field("łørđ"), 2),
|
offset(access_field("łørđ"), 2),
|
||||||
(access_field("mосква"), 10)
|
offset(access_field("mосква"), 10)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("y[1].mосква").unwrap().0,
|
ParsedPath::parse("y[1].mосква").unwrap().0,
|
||||||
&[
|
&[
|
||||||
(access_field("y"), 1),
|
offset(access_field("y"), 1),
|
||||||
(Access::ListIndex(1), 2),
|
offset(Access::ListIndex(1), 2),
|
||||||
(access_field("mосква"), 5)
|
offset(access_field("mосква"), 5)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("z.0.1").unwrap().0,
|
ParsedPath::parse("z.0.1").unwrap().0,
|
||||||
&[
|
&[
|
||||||
(access_field("z"), 1),
|
offset(access_field("z"), 1),
|
||||||
(Access::TupleIndex(0), 2),
|
offset(Access::TupleIndex(0), 2),
|
||||||
(Access::TupleIndex(1), 4),
|
offset(Access::TupleIndex(1), 4),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("x#0").unwrap().0,
|
ParsedPath::parse("x#0").unwrap().0,
|
||||||
&[(access_field("x"), 1), (Access::FieldIndex(0), 2)]
|
&[
|
||||||
|
offset(access_field("x"), 1),
|
||||||
|
offset(Access::FieldIndex(0), 2)
|
||||||
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("x#0#1").unwrap().0,
|
ParsedPath::parse("x#0#1").unwrap().0,
|
||||||
&[
|
&[
|
||||||
(access_field("x"), 1),
|
offset(access_field("x"), 1),
|
||||||
(Access::FieldIndex(0), 2),
|
offset(Access::FieldIndex(0), 2),
|
||||||
(Access::FieldIndex(1), 4)
|
offset(Access::FieldIndex(1), 4)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -605,52 +734,59 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
a.reflect_path("x.notreal").err().unwrap(),
|
a.reflect_path("x.notreal").err().unwrap(),
|
||||||
ReflectPathError::InvalidAccess {
|
ReflectPathError::InvalidAccess(AccessError {
|
||||||
offset: 2,
|
kind: AccessErrorKind::MissingField(TypeKind::Struct),
|
||||||
error: AccessError(access::Error::Access {
|
access: access_field("notreal"),
|
||||||
ty: TypeShape::Struct,
|
offset: Some(2),
|
||||||
access: access_field("notreal"),
|
})
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
a.reflect_path("unit_variant.0").err().unwrap(),
|
a.reflect_path("unit_variant.0").err().unwrap(),
|
||||||
ReflectPathError::InvalidAccess {
|
ReflectPathError::InvalidAccess(AccessError {
|
||||||
offset: 13,
|
kind: AccessErrorKind::IncompatibleEnumVariantTypes {
|
||||||
error: AccessError(access::Error::Enum {
|
actual: VariantType::Unit,
|
||||||
actual: TypeShape::Unit,
|
expected: VariantType::Tuple,
|
||||||
expected: TypeShape::Tuple
|
},
|
||||||
}),
|
access: ParsedPath::parse_static("unit_variant.0").unwrap()[1]
|
||||||
}
|
.access
|
||||||
|
.clone(),
|
||||||
|
offset: Some(13),
|
||||||
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
a.reflect_path("x[0]").err().unwrap(),
|
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!(
|
assert_eq!(
|
||||||
a.reflect_path("y.x").err().unwrap(),
|
a.reflect_path("y.x").err().unwrap(),
|
||||||
invalid_access(2, TypeShape::List, TypeShape::Struct)
|
invalid_access(2, TypeKind::List, TypeKind::Struct, "y.x")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accept_leading_tokens() {
|
fn accept_leading_tokens() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse(".w").unwrap().0,
|
ParsedPath::parse(".w").unwrap().0,
|
||||||
&[(access_field("w"), 1)]
|
&[offset(access_field("w"), 1)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("#0.foo").unwrap().0,
|
ParsedPath::parse("#0.foo").unwrap().0,
|
||||||
&[(Access::FieldIndex(0), 1), (access_field("foo"), 3)]
|
&[
|
||||||
|
offset(Access::FieldIndex(0), 1),
|
||||||
|
offset(access_field("foo"), 3)
|
||||||
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse(".5").unwrap().0,
|
ParsedPath::parse(".5").unwrap().0,
|
||||||
&[(Access::TupleIndex(5), 1)]
|
&[offset(Access::TupleIndex(5), 1)]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&*ParsedPath::parse("[0].łørđ").unwrap().0,
|
ParsedPath::parse("[0].łørđ").unwrap().0,
|
||||||
&[(Access::ListIndex(0), 1), (access_field("łørđ"), 4)]
|
&[
|
||||||
|
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;
|
use thiserror::Error;
|
||||||
|
|
||||||
@ -102,12 +106,15 @@ impl<'a> Iterator for PathParser<'a> {
|
|||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let token = self.next_token()?;
|
let token = self.next_token()?;
|
||||||
let offset = self.offset();
|
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,
|
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<'_> {
|
impl fmt::Display for Token<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let text = match self {
|
match self {
|
||||||
Token::Dot => ".",
|
Token::Dot => f.write_char('.'),
|
||||||
Token::Pound => "#",
|
Token::Pound => f.write_char('#'),
|
||||||
Token::OpenBracket => "[",
|
Token::OpenBracket => f.write_char('['),
|
||||||
Token::CloseBracket => "]",
|
Token::CloseBracket => f.write_char(']'),
|
||||||
Token::Ident(ident) => ident.0,
|
Token::Ident(ident) => f.write_str(ident.0),
|
||||||
};
|
}
|
||||||
f.write_str(text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Token<'a> {
|
impl<'a> Token<'a> {
|
||||||
|
Loading…
Reference in New Issue
Block a user