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-7145main
parent
77fd92bbb2
commit
955131217b
|
@ -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) => {
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
)))),
|
||||
|
|
Loading…
Reference in New Issue