Repository: toml-rs/toml (language: rust)

Issue:
Support semantic error messages by supporting deserializing spans
See [toml::Spanned](https://docs.rs/toml/latest/toml/struct.Spanned.html) for inspiration


Discussion / hints:
(I rolled by this issue because my project uses toml-edit for it's higher-quality serialization, but its low-quality spans for deserialization means I've been experimenting with preferring toml-rs for the other side of the equation, even though toml-edit/easy is explicitly designed to preserve more info, alas!)

I've been using this API in toml-rs in anger a lot this week, so I figured I'd chime in with some details on how it works, and some issues it has:

Spanned defines four magic strings:

```rust
pub(crate) const NAME: &str = "$__toml_private_Spanned";
pub(crate) const START: &str = "$__toml_private_start";
pub(crate) const END: &str = "$__toml_private_end";
pub(crate) const VALUE: &str = "$__toml_private_value";
```

And then uses a custom visitor and does the following:

```rust
        let visitor = SpannedVisitor(::std::marker::PhantomData);

        static FIELDS: [&str; 3] = [START, END, VALUE];
        deserializer.deserialize_struct(NAME, &FIELDS, visitor)
```

The deserializer then, [at various points in the code](https://github.com/alexcrichton/toml-rs/blob/70caf4085324bff452d8c20ead55974513030696/src/de.rs#L302), checks for that exact state and wraps itself in a SpannedDeserializer that emits the 3 magic fields, the first 2 being the span info:

```rust
        if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] {
            let start = 0;
            let end = self.input.len();

            let res = visitor.visit_map(SpannedDeserializer {
                phantom_data: PhantomData,
                start: Some(start),
                value: Some(self),
                end: Some(end),
            });
            return res;
        }
```

That the SpannedVisitor expects:

```rust
            fn visit_map<V>(self, mut visitor: V) -> Result<Spanned<T>, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                if visitor.next_key()? != Some(START) {
                    return Err(de::Error::custom("spanned start key not found"));
                }

                let start: usize = visitor.next_value()?;
                ...
```

I've been trying to use this in anger a fair bit this week and it seems to largely work for "normal" derived types but is more of a mess with custom deserialization. Because it relies on random places in the code detecting the magic fields, if you ever invoke it in a way it wasn't expecting (like variants on the ["string or struct" pattern in serde's docs](https://serde.rs/string-or-struct.html)) then you get nasty "expected spanned" from it not creating the SpannedDeserializer and emitting the magic fields.

I'm not sure if those problems are "I'm bad at serde" or "this design is buggy" or "serde makes this impossible to do robustly".

The actual Spanned type also has a pretty suboptimal API. It's very opaque, and you have to explicitly call a method to get the wrapped value, so retroactively adding spans to your existing types is extremely disruptive to code. Thankfully because it communicates with magic fields, you're able to "fork" just Spanned. I did this to make it more like `Box<T>`, in that it does its best to forward everything from T via Deref and trait impls. All that actual span methods I made into associated functions so you have to do things like `Spanned::start(&spanned_val)`, which is fine since you don't actually care about the span until you want to emit an error.

I am... incredibly bad at serde stuff so I'm not sure if I could help with bringing this stuff up, but here's a brain dump.
Thanks for the brain dump!  These details will be important when implementing this!
Some extra notes from landing this:

* `serde(from = "toml::Spanned<T>")` seems to work well as an alternative to get a better API without importing the private details
* It seemed that my woes were rooted more in `#[serde(flatten)]` than anything else. `#[serde(untagged)]` did also seem messed up. basically anything that requires the input to be "extra" buffered makes `Spanned` break. I worked around this by replacing these kinds of things with a `#[serde(try_from = "OuterTypeWithEveryPossibleField")]` that manually implements flatten/untagged logic after deserializing all the `Spanned` more naturally. Not sure if the problem is in serde or Spanned, and whether it's fundamental to the design.
I'm pretty sure the answer is "serde makes this impossible to do robustly". I looked into span support for serde things a bit ago in https://github.com/serde-rs/serde/issues/1811#issuecomment-1282900892, and as far as I can tell there's no way to propagate span info through serde without resorting to wild hacks like what `toml::Spanned` was doing previously. 

It's possible to extend serde's API to better support spans. Unfortunately, with the designs I've found so far, you generally have two choices:

 - Support propagating spans in a way that allows the deserializer to choose an appropriate span representation, which requires a breaking change to serde's API
 - Bake a particular representation choice into serde's API, which will not be appropriate for all formats
I wonder if it'd make sense to check that the magic strings are **the same** instead of checking if they are *equal*, that means comparing the address rather than contents. Unless I'm missing something this should make it more robust (if anyone for whatever reason uses these strings in the input it shouldn't confuse the parser). If there was some way to prevent the linker from merging same symbols they could all be empty strings which would be even nicer but I didn't find how to do that.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5add1f0c..7b4a40e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -958,6 +958,7 @@ dependencies = [
  "nom8",
  "serde",
  "serde_json",
+ "serde_spanned",
  "snapbox",
  "toml 0.5.10",
  "toml-test-data",
diff --git a/crates/serde_spanned/src/spanned.rs b/crates/serde_spanned/src/spanned.rs
index db705959..f2cfbeae 100644
--- a/crates/serde_spanned/src/spanned.rs
+++ b/crates/serde_spanned/src/spanned.rs
@@ -123,19 +123,16 @@ where
                 if visitor.next_key()? != Some(START_FIELD) {
                     return Err(serde::de::Error::custom("spanned start key not found"));
                 }
-
                 let start: usize = visitor.next_value()?;
 
                 if visitor.next_key()? != Some(END_FIELD) {
                     return Err(serde::de::Error::custom("spanned end key not found"));
                 }
-
                 let end: usize = visitor.next_value()?;
 
                 if visitor.next_key()? != Some(VALUE_FIELD) {
                     return Err(serde::de::Error::custom("spanned value key not found"));
                 }
-
                 let value: T = visitor.next_value()?;
 
                 Ok(Spanned {
diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml
index e15d5211..515ee0e9 100644
--- a/crates/toml_edit/Cargo.toml
+++ b/crates/toml_edit/Cargo.toml
@@ -38,7 +38,7 @@ pre-release-replacements = [
 default = []
 easy = ["serde"]
 perf = ["dep:kstring"]
-serde = ["dep:serde", "toml_datetime/serde"]
+serde = ["dep:serde", "toml_datetime/serde", "serde_spanned"]
 # Provide a method disable_recursion_limit to parse arbitrarily deep structures
 # without any consideration for overflowing the stack. Additionally you will
 # need to be careful around other recursive operations on the parsed result
@@ -53,6 +53,7 @@ itertools = "0.10.5"
 serde = { version = "1.0.145", features = ["derive"], optional = true }
 kstring = { version = "2.0.0", features = ["max_inline"], optional = true }
 toml_datetime = { version = "0.5.0", path = "../toml_datetime" }
+serde_spanned = { version = "0.5.0", path = "../serde_spanned", features = ["serde"], optional = true }
 
 [dev-dependencies]
 serde_json = "1.0.91"
diff --git a/crates/toml_edit/src/de/array.rs b/crates/toml_edit/src/de/array.rs
index 4aaacdad..bc398148 100644
--- a/crates/toml_edit/src/de/array.rs
+++ b/crates/toml_edit/src/de/array.rs
@@ -2,11 +2,12 @@ use crate::de::Error;
 
 pub(crate) struct ArrayDeserializer {
     input: Vec<crate::Item>,
+    span: Option<std::ops::Range<usize>>,
 }
 
 impl ArrayDeserializer {
-    pub(crate) fn new(input: Vec<crate::Item>) -> Self {
-        Self { input }
+    pub(crate) fn new(input: Vec<crate::Item>, span: Option<std::ops::Range<usize>>) -> Self {
+        Self { input, span }
     }
 }
 
@@ -20,10 +21,36 @@ impl<'de> serde::Deserializer<'de> for ArrayDeserializer {
         visitor.visit_seq(ArraySeqAccess::new(self.input))
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span.clone() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map option unit newtype_struct
-        ignored_any unit_struct tuple_struct tuple enum identifier struct
+        ignored_any unit_struct tuple_struct tuple enum identifier
+    }
+}
+
+impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ArrayDeserializer {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self {
+        self
     }
 }
 
@@ -37,10 +64,28 @@ impl<'de> serde::Deserializer<'de> for crate::Array {
         visitor.visit_seq(ArraySeqAccess::with_array(self))
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map option unit newtype_struct
-        ignored_any unit_struct tuple_struct tuple enum identifier struct
+        ignored_any unit_struct tuple_struct tuple enum identifier
     }
 }
 
@@ -62,10 +107,28 @@ impl<'de> serde::Deserializer<'de> for crate::ArrayOfTables {
         visitor.visit_seq(ArraySeqAccess::with_array_of_tables(self))
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map option unit newtype_struct
-        ignored_any unit_struct tuple_struct tuple enum identifier struct
+        ignored_any unit_struct tuple_struct tuple enum identifier
     }
 }
 
diff --git a/crates/toml_edit/src/de/inline_table.rs b/crates/toml_edit/src/de/inline_table.rs
index becb3ea6..2155772d 100644
--- a/crates/toml_edit/src/de/inline_table.rs
+++ b/crates/toml_edit/src/de/inline_table.rs
@@ -34,13 +34,19 @@ impl<'de> serde::Deserializer<'de> for crate::InlineTable {
 
     fn deserialize_struct<V>(
         self,
-        _name: &'static str,
-        _fields: &'static [&'static str],
+        name: &'static str,
+        fields: &'static [&'static str],
         visitor: V,
     ) -> Result<V::Value, Error>
     where
         V: serde::de::Visitor<'de>,
     {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
         self.deserialize_any(visitor)
     }
 
@@ -87,7 +93,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::InlineTa
 pub(crate) struct InlineTableMapAccess {
     iter: indexmap::map::IntoIter<crate::InternalString, crate::table::TableKeyValue>,
     span: Option<std::ops::Range<usize>>,
-    value: Option<(crate::InternalString, crate::Item)>,
+    value: Option<crate::Item>,
 }
 
 impl InlineTableMapAccess {
@@ -110,15 +116,16 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess {
     {
         match self.iter.next() {
             Some((k, v)) => {
-                let ret = seed.deserialize(k.into_deserializer()).map(Some).map_err(
-                    |mut e: Self::Error| {
+                let ret = seed
+                    .deserialize(super::KeyDeserializer::new(k, v.key.span()))
+                    .map(Some)
+                    .map_err(|mut e: Self::Error| {
                         if e.span().is_none() {
                             e.set_span(v.key.span());
                         }
                         e
-                    },
-                );
-                self.value = Some((k, v.value));
+                    });
+                self.value = Some(v.value);
                 ret
             }
             None => Ok(None),
@@ -130,7 +137,7 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess {
         V: serde::de::DeserializeSeed<'de>,
     {
         match self.value.take() {
-            Some((_k, v)) => {
+            Some(v) => {
                 let span = v.span();
                 seed.deserialize(crate::de::ItemDeserializer::new(v))
                     .map_err(|mut err| {
diff --git a/crates/toml_edit/src/de/item.rs b/crates/toml_edit/src/de/item.rs
index 238e59b1..ab8b60fd 100644
--- a/crates/toml_edit/src/de/item.rs
+++ b/crates/toml_edit/src/de/item.rs
@@ -66,6 +66,12 @@ impl<'de> serde::Deserializer<'de> for ItemDeserializer {
     where
         V: serde::de::Visitor<'de>,
     {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.input.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self.input, span));
+            }
+        }
+
         if self.validate_struct_keys {
             match &self.input {
                 crate::Item::Table(values) => super::validate_struct_keys(&values.items, fields)?,
@@ -99,6 +105,14 @@ impl<'de> serde::Deserializer<'de> for ItemDeserializer {
     }
 }
 
+impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ItemDeserializer {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self {
+        self
+    }
+}
+
 impl<'de> serde::Deserializer<'de> for crate::Item {
     type Error = Error;
 
@@ -168,6 +182,24 @@ impl<'de> serde::Deserializer<'de> for crate::Item {
             })
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     // Called when the type to deserialize is an enum, as opposed to a field in the type.
     fn deserialize_enum<V>(
         self,
@@ -217,7 +249,7 @@ impl<'de> serde::Deserializer<'de> for crate::Item {
 
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
-        bytes byte_buf map unit struct
+        bytes byte_buf map unit
         ignored_any unit_struct tuple_struct tuple identifier
     }
 }
diff --git a/crates/toml_edit/src/de/key.rs b/crates/toml_edit/src/de/key.rs
new file mode 100644
index 00000000..1816ce76
--- /dev/null
+++ b/crates/toml_edit/src/de/key.rs
@@ -0,0 +1,140 @@
+use serde::de::IntoDeserializer;
+
+use super::Error;
+
+pub(crate) struct KeyDeserializer {
+    span: Option<std::ops::Range<usize>>,
+    key: crate::InternalString,
+}
+
+impl KeyDeserializer {
+    pub(crate) fn new(key: crate::InternalString, span: Option<std::ops::Range<usize>>) -> Self {
+        KeyDeserializer { span, key }
+    }
+}
+
+impl<'de> serde::de::IntoDeserializer<'de, Error> for KeyDeserializer {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+impl<'de> serde::de::Deserializer<'de> for KeyDeserializer {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        self.key.into_deserializer().deserialize_any(visitor)
+    }
+
+    fn deserialize_enum<V>(
+        self,
+        name: &str,
+        variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        let _ = name;
+        let _ = variants;
+        visitor.visit_enum(self)
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span.clone() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self.key.as_str(), span));
+            }
+        }
+        self.deserialize_any(visitor)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map option unit newtype_struct
+        ignored_any unit_struct tuple_struct tuple identifier
+    }
+}
+
+impl<'de> serde::de::EnumAccess<'de> for KeyDeserializer {
+    type Error = super::Error;
+    type Variant = UnitOnly<Self::Error>;
+
+    fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error>
+    where
+        T: serde::de::DeserializeSeed<'de>,
+    {
+        seed.deserialize(self).map(unit_only)
+    }
+}
+
+pub(crate) struct UnitOnly<E> {
+    marker: std::marker::PhantomData<E>,
+}
+
+fn unit_only<T, E>(t: T) -> (T, UnitOnly<E>) {
+    (
+        t,
+        UnitOnly {
+            marker: std::marker::PhantomData,
+        },
+    )
+}
+
+impl<'de, E> serde::de::VariantAccess<'de> for UnitOnly<E>
+where
+    E: serde::de::Error,
+{
+    type Error = E;
+
+    fn unit_variant(self) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value, Self::Error>
+    where
+        T: serde::de::DeserializeSeed<'de>,
+    {
+        Err(serde::de::Error::invalid_type(
+            serde::de::Unexpected::UnitVariant,
+            &"newtype variant",
+        ))
+    }
+
+    fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        Err(serde::de::Error::invalid_type(
+            serde::de::Unexpected::UnitVariant,
+            &"tuple variant",
+        ))
+    }
+
+    fn struct_variant<V>(
+        self,
+        _fields: &'static [&'static str],
+        _visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        Err(serde::de::Error::invalid_type(
+            serde::de::Unexpected::UnitVariant,
+            &"struct variant",
+        ))
+    }
+}
diff --git a/crates/toml_edit/src/de/mod.rs b/crates/toml_edit/src/de/mod.rs
index e45d8c1b..cfa81470 100644
--- a/crates/toml_edit/src/de/mod.rs
+++ b/crates/toml_edit/src/de/mod.rs
@@ -8,6 +8,8 @@ use serde::de::DeserializeOwned;
 mod array;
 mod inline_table;
 mod item;
+mod key;
+mod spanned;
 mod table;
 mod table_enum;
 mod value;
@@ -15,6 +17,8 @@ mod value;
 use array::*;
 use inline_table::*;
 use item::*;
+use key::*;
+use spanned::*;
 use table::*;
 use table_enum::*;
 
@@ -148,6 +152,18 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
         self.input.deserialize_option(visitor)
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        self.input.deserialize_struct(name, fields, visitor)
+    }
+
     // Called when the type to deserialize is an enum, as opposed to a field in the type.
     fn deserialize_enum<V>(
         self,
@@ -164,7 +180,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map unit newtype_struct
-        ignored_any unit_struct tuple_struct tuple identifier struct
+        ignored_any unit_struct tuple_struct tuple identifier
     }
 }
 
@@ -199,6 +215,24 @@ impl<'de> serde::Deserializer<'de> for crate::Document {
             })
     }
 
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        if is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(SpannedDeserializer::new(self, span));
+            }
+        }
+
+        self.deserialize_any(visitor)
+    }
+
     // Called when the type to deserialize is an enum, as opposed to a field in the type.
     fn deserialize_enum<V>(
         self,
@@ -221,7 +255,7 @@ impl<'de> serde::Deserializer<'de> for crate::Document {
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
         bytes byte_buf map unit newtype_struct
-        ignored_any unit_struct tuple_struct tuple identifier struct
+        ignored_any unit_struct tuple_struct tuple identifier
     }
 }
 
diff --git a/crates/toml_edit/src/de/spanned.rs b/crates/toml_edit/src/de/spanned.rs
new file mode 100644
index 00000000..0ebfb7fd
--- /dev/null
+++ b/crates/toml_edit/src/de/spanned.rs
@@ -0,0 +1,74 @@
+use serde::de::value::BorrowedStrDeserializer;
+use serde::de::IntoDeserializer as _;
+
+use super::Error;
+
+pub(crate) struct SpannedDeserializer<'de, T: serde::de::IntoDeserializer<'de, Error>> {
+    phantom_data: std::marker::PhantomData<&'de ()>,
+    start: Option<usize>,
+    end: Option<usize>,
+    value: Option<T>,
+}
+
+impl<'de, T> SpannedDeserializer<'de, T>
+where
+    T: serde::de::IntoDeserializer<'de, Error>,
+{
+    pub(crate) fn new(value: T, span: std::ops::Range<usize>) -> Self {
+        Self {
+            phantom_data: Default::default(),
+            start: Some(span.start),
+            end: Some(span.end),
+            value: Some(value),
+        }
+    }
+}
+
+impl<'de, T> serde::de::MapAccess<'de> for SpannedDeserializer<'de, T>
+where
+    T: serde::de::IntoDeserializer<'de, Error>,
+{
+    type Error = Error;
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: serde::de::DeserializeSeed<'de>,
+    {
+        if self.start.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::START_FIELD))
+                .map(Some)
+        } else if self.end.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::END_FIELD))
+                .map(Some)
+        } else if self.value.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::VALUE_FIELD))
+                .map(Some)
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: serde::de::DeserializeSeed<'de>,
+    {
+        if let Some(start) = self.start.take() {
+            seed.deserialize(start.into_deserializer())
+        } else if let Some(end) = self.end.take() {
+            seed.deserialize(end.into_deserializer())
+        } else if let Some(value) = self.value.take() {
+            seed.deserialize(value.into_deserializer())
+        } else {
+            panic!("next_value_seed called before next_key_seed")
+        }
+    }
+}
+
+pub(crate) fn is_spanned(name: &'static str, fields: &'static [&'static str]) -> bool {
+    name == serde_spanned::NAME
+        && fields
+            == [
+                serde_spanned::START_FIELD,
+                serde_spanned::END_FIELD,
+                serde_spanned::VALUE_FIELD,
+            ]
+}
diff --git a/crates/toml_edit/src/de/table.rs b/crates/toml_edit/src/de/table.rs
index fd5aca32..ba19796e 100644
--- a/crates/toml_edit/src/de/table.rs
+++ b/crates/toml_edit/src/de/table.rs
@@ -34,13 +34,19 @@ impl<'de> serde::Deserializer<'de> for crate::Table {
 
     fn deserialize_struct<V>(
         self,
-        _name: &'static str,
-        _fields: &'static [&'static str],
+        name: &'static str,
+        fields: &'static [&'static str],
         visitor: V,
     ) -> Result<V::Value, Error>
     where
         V: serde::de::Visitor<'de>,
     {
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
         self.deserialize_any(visitor)
     }
 
@@ -87,7 +93,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Table {
 pub(crate) struct TableMapAccess {
     iter: indexmap::map::IntoIter<crate::InternalString, crate::table::TableKeyValue>,
     span: Option<std::ops::Range<usize>>,
-    value: Option<(crate::InternalString, crate::Item)>,
+    value: Option<crate::Item>,
 }
 
 impl TableMapAccess {
@@ -110,15 +116,16 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess {
     {
         match self.iter.next() {
             Some((k, v)) => {
-                let ret = seed.deserialize(k.into_deserializer()).map(Some).map_err(
-                    |mut e: Self::Error| {
+                let ret = seed
+                    .deserialize(super::KeyDeserializer::new(k, v.key.span()))
+                    .map(Some)
+                    .map_err(|mut e: Self::Error| {
                         if e.span().is_none() {
                             e.set_span(v.key.span());
                         }
                         e
-                    },
-                );
-                self.value = Some((k, v.value));
+                    });
+                self.value = Some(v.value);
                 ret
             }
             None => Ok(None),
@@ -130,7 +137,7 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess {
         V: serde::de::DeserializeSeed<'de>,
     {
         match self.value.take() {
-            Some((_k, v)) => seed.deserialize(crate::de::ItemDeserializer::new(v)),
+            Some(v) => seed.deserialize(crate::de::ItemDeserializer::new(v)),
             None => {
                 panic!("no more values in next_value_seed, internal error in ItemDeserializer")
             }
diff --git a/crates/toml_edit/src/de/table_enum.rs b/crates/toml_edit/src/de/table_enum.rs
index bea79d41..40cfe62a 100644
--- a/crates/toml_edit/src/de/table_enum.rs
+++ b/crates/toml_edit/src/de/table_enum.rs
@@ -82,7 +82,7 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer {
 
                 if tuple_values.len() == len {
                     serde::de::Deserializer::deserialize_seq(
-                        super::ArrayDeserializer::new(tuple_values),
+                        super::ArrayDeserializer::new(tuple_values, values_span),
                         visitor,
                     )
                 } else {
@@ -125,7 +125,7 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer {
 
                 if tuple_values.len() == len {
                     serde::de::Deserializer::deserialize_seq(
-                        super::ArrayDeserializer::new(tuple_values),
+                        super::ArrayDeserializer::new(tuple_values, values_span),
                         visitor,
                     )
                 } else {
diff --git a/crates/toml_edit/src/de/value.rs b/crates/toml_edit/src/de/value.rs
index f24bc54c..1ffa2a0f 100644
--- a/crates/toml_edit/src/de/value.rs
+++ b/crates/toml_edit/src/de/value.rs
@@ -43,6 +43,12 @@ impl<'de> serde::Deserializer<'de> for crate::Value {
             }
         }
 
+        if super::is_spanned(name, fields) {
+            if let Some(span) = self.span() {
+                return visitor.visit_map(super::SpannedDeserializer::new(self, span));
+            }
+        }
+
         self.deserialize_any(visitor)
     }
 
diff --git a/crates/toml_edit/src/easy/mod.rs b/crates/toml_edit/src/easy/mod.rs
index 78c09e81..fbf081c1 100644
--- a/crates/toml_edit/src/easy/mod.rs
+++ b/crates/toml_edit/src/easy/mod.rs
@@ -147,5 +147,6 @@ pub use crate::toml;
 pub use de::{from_document, from_slice, from_str, Deserializer};
 #[doc(no_inline)]
 pub use ser::{to_document, to_string, to_string_pretty, to_vec, Serializer};
+pub use serde_spanned::Spanned;
 #[doc(no_inline)]
 pub use value::Value;


Test patch:
diff --git a/crates/toml_edit/tests/testsuite/main.rs b/crates/toml_edit/tests/testsuite/main.rs
index afb1f328..16943b15 100644
--- a/crates/toml_edit/tests/testsuite/main.rs
+++ b/crates/toml_edit/tests/testsuite/main.rs
@@ -8,4 +8,6 @@ mod macros;
 mod parse;
 mod pretty;
 mod serde;
+mod spanned;
+mod spanned_impls;
 mod stackoverflow;
diff --git a/crates/toml_edit/tests/testsuite/spanned.rs b/crates/toml_edit/tests/testsuite/spanned.rs
new file mode 100644
index 00000000..1359f4bf
--- /dev/null
+++ b/crates/toml_edit/tests/testsuite/spanned.rs
@@ -0,0 +1,237 @@
+#![cfg(feature = "easy")]
+#![allow(renamed_and_removed_lints)]
+#![allow(clippy::blacklisted_name)]
+
+use std::collections::HashMap;
+use std::fmt::Debug;
+
+use toml_edit::easy as toml;
+
+use serde::Deserialize;
+use toml_edit::easy::value::Datetime;
+use toml_edit::easy::Spanned;
+
+/// A set of good datetimes.
+pub fn good_datetimes() -> Vec<&'static str> {
+    vec![
+        "1997-09-09T09:09:09Z",
+        "1997-09-09T09:09:09+09:09",
+        "1997-09-09T09:09:09-09:09",
+        "1997-09-09T09:09:09",
+        "1997-09-09",
+        "09:09:09",
+        "1997-09-09T09:09:09.09Z",
+        "1997-09-09T09:09:09.09+09:09",
+        "1997-09-09T09:09:09.09-09:09",
+        "1997-09-09T09:09:09.09",
+        "09:09:09.09",
+    ]
+}
+
+#[test]
+fn test_spanned_field() {
+    #[derive(Deserialize)]
+    struct Foo<T> {
+        foo: Spanned<T>,
+    }
+
+    #[derive(Deserialize)]
+    struct BareFoo<T> {
+        foo: T,
+    }
+
+    fn good<T>(s: &str, expected: &str, end: Option<usize>)
+    where
+        T: serde::de::DeserializeOwned + Debug + PartialEq,
+    {
+        let foo: Foo<T> = toml::from_str(s).unwrap();
+
+        assert_eq!(6, foo.foo.span().start);
+        if let Some(end) = end {
+            assert_eq!(end, foo.foo.span().end);
+        } else {
+            assert_eq!(s.len(), foo.foo.span().end);
+        }
+        assert_eq!(expected, &s[foo.foo.span()]);
+
+        // Test for Spanned<> at the top level
+        let foo_outer: Spanned<BareFoo<T>> = toml::from_str(s).unwrap();
+
+        assert_eq!(0, foo_outer.span().start);
+        assert_eq!(s.len(), foo_outer.span().end);
+        assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo);
+    }
+
+    good::<String>("foo = \"foo\"", "\"foo\"", None);
+    good::<u32>("foo = 42", "42", None);
+    // leading plus
+    good::<u32>("foo = +42", "+42", None);
+    // table
+    good::<HashMap<String, u32>>(
+        "foo = {\"foo\" = 42, \"bar\" = 42}",
+        "{\"foo\" = 42, \"bar\" = 42}",
+        None,
+    );
+    // array
+    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None);
+    // datetime
+    good::<String>(
+        "foo = \"1997-09-09T09:09:09Z\"",
+        "\"1997-09-09T09:09:09Z\"",
+        None,
+    );
+
+    for expected in good_datetimes() {
+        let s = format!("foo = {}", expected);
+        good::<Datetime>(&s, expected, None);
+    }
+    // ending at something other than the absolute end
+    good::<u32>("foo = 42\nnoise = true", "42", Some(8));
+}
+
+#[test]
+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.span().start, 0);
+            assert_eq!(foo.foo.span().end, 73);
+        } else {
+            assert_eq!(foo.foo.span().start, s.find('{').unwrap());
+            assert_eq!(foo.foo.span().end, s.find('}').unwrap() + 1);
+        }
+        for (k, v) in foo.foo.as_ref().iter() {
+            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
+            assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_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>>,
+    }
+
+    fn good(s: &str) {
+        let foo: Foo = toml::from_str(s).unwrap();
+
+        for (k, v) in foo.foo.iter() {
+            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
+            assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref());
+        }
+    }
+
+    good(
+        "
+        [foo]
+        a = 'b'
+        bar = 'baz'
+        c = 'd'
+        e = \"f\"
+    ",
+    );
+
+    good(
+        "
+        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
+    ",
+    );
+}
+
+#[test]
+fn test_spanned_nested() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: HashMap<Spanned<String>, HashMap<Spanned<String>, Spanned<String>>>,
+    }
+
+    fn good(s: &str) {
+        let foo: Foo = toml::from_str(s).unwrap();
+
+        for (k, v) in foo.foo.iter() {
+            assert_eq!(&s[k.span().start..k.span().end], k.as_ref());
+            for (n_k, n_v) in v.iter() {
+                assert_eq!(&s[n_k.span().start..n_k.span().end], n_k.as_ref());
+                assert_eq!(
+                    &s[(n_v.span().start + 1)..(n_v.span().end - 1)],
+                    n_v.as_ref()
+                );
+            }
+        }
+    }
+
+    good(
+        "
+        [foo.a]
+        a = 'b'
+        c = 'd'
+        e = \"f\"
+        [foo.bar]
+        baz = 'true'
+    ",
+    );
+
+    good(
+        "
+        [foo]
+        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
+        bazz = {}
+        g = { h = 'i' }
+    ",
+    );
+}
+
+#[test]
+fn test_spanned_array() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: Vec<Spanned<HashMap<Spanned<String>, Spanned<String>>>>,
+    }
+
+    let toml = "\
+        [[foo]]
+        a = 'b'
+        bar = 'baz'
+        c = 'd'
+        e = \"f\"
+        [[foo]]
+        a = 'c'
+        bar = 'baz'
+        c = 'g'
+        e = \"h\"
+    ";
+    let foo_list: Foo = toml::from_str(toml).unwrap();
+
+    for (foo, expected) in foo_list.foo.iter().zip([0..75, 84..159]) {
+        assert_eq!(foo.span(), expected);
+        for (k, v) in foo.as_ref().iter() {
+            assert_eq!(&toml[k.span().start..k.span().end], k.as_ref());
+            assert_eq!(&toml[(v.span().start + 1)..(v.span().end - 1)], v.as_ref());
+        }
+    }
+}
diff --git a/crates/toml_edit/tests/testsuite/spanned_impls.rs b/crates/toml_edit/tests/testsuite/spanned_impls.rs
new file mode 100644
index 00000000..ceb21991
--- /dev/null
+++ b/crates/toml_edit/tests/testsuite/spanned_impls.rs
@@ -0,0 +1,43 @@
+#![cfg(feature = "easy")]
+
+use std::cmp::{Ord, Ordering, PartialOrd};
+
+use serde::Deserialize;
+use toml_edit::easy::{from_str, Spanned};
+
+#[test]
+fn test_spans_impls() {
+    #[derive(Deserialize)]
+    struct Foo {
+        bar: Spanned<bool>,
+        baz: Spanned<String>,
+    }
+    let f: Foo = from_str(
+        "
+    bar = true
+    baz = \"yes\"
+    ",
+    )
+    .unwrap();
+    let g: Foo = from_str(
+        "
+    baz = \"yes\"
+    bar = true
+    ",
+    )
+    .unwrap();
+    assert!(f.bar.span() != g.bar.span());
+    assert!(f.baz.span() != g.baz.span());
+
+    // test that eq still holds
+    assert_eq!(f.bar, g.bar);
+    assert_eq!(f.baz, g.baz);
+
+    // test that Ord returns equal order
+    assert_eq!(f.bar.cmp(&g.bar), Ordering::Equal);
+    assert_eq!(f.baz.cmp(&g.baz), Ordering::Equal);
+
+    // test that PartialOrd returns equal order
+    assert_eq!(f.bar.partial_cmp(&g.bar), Some(Ordering::Equal));
+    assert_eq!(f.baz.partial_cmp(&g.baz), Some(Ordering::Equal));
+}