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-7145
main
Mike Gerwitz 2022-07-21 00:27:33 -04:00
parent 184ff6bdcc
commit 3a764d111e
4 changed files with 191 additions and 26 deletions

View File

@ -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

View File

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

View File

@ -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?) => ...`.

View File

@ -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]