tamer: ir::xir::tree::Element::attrs: Wrap in Option

This allows AttrList not only to be lazily initialized (which is less of a
problem at the moment with Vec, but may become one in the future), but also
leaves a space open for attributes to be added _after_ having been
parsed.  It further leaves room to _take_ attributes from their `Element`.

This is important because the next commit will re-introduce the ability to
parse attributes independently, allowing us to put the parser in a state
where we can parse AttrList without an Element context.  To re-use that
parsing under an Element context, we can simply attach an AttrList after it
has been parsed.

Option adds no additional size cost to Vec, so we get this for free (except
for the tiny change that initializes the attribute list when we try to push
to it).

I also think this reads better ("attrs: None").  Though it makes the API
slightly more of a pain to work with.

DEV-10863
main
Mike Gerwitz 2021-10-29 16:34:05 -04:00
parent a9fd1c7557
commit d045786cfb
4 changed files with 24 additions and 18 deletions

View File

@ -307,7 +307,7 @@ impl Tree {
pub struct Element {
name: QName,
/// Zero or more attributes.
attrs: AttrList,
attrs: Option<AttrList>,
/// Zero or more child nodes.
children: Vec<Tree>,
/// Spans for opening and closing tags respectively.
@ -329,8 +329,8 @@ impl Element {
/// Attributes of this element.
#[inline]
pub fn attrs(&self) -> &AttrList {
&self.attrs
pub fn attrs(&self) -> Option<&AttrList> {
self.attrs.as_ref()
}
/// Opens an element for incremental construction.
@ -343,7 +343,7 @@ impl Element {
fn open(name: QName, span: Span) -> Self {
Self {
name,
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (span, span), // We do not yet know where the span will end
}
@ -450,7 +450,7 @@ impl ElementStack {
/// Push the provided [`Attr`] onto the attribute list of the inner
/// [`Element`].
fn consume_attr(mut self, attr: Attr) -> Self {
self.element.attrs.push(attr);
self.element.attrs.get_or_insert_default().push(attr);
self
}

View File

@ -39,7 +39,7 @@ mod tree {
fn element_from_tree() {
let ele = Element {
name: "foo".unwrap_into(),
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (*S, *S2),
};
@ -94,7 +94,7 @@ fn empty_element_self_close_from_toks() {
let expected = Element {
name,
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (*S, *S2),
};
@ -120,7 +120,7 @@ fn empty_element_balanced_close_from_toks() {
let expected = Element {
name,
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (*S, *S2),
};
@ -188,14 +188,14 @@ fn empty_element_with_attrs_from_toks() {
let expected = Element {
name,
attrs: AttrList::from(vec![
attrs: Some(AttrList::from(vec![
Attr::new(attr1, val1, (*S, *S2)),
Attr::from_fragments(
attr2,
*S,
vec![(val2a, *S), (val2b, *S2), (val2c, *S3)],
),
]),
])),
children: vec![],
span: (*S, *S2),
};
@ -234,17 +234,17 @@ fn element_with_empty_sibling_children() {
let expected = Element {
name: parent,
attrs: AttrList::new(),
attrs: None,
children: vec![
Tree::Element(Element {
name: childa,
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (*S, *S2),
}),
Tree::Element(Element {
name: childb,
attrs: AttrList::new(),
attrs: None,
children: vec![],
span: (*S, *S2),
}),
@ -278,10 +278,10 @@ fn element_with_child_with_attributes() {
let expected = Element {
name: parent,
attrs: AttrList::new(),
attrs: None,
children: vec![Tree::Element(Element {
name: child,
attrs: AttrList::from([Attr::new(attr, value, (*S, *S2))]),
attrs: Some(AttrList::from([Attr::new(attr, value, (*S, *S2))])),
children: vec![],
span: (*S, *S3),
})],
@ -308,7 +308,7 @@ fn element_with_text() {
let expected = Element {
name: parent,
attrs: AttrList::new(),
attrs: None,
children: vec![Tree::Text(text, *S2)],
span: (*S, *S3),
};
@ -335,7 +335,7 @@ fn parser_from_filters_incomplete() {
let expected = Element {
name,
attrs: AttrList::from([Attr::new(attr, val, (*S, *S2))]),
attrs: Some(AttrList::from([Attr::new(attr, val, (*S, *S2))])),
children: vec![],
span: (*S, *S2),
};

View File

@ -252,7 +252,7 @@ fn test_writes_deps() -> TestResult {
p_syms.enumerate().for_each(|(i, ele)| {
let ident = &objs[i];
let attrs = ele.attrs();
let attrs = ele.attrs().unwrap();
assert_eq!(
attrs.find(QN_NAME).and_then(|a| a.value_atom()),
@ -435,6 +435,7 @@ fn test_writes_map_froms() -> TestResult {
from.as_element()
.unwrap()
.attrs()
.unwrap()
.find(QN_NAME)
.expect("expecting @name")
.value_atom()

View File

@ -36,6 +36,11 @@
// Can be replaced with `assert!(matches!(...))`,
// but at a loss of a better error message.
#![feature(assert_matches)]
// Simplifies creating `Option` default values.
// To remove this feature,
// this can be done more verbosely in the usual way,
// or we can write our own version.
#![feature(option_get_or_insert_default)]
// We build docs for private items.
#![allow(rustdoc::private_intra_doc_links)]