tamer: xir::parse::attr: Fallible value parsing
Values can be parsed using `TryFrom<Attr>`. Previously only `From<Attr>` was supported, which could not fail. This is critical for parsing values into types, which will wrap `SymbolId` to provide data assurances. DEV-7145main
parent
184ff6bdcc
commit
3a764d111e
|
@ -82,7 +82,7 @@ pub use report::{Reporter, VisualReporter};
|
|||
pub use resolve::FsSpanResolver;
|
||||
|
||||
use core::fmt;
|
||||
use std::{borrow::Cow, error::Error, fmt::Display};
|
||||
use std::{borrow::Cow, convert::Infallible, error::Error, fmt::Display};
|
||||
|
||||
use crate::span::Span;
|
||||
|
||||
|
@ -97,6 +97,14 @@ pub trait Diagnostic: Error + Sized {
|
|||
fn describe(&self) -> Vec<AnnotatedSpan>;
|
||||
}
|
||||
|
||||
impl Diagnostic for Infallible {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
// This should never actually happen unless someone is explicitly
|
||||
// invoking this method on `Infallible`.
|
||||
unreachable!("Infallible is not supposed to fail")
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic severity level.
|
||||
///
|
||||
/// Levels are used both for entire reports and for styling of individual
|
||||
|
|
|
@ -177,7 +177,9 @@ impl crate::parse::Object for Attr {}
|
|||
|
||||
impl Display for Attr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "`@{}=\"{}\"` at {}", self.0, self.1, self.2 .0)
|
||||
match self {
|
||||
Self(key, value, _) => write!(f, "`@{key}=\"{value}\"`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ use crate::{
|
|||
span::Span,
|
||||
xir::{attr::Attr, fmt::XmlAttrList, EleSpan, OpenSpan, QName},
|
||||
};
|
||||
use std::{error::Error, fmt::Display};
|
||||
use std::{convert::Infallible, error::Error, fmt::Display};
|
||||
|
||||
pub type ElementQName = QName;
|
||||
pub type FirstSpan = Span;
|
||||
|
@ -88,10 +88,16 @@ pub enum AttrParseError<S: AttrParseState> {
|
|||
/// displaying an attribute value in an error message is asking for
|
||||
/// too much trouble given that it is arbitrary text.
|
||||
DuplicateAttr(Attr, FirstSpan, ElementQName),
|
||||
|
||||
/// An error occurred while parsing an attribute value into the
|
||||
/// declared type.
|
||||
InvalidValue(S::ValueError, ElementQName),
|
||||
}
|
||||
|
||||
impl<S: AttrParseState> Display for AttrParseError<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use crate::fmt::{DisplayWrapper, TtQuote};
|
||||
|
||||
match self {
|
||||
Self::MissingRequired(st) => {
|
||||
let ele_name = st.element_name();
|
||||
|
@ -115,6 +121,11 @@ impl<S: AttrParseState> Display for AttrParseError<S> {
|
|||
element element `{ele_name}`"
|
||||
)
|
||||
}
|
||||
|
||||
Self::InvalidValue(ev, ele_name) => {
|
||||
Display::fmt(ev, f)?;
|
||||
write!(f, " for element {}", TtQuote::wrap(ele_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +168,8 @@ impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
|
|||
)),
|
||||
]
|
||||
}
|
||||
|
||||
Self::InvalidValue(ev, _) => ev.describe(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +178,15 @@ impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
|
|||
///
|
||||
/// These parsers are generated by [`attr_parse!`](crate::attr_parse).
|
||||
pub trait AttrParseState: ParseState {
|
||||
/// Type of error for failed parsing of attribute values.
|
||||
///
|
||||
/// These originate from [`TryFrom`] conversions on the attribute
|
||||
/// value.
|
||||
/// The default is [`Infallible`],
|
||||
/// meaning such conversion cannot fail and [`From`] may be used in
|
||||
/// place of [`TryFrom`].
|
||||
type ValueError: Diagnostic = Infallible;
|
||||
|
||||
/// Begin attribute parsing within the context of the provided element.
|
||||
///
|
||||
/// This is used to provide diagnostic information.
|
||||
|
@ -206,7 +228,9 @@ pub fn parse_attrs<S: AttrParseState>(ele: QName, span: OpenSpan) -> S {
|
|||
#[macro_export]
|
||||
macro_rules! attr_parse {
|
||||
($(#[$sattr:meta])*
|
||||
$vis:vis struct $state_name:ident -> $struct_name:ident {
|
||||
$(type ValueError = $evty:ty;)?
|
||||
|
||||
struct $state_name:ident -> $struct_name:ident {
|
||||
$(
|
||||
$(#[$fattr:meta])*
|
||||
$field:ident: ($qname:ident $($fmod:tt)?) => $ty:ty,
|
||||
|
@ -232,7 +256,7 @@ macro_rules! attr_parse {
|
|||
/// This object is exposed for recovery and error reporting on
|
||||
/// [`AttrParseError::MissingRequired`].
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
$vis struct $state_name {
|
||||
struct $state_name {
|
||||
#[doc(hidden)]
|
||||
___ctx: (crate::xir::QName, crate::xir::OpenSpan),
|
||||
#[doc(hidden)]
|
||||
|
@ -244,6 +268,8 @@ macro_rules! attr_parse {
|
|||
}
|
||||
|
||||
impl crate::xir::parse::AttrParseState for $state_name {
|
||||
type ValueError = $crate::attr_parse!(@evty $($evty)?);
|
||||
|
||||
fn with_element(
|
||||
ele: crate::xir::QName,
|
||||
span: crate::xir::OpenSpan
|
||||
|
@ -332,7 +358,7 @@ macro_rules! attr_parse {
|
|||
"`]."
|
||||
)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
$vis struct $struct_name {
|
||||
struct $struct_name {
|
||||
$(
|
||||
$(#[$fattr])*
|
||||
pub $field: $ty,
|
||||
|
@ -378,6 +404,8 @@ macro_rules! attr_parse {
|
|||
#[allow(unused_imports)]
|
||||
use crate::xir::attr::{Attr, AttrSpan}; // unused if no attrs
|
||||
|
||||
let ele_name = self.element_name();
|
||||
|
||||
match tok {
|
||||
$(
|
||||
// Use guard so we don't bind as a variable if we
|
||||
|
@ -390,8 +418,6 @@ macro_rules! attr_parse {
|
|||
match self.$field {
|
||||
// Duplicate attribute name
|
||||
Some((_, first_kspan)) => {
|
||||
let ele_name = self.element_name();
|
||||
|
||||
Transition(self).err(
|
||||
AttrParseError::DuplicateAttr(
|
||||
attr,
|
||||
|
@ -403,22 +429,36 @@ macro_rules! attr_parse {
|
|||
|
||||
// First time seeing attribute name
|
||||
None => {
|
||||
self.$field.replace((
|
||||
$crate::attr_parse!(
|
||||
@into_value $($fmod)? attr
|
||||
),
|
||||
kspan,
|
||||
));
|
||||
let result = $crate::attr_parse!(
|
||||
@into_value $($fmod)? attr
|
||||
);
|
||||
|
||||
Transition(self).incomplete()
|
||||
match result {
|
||||
Ok(value) => {
|
||||
self.$field.replace((
|
||||
value,
|
||||
kspan,
|
||||
));
|
||||
|
||||
Transition(self).incomplete()
|
||||
},
|
||||
|
||||
Err(e) => Transition(self).err(
|
||||
// Will complain about
|
||||
// `Into::into` if Infallible.
|
||||
#[allow(unreachable_code)]
|
||||
AttrParseError::InvalidValue(
|
||||
e.into(),
|
||||
ele_name,
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
flat::XirfToken::Attr(attr) => {
|
||||
let ele_name = self.element_name();
|
||||
|
||||
Transition(self).err(AttrParseError::UnexpectedAttr(
|
||||
attr,
|
||||
ele_name,
|
||||
|
@ -459,16 +499,27 @@ macro_rules! attr_parse {
|
|||
// on.
|
||||
const _: fn() = || {
|
||||
trait OptionFromAttr {}
|
||||
impl<T: From<Attr>> OptionFromAttr for Option<T> {}
|
||||
impl<T: TryFrom<Attr>> OptionFromAttr for Option<T> {}
|
||||
|
||||
// Fail when `$ty` is not Option<impl From<Attr>>.
|
||||
// Fail when `$ty` is not Option<impl TryFrom<Attr>>.
|
||||
fn assert_attr_option<T: OptionFromAttr>() {}
|
||||
assert_attr_option::<$ty>();
|
||||
};
|
||||
};
|
||||
|
||||
(@ty_assert $ty:ty) => {
|
||||
assert_impl_all!($ty: From<crate::xir::attr::Attr>);
|
||||
assert_impl_all!($ty: TryFrom<crate::xir::attr::Attr>);
|
||||
};
|
||||
|
||||
(@evty $evty:ty) => {
|
||||
$evty
|
||||
};
|
||||
|
||||
// If no ValueError type is provided,
|
||||
// then it's not possible for values to fail parsing
|
||||
// (their SymbolId is their final value).
|
||||
(@evty) => {
|
||||
std::convert::Infallible
|
||||
};
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
|
@ -486,11 +537,11 @@ macro_rules! attr_parse {
|
|||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
(@into_value ? $from:ident) => {
|
||||
Some($from.into())
|
||||
$from.try_into().map(Some)
|
||||
};
|
||||
|
||||
(@into_value $from:ident) => {
|
||||
$from.into()
|
||||
$from.try_into()
|
||||
};
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
|
|
|
@ -21,6 +21,7 @@ use super::*;
|
|||
use crate::{
|
||||
parse::{ParseError, ParseState, Parsed, Parser, TokenStream},
|
||||
span::{Span, DUMMY_SPAN},
|
||||
sym::SymbolId,
|
||||
xir::{
|
||||
attr::{Attr, AttrSpan},
|
||||
flat::{test::close_empty, Depth, XirfToken},
|
||||
|
@ -185,24 +186,127 @@ fn optional_with_values() {
|
|||
);
|
||||
}
|
||||
|
||||
// This test would fail at compile time.
|
||||
#[test]
|
||||
fn attr_value_into() {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Foo;
|
||||
struct Foo(SymbolId);
|
||||
|
||||
impl From<Attr> for Foo {
|
||||
fn from(_: Attr) -> Self {
|
||||
fn from(attr: Attr) -> Self {
|
||||
Foo(attr.value())
|
||||
}
|
||||
}
|
||||
|
||||
attr_parse! {
|
||||
// Note that associated type `ValueError` defaults to `Infallible`,
|
||||
// which is why `From` is sufficient above.
|
||||
struct ValueIntoState -> ValueInto {
|
||||
name: (QN_NAME) => Foo,
|
||||
yields: (QN_YIELDS?) => Option<Foo>,
|
||||
}
|
||||
}
|
||||
|
||||
let val_name = "val_name".into();
|
||||
let val_yields = "val_yields".into();
|
||||
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
ValueInto {
|
||||
name: Foo(val_name),
|
||||
yields: Some(Foo(val_yields)),
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<ValueIntoState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// This test would fail at compile time.
|
||||
#[test]
|
||||
fn attr_value_error() {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Foo;
|
||||
|
||||
impl TryFrom<Attr> for Foo {
|
||||
type Error = FooError;
|
||||
|
||||
fn try_from(attr: Attr) -> Result<Self, Self::Error> {
|
||||
Err(FooError(attr.value()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FooError(SymbolId);
|
||||
|
||||
impl Error for FooError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FooError {
|
||||
fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for FooError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
attr_parse! {
|
||||
struct OptValuesState -> OptValues {
|
||||
type ValueError = FooError;
|
||||
|
||||
struct ValueIntoState -> ValueInto {
|
||||
name: (QN_NAME) => Foo,
|
||||
yields: (QN_YIELDS?) => Option<Foo>,
|
||||
}
|
||||
}
|
||||
|
||||
let val_name = "val_name".into();
|
||||
let val_yields = "val_yields".into();
|
||||
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
];
|
||||
|
||||
let mut sut = Parser::with_state(
|
||||
ValueIntoState::with_element(QN_ELE, SE),
|
||||
toks.into_iter(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(val_name),
|
||||
QN_ELE
|
||||
))))
|
||||
);
|
||||
|
||||
// TryInto on `Option` inner type.
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(val_yields),
|
||||
QN_ELE
|
||||
))))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue