tamer: xir::parse::ele: Attribute dead state recovery

If attributes fail to parse (e.g. missing required attribute) and parsing
reaches a dead state, this will recover by ignoring the entire element.  It
previously panicked with a TODO.

DEV-7145
main
Mike Gerwitz 2022-08-18 01:05:52 -04:00
parent 77fd92bbb2
commit 955131217b
3 changed files with 98 additions and 19 deletions

View File

@ -356,7 +356,7 @@ where
tok: <Self as ParseState>::Token,
mut context: C,
into: impl FnOnce(<Self as ParseState>::Super) -> Transition<SP>,
_dead: impl FnOnce() -> Transition<SP>,
dead: impl FnOnce() -> Transition<SP>,
objf: impl FnOnce(
<Self as ParseState>::Super,
<Self as ParseState>::Object,
@ -372,9 +372,8 @@ where
self.parse_token(tok, context.as_mut());
match data {
TransitionData::Dead(Lookahead(_lookahead)) => {
// Or restrict this to certain types of ParseState
todo!("expecting object, so what should we do on Dead?")
TransitionData::Dead(Lookahead(lookahead)) => {
dead().incomplete().with_lookahead(lookahead)
}
TransitionData::Result(Ok(Obj(obj)), lookahead) => {

View File

@ -495,7 +495,7 @@ macro_rules! ele_parse {
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::OpenSpan,
crate::xir::flat::Depth
),
crate::span::Span
@ -505,7 +505,7 @@ macro_rules! ele_parse {
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::OpenSpan,
crate::xir::flat::Depth
),
[<$nt AttrsState_>]
@ -515,7 +515,7 @@ macro_rules! ele_parse {
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::OpenSpan,
crate::xir::flat::Depth
),
),
@ -524,7 +524,7 @@ macro_rules! ele_parse {
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::OpenSpan,
crate::xir::flat::Depth
),
),
@ -759,7 +759,7 @@ macro_rules! ele_parse {
/// The span corresponds to the opening tag.
CloseExpected(
crate::xir::QName,
crate::span::Span,
crate::xir::OpenSpan,
crate::xir::flat::XirfToken<crate::xir::flat::RefinedText>,
),
@ -837,7 +837,10 @@ macro_rules! ele_parse {
diagnose::Annotate,
fmt::{DisplayWrapper, TtQuote},
parse::Token,
xir::fmt::{TtCloseXmlEle},
xir::{
EleSpan,
fmt::TtCloseXmlEle,
},
};
match self {
@ -857,8 +860,8 @@ macro_rules! ele_parse {
.into()
}
Self::CloseExpected(qname, span, tok) => vec![
span.note("element starts here"),
Self::CloseExpected(qname, ospan, tok) => vec![
ospan.span().note("element starts here"),
tok.span().error(format!(
"expected {}",
TtCloseXmlEle::wrap(qname),
@ -912,7 +915,7 @@ macro_rules! ele_parse {
XirfToken::Open(qname, span, depth)
) if $nt::matches(qname) => {
let transition = Transition(Attrs_(
(cfg, qname, span.tag_span(), depth),
(cfg, qname, span, depth),
parse_attrs(qname, span)
));
@ -938,7 +941,7 @@ macro_rules! ele_parse {
XirfToken::Open(qname, span, depth)
) if cfg.repeat && Self::matches(qname) => {
Transition(Attrs_(
(cfg, qname, span.tag_span(), depth),
(cfg, qname, span, depth),
parse_attrs(qname, span)
)).incomplete()
},
@ -1027,12 +1030,16 @@ macro_rules! ele_parse {
// which overrides this match directly above
// (xref <<SATTR>>).
#[allow(unreachable_patterns)]
(Attrs_(meta @ (_, qname, _, _), sa), tok) => {
(Attrs_(meta @ (cfg, qname, span, depth), sa), tok) => {
sa.delegate_until_obj::<Self, _>(
tok,
EmptyContext,
|sa| Transition(Attrs_(meta, sa)),
|| unreachable!("see ParseState::delegate_until_obj dead"),
// If we enter a dead state then we have
// failed produce an attribute object,
// in which case we'll recover by
// ignoring the entire element.
|| Transition(RecoverEleIgnore_(cfg, qname, span, depth)),
|#[allow(unused_variables)] sa, attrs| {
let obj = match attrs {
// Attribute field bindings for `$attrmap`

View File

@ -274,7 +274,7 @@ fn empty_element_ns_prefix_invalid_close_contains_matching_qname() {
ParseError::StateError(SutError_::Root(RootError_::CloseExpected(
// Verify that the error includes the QName that actually matched.
QN_C_EQ,
OpenSpan(S1, N).tag_span(),
OpenSpan(S1, N),
XirfToken::Open(unexpected, span_unexpected, Depth(1)),
))),
err,
@ -370,6 +370,79 @@ fn empty_element_with_attr_bindings() {
);
}
// This only tests one scenario under which attribute parsing may fail
// (others are tested with `attr_parse!`).
// Failure to parse an attribute is considered a failure at the element
// level and recovery will skip the entire element.
#[test]
fn element_with_failed_attr_parsing() {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Open,
Close,
Child,
}
impl crate::parse::Object for Foo {}
const QN_ROOT: QName = QN_PACKAGE;
const QN_CHILD: QName = QN_DIM;
ele_parse! {
enum Sut;
type Object = Foo;
Root := QN_ROOT {
@ {
_name: (QN_NAME) => Attr,
} => Foo::Open,
// Important to check that this is not emitted.
/ => Foo::Close,
};
Child := QN_CHILD {
@ {} => Foo::Child,
};
}
let toks = vec![
XirfToken::Open(QN_ROOT, OpenSpan(S1, N), Depth(0)),
// Child elements should be ignored.
XirfToken::Open(QN_CHILD, OpenSpan(S4, N), Depth(1)),
XirfToken::Close(None, CloseSpan::empty(S5), Depth(1)),
// Recovery ends at the closing tag.
XirfToken::Close(Some(QN_ROOT), CloseSpan::empty(S6), Depth(0)),
];
let mut sut = Sut::parse(toks.into_iter());
use Parsed::*;
// Root will open normally.
assert_eq!(sut.next(), Some(Ok(Incomplete))); // [Root] Root Open
// But the child will result in an error because we have not provided a
// required attribute.
let err = sut.next().unwrap().unwrap_err();
assert_matches!(
err,
ParseError::StateError(SutError_::Root(RootError_::Attrs(..))),
); // [Root] Child Open (>LA)
// The remaining tokens should be ignored and we should finish parsing.
// Since the opening object was not emitted,
// we must not emit the closing.
assert_eq!(
Ok(vec![
Incomplete, // [Root!] Child Open (<LA)
Incomplete, // [Root!] Child Close
Incomplete, // [Root] Root Close
]),
sut.collect(),
);
}
// Rather than using aggregate attributes,
// `[test]` allows for dynamic streaming attribute parsing.
// This is necessary for elements like short-hand template applications.
@ -965,7 +1038,7 @@ fn child_error_and_recovery_at_close() {
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::Root(RootError_::CloseExpected(
QN_PACKAGE,
OpenSpan(S1, N).tag_span(),
OpenSpan(S1, N),
XirfToken::Open(unexpected_a, span_a, Depth(1)),
))),
err,
@ -1669,7 +1742,7 @@ fn child_repetition_invalid_tok_dead() {
Some(Err(ParseError::StateError(SutError_::Root(
RootError_::CloseExpected(
QN_ROOT,
OpenSpan(S1, N).tag_span(),
OpenSpan(S1, N),
XirfToken::Open(unexpected, OpenSpan(S2, N), Depth(1)),
)
)))),