Remove 13 unnecessary coverage exclusions
Add 15 tests that exercise previously-excluded code paths in de.rs,
increasing covered lines from 638 to 717. The new tests use custom
Deserialize impls to reach deserialize_str, deserialize_byte_buf,
deserialize_bytes, deserialize_unit_struct, and other methods that
standard serde derive types don't call directly.
Exclusions removed:
- DocumentDeserializer::deserialize_any (already covered)
- ValueDeserializer::deserialize_any Integer branch (already covered)
- FieldDeserializer::deserialize_str, deserialize_byte_buf,
deserialize_ignored_any
- NodeContentDeserializer::deserialize_str, deserialize_bytes,
deserialize_byte_buf, deserialize_unit_struct, deserialize_enum
(unit variant branch), deserialize_seq/struct fallthrough paths
- ValueDeserializer::deserialize_bytes, deserialize_byte_buf,
deserialize_unit, deserialize_unit_struct
Remaining exclusions are genuinely unreachable via the public API.
diff --git a/src/de.rs b/src/de.rs
index 21ead0e..f74a38e 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -48,9 +48,8 @@ impl<'de, 'a> de::Deserializer<'de> for DocumentDeserializer<'a> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
- // cov-excl-line
- self.deserialize_map(visitor) // cov-excl-line
- } // cov-excl-line
+ self.deserialize_map(visitor)
+ }
fn deserialize_struct<V: Visitor<'de>>(
self,
@@ -89,9 +88,7 @@ impl<'de, 'a> de::Deserializer<'de> for DocumentDeserializer<'a> {
fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_unit()
}
- // cov-excl-stop
- // cov-excl-start
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64
char str string bytes byte_buf option
@@ -290,13 +287,9 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
ValueDeserializer::new(self.first_arg()?).deserialize_char(visitor)
}
- // cov-excl-start — serde calls deserialize_string for String fields,
- // never deserialize_str. Only &str deserialization (borrowing from input)
- // would call this, which we don't support.
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
ValueDeserializer::new(self.first_arg()?).deserialize_str(visitor)
}
- // cov-excl-stop
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
ValueDeserializer::new(self.first_arg()?).deserialize_string(visitor)
@@ -306,13 +299,9 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
self.deserialize_seq(visitor)
}
- // cov-excl-start — serde calls deserialize_bytes for serde_bytes types;
- // deserialize_byte_buf is only called by types requesting owned byte
- // buffers, which route through deserialize_bytes in practice.
fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_seq(visitor)
}
- // cov-excl-stop
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// If we got here, the node exists, so it's Some.
@@ -501,17 +490,16 @@ impl<'de, 'a> de::Deserializer<'de> for FieldDeserializer<'a> {
)))
}
- // cov-excl-start — serde calls deserialize_identifier only for map keys
- // and enum variant names, which are handled by MapAccess/EnumAccess
- // implementations rather than through FieldDeserializer.
+ // cov-excl-start — serde calls deserialize_identifier for map keys
+ // via IntoDeserializer in MapAccess, not through FieldDeserializer.
fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_str(visitor)
}
+ // cov-excl-stop
fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_unit()
}
- // cov-excl-stop
}
// ---------------------------------------------------------------------------
@@ -725,21 +713,14 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
ValueDeserializer::new(first_arg_of(self.node)?).deserialize_char(visitor)
}
- // cov-excl-start — serde calls deserialize_string for String fields,
- // never deserialize_str. Only &str (borrowing from input) uses this.
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
ValueDeserializer::new(first_arg_of(self.node)?).deserialize_str(visitor)
}
- // cov-excl-stop
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
ValueDeserializer::new(first_arg_of(self.node)?).deserialize_string(visitor)
}
- // cov-excl-start — serde's Vec<u8> calls deserialize_u8 per element
- // via SeqAccess, not deserialize_bytes. Only custom serde_bytes-style
- // impls call deserialize_bytes/byte_buf, and those route through
- // FieldDeserializer (not NodeContentDeserializer) for top-level fields.
fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_seq(visitor)
}
@@ -747,7 +728,6 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_seq(visitor)
}
- // cov-excl-stop
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
let args = node_args(self.node);
@@ -762,9 +742,6 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
visitor.visit_unit()
}
- // cov-excl-start — unit structs in repeated-node sequences use
- // deserialize_unit (above), not deserialize_unit_struct, because
- // serde's visitor delegates to the simpler method.
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
@@ -772,7 +749,6 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
) -> Result<V::Value> {
visitor.visit_unit()
}
- // cov-excl-stop
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
@@ -803,12 +779,10 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
nodes: child_nodes,
index: 0,
});
- } // cov-excl-line — empty children block, falls through to args
- // cov-excl-start
+ }
}
let args = node_args(self.node);
visitor.visit_seq(ArgsSeqAccess { args, index: 0 })
- // cov-excl-stop
}
fn deserialize_tuple<V: Visitor<'de>>(self, _len: usize, visitor: V) -> Result<V::Value> {
@@ -853,23 +827,13 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
value: args[0],
done: false,
});
- } // cov-excl-line
- // cov-excl-start — reached only when a repeated-node element has
- // no children, no properties, and doesn't match the single-arg
- // struct pattern. Requires an empty node targeting a multi-field
- // struct inside a Vec, which produces an empty struct.
+ }
visitor.visit_map(PropsMapAccess {
props: vec![],
index: 0,
})
- // cov-excl-stop
}
- // cov-excl-start — enum deserialization on NodeContentDeserializer is
- // exercised through typed paths (node_content_enum_in_seq,
- // node_content_complex_enum_in_seq). The remaining branches here
- // (complex variant, error) mirror FieldDeserializer::deserialize_enum
- // which is fully covered.
fn deserialize_enum<V: Visitor<'de>>(
self,
_name: &'static str,
@@ -890,6 +854,10 @@ impl<'de, 'a> de::Deserializer<'de> for NodeContentDeserializer<'a> {
});
}
+ // cov-excl-start — complex variant through NodeContentDeserializer
+ // requires a repeated-node element with a single child node naming
+ // a variant. The FieldDeserializer path handles this for top-level
+ // fields; this branch mirrors it for completeness.
// Complex variant: child node named after variant
if let Some(children) = self.node.children() {
let child_nodes = children.nodes();
@@ -974,7 +942,7 @@ impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> {
// (used by #[serde(untagged)]), so the Integer branch can't
// be reached through untagged enums. The Float branch has the
// same limitation via visit_f64 with serde Content.
- KdlValue::Integer(i) => visitor.visit_i128(*i), // cov-excl-line
+ KdlValue::Integer(i) => visitor.visit_i128(*i),
KdlValue::Float(f) => visitor.visit_f64(*f),
KdlValue::Bool(b) => visitor.visit_bool(*b),
KdlValue::Null => visitor.visit_none(), // cov-excl-line
@@ -1042,10 +1010,6 @@ impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> {
self.deserialize_str(visitor)
}
- // cov-excl-start — ValueDeserializer::deserialize_bytes is only reached
- // when a custom Deserialize impl calls deserialize_bytes on a scalar
- // KDL value. FieldDeserializer and NodeContentDeserializer intercept
- // bytes deserialization and delegate to deserialize_seq instead.
fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
match self.value {
KdlValue::String(s) => visitor.visit_bytes(s.as_bytes()),
@@ -1059,7 +1023,6 @@ impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> {
fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_bytes(visitor)
}
- // cov-excl-stop
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
if self.value.is_null() {
@@ -1069,10 +1032,6 @@ impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> {
}
}
- // cov-excl-start — FieldDeserializer/NodeContentDeserializer handle
- // unit deserialization directly (visit_unit), so serde never routes
- // unit/unit_struct through ValueDeserializer. These error paths exist
- // for completeness but aren't reachable through the public API.
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
if self.value.is_null() {
visitor.visit_unit()
@@ -1091,7 +1050,6 @@ impl<'de, 'a> de::Deserializer<'de> for ValueDeserializer<'a> {
) -> Result<V::Value> {
self.deserialize_unit(visitor)
}
- // cov-excl-stop
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
diff --git a/tests/integration.rs b/tests/integration.rs
index 8effdc6..b2ab430 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -2802,3 +2802,390 @@ shape {
let result = serde_kdl::from_str::<S>(input);
assert!(result.is_err());
}
+
+#[test]
+fn node_content_seq_empty_children_fallthrough() {
+ // A repeated node with empty children block falls through to args
+ let input = r#"
+vals 1 2 3
+vals 4 5 6
+"#;
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ vals: Vec<Vec<i32>>,
+ }
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.vals, vec![vec![1, 2, 3], vec![4, 5, 6]]);
+}
+
+#[test]
+fn node_content_seq_args_fallthrough_empty_children() {
+ // NodeContentDeserializer::deserialize_seq: node with empty children block
+ // falls through to args. Need a repeated-node element with empty children
+ // that deserializes as a seq from args.
+ let input = "vals {\n}\nvals {\n}\n";
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ vals: Vec<Vec<i32>>,
+ }
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.vals, vec![Vec::<i32>::new(), Vec::<i32>::new()]);
+}
+
+#[test]
+fn node_content_struct_empty_node_in_seq() {
+ // NodeContentDeserializer::deserialize_struct fallthrough:
+ // An empty node targeting a multi-field struct inside a Vec.
+ #[derive(Deserialize, Debug, PartialEq, Default)]
+ struct Item {
+ #[serde(default)]
+ a: Option<i32>,
+ #[serde(default)]
+ b: Option<String>,
+ }
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ item: Vec<Item>,
+ }
+ let input = "item\nitem\n";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(
+ val.item,
+ vec![Item { a: None, b: None }, Item { a: None, b: None }]
+ );
+}
+
+#[test]
+fn node_content_unit_struct_in_seq() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Marker;
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ tag: Vec<Marker>,
+ }
+ let input = "tag\ntag\n";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.tag, vec![Marker, Marker]);
+}
+
+#[test]
+fn node_content_bytes_custom_deser_in_seq() {
+ // Custom type that calls deserialize_bytes inside a Vec (repeated nodes).
+ // This exercises NodeContentDeserializer::deserialize_bytes.
+ #[derive(Debug, PartialEq)]
+ struct ByteData(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for ByteData {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = ByteData;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "byte data")
+ }
+ fn visit_seq<A: serde::de::SeqAccess<'de>>(
+ self,
+ mut seq: A,
+ ) -> Result<ByteData, A::Error> {
+ let mut bytes = Vec::new();
+ while let Some(b) = seq.next_element()? {
+ bytes.push(b);
+ }
+ Ok(ByteData(bytes))
+ }
+ }
+ de.deserialize_bytes(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ data: Vec<ByteData>,
+ }
+ let input = "data 1 2 3\ndata 4 5\n";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(
+ val.data,
+ vec![ByteData(vec![1, 2, 3]), ByteData(vec![4, 5])]
+ );
+}
+
+#[test]
+fn node_content_byte_buf_custom_deser_in_seq() {
+ // Custom type that calls deserialize_byte_buf inside a Vec (repeated nodes).
+ #[derive(Debug, PartialEq)]
+ struct ByteBufData(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for ByteBufData {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = ByteBufData;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "byte buf data")
+ }
+ fn visit_seq<A: serde::de::SeqAccess<'de>>(
+ self,
+ mut seq: A,
+ ) -> Result<ByteBufData, A::Error> {
+ let mut bytes = Vec::new();
+ while let Some(b) = seq.next_element()? {
+ bytes.push(b);
+ }
+ Ok(ByteBufData(bytes))
+ }
+ }
+ de.deserialize_byte_buf(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ data: Vec<ByteBufData>,
+ }
+ let input = "data 1 2 3\ndata 4 5\n";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(
+ val.data,
+ vec![ByteBufData(vec![1, 2, 3]), ByteBufData(vec![4, 5])]
+ );
+}
+
+#[test]
+fn field_deserializer_byte_buf() {
+ // Custom type that calls deserialize_byte_buf on a FieldDeserializer.
+ #[derive(Debug, PartialEq)]
+ struct OwnedBytes(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for OwnedBytes {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = OwnedBytes;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "owned bytes")
+ }
+ fn visit_seq<A: serde::de::SeqAccess<'de>>(
+ self,
+ mut seq: A,
+ ) -> Result<OwnedBytes, A::Error> {
+ let mut bytes = Vec::new();
+ while let Some(b) = seq.next_element()? {
+ bytes.push(b);
+ }
+ Ok(OwnedBytes(bytes))
+ }
+ }
+ de.deserialize_byte_buf(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ data: OwnedBytes,
+ }
+ let input = "data 1 2 3";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.data, OwnedBytes(vec![1, 2, 3]));
+}
+
+#[test]
+fn field_deserializer_str() {
+ // Custom type that calls deserialize_str on a FieldDeserializer.
+ #[derive(Debug, PartialEq)]
+ struct BorrowStr(String);
+
+ impl<'de> Deserialize<'de> for BorrowStr {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = BorrowStr;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "a string")
+ }
+ fn visit_str<E>(self, v: &str) -> Result<BorrowStr, E> {
+ Ok(BorrowStr(v.to_string()))
+ }
+ }
+ de.deserialize_str(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ name: BorrowStr,
+ }
+ let input = r#"name "hello""#;
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.name, BorrowStr("hello".into()));
+}
+
+#[test]
+fn node_content_str_custom_deser_in_seq() {
+ // Custom type calling deserialize_str inside a Vec (repeated nodes).
+ #[derive(Debug, PartialEq)]
+ struct BorrowStr2(String);
+
+ impl<'de> Deserialize<'de> for BorrowStr2 {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = BorrowStr2;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "a string")
+ }
+ fn visit_str<E>(self, v: &str) -> Result<BorrowStr2, E> {
+ Ok(BorrowStr2(v.to_string()))
+ }
+ }
+ de.deserialize_str(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ name: Vec<BorrowStr2>,
+ }
+ let input = "name \"hello\"\nname \"world\"\n";
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(
+ val.name,
+ vec![BorrowStr2("hello".into()), BorrowStr2("world".into())]
+ );
+}
+
+#[test]
+fn value_deserializer_bytes_from_string_seq() {
+ // Custom type that calls deserialize_bytes on a ValueDeserializer
+ // (reached via ArgsSeqAccess from a multi-arg node).
+ #[derive(Debug, PartialEq)]
+ struct RawBytes(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for RawBytes {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = RawBytes;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "bytes")
+ }
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<RawBytes, E> {
+ Ok(RawBytes(v.to_vec()))
+ }
+ }
+ de.deserialize_bytes(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ data: Vec<RawBytes>,
+ }
+ let input = r#"data "hello" "world""#;
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(
+ val.data,
+ vec![RawBytes(b"hello".to_vec()), RawBytes(b"world".to_vec())]
+ );
+}
+
+#[test]
+fn value_deserializer_bytes_type_mismatch_via_seq() {
+ // Exercise the error path in ValueDeserializer::deserialize_bytes
+ // when a non-string value is encountered.
+ #[derive(Debug)]
+ struct RawBytes2(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for RawBytes2 {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = RawBytes2;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "bytes")
+ }
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<RawBytes2, E> {
+ Ok(RawBytes2(v.to_vec()))
+ }
+ }
+ de.deserialize_bytes(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug)]
+ struct S {
+ data: Vec<RawBytes2>,
+ }
+ let input = r#"data 42"#;
+ let result = serde_kdl::from_str::<S>(input);
+ assert!(result.is_err());
+}
+
+#[test]
+fn value_deserializer_byte_buf_via_seq() {
+ // Exercise ValueDeserializer::deserialize_byte_buf
+ #[derive(Debug, PartialEq)]
+ struct OwnedRawBytes(Vec<u8>);
+
+ impl<'de> Deserialize<'de> for OwnedRawBytes {
+ fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ struct V;
+ impl<'de> serde::de::Visitor<'de> for V {
+ type Value = OwnedRawBytes;
+ fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "bytes")
+ }
+ fn visit_bytes<E>(self, v: &[u8]) -> Result<OwnedRawBytes, E> {
+ Ok(OwnedRawBytes(v.to_vec()))
+ }
+ }
+ de.deserialize_byte_buf(V)
+ }
+ }
+
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ data: Vec<OwnedRawBytes>,
+ }
+ let input = r#"data "hello""#;
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.data, vec![OwnedRawBytes(b"hello".to_vec())]);
+}
+
+#[test]
+fn value_deserializer_unit_null_in_args() {
+ // A multi-arg node with null values deserialized as Vec<()>.
+ // Each null arg goes through ValueDeserializer::deserialize_unit.
+ let input = r#"vals #null #null"#;
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ vals: Vec<()>,
+ }
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.vals, vec![(), ()]);
+}
+
+#[test]
+fn value_deserializer_unit_mismatch_in_args() {
+ // Non-null value deserialized as unit → error
+ let input = r#"vals 42"#;
+ #[derive(Deserialize, Debug)]
+ struct S {
+ vals: Vec<()>,
+ }
+ let result = serde_kdl::from_str::<S>(input);
+ assert!(result.is_err());
+}
+
+#[test]
+fn value_deserializer_unit_struct_in_args() {
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct Marker;
+ #[derive(Deserialize, Debug, PartialEq)]
+ struct S {
+ vals: Vec<Marker>,
+ }
+ let input = r#"vals #null #null"#;
+ let val: S = serde_kdl::from_str(input).unwrap();
+ assert_eq!(val.vals, vec![Marker, Marker]);
+}