diff --git a/parsers/src/util/macro_tests.rs b/parsers/src/util/macro_tests.rs
index 173e77b9960c19a75b0d41a71cd69af5ee16381d..d66a30d3af0a40939ea8ee578839bc14ea9da5e5 100644
--- a/parsers/src/util/macro_tests.rs
+++ b/parsers/src/util/macro_tests.rs
@@ -1287,3 +1287,236 @@ fn text_extract_attribute_vec_roundtrip() {
"",
)
}
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "parent")]
+struct TextOptionalExtract {
+ #[xml(extract(namespace = NS1, name = "child", default, fields(text(type_ = String))))]
+ contents: ::std::option::Option,
+}
+
+#[test]
+fn text_optional_extract_positive_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "hello world",
+ ) {
+ Ok(TextOptionalExtract {
+ contents: Some(contents),
+ }) => {
+ assert_eq!(contents, "hello world");
+ }
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn text_optional_extract_positive_absent_child() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::("") {
+ Ok(TextOptionalExtract { contents: None }) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn text_optional_extract_roundtrip_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "hello world!",
+ )
+}
+
+#[test]
+fn text_optional_extract_roundtrip_absent() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::("")
+}
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "parent")]
+struct OptionalAttributeOptionalExtract {
+ #[xml(extract(namespace = NS1, name = "child", default, fields(attribute(name = "foo", default))))]
+ contents: ::std::option::Option,
+}
+
+#[test]
+fn optional_attribute_optional_extract_positive_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ Ok(OptionalAttributeOptionalExtract {
+ contents: Some(contents),
+ }) => {
+ assert_eq!(contents, "hello world");
+ }
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_positive_absent_attribute() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ Ok(OptionalAttributeOptionalExtract { contents: None }) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_positive_absent_element() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::("") {
+ Ok(OptionalAttributeOptionalExtract { contents: None }) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_roundtrip_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "",
+ )
+}
+
+#[test]
+fn optional_attribute_optional_extract_roundtrip_absent_attribute() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "",
+ )
+}
+
+#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
+#[xml(namespace = NS1, name = "parent")]
+struct OptionalAttributeOptionalExtractDoubleOption {
+ #[xml(extract(namespace = NS1, name = "child", default, fields(attribute(name = "foo", type_ = ::std::option::Option, default))))]
+ contents: ::std::option::Option<::std::option::Option>,
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_positive_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ Ok(OptionalAttributeOptionalExtractDoubleOption {
+ contents: Some(Some(contents)),
+ }) => {
+ assert_eq!(contents, "hello world");
+ }
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_positive_absent_attribute() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ Ok(OptionalAttributeOptionalExtractDoubleOption {
+ contents: Some(None),
+ }) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_positive_absent_element() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ match parse_str::(
+ "",
+ ) {
+ Ok(OptionalAttributeOptionalExtractDoubleOption { contents: None }) => (),
+ other => panic!("unexpected result: {:?}", other),
+ }
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_roundtrip_present() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "",
+ )
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_roundtrip_absent_attribute() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "",
+ )
+}
+
+#[test]
+fn optional_attribute_optional_extract_double_option_roundtrip_absent_child() {
+ #[allow(unused_imports)]
+ use std::{
+ option::Option::{None, Some},
+ result::Result::{Err, Ok},
+ };
+ roundtrip_full::(
+ "",
+ )
+}
diff --git a/xso-proc/src/compound.rs b/xso-proc/src/compound.rs
index 97adab2d61dcccdc244c1dda6c8aaf7f9a282cf3..bf8ec8e17115177634ee1cfa87de5d4a3e208329 100644
--- a/xso-proc/src/compound.rs
+++ b/xso-proc/src/compound.rs
@@ -15,7 +15,7 @@ use crate::field::{FieldBuilderPart, FieldDef, FieldIteratorPart, FieldTempInit}
use crate::meta::NamespaceRef;
use crate::scope::{mangle_member, AsItemsScope, FromEventsScope};
use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
-use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty};
+use crate::types::{feed_fn, namespace_ty, ncnamestr_cow_ty, phantom_lifetime_ty, ref_ty};
/// A struct or enum variant's contents.
pub(crate) struct Compound {
@@ -517,4 +517,27 @@ impl Compound {
},
})
}
+
+ /// Construct a tuple type with this compound's field's types in the same
+ /// order as they appear in the compound.
+ pub(crate) fn to_tuple_ty(&self) -> TypeTuple {
+ TypeTuple {
+ paren_token: token::Paren::default(),
+ elems: self.fields.iter().map(|x| x.ty().clone()).collect(),
+ }
+ }
+
+ /// Construct a tuple type with references to this compound's field's
+ /// types in the same order as they appear in the compound, with the given
+ /// lifetime.
+ pub(crate) fn to_ref_tuple_ty(&self, lifetime: &Lifetime) -> TypeTuple {
+ TypeTuple {
+ paren_token: token::Paren::default(),
+ elems: self
+ .fields
+ .iter()
+ .map(|x| ref_ty(x.ty().clone(), lifetime.clone()))
+ .collect(),
+ }
+ }
}
diff --git a/xso-proc/src/field.rs b/xso-proc/src/field.rs
index e7ea7c190c81a952c3f497e750c585c379b52cad..083d0ef01fba45f10e0af70a40105297f27ff1d7 100644
--- a/xso-proc/src/field.rs
+++ b/xso-proc/src/field.rs
@@ -19,8 +19,8 @@ use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{
as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, extend_fn, from_events_fn,
from_xml_builder_ty, from_xml_text_fn, into_iterator_into_iter_fn, into_iterator_item_ty,
- into_iterator_iter_ty, item_iter_ty, option_ty, ref_ty, string_ty, text_codec_decode_fn,
- text_codec_encode_fn, ty_from_ident,
+ into_iterator_iter_ty, item_iter_ty, option_as_xml_ty, option_ty, ref_ty, string_ty,
+ text_codec_decode_fn, text_codec_encode_fn, ty_from_ident,
};
/// Code slices necessary for declaring and initializing a temporary variable
@@ -306,6 +306,7 @@ impl FieldKind {
XmlFieldMeta::Extract {
span,
+ default_,
qname: QNameRef { namespace, name },
amount,
fields,
@@ -352,7 +353,7 @@ impl FieldKind {
)?;
Ok(Self::Child {
- default_: Flag::Absent,
+ default_,
amount,
extract: Some(ExtractDef {
xml_namespace,
@@ -565,12 +566,7 @@ impl FieldDef {
&Visibility::Inherited,
&from_xml_builder_ty_ident,
&state_ty_ident,
- &Type::Tuple(TypeTuple {
- paren_token: token::Paren::default(),
- elems: [
- element_ty.clone(),
- ].into_iter().collect(),
- })
+ &parts.to_tuple_ty().into(),
)?;
let from_xml_builder_ty =
ty_from_ident(from_xml_builder_ty_ident.clone()).into();
@@ -580,7 +576,31 @@ impl FieldDef {
(
extra_defs,
matcher,
- quote! { #substate_result.0 },
+ // This little ".into()" here goes a long way. It
+ // relies on one of the most underrated trait
+ // implementations in the standard library:
+ // `impl From for Option`, which creates a
+ // `Some(_)` from a `T`. Why is it so great?
+ // Because there is also `impl From