From 197cc408c4d596dcbbda999fc131119afc1b857c Mon Sep 17 00:00:00 2001
From: Bas Zalmstra <zalmstra.bas@gmail.com>
Date: Mon, 16 Dec 2024 17:32:16 +0100
Subject: [PATCH] fix: add spans to tables

---
 .../tests/snapshots/parser__crlf-2.snap       | 40 +++++---
 .../snapshots/parser__empty_table-2.snap      | 11 +--
 .../tests/snapshots/parser__fruit-2.snap      | 80 ++++++++-------
 .../snapshots/parser__table_names-2.snap      | 61 ++++++------
 .../snapshots/parser__tables_in_arrays-2.snap | 43 ++++----
 .../tests/snapshots/valid__arrays__one-2.snap | 18 ++--
 .../tests/snapshots/valid__comments-2.snap    | 16 +--
 .../tests/snapshots/valid__evil-2.snap        | 37 ++++---
 .../valid__tables__array_many-2.snap          | 42 ++++----
 .../valid__tables__array_span-2.snap          | 56 +++++++++++
 .../snapshots/valid__tables__array_span.snap  | 15 +++
 ...tables__implicit_and_explicit_after-2.snap | 29 +++---
 ...ables__implicit_and_explicit_before-2.snap | 29 +++---
 .../valid__tables__implicit_array-2.snap      | 23 ++---
 .../valid__tables__implicit_groups-2.snap     | 23 ++---
 .../valid__tables__nested_arrays-2.snap       | 83 +++++++++-------
 .../snapshots/valid__tables__sub_empty-2.snap | 15 ++-
 .../valid__tables__table_span-2.snap          | 67 +++++++++++++
 .../snapshots/valid__tables__table_span.snap  | 18 ++++
 integ-tests/tests/valid.rs                    |  1 +
 toml-span/src/de.rs                           | 98 +++++++++++++------
 21 files changed, 532 insertions(+), 273 deletions(-)
 create mode 100644 integ-tests/tests/snapshots/valid__tables__array_span-2.snap
 create mode 100644 integ-tests/tests/snapshots/valid__tables__array_span.snap
 create mode 100644 integ-tests/tests/snapshots/valid__tables__table_span-2.snap
 create mode 100644 integ-tests/tests/snapshots/valid__tables__table_span.snap

diff --git a/integ-tests/tests/snapshots/parser__crlf-2.snap b/integ-tests/tests/snapshots/parser__crlf-2.snap
index 80b4e23..fbc41d5 100644
--- a/integ-tests/tests/snapshots/parser__crlf-2.snap
+++ b/integ-tests/tests/snapshots/parser__crlf-2.snap
@@ -26,16 +26,28 @@ note[string]: root_lib_0_path
   │         ^^^^^^
 
 note[table]: root_lib_0
-  ┌─ crlf:1:1
-  │
-1 │ [project]
-  │ ^
+   ┌─ crlf:7:1
+   │  
+ 7 │ ╭ [[lib]]
+ 8 │ │ 
+ 9 │ │ path = "lib.rs"
+10 │ │ name = "splay"
+   · │
+14 │ │ contents are never required to be entirely resident in memory all at once.
+15 │ │ """
+   │ ╰───^
 
 note[array]: root_lib
-  ┌─ crlf:1:1
-  │
-1 │ [project]
-  │ ^
+   ┌─ crlf:7:1
+   │  
+ 7 │ ╭ [[lib]]
+ 8 │ │ 
+ 9 │ │ path = "lib.rs"
+10 │ │ name = "splay"
+   · │
+14 │ │ contents are never required to be entirely resident in memory all at once.
+15 │ │ """
+   │ ╰───^
 
 note[string]: root_project_authors_0
   ┌─ crlf:5:13
@@ -63,9 +75,13 @@ note[string]: root_project_version
 
 note[table]: root_project
   ┌─ crlf:1:1
-  │
-1 │ [project]
-  │ ^
+  │  
+1 │ ╭ [project]
+2 │ │ 
+3 │ │ name = "splay"
+4 │ │ version = "0.1.0"
+5 │ │ authors = ["alex@crichton.co"]
+  │ ╰──────────────────────────────^
 
 note[table]: root
    ┌─ crlf:1:1
@@ -78,5 +94,3 @@ note[table]: root
 14 │ │ contents are never required to be entirely resident in memory all at once.
 15 │ │ """
    │ ╰───^
-
-
diff --git a/integ-tests/tests/snapshots/parser__empty_table-2.snap b/integ-tests/tests/snapshots/parser__empty_table-2.snap
index 5904ae0..763da6a 100644
--- a/integ-tests/tests/snapshots/parser__empty_table-2.snap
+++ b/integ-tests/tests/snapshots/parser__empty_table-2.snap
@@ -3,10 +3,11 @@ source: integ-tests/tests/parser.rs
 expression: spans
 ---
 note[table]: root_foo
-  ┌─ empty_table:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ empty_table:2:1
+  │  
+2 │ ╭ [foo]
+3 │ │ 
+  │ ╰^
 
 note[table]: root
   ┌─ empty_table:1:1
@@ -15,5 +16,3 @@ note[table]: root
 2 │ │ [foo]
 3 │ │ 
   │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/parser__fruit-2.snap b/integ-tests/tests/snapshots/parser__fruit-2.snap
index cfb0f99..c2cc3eb 100644
--- a/integ-tests/tests/snapshots/parser__fruit-2.snap
+++ b/integ-tests/tests/snapshots/parser__fruit-2.snap
@@ -21,10 +21,12 @@ note[string]: root_fruit_0_physical_shape
   │          ^^^^^
 
 note[table]: root_fruit_0_physical
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+  ┌─ fruit:4:1
+  │  
+4 │ ╭ [fruit.physical]
+5 │ │ color = "red"
+6 │ │ shape = "round"
+  │ ╰───────────────^
 
 note[string]: root_fruit_0_variety_0_name
   ┌─ fruit:9:9
@@ -33,10 +35,11 @@ note[string]: root_fruit_0_variety_0_name
   │         ^^^^^^^^^^^^^
 
 note[table]: root_fruit_0_variety_0
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+  ┌─ fruit:8:1
+  │  
+8 │ ╭ [[fruit.variety]]
+9 │ │ name = "red delicious"
+  │ ╰──────────────────────^
 
 note[string]: root_fruit_0_variety_1_name
    ┌─ fruit:12:9
@@ -45,22 +48,25 @@ note[string]: root_fruit_0_variety_1_name
    │         ^^^^^^^^^^^^
 
 note[table]: root_fruit_0_variety_1
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+   ┌─ fruit:11:1
+   │  
+11 │ ╭ [[fruit.variety]]
+12 │ │ name = "granny smith"
+   │ ╰─────────────────────^
 
 note[array]: root_fruit_0_variety
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+  ┌─ fruit:8:1
+  │  
+8 │ ╭ [[fruit.variety]]
+9 │ │ name = "red delicious"
+  │ ╰──────────────────────^
 
 note[table]: root_fruit_0
   ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+  │  
+1 │ ╭ [[fruit]]
+2 │ │ name = "apple"
+  │ ╰──────────────^
 
 note[string]: root_fruit_1_name
    ┌─ fruit:15:9
@@ -75,28 +81,32 @@ note[string]: root_fruit_1_variety_0_name
    │         ^^^^^^^^
 
 note[table]: root_fruit_1_variety_0
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+   ┌─ fruit:17:1
+   │  
+17 │ ╭ [[fruit.variety]]
+18 │ │ name = "plantain"
+   │ ╰─────────────────^
 
 note[array]: root_fruit_1_variety
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+   ┌─ fruit:17:1
+   │  
+17 │ ╭ [[fruit.variety]]
+18 │ │ name = "plantain"
+   │ ╰─────────────────^
 
 note[table]: root_fruit_1
-  ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+   ┌─ fruit:14:1
+   │  
+14 │ ╭ [[fruit]]
+15 │ │ name = "banana"
+   │ ╰───────────────^
 
 note[array]: root_fruit
   ┌─ fruit:1:1
-  │
-1 │ [[fruit]]
-  │ ^
+  │  
+1 │ ╭ [[fruit]]
+2 │ │ name = "apple"
+  │ ╰──────────────^
 
 note[table]: root
    ┌─ fruit:1:1
@@ -109,5 +119,3 @@ note[table]: root
 18 │ │ name = "plantain"
 19 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/parser__table_names-2.snap b/integ-tests/tests/snapshots/parser__table_names-2.snap
index c1a232e..f1897cc 100644
--- a/integ-tests/tests/snapshots/parser__table_names-2.snap
+++ b/integ-tests/tests/snapshots/parser__table_names-2.snap
@@ -3,46 +3,53 @@ source: integ-tests/tests/parser.rs
 expression: spans
 ---
 note[table]: root_"
-  ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  ┌─ table_names:4:1
+  │  
+4 │ ╭ ["\""]
+5 │ │ ['a.a']
+  │ ╰^
 
 note[table]: root_""
-  ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  ┌─ table_names:6:1
+  │  
+6 │ ╭ ['""']
+7 │ │ 
+  │ ╰^
 
 note[table]: root_a_b
   ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  │  
+1 │ ╭ [a."b"]
+2 │ │ ["f f"]
+  │ ╰^
 
 note[table]: root_a
   ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  │  
+1 │ ╭ [a."b"]
+2 │ │ ["f f"]
+  │ ╰^
 
 note[table]: root_a.a
-  ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  ┌─ table_names:5:1
+  │  
+5 │ ╭ ['a.a']
+6 │ │ ['""']
+  │ ╰^
 
 note[table]: root_f f
-  ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  ┌─ table_names:2:1
+  │  
+2 │ ╭ ["f f"]
+3 │ │ ["f.f"]
+  │ ╰^
 
 note[table]: root_f.f
-  ┌─ table_names:1:1
-  │
-1 │ [a."b"]
-  │ ^
+  ┌─ table_names:3:1
+  │  
+3 │ ╭ ["f.f"]
+4 │ │ ["\""]
+  │ ╰^
 
 note[table]: root
   ┌─ table_names:1:1
@@ -55,5 +62,3 @@ note[table]: root
 6 │ │ ['""']
 7 │ │ 
   │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/parser__tables_in_arrays-2.snap b/integ-tests/tests/snapshots/parser__tables_in_arrays-2.snap
index b9f451d..8862ba6 100644
--- a/integ-tests/tests/snapshots/parser__tables_in_arrays-2.snap
+++ b/integ-tests/tests/snapshots/parser__tables_in_arrays-2.snap
@@ -3,34 +3,39 @@ source: integ-tests/tests/parser.rs
 expression: spans
 ---
 note[table]: root_foo_0_bar
-  ┌─ tables_in_arrays:1:1
-  │
-1 │ [[foo]]
-  │ ^
+  ┌─ tables_in_arrays:3:1
+  │  
+3 │ ╭ [foo.bar]
+4 │ │ #…
+  │ ╰^
 
 note[table]: root_foo_0
   ┌─ tables_in_arrays:1:1
-  │
-1 │ [[foo]]
-  │ ^
+  │  
+1 │ ╭ [[foo]]
+2 │ │ #…
+  │ ╰^
 
 note[table]: root_foo_1_bar
-  ┌─ tables_in_arrays:1:1
-  │
-1 │ [[foo]]
-  │ ^
+  ┌─ tables_in_arrays:8:1
+  │  
+8 │ ╭ [foo.bar]
+9 │ │ #...
+  │ ╰^
 
 note[table]: root_foo_1
-  ┌─ tables_in_arrays:1:1
-  │
-1 │ [[foo]]
-  │ ^
+  ┌─ tables_in_arrays:6:1
+  │  
+6 │ ╭ [[foo]] # ...
+7 │ │ #…
+  │ ╰^
 
 note[array]: root_foo
   ┌─ tables_in_arrays:1:1
-  │
-1 │ [[foo]]
-  │ ^
+  │  
+1 │ ╭ [[foo]]
+2 │ │ #…
+  │ ╰^
 
 note[table]: root
    ┌─ tables_in_arrays:1:1
@@ -43,5 +48,3 @@ note[table]: root
  9 │ │ #...
 10 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__arrays__one-2.snap b/integ-tests/tests/snapshots/valid__arrays__one-2.snap
index b0fb572..7e0c931 100644
--- a/integ-tests/tests/snapshots/valid__arrays__one-2.snap
+++ b/integ-tests/tests/snapshots/valid__arrays__one-2.snap
@@ -16,15 +16,19 @@ note[string]: root_people_0_last_name
 
 note[table]: root_people_0
   ┌─ one:1:1
-  │
-1 │ [[people]]
-  │ ^
+  │  
+1 │ ╭ [[people]]
+2 │ │ first_name = "Bruce"
+3 │ │ last_name = "Springsteen"
+  │ ╰─────────────────────────^
 
 note[array]: root_people
   ┌─ one:1:1
-  │
-1 │ [[people]]
-  │ ^
+  │  
+1 │ ╭ [[people]]
+2 │ │ first_name = "Bruce"
+3 │ │ last_name = "Springsteen"
+  │ ╰─────────────────────────^
 
 note[table]: root
   ┌─ one:1:1
@@ -33,5 +37,3 @@ note[table]: root
 2 │ │ first_name = "Bruce"
 3 │ │ last_name = "Springsteen"
   │ ╰─────────────────────────^
-
-
diff --git a/integ-tests/tests/snapshots/valid__comments-2.snap b/integ-tests/tests/snapshots/valid__comments-2.snap
index 1929ca1..61370e9 100644
--- a/integ-tests/tests/snapshots/valid__comments-2.snap
+++ b/integ-tests/tests/snapshots/valid__comments-2.snap
@@ -34,10 +34,16 @@ note[array]: root_group_more
    │ ╰─^
 
 note[table]: root_group
-  ┌─ comments:1:1
-  │
-1 │ # Top comment.
-  │ ^
+   ┌─ comments:7:1
+   │  
+ 7 │ ╭ [group] # Comment
+ 8 │ │ answer = 42 # Comment
+ 9 │ │ # no-extraneous-keys-please = 999
+10 │ │ # In between comment.
+   · │
+23 │ │ # ] Did I fool you?
+24 │ │ ] # Hopefully not.
+   │ ╰─^
 
 note[table]: root
    ┌─ comments:1:1
@@ -50,5 +56,3 @@ note[table]: root
 24 │ │ ] # Hopefully not.
 25 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__evil-2.snap b/integ-tests/tests/snapshots/valid__evil-2.snap
index c3d484c..01ec6df 100644
--- a/integ-tests/tests/snapshots/valid__evil-2.snap
+++ b/integ-tests/tests/snapshots/valid__evil-2.snap
@@ -31,10 +31,15 @@ note[string]: root_the_hard_bit#_what?
    │                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 note[table]: root_the_hard_bit#
-  ┌─ evil:1:1
-  │
-1 │ # Test file for TOML
-  │ ^
+   ┌─ evil:16:9
+   │  
+16 │ ╭         [the.hard."bit#"]
+17 │ │         "what?" = "You don't think some user won't do that?"
+18 │ │         multi_line_array = [
+19 │ │             "]",
+20 │ │             # ] Oh yes I did
+21 │ │             ]
+   │ ╰─────────────^
 
 note[string]: root_the_hard_harder_test_string
    ┌─ evil:13:27
@@ -79,10 +84,15 @@ note[array]: root_the_hard_test_array2
    │                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 note[table]: root_the_hard
-  ┌─ evil:1:1
-  │
-1 │ # Test file for TOML
-  │ ^
+   ┌─ evil:8:5
+   │  
+ 8 │ ╭     [the.hard]
+ 9 │ │     test_array = [ "] ", " # "]      # ] There you go, parse this!
+10 │ │     test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
+11 │ │     # You didn't think it'd as easy as chucking out the last #, did you?
+12 │ │     another_test_string = " Same thing, but with a string #"
+13 │ │     harder_test_string = " And when \"'s are in the string, along with # \""   # "and comments are there too"
+   │ ╰────────────────────────────────────────────────────────────────────────────^
 
 note[string]: root_the_test_string
   ┌─ evil:6:16
@@ -91,10 +101,11 @@ note[string]: root_the_test_string
   │                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 note[table]: root_the
-  ┌─ evil:1:1
-  │
-1 │ # Test file for TOML
-  │ ^
+  ┌─ evil:5:1
+  │  
+5 │ ╭ [the]
+6 │ │ test_string = "You'll hate me after this - #"          # " Annoying, isn't it?
+  │ ╰─────────────────────────────────────────────^
 
 note[table]: root
    ┌─ evil:1:1
@@ -107,5 +118,3 @@ note[table]: root
 21 │ │             ]
 22 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__array_many-2.snap b/integ-tests/tests/snapshots/valid__tables__array_many-2.snap
index 747e9a4..05a567b 100644
--- a/integ-tests/tests/snapshots/valid__tables__array_many-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__array_many-2.snap
@@ -15,10 +15,12 @@ note[string]: root_people_0_last_name
   │              ^^^^^^^^^^^
 
 note[table]: root_people_0
-  ┌─ array_many:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ array_many:2:1
+  │  
+2 │ ╭ [[people]]
+3 │ │ first_name = "Bruce"
+4 │ │ last_name = "Springsteen"
+  │ ╰─────────────────────────^
 
 note[string]: root_people_1_first_name
   ┌─ array_many:7:15
@@ -33,10 +35,12 @@ note[string]: root_people_1_last_name
   │              ^^^^^^^
 
 note[table]: root_people_1
-  ┌─ array_many:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ array_many:6:1
+  │  
+6 │ ╭ [[people]]
+7 │ │ first_name = "Eric"
+8 │ │ last_name = "Clapton"
+  │ ╰─────────────────────^
 
 note[string]: root_people_2_first_name
    ┌─ array_many:11:15
@@ -51,16 +55,20 @@ note[string]: root_people_2_last_name
    │              ^^^^^
 
 note[table]: root_people_2
-  ┌─ array_many:1:1
-  │
-1 │ 
-  │ ^
+   ┌─ array_many:10:1
+   │  
+10 │ ╭ [[people]]
+11 │ │ first_name = "Bob"
+12 │ │ last_name = "Seger"
+   │ ╰───────────────────^
 
 note[array]: root_people
-  ┌─ array_many:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ array_many:2:1
+  │  
+2 │ ╭ [[people]]
+3 │ │ first_name = "Bruce"
+4 │ │ last_name = "Springsteen"
+  │ ╰─────────────────────────^
 
 note[table]: root
    ┌─ array_many:1:1
@@ -73,5 +81,3 @@ note[table]: root
 12 │ │ last_name = "Seger"
 13 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__array_span-2.snap b/integ-tests/tests/snapshots/valid__tables__array_span-2.snap
new file mode 100644
index 0000000..c3bf654
--- /dev/null
+++ b/integ-tests/tests/snapshots/valid__tables__array_span-2.snap
@@ -0,0 +1,56 @@
+---
+source: integ-tests/tests/valid.rs
+expression: spans
+---
+note[bool]: root_array_0_members
+  ┌─ array_span:3:11
+  │
+3 │ members = true
+  │           ^^^^
+
+note[bool]: root_array_0_other
+  ┌─ array_span:4:9
+  │
+4 │ other = false
+  │         ^^^^^
+
+note[table]: root_array_0
+  ┌─ array_span:2:1
+  │  
+2 │ ╭ [[array]]
+3 │ │ members = true
+4 │ │ other = false
+  │ ╰─────────────^
+
+note[bool]: root_array_1_members
+  ┌─ array_span:6:11
+  │
+6 │ members = false
+  │           ^^^^^
+
+note[table]: root_array_1
+  ┌─ array_span:5:1
+  │  
+5 │ ╭ [[array]]
+6 │ │ members = false
+  │ ╰───────────────^
+
+note[array]: root_array
+  ┌─ array_span:2:1
+  │  
+2 │ ╭ [[array]]
+3 │ │ members = true
+4 │ │ other = false
+  │ ╰─────────────^
+
+note[table]: root
+  ┌─ array_span:1:1
+  │  
+1 │ ╭ 
+2 │ │ [[array]]
+3 │ │ members = true
+4 │ │ other = false
+  · │
+7 │ │ 
+8 │ │ 
+  │ ╰^
diff --git a/integ-tests/tests/snapshots/valid__tables__array_span.snap b/integ-tests/tests/snapshots/valid__tables__array_span.snap
new file mode 100644
index 0000000..434e5ba
--- /dev/null
+++ b/integ-tests/tests/snapshots/valid__tables__array_span.snap
@@ -0,0 +1,15 @@
+---
+source: integ-tests/tests/valid.rs
+expression: valid_toml
+---
+{
+  "array": [
+    {
+      "members": true,
+      "other": false
+    },
+    {
+      "members": false
+    }
+  ]
+}
diff --git a/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_after-2.snap b/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_after-2.snap
index 6ac9fb9..b62e047 100644
--- a/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_after-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_after-2.snap
@@ -9,16 +9,18 @@ note[integer]: root_a_b_c_answer
   │          ^^
 
 note[table]: root_a_b_c
-  ┌─ implicit_and_explicit_after:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_after:2:1
+  │  
+2 │ ╭ [a.b.c]
+3 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root_a_b
-  ┌─ implicit_and_explicit_after:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_after:2:1
+  │  
+2 │ ╭ [a.b.c]
+3 │ │ answer = 42
+  │ ╰───────────^
 
 note[integer]: root_a_better
   ┌─ implicit_and_explicit_after:6:10
@@ -27,10 +29,11 @@ note[integer]: root_a_better
   │          ^^
 
 note[table]: root_a
-  ┌─ implicit_and_explicit_after:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_after:2:1
+  │  
+2 │ ╭ [a.b.c]
+3 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root
   ┌─ implicit_and_explicit_after:1:1
@@ -43,5 +46,3 @@ note[table]: root
 6 │ │ better = 43
 7 │ │ 
   │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_before-2.snap b/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_before-2.snap
index 9d8576f..f251278 100644
--- a/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_before-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__implicit_and_explicit_before-2.snap
@@ -9,16 +9,18 @@ note[integer]: root_a_b_c_answer
   │          ^^
 
 note[table]: root_a_b_c
-  ┌─ implicit_and_explicit_before:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_before:5:1
+  │  
+5 │ ╭ [a.b.c]
+6 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root_a_b
-  ┌─ implicit_and_explicit_before:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_before:5:1
+  │  
+5 │ ╭ [a.b.c]
+6 │ │ answer = 42
+  │ ╰───────────^
 
 note[integer]: root_a_better
   ┌─ implicit_and_explicit_before:3:10
@@ -27,10 +29,11 @@ note[integer]: root_a_better
   │          ^^
 
 note[table]: root_a
-  ┌─ implicit_and_explicit_before:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ implicit_and_explicit_before:2:1
+  │  
+2 │ ╭ [a]
+3 │ │ better = 43
+  │ ╰───────────^
 
 note[table]: root
   ┌─ implicit_and_explicit_before:1:1
@@ -43,5 +46,3 @@ note[table]: root
 6 │ │ answer = 42
 7 │ │ 
   │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__implicit_array-2.snap b/integ-tests/tests/snapshots/valid__tables__implicit_array-2.snap
index 52df972..ad731d7 100644
--- a/integ-tests/tests/snapshots/valid__tables__implicit_array-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__implicit_array-2.snap
@@ -10,21 +10,24 @@ note[string]: root_albums_songs_0_name
 
 note[table]: root_albums_songs_0
   ┌─ implicit_array:1:1
-  │
-1 │ [[albums.songs]]
-  │ ^
+  │  
+1 │ ╭ [[albums.songs]]
+2 │ │ name = "Glory Days"
+  │ ╰───────────────────^
 
 note[array]: root_albums_songs
   ┌─ implicit_array:1:1
-  │
-1 │ [[albums.songs]]
-  │ ^
+  │  
+1 │ ╭ [[albums.songs]]
+2 │ │ name = "Glory Days"
+  │ ╰───────────────────^
 
 note[table]: root_albums
   ┌─ implicit_array:1:1
-  │
-1 │ [[albums.songs]]
-  │ ^
+  │  
+1 │ ╭ [[albums.songs]]
+2 │ │ name = "Glory Days"
+  │ ╰───────────────────^
 
 note[table]: root
   ┌─ implicit_array:1:1
@@ -32,5 +35,3 @@ note[table]: root
 1 │ ╭ [[albums.songs]]
 2 │ │ name = "Glory Days"
   │ ╰───────────────────^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__implicit_groups-2.snap b/integ-tests/tests/snapshots/valid__tables__implicit_groups-2.snap
index cfb2e66..3167ca1 100644
--- a/integ-tests/tests/snapshots/valid__tables__implicit_groups-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__implicit_groups-2.snap
@@ -10,21 +10,24 @@ note[integer]: root_a_b_c_answer
 
 note[table]: root_a_b_c
   ┌─ implicit_groups:1:1
-  │
-1 │ [a.b.c]
-  │ ^
+  │  
+1 │ ╭ [a.b.c]
+2 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root_a_b
   ┌─ implicit_groups:1:1
-  │
-1 │ [a.b.c]
-  │ ^
+  │  
+1 │ ╭ [a.b.c]
+2 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root_a
   ┌─ implicit_groups:1:1
-  │
-1 │ [a.b.c]
-  │ ^
+  │  
+1 │ ╭ [a.b.c]
+2 │ │ answer = 42
+  │ ╰───────────^
 
 note[table]: root
   ┌─ implicit_groups:1:1
@@ -32,5 +35,3 @@ note[table]: root
 1 │ ╭ [a.b.c]
 2 │ │ answer = 42
   │ ╰───────────^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__nested_arrays-2.snap b/integ-tests/tests/snapshots/valid__tables__nested_arrays-2.snap
index b0829a5..1c9fe57 100644
--- a/integ-tests/tests/snapshots/valid__tables__nested_arrays-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__nested_arrays-2.snap
@@ -15,10 +15,11 @@ note[string]: root_albums_0_songs_0_name
   │           ^^^^^^^^^^
 
 note[table]: root_albums_0_songs_0
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ nested_arrays:5:3
+  │  
+5 │ ╭   [[albums.songs]]
+6 │ │   name = "Jungleland"
+  │ ╰─────────────────────^
 
 note[string]: root_albums_0_songs_1_name
   ┌─ nested_arrays:9:11
@@ -27,22 +28,25 @@ note[string]: root_albums_0_songs_1_name
   │           ^^^^^^^^^^^^^^^^^^^^^^^^
 
 note[table]: root_albums_0_songs_1
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ nested_arrays:8:3
+  │  
+8 │ ╭   [[albums.songs]]
+9 │ │   name = "Meeting Across the River"
+  │ ╰───────────────────────────────────^
 
 note[array]: root_albums_0_songs
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ nested_arrays:5:3
+  │  
+5 │ ╭   [[albums.songs]]
+6 │ │   name = "Jungleland"
+  │ ╰─────────────────────^
 
 note[table]: root_albums_0
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ nested_arrays:2:1
+  │  
+2 │ ╭ [[albums]]
+3 │ │ name = "Born to Run"
+  │ ╰────────────────────^
 
 note[string]: root_albums_1_name
    ┌─ nested_arrays:12:9
@@ -57,10 +61,11 @@ note[string]: root_albums_1_songs_0_name
    │           ^^^^^^^^^^
 
 note[table]: root_albums_1_songs_0
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+   ┌─ nested_arrays:14:3
+   │  
+14 │ ╭   [[albums.songs]]
+15 │ │   name = "Glory Days"
+   │ ╰─────────────────────^
 
 note[string]: root_albums_1_songs_1_name
    ┌─ nested_arrays:18:11
@@ -69,28 +74,32 @@ note[string]: root_albums_1_songs_1_name
    │           ^^^^^^^^^^^^^^^^^^^
 
 note[table]: root_albums_1_songs_1
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+   ┌─ nested_arrays:17:3
+   │  
+17 │ ╭   [[albums.songs]]
+18 │ │   name = "Dancing in the Dark"
+   │ ╰──────────────────────────────^
 
 note[array]: root_albums_1_songs
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+   ┌─ nested_arrays:14:3
+   │  
+14 │ ╭   [[albums.songs]]
+15 │ │   name = "Glory Days"
+   │ ╰─────────────────────^
 
 note[table]: root_albums_1
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+   ┌─ nested_arrays:11:1
+   │  
+11 │ ╭ [[albums]]
+12 │ │ name = "Born in the USA"
+   │ ╰────────────────────────^
 
 note[array]: root_albums
-  ┌─ nested_arrays:1:1
-  │
-1 │ 
-  │ ^
+  ┌─ nested_arrays:2:1
+  │  
+2 │ ╭ [[albums]]
+3 │ │ name = "Born to Run"
+  │ ╰────────────────────^
 
 note[table]: root
    ┌─ nested_arrays:1:1
@@ -103,5 +112,3 @@ note[table]: root
 18 │ │   name = "Dancing in the Dark"
 19 │ │ 
    │ ╰^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__sub_empty-2.snap b/integ-tests/tests/snapshots/valid__tables__sub_empty-2.snap
index 0bb920f..da873d5 100644
--- a/integ-tests/tests/snapshots/valid__tables__sub_empty-2.snap
+++ b/integ-tests/tests/snapshots/valid__tables__sub_empty-2.snap
@@ -3,16 +3,17 @@ source: integ-tests/tests/valid.rs
 expression: spans
 ---
 note[table]: root_a_b
-  ┌─ sub_empty:1:1
+  ┌─ sub_empty:2:1
   │
-1 │ [a]
-  │ ^
+2 │ [a.b]
+  │ ^^^^^
 
 note[table]: root_a
   ┌─ sub_empty:1:1
-  │
-1 │ [a]
-  │ ^
+  │  
+1 │ ╭ [a]
+2 │ │ [a.b]
+  │ ╰^
 
 note[table]: root
   ┌─ sub_empty:1:1
@@ -20,5 +21,3 @@ note[table]: root
 1 │ ╭ [a]
 2 │ │ [a.b]
   │ ╰─────^
-
-
diff --git a/integ-tests/tests/snapshots/valid__tables__table_span-2.snap b/integ-tests/tests/snapshots/valid__tables__table_span-2.snap
new file mode 100644
index 0000000..8cc6b12
--- /dev/null
+++ b/integ-tests/tests/snapshots/valid__tables__table_span-2.snap
@@ -0,0 +1,67 @@
+---
+source: integ-tests/tests/valid.rs
+expression: spans
+---
+note[integer]: root_workspace_bar_inline_foo
+  ┌─ table_span:9:18
+  │
+9 │ inline = { foo = 3 }
+  │                  ^
+
+note[table]: root_workspace_bar_inline
+  ┌─ table_span:9:10
+  │
+9 │ inline = { foo = 3 }
+  │          ^^^^^^^^^^^
+
+note[table]: root_workspace_bar
+  ┌─ table_span:8:1
+  │  
+8 │ ╭ [workspace.bar]
+9 │ │ inline = { foo = 3 }
+  │ ╰────────────────────^
+
+note[integer]: root_workspace_foo_bar
+  ┌─ table_span:6:5
+  │
+6 │ bar=3
+  │     ^
+
+note[table]: root_workspace_foo
+  ┌─ table_span:5:1
+  │  
+5 │ ╭ [workspace.foo]
+6 │ │ bar=3
+  │ ╰─────^
+
+note[bool]: root_workspace_members
+  ┌─ table_span:3:11
+  │
+3 │ members = true
+  │           ^^^^
+
+note[bool]: root_workspace_other
+  ┌─ table_span:4:9
+  │
+4 │ other = false
+  │         ^^^^^
+
+note[table]: root_workspace
+  ┌─ table_span:2:1
+  │  
+2 │ ╭ [workspace]
+3 │ │ members = true
+4 │ │ other = false
+  │ ╰─────────────^
+
+note[table]: root
+   ┌─ table_span:1:1
+   │  
+ 1 │ ╭ 
+ 2 │ │ [workspace]
+ 3 │ │ members = true
+ 4 │ │ other = false
+   · │
+10 │ │ 
+11 │ │ 
+   │ ╰^
diff --git a/integ-tests/tests/snapshots/valid__tables__table_span.snap b/integ-tests/tests/snapshots/valid__tables__table_span.snap
new file mode 100644
index 0000000..f3abcad
--- /dev/null
+++ b/integ-tests/tests/snapshots/valid__tables__table_span.snap
@@ -0,0 +1,18 @@
+---
+source: integ-tests/tests/valid.rs
+expression: valid_toml
+---
+{
+  "workspace": {
+    "bar": {
+      "inline": {
+        "foo": 3
+      }
+    },
+    "foo": {
+      "bar": 3
+    },
+    "members": true,
+    "other": false
+  }
+}
diff --git a/integ-tests/tests/valid.rs b/integ-tests/tests/valid.rs
index 9e8dbd1..3eb3961 100644
--- a/integ-tests/tests/valid.rs
+++ b/integ-tests/tests/valid.rs
@@ -86,6 +86,7 @@ name = "Born in the USA"
 "#
     );
     valid!(sub_empty, "[a]\n[a.b]");
+    valid!(table_span, "\n[workspace]\nmembers = true\nother = false\n[workspace.foo]\nbar=3\n\n[workspace.bar]\ninline = { foo = 3 }\n\n");
 }
 
 mod numbers {
diff --git a/toml-span/src/de.rs b/toml-span/src/de.rs
index 28ca422..4d62fd8 100644
--- a/toml-span/src/de.rs
+++ b/toml-span/src/de.rs
@@ -157,6 +157,11 @@ fn deserialize_table<'de, 'b>(
                 ));
             }
 
+            let span = ttable.values.as_ref().and_then(|v| v.span).map_or_else(
+                || Span::new(ttable.at, ttable.end),
+                |span| Span::new(ttable.at.min(span.start), ttable.end.max(span.end)),
+            );
+
             let array = ttable.array && ctx.depth == ttable.header.len() - 1;
             ctx.cur += 1;
 
@@ -181,7 +186,7 @@ fn deserialize_table<'de, 'b>(
                 ValueInner::Table(tab)
             };
 
-            table.insert(key, Value::new(value));
+            table.insert(key, Value::with_span(value, span));
             continue;
         }
 
@@ -193,7 +198,7 @@ fn deserialize_table<'de, 'b>(
             return Err(ctx.error(ttable.at, Some(ttable.end), ErrorKind::RedefineAsArray));
         }
 
-        ctx.values = ttable.values.take();
+        ctx.values = ttable.values.take().map(|table| table.values);
     }
 
     Ok(ctx.cur_parent)
@@ -215,7 +220,7 @@ fn to_value<'de>(val: Val<'de>, de: &Deserializer<'de>) -> Result<Value<'de>, Er
         E::DottedTable(tab) | E::InlineTable(tab) => {
             let mut ntable = value::Table::new();
 
-            for (k, v) in tab {
+            for (k, v) in tab.values {
                 table_insert(&mut ntable, k, v, de)?;
             }
 
@@ -284,13 +289,15 @@ fn deserialize_array<'de, 'b>(
             })
             .unwrap_or(ctx.max);
 
+        let ttable = &mut tables[ctx.cur_parent];
+
+        let span = ttable.values.as_ref().and_then(|v| v.span).map_or_else(
+            || Span::new(ttable.at, ttable.end),
+            |span| Span::new(ttable.at.min(span.start), ttable.end.max(span.end)),
+        );
+
         let actx = Ctx {
-            values: Some(
-                tables[ctx.cur_parent]
-                    .values
-                    .take()
-                    .expect("no array values"),
-            ),
+            values: Some(ttable.values.take().expect("no array values").values),
             max: next,
             depth: ctx.depth + 1,
             cur: 0,
@@ -302,7 +309,7 @@ fn deserialize_array<'de, 'b>(
 
         let mut table = value::Table::new();
         deserialize_table(actx, tables, &mut table)?;
-        arr.push(Value::new(ValueInner::Table(table)));
+        arr.push(Value::with_span(ValueInner::Table(table), span));
 
         ctx.cur_parent = next;
     }
@@ -374,10 +381,16 @@ struct Table<'de> {
     at: usize,
     end: usize,
     header: InlineVec<Key<'de>>,
-    values: Option<Vec<TablePair<'de>>>,
+    values: Option<TableValues<'de>>,
     array: bool,
 }
 
+#[derive(Debug, Default)]
+struct TableValues<'de> {
+    values: Vec<TablePair<'de>>,
+    span: Option<Span>,
+}
+
 impl<'a> Deserializer<'a> {
     fn new(input: &'a str) -> Deserializer<'a> {
         Deserializer {
@@ -411,18 +424,34 @@ impl<'a> Deserializer<'a> {
                         at,
                         end,
                         header: InlineVec::new(),
-                        values: Some(Vec::new()),
+                        values: Some(TableValues::default()),
                         array,
                     };
                     while let Some(part) = header.next().map_err(|e| self.token_error(e))? {
                         cur_table.header.push(part);
                     }
+                    cur_table.end = header.tokens.current();
                 }
-                Line::KeyValue(key, value) => {
-                    if cur_table.values.is_none() {
-                        cur_table.values = Some(Vec::new());
+                Line::KeyValue {
+                    key,
+                    value,
+                    at,
+                    end,
+                } => {
+                    let table_values = cur_table.values.get_or_insert_with(|| TableValues {
+                        values: Vec::new(),
+                        span: None,
+                    });
+                    self.add_dotted_key(key, value, table_values)?;
+                    match table_values.span {
+                        Some(ref mut span) => {
+                            span.start = span.start.min(at);
+                            span.end = span.end.max(end);
+                        }
+                        None => {
+                            table_values.span = Some(Span::new(at, end));
+                        }
                     }
-                    self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?;
                 }
             }
         }
@@ -467,18 +496,25 @@ impl<'a> Deserializer<'a> {
     }
 
     fn key_value(&mut self) -> Result<Line<'a>, Error> {
+        let start = self.tokens.current();
         let key = self.dotted_key()?;
         self.eat_whitespace();
         self.expect(Token::Equals)?;
         self.eat_whitespace();
 
         let value = self.value()?;
+        let end = self.tokens.current();
         self.eat_whitespace();
         if !self.eat_comment()? {
             self.eat_newline_or_eof()?;
         }
 
-        Ok(Line::KeyValue(key, value))
+        Ok(Line::KeyValue {
+            key,
+            value,
+            at: start,
+            end,
+        })
     }
 
     fn value(&mut self) -> Result<Val<'a>, Error> {
@@ -730,8 +766,8 @@ impl<'a> Deserializer<'a> {
 
     // TODO(#140): shouldn't buffer up this entire table in memory, it'd be
     // great to defer parsing everything until later.
-    fn inline_table(&mut self) -> Result<(Span, Vec<TablePair<'a>>), Error> {
-        let mut ret = Vec::new();
+    fn inline_table(&mut self) -> Result<(Span, TableValues<'a>), Error> {
+        let mut ret = TableValues::default();
         self.eat_whitespace();
         if let Some(span) = self.eat_spanned(Token::RightBrace)? {
             return Ok((span, ret));
@@ -817,14 +853,15 @@ impl<'a> Deserializer<'a> {
         &self,
         mut key_parts: Vec<Key<'a>>,
         value: Val<'a>,
-        values: &mut Vec<TablePair<'a>>,
+        values: &mut TableValues<'a>,
     ) -> Result<(), Error> {
         let key = key_parts.remove(0);
         if key_parts.is_empty() {
-            values.push((key, value));
+            values.values.push((key, value));
             return Ok(());
         }
         match values
+            .values
             .iter_mut()
             .find(|&&mut (ref k, _)| k.name == key.name)
         {
@@ -848,19 +885,19 @@ impl<'a> Deserializer<'a> {
         }
         // The start/end value is somewhat misleading here.
         let table_values = Val {
-            e: E::DottedTable(Vec::new()),
+            e: E::DottedTable(TableValues::default()),
             start: value.start,
             end: value.end,
         };
-        values.push((key, table_values));
-        let last_i = values.len() - 1;
+        values.values.push((key, table_values));
+        let last_i = values.values.len() - 1;
         if let (
             _,
             Val {
                 e: E::DottedTable(ref mut v),
                 ..
             },
-        ) = values[last_i]
+        ) = values.values[last_i]
         {
             self.add_dotted_key(key_parts, value, v)?;
         }
@@ -1044,7 +1081,12 @@ enum Line<'a> {
         header: Header<'a>,
         array: bool,
     },
-    KeyValue(Vec<Key<'a>>, Val<'a>),
+    KeyValue {
+        at: usize,
+        end: usize,
+        key: Vec<Key<'a>>,
+        value: Val<'a>,
+    },
 }
 
 struct Header<'a> {
@@ -1098,8 +1140,8 @@ enum E<'a> {
     Boolean(bool),
     String(DeStr<'a>),
     Array(Vec<Val<'a>>),
-    InlineTable(Vec<TablePair<'a>>),
-    DottedTable(Vec<TablePair<'a>>),
+    InlineTable(TableValues<'a>),
+    DottedTable(TableValues<'a>),
 }
 
 impl E<'_> {