From ec21d604f892a06999a9d38d38c903e083ad1f08 Mon Sep 17 00:00:00 2001 From: est31 Date: Mon, 28 Oct 2019 15:01:23 +0100 Subject: Support for dotted table spans (#340) * "Support" spans for maps In toml you can declare maps via {} and via [name]. We can't obtain spans for [] maps but at least we can emit fake spans to make SpannedValue work. We also add a regression test. * Don't regress the inline table case * Also support arrays --- src/de.rs | 56 +++++++++++++++++++++++++--- test-suite/tests/spanned.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 7 deletions(-) diff --git a/src/de.rs b/src/de.rs index 9bb5204..0e1ae6a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::error; use std::f64; use std::fmt; +use std::iter; use std::marker::PhantomData; use std::mem::discriminant; use std::str; @@ -216,7 +217,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> { let mut tables = self.tables()?; let res = visitor.visit_map(MapVisitor { - values: Vec::new().into_iter(), + values: Vec::new().into_iter().peekable(), next_value: None, depth: 0, cur: 0, @@ -333,7 +334,7 @@ struct Table<'a> { } struct MapVisitor<'de, 'b> { - values: vec::IntoIter>, + values: iter::Peekable>>, next_value: Option>, depth: usize, cur: usize, @@ -440,7 +441,8 @@ impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> { .values .take() .expect("Unable to read table values") - .into_iter(); + .into_iter() + .peekable(); } } @@ -462,7 +464,7 @@ impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> { self.tables[self.cur].array && self.depth == self.tables[self.cur].header.len() - 1; self.cur += 1; let res = seed.deserialize(MapVisitor { - values: Vec::new().into_iter(), + values: Vec::new().into_iter().peekable(), next_value: None, depth: self.depth + if array { 0 } else { 1 }, cur_parent: self.cur - 1, @@ -509,7 +511,8 @@ impl<'de, 'b> de::SeqAccess<'de> for MapVisitor<'de, 'b> { .values .take() .expect("Unable to read table values") - .into_iter(), + .into_iter() + .peekable(), next_value: None, depth: self.depth + 1, cur_parent: self.cur_parent, @@ -558,6 +561,39 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> { visitor.visit_newtype_struct(self) } + fn deserialize_struct( + mut self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + if name == spanned::NAME + && fields == [spanned::START, spanned::END, spanned::VALUE] + && !(self.array && !self.values.peek().is_none()) + { + // TODO we can't actually emit spans here for the *entire* table/array + // due to the format that toml uses. Setting the start and end to 0 is + // *detectable* (and no reasonable span would look like that), + // it would be better to expose this in the API via proper + // ADTs like Option. + let start = 0; + let end = 0; + + let res = visitor.visit_map(SpannedDeserializer { + phantom_data: PhantomData, + start: Some(start), + value: Some(self), + end: Some(end), + }); + return res; + } + + self.deserialize_any(visitor) + } + fn deserialize_enum( self, _name: &'static str, @@ -591,7 +627,7 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> { serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map struct unit identifier + bytes byte_buf map unit identifier ignored_any unit_struct tuple_struct tuple } } @@ -853,6 +889,14 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> { } } +impl<'de, 'b> de::IntoDeserializer<'de, Error> for MapVisitor<'de, 'b> { + type Deserializer = MapVisitor<'de, 'b>; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + impl<'de, 'b> de::IntoDeserializer<'de, Error> for &'b mut Deserializer<'de> { type Deserializer = Self; diff --git a/test-suite/tests/spanned.rs b/test-suite/tests/spanned.rs index 1186645..d8f7a12 100644 --- a/test-suite/tests/spanned.rs +++ b/test-suite/tests/spanned.rs @@ -87,7 +87,54 @@ fn test_spanned_field() { } #[test] -fn test_spanned_table() { +fn test_inner_spanned_table() { + #[derive(Deserialize)] + struct Foo { + foo: Spanned, Spanned>>, + } + + fn good(s: &str, zero: bool) { + let foo: Foo = toml::from_str(s).unwrap(); + + if zero { + assert_eq!(foo.foo.start(), 0); + // We'd actually have to assert equality with s.len() here, + // but the current implementation doesn't support that, + // and it's not possible with toml's data format to support it + // in the general case as spans aren't always well-defined. + // So this check mainly serves as a reminder that this test should + // be updated *if* one day there is support for emitting the actual span. + assert_eq!(foo.foo.end(), 0); + } else { + assert_eq!(foo.foo.start(), s.find("{").unwrap()); + assert_eq!(foo.foo.end(), s.find("}").unwrap() + 1); + } + for (k, v) in foo.foo.get_ref().iter() { + assert_eq!(&s[k.start()..k.end()], k.get_ref()); + assert_eq!(&s[(v.start() + 1)..(v.end() - 1)], v.get_ref()); + } + } + + good( + " + [foo] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + ", + true, + ); + + good( + " + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }", + false, + ); +} + +#[test] +fn test_outer_spanned_table() { #[derive(Deserialize)] struct Foo { foo: HashMap, Spanned>, @@ -158,3 +205,45 @@ fn test_spanned_nested() { ", ); } + +#[test] +fn test_spanned_array() { + #[derive(Deserialize)] + struct Foo { + foo: Vec, Spanned>>>, + } + + fn good(s: &str) { + let foo_list: Foo = toml::from_str(s).unwrap(); + + for foo in foo_list.foo.iter() { + assert_eq!(foo.start(), 0); + // We'd actually have to assert equality with s.len() here, + // but the current implementation doesn't support that, + // and it's not possible with toml's data format to support it + // in the general case as spans aren't always well-defined. + // So this check mainly serves as a reminder that this test should + // be updated *if* one day there is support for emitting the actual span. + assert_eq!(foo.end(), 0); + for (k, v) in foo.get_ref().iter() { + assert_eq!(&s[k.start()..k.end()], k.get_ref()); + assert_eq!(&s[(v.start() + 1)..(v.end() - 1)], v.get_ref()); + } + } + } + + good( + " + [[foo]] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + [[foo]] + a = 'c' + bar = 'baz' + c = 'g' + e = \"h\" + ", + ); +} -- cgit v1.2.3