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
This commit is contained in:
parent
bf1c4ce44f
commit
52586279ce
56
src/de.rs
56
src/de.rs
|
@ -8,6 +8,7 @@ use std::borrow::Cow;
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::f64;
|
use std::f64;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::discriminant;
|
use std::mem::discriminant;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -216,7 +217,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
|
||||||
let mut tables = self.tables()?;
|
let mut tables = self.tables()?;
|
||||||
|
|
||||||
let res = visitor.visit_map(MapVisitor {
|
let res = visitor.visit_map(MapVisitor {
|
||||||
values: Vec::new().into_iter(),
|
values: Vec::new().into_iter().peekable(),
|
||||||
next_value: None,
|
next_value: None,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
cur: 0,
|
cur: 0,
|
||||||
|
@ -333,7 +334,7 @@ struct Table<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MapVisitor<'de, 'b> {
|
struct MapVisitor<'de, 'b> {
|
||||||
values: vec::IntoIter<TablePair<'de>>,
|
values: iter::Peekable<vec::IntoIter<TablePair<'de>>>,
|
||||||
next_value: Option<TablePair<'de>>,
|
next_value: Option<TablePair<'de>>,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
cur: usize,
|
cur: usize,
|
||||||
|
@ -440,7 +441,8 @@ impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> {
|
||||||
.values
|
.values
|
||||||
.take()
|
.take()
|
||||||
.expect("Unable to read table values")
|
.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.tables[self.cur].array && self.depth == self.tables[self.cur].header.len() - 1;
|
||||||
self.cur += 1;
|
self.cur += 1;
|
||||||
let res = seed.deserialize(MapVisitor {
|
let res = seed.deserialize(MapVisitor {
|
||||||
values: Vec::new().into_iter(),
|
values: Vec::new().into_iter().peekable(),
|
||||||
next_value: None,
|
next_value: None,
|
||||||
depth: self.depth + if array { 0 } else { 1 },
|
depth: self.depth + if array { 0 } else { 1 },
|
||||||
cur_parent: self.cur - 1,
|
cur_parent: self.cur - 1,
|
||||||
|
@ -509,7 +511,8 @@ impl<'de, 'b> de::SeqAccess<'de> for MapVisitor<'de, 'b> {
|
||||||
.values
|
.values
|
||||||
.take()
|
.take()
|
||||||
.expect("Unable to read table values")
|
.expect("Unable to read table values")
|
||||||
.into_iter(),
|
.into_iter()
|
||||||
|
.peekable(),
|
||||||
next_value: None,
|
next_value: None,
|
||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
cur_parent: self.cur_parent,
|
cur_parent: self.cur_parent,
|
||||||
|
@ -558,6 +561,39 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> {
|
||||||
visitor.visit_newtype_struct(self)
|
visitor.visit_newtype_struct(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_struct<V>(
|
||||||
|
mut self,
|
||||||
|
name: &'static str,
|
||||||
|
fields: &'static [&'static str],
|
||||||
|
visitor: V,
|
||||||
|
) -> Result<V::Value, Error>
|
||||||
|
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<T>.
|
||||||
|
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<V>(
|
fn deserialize_enum<V>(
|
||||||
self,
|
self,
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
|
@ -591,7 +627,7 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> {
|
||||||
|
|
||||||
serde::forward_to_deserialize_any! {
|
serde::forward_to_deserialize_any! {
|
||||||
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
|
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
|
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> {
|
impl<'de, 'b> de::IntoDeserializer<'de, Error> for &'b mut Deserializer<'de> {
|
||||||
type Deserializer = Self;
|
type Deserializer = Self;
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,54 @@ fn test_spanned_field() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spanned_table() {
|
fn test_inner_spanned_table() {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Foo {
|
||||||
|
foo: Spanned<HashMap<Spanned<String>, Spanned<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Deserialize)]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
foo: HashMap<Spanned<String>, Spanned<String>>,
|
foo: HashMap<Spanned<String>, Spanned<String>>,
|
||||||
|
@ -158,3 +205,45 @@ fn test_spanned_nested() {
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spanned_array() {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Foo {
|
||||||
|
foo: Vec<Spanned<HashMap<Spanned<String>, Spanned<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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\"
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue