Skip to content
This repository has been archived by the owner on Sep 24, 2022. It is now read-only.

Commit

Permalink
Support for dotted table spans (#340)
Browse files Browse the repository at this point in the history
* "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
  • Loading branch information
est31 authored and alexcrichton committed Oct 28, 2019
1 parent e9f5290 commit ec21d60
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 7 deletions.
56 changes: 50 additions & 6 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -333,7 +334,7 @@ struct Table<'a> {
}

struct MapVisitor<'de, 'b> {
values: vec::IntoIter<TablePair<'de>>,
values: iter::Peekable<vec::IntoIter<TablePair<'de>>>,
next_value: Option<TablePair<'de>>,
depth: usize,
cur: usize,
Expand Down Expand Up @@ -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();
}
}

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -558,6 +561,39 @@ impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> {
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>(
self,
_name: &'static str,
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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;

Expand Down
91 changes: 90 additions & 1 deletion test-suite/tests/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,54 @@ fn test_spanned_field() {
}

#[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)]
struct Foo {
foo: HashMap<Spanned<String>, Spanned<String>>,
Expand Down Expand Up @@ -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\"
",
);
}

0 comments on commit ec21d60

Please sign in to comment.