Skip to content

Commit c654479

Browse files
authored
fix(edit): Correctly deserialize Spanned implicit tables (#955)
Fixes #798
2 parents 2923f59 + 5a23b1e commit c654479

File tree

7 files changed

+226
-0
lines changed

7 files changed

+226
-0
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/toml/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ toml-test-data = "2.3.0"
6666
snapbox = "0.6.0"
6767
walkdir = "2.5.0"
6868
itertools = "0.14.0"
69+
serde-untagged = "0.1.7"
6970

7071
[[test]]
7172
name = "decoder_compliance"

crates/toml/tests/serde/spanned.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
use std::collections::HashMap;
55
use std::fmt::Debug;
66

7+
use serde::de::{Deserializer, MapAccess};
78
use serde::Deserialize;
9+
use serde_untagged::UntaggedEnumVisitor;
810
use snapbox::assert_data_eq;
911
use snapbox::prelude::*;
1012
use snapbox::str;
@@ -302,3 +304,111 @@ unknown field `fake`, expected `real`
302304
.raw()
303305
);
304306
}
307+
308+
#[test]
309+
fn implicit_tables() {
310+
#[derive(Debug)]
311+
#[allow(dead_code)]
312+
enum SpannedValue {
313+
String(String),
314+
Map(Vec<(String, Spanned<SpannedValue>)>),
315+
}
316+
317+
impl<'de> Deserialize<'de> for SpannedValue {
318+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319+
where
320+
D: Deserializer<'de>,
321+
{
322+
let data = UntaggedEnumVisitor::new()
323+
.string(|str| Ok(SpannedValue::String(str.into())))
324+
.map(|mut map| {
325+
let mut result = Vec::new();
326+
327+
while let Some((k, v)) = map.next_entry()? {
328+
result.push((k, v));
329+
}
330+
331+
Ok(SpannedValue::Map(result))
332+
})
333+
.deserialize(deserializer)?;
334+
335+
Ok(data)
336+
}
337+
}
338+
339+
const INPUT: &str = r#"
340+
[foo.bar]
341+
alice.bob = { one.two = "qux" }
342+
"#;
343+
344+
let result = crate::from_str::<SpannedValue>(INPUT);
345+
assert_data_eq!(
346+
result.unwrap().to_debug(),
347+
str![[r#"
348+
Map(
349+
[
350+
(
351+
"foo",
352+
Spanned {
353+
span: 2..5,
354+
value: Map(
355+
[
356+
(
357+
"bar",
358+
Spanned {
359+
span: 1..10,
360+
value: Map(
361+
[
362+
(
363+
"alice",
364+
Spanned {
365+
span: 11..16,
366+
value: Map(
367+
[
368+
(
369+
"bob",
370+
Spanned {
371+
span: 23..42,
372+
value: Map(
373+
[
374+
(
375+
"one",
376+
Spanned {
377+
span: 25..28,
378+
value: Map(
379+
[
380+
(
381+
"two",
382+
Spanned {
383+
span: 35..40,
384+
value: String(
385+
"qux",
386+
),
387+
},
388+
),
389+
],
390+
),
391+
},
392+
),
393+
],
394+
),
395+
},
396+
),
397+
],
398+
),
399+
},
400+
),
401+
],
402+
),
403+
},
404+
),
405+
],
406+
),
407+
},
408+
),
409+
],
410+
)
411+
412+
"#]]
413+
);
414+
}

crates/toml_edit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ proptest = "1.5.0"
6262
walkdir = "2.5.0"
6363
serde_spanned = { path = "../serde_spanned" }
6464
toml_types = { path = "../toml", package = "toml", default-features = false, features = ["preserve_order"] }
65+
serde-untagged = "0.1.7"
6566

6667
[[test]]
6768
name = "testsuite"

crates/toml_edit/src/parser/document.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ fn descend_path<'t>(
458458
table = match table.entry_format(key) {
459459
crate::Entry::Vacant(entry) => {
460460
let mut new_table = Table::new();
461+
new_table.span = key.span();
461462
new_table.set_implicit(true);
462463
new_table.set_dotted(dotted);
463464

crates/toml_edit/src/parser/inline_table.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ fn descend_path<'a>(
232232
table = match table.entry_format(key) {
233233
crate::InlineEntry::Vacant(entry) => {
234234
let mut new_table = InlineTable::new();
235+
new_table.span = key.span();
235236
new_table.set_implicit(true);
236237
new_table.set_dotted(dotted);
237238
entry

crates/toml_edit/tests/serde/spanned.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
use std::collections::HashMap;
55
use std::fmt::Debug;
66

7+
use serde::de::{Deserializer, MapAccess};
78
use serde::Deserialize;
9+
use serde_untagged::UntaggedEnumVisitor;
810
use snapbox::assert_data_eq;
911
use snapbox::prelude::*;
1012
use snapbox::str;
@@ -302,3 +304,111 @@ unknown field `fake`, expected `real`
302304
.raw()
303305
);
304306
}
307+
308+
#[test]
309+
fn implicit_tables() {
310+
#[derive(Debug)]
311+
#[allow(dead_code)]
312+
enum SpannedValue {
313+
String(String),
314+
Map(Vec<(String, Spanned<SpannedValue>)>),
315+
}
316+
317+
impl<'de> Deserialize<'de> for SpannedValue {
318+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319+
where
320+
D: Deserializer<'de>,
321+
{
322+
let data = UntaggedEnumVisitor::new()
323+
.string(|str| Ok(SpannedValue::String(str.into())))
324+
.map(|mut map| {
325+
let mut result = Vec::new();
326+
327+
while let Some((k, v)) = map.next_entry()? {
328+
result.push((k, v));
329+
}
330+
331+
Ok(SpannedValue::Map(result))
332+
})
333+
.deserialize(deserializer)?;
334+
335+
Ok(data)
336+
}
337+
}
338+
339+
const INPUT: &str = r#"
340+
[foo.bar]
341+
alice.bob = { one.two = "qux" }
342+
"#;
343+
344+
let result = crate::from_str::<SpannedValue>(INPUT);
345+
assert_data_eq!(
346+
result.unwrap().to_debug(),
347+
str![[r#"
348+
Map(
349+
[
350+
(
351+
"foo",
352+
Spanned {
353+
span: 2..5,
354+
value: Map(
355+
[
356+
(
357+
"bar",
358+
Spanned {
359+
span: 1..10,
360+
value: Map(
361+
[
362+
(
363+
"alice",
364+
Spanned {
365+
span: 11..16,
366+
value: Map(
367+
[
368+
(
369+
"bob",
370+
Spanned {
371+
span: 23..42,
372+
value: Map(
373+
[
374+
(
375+
"one",
376+
Spanned {
377+
span: 25..28,
378+
value: Map(
379+
[
380+
(
381+
"two",
382+
Spanned {
383+
span: 35..40,
384+
value: String(
385+
"qux",
386+
),
387+
},
388+
),
389+
],
390+
),
391+
},
392+
),
393+
],
394+
),
395+
},
396+
),
397+
],
398+
),
399+
},
400+
),
401+
],
402+
),
403+
},
404+
),
405+
],
406+
),
407+
},
408+
),
409+
],
410+
)
411+
412+
"#]]
413+
);
414+
}

0 commit comments

Comments
 (0)