Snap for 11426397 from 0ba44b698605385f51d234dde44b599bf8f7bbfc to 24D1-release

Change-Id: If9728e6e87ba446247383aa5cb95cb29e554a4fe
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index fbf4047..7288de0 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
 {
   "git": {
-    "sha1": "5753e4fd8ddf78980c41aabaec78a935eaba93e1"
+    "sha1": "a20848d8a228605e2e4fa30b01c1a71d448de175"
   },
   "path_in_vcs": "crates/toml_edit"
 }
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 457de19..48f9cce 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5,11 +5,13 @@
     name: "libtoml_edit",
     crate_name: "toml_edit",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.19.14",
+    cargo_pkg_version: "0.22.4",
     srcs: ["src/lib.rs"],
     edition: "2021",
     features: [
         "default",
+        "display",
+        "parse",
         "serde",
     ],
     rustlibs: [
diff --git a/Cargo.lock b/Cargo.lock
index 5dfc8c2..15a049c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -184,6 +184,15 @@
 ]
 
 [[package]]
+name = "escape8259"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -337,6 +346,18 @@
 ]
 
 [[package]]
+name = "libtest-mimic"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b"
+dependencies = [
+ "clap",
+ "escape8259",
+ "termcolor",
+ "threadpool",
+]
+
+[[package]]
 name = "linux-raw-sys"
 version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -430,18 +451,18 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
@@ -478,10 +499,16 @@
 ]
 
 [[package]]
-name = "ryu"
-version = "1.0.11"
+name = "rustversion"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "same-file"
@@ -494,22 +521,22 @@
 
 [[package]]
 name = "serde"
-version = "1.0.160"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.160"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.41",
 ]
 
 [[package]]
@@ -525,9 +552,9 @@
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.3"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
 dependencies = [
  "serde",
 ]
@@ -547,7 +574,7 @@
  "anstream",
  "anstyle",
  "ignore",
- "libtest-mimic",
+ "libtest-mimic 0.6.0",
  "normalize-line-endings",
  "similar",
  "snapbox-macros",
@@ -587,9 +614,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.15"
+version = "2.0.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -625,52 +652,53 @@
 
 [[package]]
 name = "toml-test"
-version = "0.3.4"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37351256790aa1dbd6d60f4ff08e55e7f372e292f3e9040d6e077463d9a779c3"
+checksum = "1ec3892835fb31e181a87e1758275a64b0d7c6c9e9618aeb61a647bd487314c0"
 dependencies = [
  "chrono",
+ "ryu",
  "serde",
  "serde_json",
 ]
 
 [[package]]
 name = "toml-test-data"
-version = "1.3.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93f351b6d6005ee802b0d4a53ca1cdf05636f441df4d299e62cba57f1da52646"
+checksum = "c6b5bad99e813ce8c67d1d67c9b9f37c8451933f45eae0ab2b3583975f1cc15d"
 dependencies = [
  "include_dir",
 ]
 
 [[package]]
 name = "toml-test-harness"
-version = "0.4.3"
+version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e00fda5710922fe6b3005bf6a5050c303d6f9625249c37b7386e8818f4af675"
+checksum = "1be4b8d761dee51b4694e9f1d622a1d7f9c135a8b8265459e16d09ac5b16a05d"
 dependencies = [
  "ignore",
- "libtest-mimic",
+ "libtest-mimic 0.6.0",
  "toml-test",
  "toml-test-data",
 ]
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.3"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.19.14"
+version = "0.22.4"
 dependencies = [
  "indexmap",
  "kstring",
- "libtest-mimic",
+ "libtest-mimic 0.7.0",
  "serde",
  "serde_json",
  "serde_spanned",
diff --git a/Cargo.toml b/Cargo.toml
index 26929ff..2e27069 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,9 @@
 
 [package]
 edition = "2021"
-rust-version = "1.64.0"
+rust-version = "1.69"
 name = "toml_edit"
-version = "0.19.14"
+version = "0.22.4"
 authors = [
     "Andronik Ordian <[email protected]>",
     "Ed Page <[email protected]>",
@@ -29,6 +29,7 @@
     "examples/**/*",
     "tests/**/*",
 ]
+autotests = false
 description = "Yet another format-preserving TOML parser."
 readme = "README.md"
 keywords = [
@@ -92,18 +93,35 @@
 [[example]]
 name = "visit"
 test = true
+required-features = [
+    "parse",
+    "display",
+]
+
+[[test]]
+name = "testsuite"
+required-features = [
+    "parse",
+    "display",
+]
 
 [[test]]
 name = "decoder_compliance"
 harness = false
+required-features = ["parse"]
 
 [[test]]
 name = "encoder_compliance"
 harness = false
+required-features = [
+    "parse",
+    "display",
+]
 
 [[test]]
 name = "invalid"
 harness = false
+required-features = ["parse"]
 
 [dependencies.indexmap]
 version = "2.0.0"
@@ -119,18 +137,19 @@
 optional = true
 
 [dependencies.serde_spanned]
-version = "0.6.3"
+version = "0.6.5"
 features = ["serde"]
 optional = true
 
 [dependencies.toml_datetime]
-version = "0.6.3"
+version = "0.6.5"
 
 [dependencies.winnow]
 version = "0.5.0"
+optional = true
 
 [dev-dependencies.libtest-mimic]
-version = "0.6.0"
+version = "0.7.0"
 
 [dev-dependencies.serde_json]
 version = "1.0.96"
@@ -140,13 +159,18 @@
 features = ["harness"]
 
 [dev-dependencies.toml-test-data]
-version = "1.3.0"
+version = "1.8.0"
 
 [dev-dependencies.toml-test-harness]
-version = "0.4.3"
+version = "0.4.8"
 
 [features]
-default = []
+default = [
+    "parse",
+    "display",
+]
+display = []
+parse = ["dep:winnow"]
 perf = ["dep:kstring"]
 serde = [
     "dep:serde",
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index cda679f..9ab60c1 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,10 +1,11 @@
 [package]
 name = "toml_edit"
-version = "0.19.14"
+version = "0.22.4"
 keywords = ["encoding", "toml"]
 categories = ["encoding", "parser-implementations", "parsing", "config"]
 description = "Yet another format-preserving TOML parser."
 authors = ["Andronik Ordian <[email protected]>", "Ed Page <[email protected]>"]
+autotests = false
 repository.workspace = true
 license.workspace = true
 edition.workspace = true
@@ -12,8 +13,8 @@
 include.workspace = true
 
 [package.metadata.docs.rs]
-rustdoc-args = ["--cfg", "docsrs"]
 features = ["serde"]
+rustdoc-args = ["--cfg", "docsrs"]
 
 [package.metadata.release]
 tag-name = "v{{version}}"
@@ -26,7 +27,9 @@
 ]
 
 [features]
-default = []
+default = ["parse", "display"]
+parse = ["dep:winnow"]
+display = []
 perf = ["dep:kstring"]
 serde = ["dep:serde", "toml_datetime/serde", "dep:serde_spanned"]
 # Provide a method disable_recursion_limit to parse arbitrarily deep structures
@@ -38,31 +41,39 @@
 
 [dependencies]
 indexmap = { version = "2.0.0", features = ["std"] }
-winnow = "0.5.0"
+winnow = { version = "0.5.0", optional = true }
 serde = { version = "1.0.145", optional = true }
 kstring = { version = "2.0.0", features = ["max_inline"], optional = true }
-toml_datetime = { version = "0.6.3", path = "../toml_datetime" }
-serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["serde"], optional = true }
+toml_datetime = { version = "0.6.5", path = "../toml_datetime" }
+serde_spanned = { version = "0.6.5", path = "../serde_spanned", features = ["serde"], optional = true }
 
 [dev-dependencies]
 serde_json = "1.0.96"
-toml-test-harness = "0.4.3"
-toml-test-data = "1.3.0"
-libtest-mimic = "0.6.0"
+toml-test-harness = "0.4.8"
+toml-test-data = "1.8.0"
+libtest-mimic = "0.7.0"
 snapbox = { version = "0.4.11", features = ["harness"] }
 
 [[test]]
+name = "testsuite"
+required-features = ["parse", "display"]
+
+[[test]]
 name = "decoder_compliance"
+required-features = ["parse"]
 harness = false
 
 [[test]]
 name = "encoder_compliance"
+required-features = ["parse", "display"]
 harness = false
 
 [[test]]
 name = "invalid"
+required-features = ["parse"]
 harness = false
 
 [[example]]
 name = "visit"
+required-features = ["parse", "display"]
 test = true
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
index 16fe87b..8f71f43 100644
--- a/LICENSE-APACHE
+++ b/LICENSE-APACHE
@@ -1,201 +1,202 @@
-                              Apache License
-                        Version 2.0, January 2004
-                     http://www.apache.org/licenses/
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
 
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
-1. Definitions.
+   1. Definitions.
 
-   "License" shall mean the terms and conditions for use, reproduction,
-   and distribution as defined by Sections 1 through 9 of this document.
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
 
-   "Licensor" shall mean the copyright owner or entity authorized by
-   the copyright owner that is granting the License.
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
 
-   "Legal Entity" shall mean the union of the acting entity and all
-   other entities that control, are controlled by, or are under common
-   control with that entity. For the purposes of this definition,
-   "control" means (i) the power, direct or indirect, to cause the
-   direction or management of such entity, whether by contract or
-   otherwise, or (ii) ownership of fifty percent (50%) or more of the
-   outstanding shares, or (iii) beneficial ownership of such entity.
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
 
-   "You" (or "Your") shall mean an individual or Legal Entity
-   exercising permissions granted by this License.
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
 
-   "Source" form shall mean the preferred form for making modifications,
-   including but not limited to software source code, documentation
-   source, and configuration files.
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
 
-   "Object" form shall mean any form resulting from mechanical
-   transformation or translation of a Source form, including but
-   not limited to compiled object code, generated documentation,
-   and conversions to other media types.
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
 
-   "Work" shall mean the work of authorship, whether in Source or
-   Object form, made available under the License, as indicated by a
-   copyright notice that is included in or attached to the work
-   (an example is provided in the Appendix below).
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
 
-   "Derivative Works" shall mean any work, whether in Source or Object
-   form, that is based on (or derived from) the Work and for which the
-   editorial revisions, annotations, elaborations, or other modifications
-   represent, as a whole, an original work of authorship. For the purposes
-   of this License, Derivative Works shall not include works that remain
-   separable from, or merely link (or bind by name) to the interfaces of,
-   the Work and Derivative Works thereof.
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
 
-   "Contribution" shall mean any work of authorship, including
-   the original version of the Work and any modifications or additions
-   to that Work or Derivative Works thereof, that is intentionally
-   submitted to Licensor for inclusion in the Work by the copyright owner
-   or by an individual or Legal Entity authorized to submit on behalf of
-   the copyright owner. For the purposes of this definition, "submitted"
-   means any form of electronic, verbal, or written communication sent
-   to the Licensor or its representatives, including but not limited to
-   communication on electronic mailing lists, source code control systems,
-   and issue tracking systems that are managed by, or on behalf of, the
-   Licensor for the purpose of discussing and improving the Work, but
-   excluding communication that is conspicuously marked or otherwise
-   designated in writing by the copyright owner as "Not a Contribution."
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
 
-   "Contributor" shall mean Licensor and any individual or Legal Entity
-   on behalf of whom a Contribution has been received by Licensor and
-   subsequently incorporated within the Work.
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
 
-2. Grant of Copyright License. Subject to the terms and conditions of
-   this License, each Contributor hereby grants to You a perpetual,
-   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-   copyright license to reproduce, prepare Derivative Works of,
-   publicly display, publicly perform, sublicense, and distribute the
-   Work and such Derivative Works in Source or Object form.
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
 
-3. Grant of Patent License. Subject to the terms and conditions of
-   this License, each Contributor hereby grants to You a perpetual,
-   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-   (except as stated in this section) patent license to make, have made,
-   use, offer to sell, sell, import, and otherwise transfer the Work,
-   where such license applies only to those patent claims licensable
-   by such Contributor that are necessarily infringed by their
-   Contribution(s) alone or by combination of their Contribution(s)
-   with the Work to which such Contribution(s) was submitted. If You
-   institute patent litigation against any entity (including a
-   cross-claim or counterclaim in a lawsuit) alleging that the Work
-   or a Contribution incorporated within the Work constitutes direct
-   or contributory patent infringement, then any patent licenses
-   granted to You under this License for that Work shall terminate
-   as of the date such litigation is filed.
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
 
-4. Redistribution. You may reproduce and distribute copies of the
-   Work or Derivative Works thereof in any medium, with or without
-   modifications, and in Source or Object form, provided that You
-   meet the following conditions:
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
 
-   (a) You must give any other recipients of the Work or
-       Derivative Works a copy of this License; and
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
 
-   (b) You must cause any modified files to carry prominent notices
-       stating that You changed the files; and
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
 
-   (c) You must retain, in the Source form of any Derivative Works
-       that You distribute, all copyright, patent, trademark, and
-       attribution notices from the Source form of the Work,
-       excluding those notices that do not pertain to any part of
-       the Derivative Works; and
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
 
-   (d) If the Work includes a "NOTICE" text file as part of its
-       distribution, then any Derivative Works that You distribute must
-       include a readable copy of the attribution notices contained
-       within such NOTICE file, excluding those notices that do not
-       pertain to any part of the Derivative Works, in at least one
-       of the following places: within a NOTICE text file distributed
-       as part of the Derivative Works; within the Source form or
-       documentation, if provided along with the Derivative Works; or,
-       within a display generated by the Derivative Works, if and
-       wherever such third-party notices normally appear. The contents
-       of the NOTICE file are for informational purposes only and
-       do not modify the License. You may add Your own attribution
-       notices within Derivative Works that You distribute, alongside
-       or as an addendum to the NOTICE text from the Work, provided
-       that such additional attribution notices cannot be construed
-       as modifying the License.
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
 
-   You may add Your own copyright statement to Your modifications and
-   may provide additional or different license terms and conditions
-   for use, reproduction, or distribution of Your modifications, or
-   for any such Derivative Works as a whole, provided Your use,
-   reproduction, and distribution of the Work otherwise complies with
-   the conditions stated in this License.
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
 
-5. Submission of Contributions. Unless You explicitly state otherwise,
-   any Contribution intentionally submitted for inclusion in the Work
-   by You to the Licensor shall be under the terms and conditions of
-   this License, without any additional terms or conditions.
-   Notwithstanding the above, nothing herein shall supersede or modify
-   the terms of any separate license agreement you may have executed
-   with Licensor regarding such Contributions.
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
 
-6. Trademarks. This License does not grant permission to use the trade
-   names, trademarks, service marks, or product names of the Licensor,
-   except as required for reasonable and customary use in describing the
-   origin of the Work and reproducing the content of the NOTICE file.
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
 
-7. Disclaimer of Warranty. Unless required by applicable law or
-   agreed to in writing, Licensor provides the Work (and each
-   Contributor provides its Contributions) on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-   implied, including, without limitation, any warranties or conditions
-   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-   PARTICULAR PURPOSE. You are solely responsible for determining the
-   appropriateness of using or redistributing the Work and assume any
-   risks associated with Your exercise of permissions under this License.
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
 
-8. Limitation of Liability. In no event and under no legal theory,
-   whether in tort (including negligence), contract, or otherwise,
-   unless required by applicable law (such as deliberate and grossly
-   negligent acts) or agreed to in writing, shall any Contributor be
-   liable to You for damages, including any direct, indirect, special,
-   incidental, or consequential damages of any character arising as a
-   result of this License or out of the use or inability to use the
-   Work (including but not limited to damages for loss of goodwill,
-   work stoppage, computer failure or malfunction, or any and all
-   other commercial damages or losses), even if such Contributor
-   has been advised of the possibility of such damages.
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
 
-9. Accepting Warranty or Additional Liability. While redistributing
-   the Work or Derivative Works thereof, You may choose to offer,
-   and charge a fee for, acceptance of support, warranty, indemnity,
-   or other liability obligations and/or rights consistent with this
-   License. However, in accepting such obligations, You may act only
-   on Your own behalf and on Your sole responsibility, not on behalf
-   of any other Contributor, and only if You agree to indemnify,
-   defend, and hold each Contributor harmless for any liability
-   incurred by, or claims asserted against, such Contributor by reason
-   of your accepting any such warranty or additional liability.
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
 
-END OF TERMS AND CONDITIONS
+   END OF TERMS AND CONDITIONS
 
-APPENDIX: How to apply the Apache License to your work.
+   APPENDIX: How to apply the Apache License to your work.
 
-   To apply the Apache License to your work, attach the following
-   boilerplate notice, with the fields enclosed by brackets "[]"
-   replaced with your own identifying information. (Don't include
-   the brackets!)  The text should be enclosed in the appropriate
-   comment syntax for the file format. We also recommend that a
-   file or class name and description of purpose be included on the
-   same "printed page" as the copyright notice for easier
-   identification within third-party archives.
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
 
-Copyright [yyyy] [name of copyright owner]
+   Copyright {yyyy} {name of copyright owner}
 
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
 
-	http://www.apache.org/licenses/LICENSE-2.0
+       http://www.apache.org/licenses/LICENSE-2.0
 
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/LICENSE-MIT b/LICENSE-MIT
index b9e61a2..a2d0108 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,6 +1,4 @@
-MIT License
-
-Copyright (c) 2017 Andronik Ordian
+Copyright (c) Individual contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/METADATA b/METADATA
index 6a96df6..6c3f465 100644
--- a/METADATA
+++ b/METADATA
@@ -1,20 +1,24 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update external/rust/crates/toml_edit
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
+
 name: "toml_edit"
 description: "Yet another format-preserving TOML parser."
 third_party {
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2024
+    month: 2
+    day: 7
+  }
   identifier {
     type: "crates.io"
-    value: "https://crates.io/crates/toml_edit"
+    value: "https://static.crates.io/crates/toml_edit/toml_edit-0.22.4.crate"
+    version: "0.19.14"
   }
   identifier {
     type: "Archive"
     value: "https://static.crates.io/crates/toml_edit/toml_edit-0.19.14.crate"
-  }
-  version: "0.19.14"
-  # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
-  license_type: NOTICE
-  last_upgrade_date {
-    year: 2023
-    month: 8
-    day: 23
+    version: "0.22.4"
   }
 }
diff --git a/README.md b/README.md
index f1c74ee..b6d1275 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # toml_edit
 
-[![Build Status](https://github.com/ordian/toml_edit/workflows/Continuous%20integration/badge.svg)](https://github.com/ordian/toml_edit/actions)
-[![codecov](https://codecov.io/gh/ordian/toml_edit/branch/master/graph/badge.svg)](https://codecov.io/gh/ordian/toml_edit)
+[![Build Status](https://github.com/toml-rs/toml/workflows/Continuous%20integration/badge.svg)](https://github.com/toml-rs/toml/actions)
+[![codecov](https://codecov.io/gh/toml-rs/toml/branch/master/graph/badge.svg)](https://codecov.io/gh/toml-rs/toml)
 [![crates.io](https://img.shields.io/crates/v/toml_edit.svg)](https://crates.io/crates/toml_edit)
 [![docs](https://docs.rs/toml_edit/badge.svg)](https://docs.rs/toml_edit)
 [![Join the chat at https://gitter.im/toml_edit/Lobby](https://badges.gitter.im/a.svg)](https://gitter.im/toml_edit/Lobby)
@@ -9,7 +9,7 @@
 
 This crate allows you to parse and modify toml
 documents, while preserving comments, spaces *and
-relative order* or items.
+relative order* of items.
 
 `toml_edit` is primarily tailored for [cargo-edit](https://github.com/killercup/cargo-edit/) needs.
 
@@ -42,8 +42,7 @@
 
 Things it does not preserve:
 
-* Scattered array of tables (tables are reordered by default, see [test]).
-* Order of dotted keys, see [issue](https://github.com/ordian/toml_edit/issues/163).
+* Order of dotted keys, see [issue](https://github.com/toml-rs/toml/issues/163).
 
 ## License
 
@@ -55,5 +54,3 @@
 ### Contribution
 
 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
-
-[test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84
diff --git a/src/array.rs b/src/array.rs
index 045b451..377f676 100644
--- a/src/array.rs
+++ b/src/array.rs
@@ -183,9 +183,11 @@
     /// # Examples
     ///
     /// ```rust
+    /// # #[cfg(feature = "parse")] {
     /// let formatted_value = "'literal'".parse::<toml_edit::Value>().unwrap();
     /// let mut arr = toml_edit::Array::new();
     /// arr.push_formatted(formatted_value);
+    /// # }
     /// ```
     pub fn push_formatted(&mut self, v: Value) {
         self.values.push(Item::Value(v));
@@ -223,12 +225,14 @@
     /// # Examples
     ///
     /// ```rust
+    /// # #[cfg(feature = "parse")] {
     /// let mut arr = toml_edit::Array::new();
     /// arr.push(1);
     /// arr.push("foo");
     ///
     /// let formatted_value = "'start'".parse::<toml_edit::Value>().unwrap();
     /// arr.insert_formatted(0, formatted_value);
+    /// # }
     /// ```
     pub fn insert_formatted(&mut self, index: usize, v: Value) {
         self.values.insert(index, Item::Value(v))
@@ -269,12 +273,14 @@
     /// # Examples
     ///
     /// ```rust
+    /// # #[cfg(feature = "parse")] {
     /// let mut arr = toml_edit::Array::new();
     /// arr.push(1);
     /// arr.push("foo");
     ///
     /// let formatted_value = "'start'".parse::<toml_edit::Value>().unwrap();
     /// arr.replace_formatted(0, formatted_value);
+    /// # }
     /// ```
     pub fn replace_formatted(&mut self, index: usize, v: Value) -> Value {
         match mem::replace(&mut self.values[index], Item::Value(v)) {
@@ -317,6 +323,56 @@
             .retain(|item| item.as_value().map(&mut keep).unwrap_or(false));
     }
 
+    /// Sorts the slice with a comparator function.
+    ///
+    /// This sort is stable (i.e., does not reorder equal elements) and *O*(*n* \* log(*n*)) worst-case.
+    ///
+    /// The comparator function must define a total ordering for the elements in the slice. If
+    /// the ordering is not total, the order of the elements is unspecified. An order is a
+    /// total order if it is (for all `a`, `b` and `c`):
+    ///
+    /// * total and antisymmetric: exactly one of `a < b`, `a == b` or `a > b` is true, and
+    /// * transitive, `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`.
+    ///
+    /// For example, while [`f64`] doesn't implement [`Ord`] because `NaN != NaN`, we can use
+    /// `partial_cmp` as our sort function when we know the slice doesn't contain a `NaN`.
+    #[inline]
+    pub fn sort_by<F>(&mut self, mut compare: F)
+    where
+        F: FnMut(&Value, &Value) -> std::cmp::Ordering,
+    {
+        self.values.sort_by(move |lhs, rhs| {
+            let lhs = lhs.as_value();
+            let rhs = rhs.as_value();
+            match (lhs, rhs) {
+                (None, None) => std::cmp::Ordering::Equal,
+                (Some(_), None) => std::cmp::Ordering::Greater,
+                (None, Some(_)) => std::cmp::Ordering::Less,
+                (Some(lhs), Some(rhs)) => compare(lhs, rhs),
+            }
+        })
+    }
+
+    /// Sorts the array with a key extraction function.
+    ///
+    /// This sort is stable (i.e., does not reorder equal elements) and *O*(*m* \* *n* \* log(*n*))
+    /// worst-case, where the key function is *O*(*m*).
+    #[inline]
+    pub fn sort_by_key<K, F>(&mut self, mut f: F)
+    where
+        F: FnMut(&Value) -> K,
+        K: Ord,
+    {
+        #[allow(clippy::manual_map)] // needed for lifetimes
+        self.values.sort_by_key(move |item| {
+            if let Some(value) = item.as_value() {
+                Some(f(value))
+            } else {
+                None
+            }
+        });
+    }
+
     fn value_op<T>(
         &mut self,
         v: Value,
@@ -333,9 +389,10 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for Array {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        crate::encode::Encode::encode(self, f, None, ("", ""))
+        crate::encode::encode_array(self, f, None, ("", ""))
     }
 }
 
diff --git a/src/array_of_tables.rs b/src/array_of_tables.rs
index c4d7194..2e602a2 100644
--- a/src/array_of_tables.rs
+++ b/src/array_of_tables.rs
@@ -158,6 +158,7 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for ArrayOfTables {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         // HACK: Without the header, we don't really have a proper way of printing this
diff --git a/src/de/key.rs b/src/de/key.rs
index 3da41df..a3b2825 100644
--- a/src/de/key.rs
+++ b/src/de/key.rs
@@ -62,9 +62,20 @@
         self.deserialize_any(visitor)
     }
 
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
     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
+        bytes byte_buf map option unit
         ignored_any unit_struct tuple_struct tuple identifier
     }
 }
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 09ea120..9b8a2c7 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -87,6 +87,7 @@
 impl std::error::Error for Error {}
 
 /// Convert a value into `T`.
+#[cfg(feature = "parse")]
 pub fn from_str<T>(s: &'_ str) -> Result<T, Error>
 where
     T: DeserializeOwned,
@@ -96,6 +97,7 @@
 }
 
 /// Convert a value into `T`.
+#[cfg(feature = "parse")]
 pub fn from_slice<T>(s: &'_ [u8]) -> Result<T, Error>
 where
     T: DeserializeOwned,
@@ -125,6 +127,7 @@
     }
 }
 
+#[cfg(feature = "parse")]
 impl std::str::FromStr for Deserializer {
     type Err = Error;
 
diff --git a/src/de/table.rs b/src/de/table.rs
index 0b6183e..33aa397 100644
--- a/src/de/table.rs
+++ b/src/de/table.rs
@@ -118,7 +118,7 @@
 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::Key, crate::Item)>,
 }
 
 impl TableMapAccess {
@@ -149,7 +149,7 @@
                         }
                         e
                     });
-                self.value = Some((v.key.into(), v.value));
+                self.value = Some((v.key, v.value));
                 ret
             }
             None => Ok(None),
@@ -162,13 +162,13 @@
     {
         match self.value.take() {
             Some((k, v)) => {
-                let span = v.span();
+                let span = v.span().or_else(|| k.span());
                 seed.deserialize(crate::de::ValueDeserializer::new(v))
                     .map_err(|mut e: Self::Error| {
                         if e.span().is_none() {
                             e.set_span(span);
                         }
-                        e.add_key(k.as_str().to_owned());
+                        e.add_key(k.get().to_owned());
                         e
                     })
             }
diff --git a/src/de/table_enum.rs b/src/de/table_enum.rs
index 197ad6e..0ceeab6 100644
--- a/src/de/table_enum.rs
+++ b/src/de/table_enum.rs
@@ -16,6 +16,20 @@
 
     fn unit_variant(self) -> Result<(), Self::Error> {
         match self.value {
+            crate::Item::ArrayOfTables(values) => {
+                if values.is_empty() {
+                    Ok(())
+                } else {
+                    Err(Error::custom("expected empty array", values.span()))
+                }
+            }
+            crate::Item::Value(crate::Value::Array(values)) => {
+                if values.is_empty() {
+                    Ok(())
+                } else {
+                    Err(Error::custom("expected empty table", values.span()))
+                }
+            }
             crate::Item::Table(values) => {
                 if values.is_empty() {
                     Ok(())
@@ -49,9 +63,41 @@
         V: serde::de::Visitor<'de>,
     {
         match self.value {
+            crate::Item::ArrayOfTables(values) => {
+                let values_span = values.span();
+                let tuple_values = values.values.into_iter().collect::<Vec<_>>();
+
+                if tuple_values.len() == len {
+                    serde::de::Deserializer::deserialize_seq(
+                        super::ArrayDeserializer::new(tuple_values, values_span),
+                        visitor,
+                    )
+                } else {
+                    Err(Error::custom(
+                        format!("expected tuple with length {}", len),
+                        values_span,
+                    ))
+                }
+            }
+            crate::Item::Value(crate::Value::Array(values)) => {
+                let values_span = values.span();
+                let tuple_values = values.values.into_iter().collect::<Vec<_>>();
+
+                if tuple_values.len() == len {
+                    serde::de::Deserializer::deserialize_seq(
+                        super::ArrayDeserializer::new(tuple_values, values_span),
+                        visitor,
+                    )
+                } else {
+                    Err(Error::custom(
+                        format!("expected tuple with length {}", len),
+                        values_span,
+                    ))
+                }
+            }
             crate::Item::Table(values) => {
                 let values_span = values.span();
-                let tuple_values = values
+                let tuple_values: Result<Vec<_>, _> = values
                     .items
                     .into_iter()
                     .enumerate()
@@ -68,17 +114,8 @@
                             )),
                         },
                     )
-                    // Fold all values into a `Vec`, or return the first error.
-                    .fold(Ok(Vec::with_capacity(len)), |result, value_result| {
-                        result.and_then(move |mut tuple_values| match value_result {
-                            Ok(value) => {
-                                tuple_values.push(value);
-                                Ok(tuple_values)
-                            }
-                            // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>`
-                            Err(e) => Err(e),
-                        })
-                    })?;
+                    .collect();
+                let tuple_values = tuple_values?;
 
                 if tuple_values.len() == len {
                     serde::de::Deserializer::deserialize_seq(
@@ -94,7 +131,7 @@
             }
             crate::Item::Value(crate::Value::InlineTable(values)) => {
                 let values_span = values.span();
-                let tuple_values = values
+                let tuple_values: Result<Vec<_>, _> = values
                     .items
                     .into_iter()
                     .enumerate()
@@ -111,17 +148,8 @@
                             )),
                         },
                     )
-                    // Fold all values into a `Vec`, or return the first error.
-                    .fold(Ok(Vec::with_capacity(len)), |result, value_result| {
-                        result.and_then(move |mut tuple_values| match value_result {
-                            Ok(value) => {
-                                tuple_values.push(value);
-                                Ok(tuple_values)
-                            }
-                            // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>`
-                            Err(e) => Err(e),
-                        })
-                    })?;
+                    .collect();
+                let tuple_values = tuple_values?;
 
                 if tuple_values.len() == len {
                     serde::de::Deserializer::deserialize_seq(
diff --git a/src/de/value.rs b/src/de/value.rs
index 3984287..ba6ce6d 100644
--- a/src/de/value.rs
+++ b/src/de/value.rs
@@ -5,7 +5,7 @@
 
 /// Deserialization implementation for TOML [values][crate::Value].
 ///
-/// Can be creater either directly from TOML strings, using [`std::str::FromStr`],
+/// Can be created either directly from TOML strings, using [`std::str::FromStr`],
 /// or from parsed [values][crate::Value] using [`serde::de::IntoDeserializer::into_deserializer`].
 ///
 /// # Example
@@ -241,6 +241,7 @@
     }
 }
 
+#[cfg(feature = "parse")]
 impl std::str::FromStr for ValueDeserializer {
     type Err = Error;
 
diff --git a/src/document.rs b/src/document.rs
index 67dd293..f20e61a 100644
--- a/src/document.rs
+++ b/src/document.rs
@@ -1,6 +1,5 @@
 use std::str::FromStr;
 
-use crate::parser;
 use crate::table::Iter;
 use crate::{Item, RawString, Table};
 
@@ -78,12 +77,13 @@
     }
 }
 
+#[cfg(feature = "parse")]
 impl FromStr for Document {
     type Err = crate::TomlError;
 
     /// Parses a document from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let mut d = parser::parse_document(s)?;
+        let mut d = crate::parser::parse_document(s)?;
         d.despan();
         Ok(d)
     }
diff --git a/src/encode.rs b/src/encode.rs
index 9940f28..30b153a 100644
--- a/src/encode.rs
+++ b/src/encode.rs
@@ -13,218 +13,185 @@
 };
 use crate::{Array, InlineTable, Item, Table, Value};
 
-pub(crate) trait Encode {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result;
+pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result {
+    if let Some(input) = input {
+        let repr = this
+            .as_repr()
+            .map(Cow::Borrowed)
+            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
+        repr.encode(buf, input)?;
+    } else {
+        let repr = this.display_repr();
+        write!(buf, "{}", repr)?;
+    };
+
+    Ok(())
 }
 
-impl Encode for Key {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        let decor = self.decor();
-        decor.prefix_encode(buf, input, default_decor.0)?;
+fn encode_key_path(
+    this: &[Key],
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    let leaf_decor = this.last().expect("always at least one key").leaf_decor();
+    for (i, key) in this.iter().enumerate() {
+        let dotted_decor = key.dotted_decor();
 
-        if let Some(input) = input {
-            let repr = self
-                .as_repr()
-                .map(Cow::Borrowed)
-                .unwrap_or_else(|| Cow::Owned(self.default_repr()));
-            repr.encode(buf, input)?;
+        let first = i == 0;
+        let last = i + 1 == this.len();
+
+        if first {
+            leaf_decor.prefix_encode(buf, input, default_decor.0)?;
         } else {
-            let repr = self.display_repr();
-            write!(buf, "{}", repr)?;
-        };
-
-        decor.suffix_encode(buf, input, default_decor.1)?;
-        Ok(())
-    }
-}
-
-impl<'k> Encode for &'k [Key] {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        for (i, key) in self.iter().enumerate() {
-            let first = i == 0;
-            let last = i + 1 == self.len();
-
-            let prefix = if first {
-                default_decor.0
-            } else {
-                DEFAULT_KEY_PATH_DECOR.0
-            };
-            let suffix = if last {
-                default_decor.1
-            } else {
-                DEFAULT_KEY_PATH_DECOR.1
-            };
-
-            if !first {
-                write!(buf, ".")?;
-            }
-            key.encode(buf, input, (prefix, suffix))?;
+            write!(buf, ".")?;
+            dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
         }
-        Ok(())
-    }
-}
 
-impl<'k> Encode for &'k [&'k Key] {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        for (i, key) in self.iter().enumerate() {
-            let first = i == 0;
-            let last = i + 1 == self.len();
+        encode_key(key, buf, input)?;
 
-            let prefix = if first {
-                default_decor.0
-            } else {
-                DEFAULT_KEY_PATH_DECOR.0
-            };
-            let suffix = if last {
-                default_decor.1
-            } else {
-                DEFAULT_KEY_PATH_DECOR.1
-            };
-
-            if !first {
-                write!(buf, ".")?;
-            }
-            key.encode(buf, input, (prefix, suffix))?;
-        }
-        Ok(())
-    }
-}
-
-impl<T> Encode for Formatted<T>
-where
-    T: ValueRepr,
-{
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        let decor = self.decor();
-        decor.prefix_encode(buf, input, default_decor.0)?;
-
-        if let Some(input) = input {
-            let repr = self
-                .as_repr()
-                .map(Cow::Borrowed)
-                .unwrap_or_else(|| Cow::Owned(self.default_repr()));
-            repr.encode(buf, input)?;
+        if last {
+            leaf_decor.suffix_encode(buf, input, default_decor.1)?;
         } else {
-            let repr = self.display_repr();
-            write!(buf, "{}", repr)?;
-        };
-
-        decor.suffix_encode(buf, input, default_decor.1)?;
-        Ok(())
+            dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
+        }
     }
+    Ok(())
 }
 
-impl Encode for Array {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        let decor = self.decor();
-        decor.prefix_encode(buf, input, default_decor.0)?;
-        write!(buf, "[")?;
+pub(crate) fn encode_key_path_ref(
+    this: &[&Key],
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    let leaf_decor = this.last().expect("always at least one key").leaf_decor();
+    for (i, key) in this.iter().enumerate() {
+        let dotted_decor = key.dotted_decor();
 
-        for (i, elem) in self.iter().enumerate() {
-            let inner_decor;
-            if i == 0 {
-                inner_decor = DEFAULT_LEADING_VALUE_DECOR;
-            } else {
-                inner_decor = DEFAULT_VALUE_DECOR;
-                write!(buf, ",")?;
-            }
-            elem.encode(buf, input, inner_decor)?;
+        let first = i == 0;
+        let last = i + 1 == this.len();
+
+        if first {
+            leaf_decor.prefix_encode(buf, input, default_decor.0)?;
+        } else {
+            write!(buf, ".")?;
+            dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
         }
-        if self.trailing_comma() && !self.is_empty() {
+
+        encode_key(key, buf, input)?;
+
+        if last {
+            leaf_decor.suffix_encode(buf, input, default_decor.1)?;
+        } else {
+            dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
+        }
+    }
+    Ok(())
+}
+
+pub(crate) fn encode_formatted<T: ValueRepr>(
+    this: &Formatted<T>,
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    let decor = this.decor();
+    decor.prefix_encode(buf, input, default_decor.0)?;
+
+    if let Some(input) = input {
+        let repr = this
+            .as_repr()
+            .map(Cow::Borrowed)
+            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
+        repr.encode(buf, input)?;
+    } else {
+        let repr = this.display_repr();
+        write!(buf, "{}", repr)?;
+    };
+
+    decor.suffix_encode(buf, input, default_decor.1)?;
+    Ok(())
+}
+
+pub(crate) fn encode_array(
+    this: &Array,
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    let decor = this.decor();
+    decor.prefix_encode(buf, input, default_decor.0)?;
+    write!(buf, "[")?;
+
+    for (i, elem) in this.iter().enumerate() {
+        let inner_decor;
+        if i == 0 {
+            inner_decor = DEFAULT_LEADING_VALUE_DECOR;
+        } else {
+            inner_decor = DEFAULT_VALUE_DECOR;
             write!(buf, ",")?;
         }
-
-        self.trailing().encode_with_default(buf, input, "")?;
-        write!(buf, "]")?;
-        decor.suffix_encode(buf, input, default_decor.1)?;
-
-        Ok(())
+        encode_value(elem, buf, input, inner_decor)?;
     }
+    if this.trailing_comma() && !this.is_empty() {
+        write!(buf, ",")?;
+    }
+
+    this.trailing().encode_with_default(buf, input, "")?;
+    write!(buf, "]")?;
+    decor.suffix_encode(buf, input, default_decor.1)?;
+
+    Ok(())
 }
 
-impl Encode for InlineTable {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        let decor = self.decor();
-        decor.prefix_encode(buf, input, default_decor.0)?;
-        write!(buf, "{{")?;
-        self.preamble().encode_with_default(buf, input, "")?;
+pub(crate) fn encode_table(
+    this: &InlineTable,
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    let decor = this.decor();
+    decor.prefix_encode(buf, input, default_decor.0)?;
+    write!(buf, "{{")?;
+    this.preamble().encode_with_default(buf, input, "")?;
 
-        let children = self.get_values();
-        let len = children.len();
-        for (i, (key_path, value)) in children.into_iter().enumerate() {
-            if i != 0 {
-                write!(buf, ",")?;
-            }
-            let inner_decor = if i == len - 1 {
-                DEFAULT_TRAILING_VALUE_DECOR
-            } else {
-                DEFAULT_VALUE_DECOR
-            };
-            key_path
-                .as_slice()
-                .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?;
-            write!(buf, "=")?;
-            value.encode(buf, input, inner_decor)?;
+    let children = this.get_values();
+    let len = children.len();
+    for (i, (key_path, value)) in children.into_iter().enumerate() {
+        if i != 0 {
+            write!(buf, ",")?;
         }
-
-        write!(buf, "}}")?;
-        decor.suffix_encode(buf, input, default_decor.1)?;
-
-        Ok(())
+        let inner_decor = if i == len - 1 {
+            DEFAULT_TRAILING_VALUE_DECOR
+        } else {
+            DEFAULT_VALUE_DECOR
+        };
+        encode_key_path_ref(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR)?;
+        write!(buf, "=")?;
+        encode_value(value, buf, input, inner_decor)?;
     }
+
+    write!(buf, "}}")?;
+    decor.suffix_encode(buf, input, default_decor.1)?;
+
+    Ok(())
 }
 
-impl Encode for Value {
-    fn encode(
-        &self,
-        buf: &mut dyn Write,
-        input: Option<&str>,
-        default_decor: (&str, &str),
-    ) -> Result {
-        match self {
-            Value::String(repr) => repr.encode(buf, input, default_decor),
-            Value::Integer(repr) => repr.encode(buf, input, default_decor),
-            Value::Float(repr) => repr.encode(buf, input, default_decor),
-            Value::Boolean(repr) => repr.encode(buf, input, default_decor),
-            Value::Datetime(repr) => repr.encode(buf, input, default_decor),
-            Value::Array(array) => array.encode(buf, input, default_decor),
-            Value::InlineTable(table) => table.encode(buf, input, default_decor),
-        }
+pub(crate) fn encode_value(
+    this: &Value,
+    buf: &mut dyn Write,
+    input: Option<&str>,
+    default_decor: (&str, &str),
+) -> Result {
+    match this {
+        Value::String(repr) => encode_formatted(repr, buf, input, default_decor),
+        Value::Integer(repr) => encode_formatted(repr, buf, input, default_decor),
+        Value::Float(repr) => encode_formatted(repr, buf, input, default_decor),
+        Value::Boolean(repr) => encode_formatted(repr, buf, input, default_decor),
+        Value::Datetime(repr) => encode_formatted(repr, buf, input, default_decor),
+        Value::Array(array) => encode_array(array, buf, input, default_decor),
+        Value::InlineTable(table) => encode_table(table, buf, input, default_decor),
     }
 }
 
@@ -275,11 +242,7 @@
     for kv in table.items.values() {
         match kv.value {
             Item::Table(ref t) => {
-                let mut key = kv.key.clone();
-                if t.is_dotted() {
-                    // May have newlines and generally isn't written for standard tables
-                    key.decor_mut().clear();
-                }
+                let key = kv.key.clone();
                 path.push(key);
                 visit_nested_tables(t, path, false, callback)?;
                 path.pop();
@@ -332,7 +295,7 @@
         };
         table.decor.prefix_encode(buf, input, default_decor.0)?;
         write!(buf, "[[")?;
-        path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?;
+        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?;
         write!(buf, "]]")?;
         table.decor.suffix_encode(buf, input, default_decor.1)?;
         writeln!(buf)?;
@@ -345,16 +308,16 @@
         };
         table.decor.prefix_encode(buf, input, default_decor.0)?;
         write!(buf, "[")?;
-        path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?;
+        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?;
         write!(buf, "]")?;
         table.decor.suffix_encode(buf, input, default_decor.1)?;
         writeln!(buf)?;
     }
     // print table body
     for (key_path, value) in children {
-        key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?;
+        encode_key_path_ref(&key_path, buf, input, DEFAULT_KEY_DECOR)?;
         write!(buf, "=")?;
-        value.encode(buf, input, DEFAULT_VALUE_DECOR)?;
+        encode_value(value, buf, input, DEFAULT_VALUE_DECOR)?;
         writeln!(buf)?;
     }
     Ok(())
@@ -390,7 +353,7 @@
                 '\u{8}' => output.push_str("\\b"),
                 '\u{9}' => output.push_str("\\t"),
                 '\u{a}' => match style {
-                    StringStyle::NewlineTripple => output.push('\n'),
+                    StringStyle::NewlineTriple => output.push('\n'),
                     StringStyle::OnelineSingle => output.push_str("\\n"),
                     _ => unreachable!(),
                 },
@@ -412,59 +375,56 @@
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(crate) enum StringStyle {
-    NewlineTripple,
-    OnelineTripple,
+    NewlineTriple,
+    OnelineTriple,
     OnelineSingle,
 }
 
 impl StringStyle {
     fn literal_start(self) -> &'static str {
         match self {
-            Self::NewlineTripple => "'''\n",
-            Self::OnelineTripple => "'''",
+            Self::NewlineTriple => "'''\n",
+            Self::OnelineTriple => "'''",
             Self::OnelineSingle => "'",
         }
     }
     fn literal_end(self) -> &'static str {
         match self {
-            Self::NewlineTripple => "'''",
-            Self::OnelineTripple => "'''",
+            Self::NewlineTriple => "'''",
+            Self::OnelineTriple => "'''",
             Self::OnelineSingle => "'",
         }
     }
 
     fn standard_start(self) -> &'static str {
         match self {
-            Self::NewlineTripple => "\"\"\"\n",
-            // note: OnelineTripple can happen if do_pretty wants to do
+            Self::NewlineTriple => "\"\"\"\n",
+            // note: OnelineTriple can happen if do_pretty wants to do
             // '''it's one line'''
             // but literal == false
-            Self::OnelineTripple | Self::OnelineSingle => "\"",
+            Self::OnelineTriple | Self::OnelineSingle => "\"",
         }
     }
 
     fn standard_end(self) -> &'static str {
         match self {
-            Self::NewlineTripple => "\"\"\"",
-            // note: OnelineTripple can happen if do_pretty wants to do
+            Self::NewlineTriple => "\"\"\"",
+            // note: OnelineTriple can happen if do_pretty wants to do
             // '''it's one line'''
             // but literal == false
-            Self::OnelineTripple | Self::OnelineSingle => "\"",
+            Self::OnelineTriple | Self::OnelineSingle => "\"",
         }
     }
 }
 
 fn infer_style(value: &str) -> (StringStyle, bool) {
-    // For doing pretty prints we store in a new String
-    // because there are too many cases where pretty cannot
-    // work. We need to determine:
+    // We need to determine:
     // - if we are a "multi-line" pretty (if there are \n)
     // - if ['''] appears if multi or ['] if single
     // - if there are any invalid control characters
     //
     // Doing it any other way would require multiple passes
     // to determine if a pretty string works or not.
-    let mut out = String::with_capacity(value.len() * 2);
     let mut ty = StringStyle::OnelineSingle;
     // found consecutive single quotes
     let mut max_found_singles = 0;
@@ -490,18 +450,17 @@
                 '\\' => {
                     prefer_literal = true;
                 }
-                '\n' => ty = StringStyle::NewlineTripple,
+                '\n' => ty = StringStyle::NewlineTriple,
                 // Escape codes are needed if any ascii control
                 // characters are present, including \b \f \r.
                 c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
                 _ => {}
             }
-            out.push(ch);
         } else {
             // the string cannot be represented as pretty,
             // still check if it should be multiline
             if ch == '\n' {
-                ty = StringStyle::NewlineTripple;
+                ty = StringStyle::NewlineTriple;
             }
         }
     }
@@ -513,7 +472,7 @@
         can_be_pretty = false;
     }
     if !can_be_pretty {
-        debug_assert!(ty != StringStyle::OnelineTripple);
+        debug_assert!(ty != StringStyle::OnelineTriple);
         return (ty, false);
     }
     if found_singles > max_found_singles {
@@ -522,7 +481,7 @@
     debug_assert!(max_found_singles < 3);
     if ty == StringStyle::OnelineSingle && max_found_singles >= 1 {
         // no newlines, but must use ''' because it has ' in it
-        ty = StringStyle::OnelineTripple;
+        ty = StringStyle::OnelineTriple;
     }
     (ty, true)
 }
diff --git a/src/parser/errors.rs b/src/error.rs
similarity index 71%
rename from src/parser/errors.rs
rename to src/error.rs
index 859ed53..a983019 100644
--- a/src/parser/errors.rs
+++ b/src/error.rs
@@ -1,12 +1,6 @@
 use std::error::Error as StdError;
 use std::fmt::{Display, Formatter, Result};
 
-use crate::parser::prelude::*;
-use crate::Key;
-
-use winnow::error::ContextError;
-use winnow::error::ParseError;
-
 /// Type representing a TOML parse error
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
 pub struct TomlError {
@@ -17,7 +11,14 @@
 }
 
 impl TomlError {
-    pub(crate) fn new(error: ParseError<Input<'_>, ContextError>, mut original: Input<'_>) -> Self {
+    #[cfg(feature = "parse")]
+    pub(crate) fn new(
+        error: winnow::error::ParseError<
+            crate::parser::prelude::Input<'_>,
+            winnow::error::ContextError,
+        >,
+        mut original: crate::parser::prelude::Input<'_>,
+    ) -> Self {
         use winnow::stream::Stream;
 
         let offset = error.offset();
@@ -166,7 +167,6 @@
         None => 0,
     };
     let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
-    let line = line;
 
     let column = std::str::from_utf8(&input[line_start..=index])
         .map(|s| s.chars().count() - 1)
@@ -244,73 +244,3 @@
         assert_eq!(position, (1, 2));
     }
 }
-
-#[derive(Debug, Clone)]
-pub(crate) enum CustomError {
-    DuplicateKey {
-        key: String,
-        table: Option<Vec<Key>>,
-    },
-    DottedKeyExtendWrongType {
-        key: Vec<Key>,
-        actual: &'static str,
-    },
-    OutOfRange,
-    #[cfg_attr(feature = "unbounded", allow(dead_code))]
-    RecursionLimitExceeded,
-}
-
-impl CustomError {
-    pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self {
-        assert!(i < path.len());
-        let key = &path[i];
-        let repr = key.display_repr();
-        Self::DuplicateKey {
-            key: repr.into(),
-            table: Some(path[..i].to_vec()),
-        }
-    }
-
-    pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self {
-        assert!(i < path.len());
-        Self::DottedKeyExtendWrongType {
-            key: path[..=i].to_vec(),
-            actual,
-        }
-    }
-}
-
-impl StdError for CustomError {
-    fn description(&self) -> &'static str {
-        "TOML parse error"
-    }
-}
-
-impl Display for CustomError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
-        match self {
-            CustomError::DuplicateKey { key, table } => {
-                if let Some(table) = table {
-                    if table.is_empty() {
-                        write!(f, "duplicate key `{}` in document root", key)
-                    } else {
-                        let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
-                        write!(f, "duplicate key `{}` in table `{}`", key, path)
-                    }
-                } else {
-                    write!(f, "duplicate key `{}`", key)
-                }
-            }
-            CustomError::DottedKeyExtendWrongType { key, actual } => {
-                let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
-                write!(
-                    f,
-                    "dotted key `{}` attempted to extend non-table type ({})",
-                    path, actual
-                )
-            }
-            CustomError::OutOfRange => write!(f, "value is out of range"),
-            CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceded"),
-        }
-    }
-}
diff --git a/src/inline_table.rs b/src/inline_table.rs
index 3dc6c0c..316637d 100644
--- a/src/inline_table.rs
+++ b/src/inline_table.rs
@@ -11,6 +11,8 @@
 pub struct InlineTable {
     // `preamble` represents whitespaces in an empty table
     preamble: RawString,
+    // Whether to hide an empty table
+    pub(crate) implicit: bool,
     // prefix before `{` and suffix after `}`
     decor: Decor,
     pub(crate) span: Option<std::ops::Range<usize>>,
@@ -55,10 +57,10 @@
         values
     }
 
-    pub(crate) fn append_values<'s, 'c>(
+    pub(crate) fn append_values<'s>(
         &'s self,
         parent: &[&'s Key],
-        values: &'c mut Vec<(Vec<&'s Key>, &'s Value)>,
+        values: &mut Vec<(Vec<&'s Key>, &'s Value)>,
     ) {
         for value in self.items.values() {
             let mut path = parent.to_vec();
@@ -133,6 +135,36 @@
         }
     }
 
+    /// If a table has no key/value pairs and implicit, it will not be displayed.
+    ///
+    /// # Examples
+    ///
+    /// ```notrust
+    /// [target."x86_64/windows.json".dependencies]
+    /// ```
+    ///
+    /// In the document above, tables `target` and `target."x86_64/windows.json"` are implicit.
+    ///
+    /// ```
+    /// # #[cfg(feature = "parse")] {
+    /// # #[cfg(feature = "display")] {
+    /// use toml_edit::Document;
+    /// let mut doc = "[a]\n[a.b]\n".parse::<Document>().expect("invalid toml");
+    ///
+    /// doc["a"].as_table_mut().unwrap().set_implicit(true);
+    /// assert_eq!(doc.to_string(), "[a.b]\n");
+    /// # }
+    /// # }
+    /// ```
+    pub(crate) fn set_implicit(&mut self, implicit: bool) {
+        self.implicit = implicit;
+    }
+
+    /// If a table has no key/value pairs and implicit, it will not be displayed.
+    pub(crate) fn is_implicit(&self) -> bool {
+        self.implicit
+    }
+
     /// Change this table's dotted status
     pub fn set_dotted(&mut self, yes: bool) {
         self.dotted = yes;
@@ -153,14 +185,28 @@
         &self.decor
     }
 
-    /// Returns the decor associated with a given key of the table.
-    pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
-        self.items.get_mut(key).map(|kv| &mut kv.key.decor)
+    /// Returns an accessor to a key's formatting
+    pub fn key(&self, key: &str) -> Option<&'_ Key> {
+        self.items.get(key).map(|kv| &kv.key)
+    }
+
+    /// Returns an accessor to a key's formatting
+    pub fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> {
+        self.items.get_mut(key).map(|kv| kv.key.as_mut())
     }
 
     /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
+    pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
+        #![allow(deprecated)]
+        self.items.get_mut(key).map(|kv| kv.key.leaf_decor_mut())
+    }
+
+    /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
     pub fn key_decor(&self, key: &str) -> Option<&Decor> {
-        self.items.get(key).map(|kv| &kv.key.decor)
+        #![allow(deprecated)]
+        self.items.get(key).map(|kv| kv.key.leaf_decor())
     }
 
     /// Set whitespace after before element
@@ -383,9 +429,10 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for InlineTable {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        crate::encode::Encode::encode(self, f, None, ("", ""))
+        crate::encode::encode_table(self, f, None, ("", ""))
     }
 }
 
@@ -436,13 +483,14 @@
 }
 
 fn decorate_inline_table(table: &mut InlineTable) {
-    for (key_decor, value) in table
+    for (mut key, value) in table
         .items
         .iter_mut()
-        .filter(|&(_, ref kv)| kv.value.is_value())
-        .map(|(_, kv)| (&mut kv.key.decor, kv.value.as_value_mut().unwrap()))
+        .filter(|(_, kv)| kv.value.is_value())
+        .map(|(_, kv)| (kv.key.as_mut(), kv.value.as_value_mut().unwrap()))
     {
-        key_decor.clear();
+        key.leaf_decor_mut().clear();
+        key.dotted_decor_mut().clear();
         value.decor_mut().clear();
     }
 }
@@ -530,10 +578,18 @@
         self.is_dotted()
     }
 
+    fn key(&self, key: &str) -> Option<&'_ Key> {
+        self.key(key)
+    }
+    fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> {
+        self.key_mut(key)
+    }
     fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
+        #![allow(deprecated)]
         self.key_decor_mut(key)
     }
     fn key_decor(&self, key: &str) -> Option<&Decor> {
+        #![allow(deprecated)]
         self.key_decor(key)
     }
 }
diff --git a/src/item.rs b/src/item.rs
index 2025fd9..b58806e 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -7,9 +7,10 @@
 use crate::{Array, InlineTable, Table, Value};
 
 /// Type representing either a value, a table, an array of tables, or none.
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub enum Item {
     /// Type representing none.
+    #[default]
     None,
     /// Type representing value.
     Value(Value),
@@ -328,12 +329,7 @@
     }
 }
 
-impl Default for Item {
-    fn default() -> Self {
-        Item::None
-    }
-}
-
+#[cfg(feature = "parse")]
 impl FromStr for Item {
     type Err = crate::TomlError;
 
@@ -344,6 +340,7 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for Item {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match &self {
@@ -363,6 +360,7 @@
 ///
 /// # Examples
 /// ```rust
+/// # #[cfg(feature = "display")] {
 /// # use snapbox::assert_eq;
 /// # use toml_edit::*;
 /// let mut table = Table::default();
@@ -377,6 +375,7 @@
 /// key2 = 42
 /// key3 = ["hello", '\, world']
 /// "#);
+/// # }
 /// ```
 pub fn value<V: Into<Value>>(v: V) -> Item {
     Item::Value(v.into())
diff --git a/src/key.rs b/src/key.rs
index c1ee165..2f4c30a 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -1,9 +1,6 @@
 use std::borrow::Cow;
 use std::str::FromStr;
 
-use crate::encode::{to_string_repr, StringStyle};
-use crate::parser;
-use crate::parser::key::is_unquoted_char;
 use crate::repr::{Decor, Repr};
 use crate::InternalString;
 
@@ -33,7 +30,8 @@
 pub struct Key {
     key: InternalString,
     pub(crate) repr: Option<Repr>,
-    pub(crate) decor: Decor,
+    pub(crate) leaf_decor: Decor,
+    pub(crate) dotted_decor: Decor,
 }
 
 impl Key {
@@ -42,13 +40,15 @@
         Self {
             key: key.into(),
             repr: None,
-            decor: Default::default(),
+            leaf_decor: Default::default(),
+            dotted_decor: Default::default(),
         }
     }
 
     /// Parse a TOML key expression
     ///
     /// Unlike `"".parse<Key>()`, this supports dotted keys.
+    #[cfg(feature = "parse")]
     pub fn parse(repr: &str) -> Result<Vec<Self>, crate::TomlError> {
         Self::try_parse_path(repr)
     }
@@ -59,8 +59,20 @@
     }
 
     /// While creating the `Key`, add `Decor` to it
-    pub fn with_decor(mut self, decor: Decor) -> Self {
-        self.decor = decor;
+    #[deprecated(since = "0.21.1", note = "Replaced with `with_leaf_decor`")]
+    pub fn with_decor(self, decor: Decor) -> Self {
+        self.with_leaf_decor(decor)
+    }
+
+    /// While creating the `Key`, add `Decor` to it for the line entry
+    pub fn with_leaf_decor(mut self, decor: Decor) -> Self {
+        self.leaf_decor = decor;
+        self
+    }
+
+    /// While creating the `Key`, add `Decor` to it for between dots
+    pub fn with_dotted_decor(mut self, decor: Decor) -> Self {
+        self.dotted_decor = decor;
         self
     }
 
@@ -84,11 +96,13 @@
     }
 
     /// Returns the default raw representation.
+    #[cfg(feature = "display")]
     pub fn default_repr(&self) -> Repr {
         to_key_repr(&self.key)
     }
 
     /// Returns a raw representation.
+    #[cfg(feature = "display")]
     pub fn display_repr(&self) -> Cow<'_, str> {
         self.as_repr()
             .and_then(|r| r.as_raw().as_str())
@@ -99,13 +113,35 @@
     }
 
     /// Returns the surrounding whitespace
+    #[deprecated(since = "0.21.1", note = "Replaced with `decor_mut`")]
     pub fn decor_mut(&mut self) -> &mut Decor {
-        &mut self.decor
+        self.leaf_decor_mut()
+    }
+
+    /// Returns the surrounding whitespace for the line entry
+    pub fn leaf_decor_mut(&mut self) -> &mut Decor {
+        &mut self.leaf_decor
+    }
+
+    /// Returns the surrounding whitespace for between dots
+    pub fn dotted_decor_mut(&mut self) -> &mut Decor {
+        &mut self.dotted_decor
     }
 
     /// Returns the surrounding whitespace
+    #[deprecated(since = "0.21.1", note = "Replaced with `decor`")]
     pub fn decor(&self) -> &Decor {
-        &self.decor
+        self.leaf_decor()
+    }
+
+    /// Returns the surrounding whitespace for the line entry
+    pub fn leaf_decor(&self) -> &Decor {
+        &self.leaf_decor
+    }
+
+    /// Returns the surrounding whitespace for between dots
+    pub fn dotted_decor(&self) -> &Decor {
+        &self.dotted_decor
     }
 
     /// Returns the location within the original document
@@ -115,7 +151,8 @@
     }
 
     pub(crate) fn despan(&mut self, input: &str) {
-        self.decor.despan(input);
+        self.leaf_decor.despan(input);
+        self.dotted_decor.despan(input);
         if let Some(repr) = &mut self.repr {
             repr.despan(input)
         }
@@ -123,18 +160,21 @@
 
     /// Auto formats the key.
     pub fn fmt(&mut self) {
-        self.repr = Some(to_key_repr(&self.key));
-        self.decor.clear();
+        self.repr = None;
+        self.leaf_decor.clear();
+        self.dotted_decor.clear();
     }
 
+    #[cfg(feature = "parse")]
     fn try_parse_simple(s: &str) -> Result<Key, crate::TomlError> {
-        let mut key = parser::parse_key(s)?;
+        let mut key = crate::parser::parse_key(s)?;
         key.despan(s);
         Ok(key)
     }
 
+    #[cfg(feature = "parse")]
     fn try_parse_path(s: &str) -> Result<Vec<Key>, crate::TomlError> {
-        let mut keys = parser::parse_key_path(s)?;
+        let mut keys = crate::parser::parse_key_path(s)?;
         for key in &mut keys {
             key.despan(s);
         }
@@ -148,7 +188,8 @@
         Self {
             key: self.key.clone(),
             repr: self.repr.clone(),
-            decor: self.decor.clone(),
+            leaf_decor: self.leaf_decor.clone(),
+            dotted_decor: self.dotted_decor.clone(),
         }
     }
 }
@@ -209,12 +250,14 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for Key {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        crate::encode::Encode::encode(self, f, None, ("", ""))
+        crate::encode::encode_key(self, f, None)
     }
 }
 
+#[cfg(feature = "parse")]
 impl FromStr for Key {
     type Err = crate::TomlError;
 
@@ -226,11 +269,33 @@
     }
 }
 
+#[cfg(feature = "display")]
 fn to_key_repr(key: &str) -> Repr {
-    if key.as_bytes().iter().copied().all(is_unquoted_char) && !key.is_empty() {
-        Repr::new_unchecked(key)
-    } else {
-        to_string_repr(key, Some(StringStyle::OnelineSingle), Some(false))
+    #[cfg(feature = "parse")]
+    {
+        if key
+            .as_bytes()
+            .iter()
+            .copied()
+            .all(crate::parser::key::is_unquoted_char)
+            && !key.is_empty()
+        {
+            Repr::new_unchecked(key)
+        } else {
+            crate::encode::to_string_repr(
+                key,
+                Some(crate::encode::StringStyle::OnelineSingle),
+                Some(false),
+            )
+        }
+    }
+    #[cfg(not(feature = "parse"))]
+    {
+        crate::encode::to_string_repr(
+            key,
+            Some(crate::encode::StringStyle::OnelineSingle),
+            Some(false),
+        )
     }
 }
 
@@ -265,7 +330,7 @@
     }
 }
 
-/// A mutable reference to a `Key`
+/// A mutable reference to a `Key`'s formatting
 #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
 pub struct KeyMut<'k> {
     key: &'k mut Key,
@@ -283,25 +348,51 @@
     }
 
     /// Returns the default raw representation.
+    #[cfg(feature = "display")]
     pub fn default_repr(&self) -> Repr {
         self.key.default_repr()
     }
 
     /// Returns a raw representation.
+    #[cfg(feature = "display")]
     pub fn display_repr(&self) -> Cow<str> {
         self.key.display_repr()
     }
 
     /// Returns the surrounding whitespace
+    #[deprecated(since = "0.21.1", note = "Replaced with `decor_mut`")]
     pub fn decor_mut(&mut self) -> &mut Decor {
+        #![allow(deprecated)]
         self.key.decor_mut()
     }
 
+    /// Returns the surrounding whitespace for the line entry
+    pub fn leaf_decor_mut(&mut self) -> &mut Decor {
+        self.key.leaf_decor_mut()
+    }
+
+    /// Returns the surrounding whitespace for between dots
+    pub fn dotted_decor_mut(&mut self) -> &mut Decor {
+        self.key.dotted_decor_mut()
+    }
+
     /// Returns the surrounding whitespace
+    #[deprecated(since = "0.21.1", note = "Replaced with `decor`")]
     pub fn decor(&self) -> &Decor {
+        #![allow(deprecated)]
         self.key.decor()
     }
 
+    /// Returns the surrounding whitespace for the line entry
+    pub fn leaf_decor(&self) -> &Decor {
+        self.key.leaf_decor()
+    }
+
+    /// Returns the surrounding whitespace for between dots
+    pub fn dotted_decor(&self) -> &Decor {
+        self.key.dotted_decor()
+    }
+
     /// Auto formats the key.
     pub fn fmt(&mut self) {
         self.key.fmt()
@@ -337,6 +428,7 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl<'k> std::fmt::Display for KeyMut<'k> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&self.key, f)
diff --git a/src/lib.rs b/src/lib.rs
index 80c0ddd..25e3d20 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,8 @@
 //! # Example
 //!
 //! ```rust
+//! # #[cfg(feature = "parse")] {
+//! # #[cfg(feature = "display")] {
 //! use toml_edit::{Document, value};
 //!
 //! let toml = r#"
@@ -32,47 +34,54 @@
 //! c = { d = "hello" }
 //! "#;
 //! assert_eq!(doc.to_string(), expected);
+//! # }
+//! # }
 //! ```
 //!
 //! ## Controlling formatting
 //!
 //! By default, values are created with default formatting
 //! ```rust
+//! # #[cfg(feature = "display")] {
 //! let mut doc = toml_edit::Document::new();
 //! doc["foo"] = toml_edit::value("bar");
 //! let expected = r#"foo = "bar"
 //! "#;
 //! assert_eq!(doc.to_string(), expected);
+//! # }
 //! ```
 //!
 //! You can choose a custom TOML representation by parsing the value.
 //! ```rust
+//! # #[cfg(feature = "display")] {
 //! let mut doc = toml_edit::Document::new();
 //! doc["foo"] = "'bar'".parse::<toml_edit::Item>().unwrap();
 //! let expected = r#"foo = 'bar'
 //! "#;
 //! assert_eq!(doc.to_string(), expected);
+//! # }
 //! ```
 //!
 //! ## Limitations
 //!
 //! Things it does not preserve:
 //!
-//! * Scattered array of tables (tables are reordered by default, see [test]).
-//! * Order of dotted keys, see [issue](https://github.com/ordian/toml_edit/issues/163).
+//! * Order of dotted keys, see [issue](https://github.com/toml-rs/toml/issues/163).
 //!
 //! [`toml`]: https://docs.rs/toml/latest/toml/
-//! [test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84
 
 mod array;
 mod array_of_tables;
 mod document;
+#[cfg(feature = "display")]
 mod encode;
+mod error;
 mod index;
 mod inline_table;
 mod internal_string;
 mod item;
 mod key;
+#[cfg(feature = "parse")]
 mod parser;
 mod raw_string;
 mod repr;
@@ -92,6 +101,7 @@
     ArrayOfTables, ArrayOfTablesIntoIter, ArrayOfTablesIter, ArrayOfTablesIterMut,
 };
 pub use crate::document::Document;
+pub use crate::error::TomlError;
 pub use crate::inline_table::{
     InlineEntry, InlineOccupiedEntry, InlineTable, InlineTableIntoIter, InlineTableIter,
     InlineTableIterMut, InlineVacantEntry,
@@ -99,7 +109,6 @@
 pub use crate::internal_string::InternalString;
 pub use crate::item::{array, table, value, Item};
 pub use crate::key::{Key, KeyMut};
-pub use crate::parser::TomlError;
 pub use crate::raw_string::RawString;
 pub use crate::repr::{Decor, Formatted, Repr};
 pub use crate::table::{
diff --git a/src/parser/array.rs b/src/parser/array.rs
index e3b1f3f..0783191 100644
--- a/src/parser/array.rs
+++ b/src/parser/array.rs
@@ -81,6 +81,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs
index 6e89b97..945dc69 100644
--- a/src/parser/datetime.rs
+++ b/src/parser/datetime.rs
@@ -1,6 +1,6 @@
 use std::ops::RangeInclusive;
 
-use crate::parser::errors::CustomError;
+use crate::parser::error::CustomError;
 use crate::parser::prelude::*;
 use crate::parser::trivia::from_utf8_unchecked;
 
@@ -9,6 +9,7 @@
 use winnow::combinator::cut_err;
 use winnow::combinator::opt;
 use winnow::combinator::preceded;
+use winnow::stream::Stream as _;
 use winnow::token::one_of;
 use winnow::token::take_while;
 use winnow::trace::trace;
@@ -53,12 +54,35 @@
 
 // full-date      = date-fullyear "-" date-month "-" date-mday
 pub(crate) fn full_date(input: &mut Input<'_>) -> PResult<Date> {
-    trace(
-        "full-date",
-        (date_fullyear, b'-', cut_err((date_month, b'-', date_mday)))
-            .map(|(year, _, (month, _, day))| Date { year, month, day }),
-    )
-    .parse_next(input)
+    trace("full-date", full_date_).parse_next(input)
+}
+
+fn full_date_(input: &mut Input<'_>) -> PResult<Date> {
+    let year = date_fullyear.parse_next(input)?;
+    let _ = b'-'.parse_next(input)?;
+    let month = cut_err(date_month).parse_next(input)?;
+    let _ = cut_err(b'-').parse_next(input)?;
+    let day_start = input.checkpoint();
+    let day = cut_err(date_mday).parse_next(input)?;
+
+    let is_leap_year = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
+    let max_days_in_month = match month {
+        2 if is_leap_year => 29,
+        2 => 28,
+        4 | 6 | 9 | 11 => 30,
+        _ => 31,
+    };
+    if max_days_in_month < day {
+        input.reset(day_start);
+        return Err(winnow::error::ErrMode::from_external_error(
+            input,
+            winnow::error::ErrorKind::Verify,
+            CustomError::OutOfRange,
+        )
+        .cut());
+    }
+
+    Ok(Date { year, month, day })
 }
 
 // partial-time   = time-hour ":" time-minute ":" time-second [time-secfrac]
@@ -239,6 +263,8 @@
 const DIGIT: RangeInclusive<u8> = b'0'..=b'9';
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
diff --git a/src/parser/error.rs b/src/parser/error.rs
new file mode 100644
index 0000000..22e8e66
--- /dev/null
+++ b/src/parser/error.rs
@@ -0,0 +1,87 @@
+use std::error::Error as StdError;
+use std::fmt::{Display, Formatter, Result};
+
+use crate::Key;
+
+#[derive(Debug, Clone)]
+pub(crate) enum CustomError {
+    DuplicateKey {
+        key: String,
+        table: Option<Vec<Key>>,
+    },
+    DottedKeyExtendWrongType {
+        key: Vec<Key>,
+        actual: &'static str,
+    },
+    OutOfRange,
+    #[cfg_attr(feature = "unbounded", allow(dead_code))]
+    RecursionLimitExceeded,
+}
+
+impl CustomError {
+    pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self {
+        assert!(i < path.len());
+        let key = &path[i];
+        let repr = key
+            .as_repr()
+            .and_then(|key| key.as_raw().as_str())
+            .map(|s| s.to_owned())
+            .unwrap_or_else(|| {
+                #[cfg(feature = "display")]
+                {
+                    key.default_repr().as_raw().as_str().unwrap().to_owned()
+                }
+                #[cfg(not(feature = "display"))]
+                {
+                    format!("{:?}", key.get())
+                }
+            });
+        Self::DuplicateKey {
+            key: repr,
+            table: Some(path[..i].to_vec()),
+        }
+    }
+
+    pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self {
+        assert!(i < path.len());
+        Self::DottedKeyExtendWrongType {
+            key: path[..=i].to_vec(),
+            actual,
+        }
+    }
+}
+
+impl StdError for CustomError {
+    fn description(&self) -> &'static str {
+        "TOML parse error"
+    }
+}
+
+impl Display for CustomError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        match self {
+            CustomError::DuplicateKey { key, table } => {
+                if let Some(table) = table {
+                    if table.is_empty() {
+                        write!(f, "duplicate key `{}` in document root", key)
+                    } else {
+                        let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
+                        write!(f, "duplicate key `{}` in table `{}`", key, path)
+                    }
+                } else {
+                    write!(f, "duplicate key `{}`", key)
+                }
+            }
+            CustomError::DottedKeyExtendWrongType { key, actual } => {
+                let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join(".");
+                write!(
+                    f,
+                    "dotted key `{}` attempted to extend non-table type ({})",
+                    path, actual
+                )
+            }
+            CustomError::OutOfRange => write!(f, "value is out of range"),
+            CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceeded"),
+        }
+    }
+}
diff --git a/src/parser/inline_table.rs b/src/parser/inline_table.rs
index 994e003..c2e6619 100644
--- a/src/parser/inline_table.rs
+++ b/src/parser/inline_table.rs
@@ -5,7 +5,7 @@
 use winnow::trace::trace;
 
 use crate::key::Key;
-use crate::parser::errors::CustomError;
+use crate::parser::error::CustomError;
 use crate::parser::key::key;
 use crate::parser::prelude::*;
 use crate::parser::trivia::ws;
@@ -44,6 +44,16 @@
 
     for (path, kv) in v {
         let table = descend_path(&mut root, &path)?;
+
+        // "Likewise, using dotted keys to redefine tables already defined in [table] form is not allowed"
+        let mixed_table_types = table.is_dotted() == path.is_empty();
+        if mixed_table_types {
+            return Err(CustomError::DuplicateKey {
+                key: kv.key.get().into(),
+                table: None,
+            });
+        }
+
         let key: InternalString = kv.key.get_internal().into();
         match table.items.entry(key) {
             Entry::Vacant(o) => {
@@ -64,15 +74,26 @@
     mut table: &'a mut InlineTable,
     path: &'a [Key],
 ) -> Result<&'a mut InlineTable, CustomError> {
+    let dotted = !path.is_empty();
     for (i, key) in path.iter().enumerate() {
         let entry = table.entry_format(key).or_insert_with(|| {
             let mut new_table = InlineTable::new();
-            new_table.set_dotted(true);
+            new_table.set_implicit(dotted);
+            new_table.set_dotted(dotted);
 
             Value::InlineTable(new_table)
         });
         match *entry {
             Value::InlineTable(ref mut sweet_child_of_mine) => {
+                // Since tables cannot be defined more than once, redefining such tables using a
+                // [table] header is not allowed. Likewise, using dotted keys to redefine tables
+                // already defined in [table] form is not allowed.
+                if dotted && !sweet_child_of_mine.is_implicit() {
+                    return Err(CustomError::DuplicateKey {
+                        key: key.get().into(),
+                        table: None,
+                    });
+                }
                 table = sweet_child_of_mine;
             }
             ref v => {
@@ -144,6 +165,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
diff --git a/src/parser/key.rs b/src/parser/key.rs
index 12715da..e72b195 100644
--- a/src/parser/key.rs
+++ b/src/parser/key.rs
@@ -7,7 +7,7 @@
 use winnow::trace::trace;
 
 use crate::key::Key;
-use crate::parser::errors::CustomError;
+use crate::parser::error::CustomError;
 use crate::parser::prelude::*;
 use crate::parser::strings::{basic_string, literal_string};
 use crate::parser::trivia::{from_utf8_unchecked, ws};
@@ -18,13 +18,13 @@
 // key = simple-key / dotted-key
 // dotted-key = simple-key 1*( dot-sep simple-key )
 pub(crate) fn key(input: &mut Input<'_>) -> PResult<Vec<Key>> {
-    trace(
+    let mut key_path = trace(
         "dotted-key",
         separated1(
             (ws.span(), simple_key, ws.span()).map(|(pre, (raw, key), suffix)| {
                 Key::new(key)
                     .with_repr_unchecked(Repr::new_unchecked(raw))
-                    .with_decor(Decor::new(
+                    .with_dotted_decor(Decor::new(
                         RawString::with_span(pre),
                         RawString::with_span(suffix),
                     ))
@@ -38,7 +38,31 @@
             Ok::<_, CustomError>(k)
         }),
     )
-    .parse_next(input)
+    .parse_next(input)?;
+
+    let mut leaf_decor = Decor::new("", "");
+    {
+        let first_dotted_decor = key_path
+            .first_mut()
+            .expect("always at least one key")
+            .dotted_decor_mut();
+        if let Some(prefix) = first_dotted_decor.prefix().cloned() {
+            leaf_decor.set_prefix(prefix);
+            first_dotted_decor.set_prefix("");
+        }
+    }
+    let last_key = &mut key_path.last_mut().expect("always at least one key");
+    {
+        let last_dotted_decor = last_key.dotted_decor_mut();
+        if let Some(suffix) = last_dotted_decor.suffix().cloned() {
+            leaf_decor.set_suffix(suffix);
+            last_dotted_decor.set_suffix("");
+        }
+    }
+
+    *last_key.leaf_decor_mut() = leaf_decor;
+
+    Ok(key_path)
 }
 
 // simple-key = quoted-key / unquoted-key
@@ -88,6 +112,8 @@
 const DOT_SEP: u8 = b'.';
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
@@ -96,7 +122,7 @@
         let cases = [
             ("a", "a"),
             (r#""hello\n ""#, "hello\n "),
-            (r#"'hello\n '"#, "hello\\n "),
+            (r"'hello\n '", "hello\\n "),
         ];
 
         for (input, expected) in cases {
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1b3cc4f..e032202 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -3,7 +3,7 @@
 pub(crate) mod array;
 pub(crate) mod datetime;
 pub(crate) mod document;
-pub(crate) mod errors;
+pub(crate) mod error;
 pub(crate) mod inline_table;
 pub(crate) mod key;
 pub(crate) mod numbers;
@@ -13,7 +13,7 @@
 pub(crate) mod trivia;
 pub(crate) mod value;
 
-pub use errors::TomlError;
+pub use crate::error::TomlError;
 
 pub(crate) fn parse_document(raw: &str) -> Result<crate::Document, TomlError> {
     use prelude::*;
@@ -95,11 +95,11 @@
 
     #[cfg(not(feature = "unbounded"))]
     impl RecursionCheck {
-        pub(crate) fn check_depth(depth: usize) -> Result<(), super::errors::CustomError> {
+        pub(crate) fn check_depth(depth: usize) -> Result<(), super::error::CustomError> {
             if depth < 128 {
                 Ok(())
             } else {
-                Err(super::errors::CustomError::RecursionLimitExceeded)
+                Err(super::error::CustomError::RecursionLimitExceeded)
             }
         }
 
@@ -114,7 +114,7 @@
                 Err(winnow::error::ErrMode::from_external_error(
                     input,
                     winnow::error::ErrorKind::Eof,
-                    super::errors::CustomError::RecursionLimitExceeded,
+                    super::error::CustomError::RecursionLimitExceeded,
                 ))
             }
         }
@@ -126,7 +126,7 @@
 
     #[cfg(feature = "unbounded")]
     impl RecursionCheck {
-        pub(crate) fn check_depth(_depth: usize) -> Result<(), super::errors::CustomError> {
+        pub(crate) fn check_depth(_depth: usize) -> Result<(), super::error::CustomError> {
             Ok(())
         }
 
@@ -140,6 +140,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
@@ -182,10 +184,10 @@
     "omega"
 ]
 
-   'some.wierd .stuff'   =  """
+   'some.weird .stuff'   =  """
                          like
                          that
-                      #   """ # this broke my sintax highlighting
+                      #   """ # this broke my syntax highlighting
    " also. like " = '''
 that
 '''
diff --git a/src/parser/numbers.rs b/src/parser/numbers.rs
index 6e4757f..9681526 100644
--- a/src/parser/numbers.rs
+++ b/src/parser/numbers.rs
@@ -301,7 +301,7 @@
 const INF: &[u8] = b"inf";
 // nan = %x6e.61.6e  ; nan
 pub(crate) fn nan(input: &mut Input<'_>) -> PResult<f64> {
-    tag(NAN).value(f64::NAN).parse_next(input)
+    tag(NAN).value(f64::NAN.copysign(1.0)).parse_next(input)
 }
 const NAN: &[u8] = b"nan";
 
@@ -319,6 +319,8 @@
     (DIGIT, b'A'..=b'F', b'a'..=b'f');
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
@@ -353,6 +355,7 @@
     fn assert_float_eq(actual: f64, expected: f64) {
         if expected.is_nan() {
             assert!(actual.is_nan());
+            assert_eq!(expected.is_sign_positive(), actual.is_sign_positive());
         } else if expected.is_infinite() {
             assert!(actual.is_infinite());
             assert_eq!(expected.is_sign_positive(), actual.is_sign_positive());
@@ -376,9 +379,9 @@
             ("9_224_617.445_991_228_313", 9_224_617.445_991_227),
             ("-1.7976931348623157e+308", std::f64::MIN),
             ("1.7976931348623157e+308", std::f64::MAX),
-            ("nan", f64::NAN),
-            ("+nan", f64::NAN),
-            ("-nan", f64::NAN),
+            ("nan", f64::NAN.copysign(1.0)),
+            ("+nan", f64::NAN.copysign(1.0)),
+            ("-nan", f64::NAN.copysign(-1.0)),
             ("inf", f64::INFINITY),
             ("+inf", f64::INFINITY),
             ("-inf", f64::NEG_INFINITY),
diff --git a/src/parser/state.rs b/src/parser/state.rs
index efa884d..187dd5f 100644
--- a/src/parser/state.rs
+++ b/src/parser/state.rs
@@ -1,5 +1,5 @@
 use crate::key::Key;
-use crate::parser::errors::CustomError;
+use crate::parser::error::CustomError;
 use crate::repr::Decor;
 use crate::table::TableKeyValue;
 use crate::{ArrayOfTables, Document, InternalString, Item, RawString, Table};
@@ -39,26 +39,21 @@
 
     pub(crate) fn on_keyval(
         &mut self,
-        mut path: Vec<Key>,
+        path: Vec<Key>,
         mut kv: TableKeyValue,
     ) -> Result<(), CustomError> {
         {
             let mut prefix = self.trailing.take();
-            let first_key = if path.is_empty() {
-                &mut kv.key
-            } else {
-                &mut path[0]
-            };
             let prefix = match (
                 prefix.take(),
-                first_key.decor.prefix().and_then(|d| d.span()),
+                kv.key.leaf_decor.prefix().and_then(|d| d.span()),
             ) {
                 (Some(p), Some(k)) => Some(p.start..k.end),
                 (Some(p), None) | (None, Some(p)) => Some(p),
                 (None, None) => None,
             };
-            first_key
-                .decor
+            kv.key
+                .leaf_decor
                 .set_prefix(prefix.map(RawString::with_span).unwrap_or_default());
         }
 
@@ -94,7 +89,7 @@
         Ok(())
     }
 
-    pub(crate) fn start_aray_table(
+    pub(crate) fn start_array_table(
         &mut self,
         path: Vec<Key>,
         decor: Decor,
@@ -217,9 +212,9 @@
         Ok(())
     }
 
-    pub(crate) fn descend_path<'t, 'k>(
+    pub(crate) fn descend_path<'t>(
         mut table: &'t mut Table,
-        path: &'k [Key],
+        path: &[Key],
         dotted: bool,
     ) -> Result<&'t mut Table, CustomError> {
         for (i, key) in path.iter().enumerate() {
@@ -297,7 +292,7 @@
             .take()
             .map(RawString::with_span)
             .unwrap_or_default();
-        self.start_aray_table(
+        self.start_array_table(
             path,
             Decor::new(leading, RawString::with_span(trailing)),
             span,
diff --git a/src/parser/strings.rs b/src/parser/strings.rs
index 26f9cc2..675b5c6 100644
--- a/src/parser/strings.rs
+++ b/src/parser/strings.rs
@@ -21,7 +21,7 @@
 use winnow::token::take_while;
 use winnow::trace::trace;
 
-use crate::parser::errors::CustomError;
+use crate::parser::error::CustomError;
 use crate::parser::numbers::HEXDIG;
 use crate::parser::prelude::*;
 use crate::parser::trivia::{from_utf8_unchecked, newline, ws, ws_newlines, NON_ASCII, WSCHAR};
@@ -363,6 +363,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
@@ -440,10 +442,10 @@
     #[test]
     fn literal_string() {
         let inputs = [
-            r#"'C:\Users\nodejs\templates'"#,
-            r#"'\\ServerX\admin$\system32\'"#,
+            r"'C:\Users\nodejs\templates'",
+            r"'\\ServerX\admin$\system32\'",
             r#"'Tom "Dubs" Preston-Werner'"#,
-            r#"'<\i\c*\s*>'"#,
+            r"'<\i\c*\s*>'",
         ];
 
         for input in &inputs {
@@ -456,7 +458,7 @@
     #[test]
     fn ml_literal_string() {
         let inputs = [
-            r#"'''I [dw]on't need \d{2} apples'''"#,
+            r"'''I [dw]on't need \d{2} apples'''",
             r#"''''one_quote''''"#,
         ];
         for input in &inputs {
diff --git a/src/parser/trivia.rs b/src/parser/trivia.rs
index a359805..4575fb1 100644
--- a/src/parser/trivia.rs
+++ b/src/parser/trivia.rs
@@ -120,6 +120,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
diff --git a/src/parser/value.rs b/src/parser/value.rs
index 14cd951..33300ec 100644
--- a/src/parser/value.rs
+++ b/src/parser/value.rs
@@ -121,6 +121,8 @@
 }
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod test {
     use super::*;
 
@@ -131,7 +133,7 @@
             "-239",
             "1e200",
             "9_224_617.445_991_228_313",
-            r#"'''I [dw]on't need \d{2} apples'''"#,
+            r"'''I [dw]on't need \d{2} apples'''",
             r#"'''
 The first newline is
 trimmed in raw strings.
diff --git a/src/raw_string.rs b/src/raw_string.rs
index c5961f1..53714a1 100644
--- a/src/raw_string.rs
+++ b/src/raw_string.rs
@@ -80,6 +80,7 @@
         }
     }
 
+    #[cfg(feature = "display")]
     pub(crate) fn encode(&self, buf: &mut dyn std::fmt::Write, input: &str) -> std::fmt::Result {
         let raw = self.to_str(input);
         for part in raw.split('\r') {
@@ -88,6 +89,7 @@
         Ok(())
     }
 
+    #[cfg(feature = "display")]
     pub(crate) fn encode_with_default(
         &self,
         buf: &mut dyn std::fmt::Write,
diff --git a/src/repr.rs b/src/repr.rs
index d4ab6c2..ad41bbf 100644
--- a/src/repr.rs
+++ b/src/repr.rs
@@ -44,11 +44,13 @@
     }
 
     /// Returns the default raw representation.
+    #[cfg(feature = "display")]
     pub fn default_repr(&self) -> Repr {
         self.value.to_repr()
     }
 
     /// Returns a raw representation.
+    #[cfg(feature = "display")]
     pub fn display_repr(&self) -> Cow<str> {
         self.as_repr()
             .and_then(|r| r.as_raw().as_str())
@@ -82,7 +84,7 @@
 
     /// Auto formats the value.
     pub fn fmt(&mut self) {
-        self.repr = Some(self.value.to_repr());
+        self.repr = None;
     }
 }
 
@@ -103,20 +105,33 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl<T> std::fmt::Display for Formatted<T>
 where
     T: ValueRepr,
 {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        crate::encode::Encode::encode(self, f, None, ("", ""))
+        crate::encode::encode_formatted(self, f, None, ("", ""))
     }
 }
 
 pub trait ValueRepr: crate::private::Sealed {
     /// The TOML representation of the value
+    #[cfg(feature = "display")]
     fn to_repr(&self) -> Repr;
 }
 
+#[cfg(not(feature = "display"))]
+mod inner {
+    use super::ValueRepr;
+
+    impl ValueRepr for String {}
+    impl ValueRepr for i64 {}
+    impl ValueRepr for f64 {}
+    impl ValueRepr for bool {}
+    impl ValueRepr for toml_datetime::Datetime {}
+}
+
 /// TOML-encoded value
 #[derive(Eq, PartialEq, Clone, Hash)]
 pub struct Repr {
@@ -144,6 +159,7 @@
         self.raw_value.despan(input)
     }
 
+    #[cfg(feature = "display")]
     pub(crate) fn encode(&self, buf: &mut dyn std::fmt::Write, input: &str) -> std::fmt::Result {
         self.as_raw().encode(buf, input)
     }
@@ -185,6 +201,7 @@
         self.prefix.as_ref()
     }
 
+    #[cfg(feature = "display")]
     pub(crate) fn prefix_encode(
         &self,
         buf: &mut dyn std::fmt::Write,
@@ -208,6 +225,7 @@
         self.suffix.as_ref()
     }
 
+    #[cfg(feature = "display")]
     pub(crate) fn suffix_encode(
         &self,
         buf: &mut dyn std::fmt::Write,
diff --git a/src/ser/map.rs b/src/ser/map.rs
index d743e3d..47e56ba 100644
--- a/src/ser/map.rs
+++ b/src/ser/map.rs
@@ -1,4 +1,4 @@
-use super::{Error, KeySerializer};
+use super::{Error, KeySerializer, SerializeValueArray, ValueSerializer};
 
 #[doc(hidden)]
 pub enum SerializeMap {
@@ -165,7 +165,6 @@
     where
         T: serde::ser::Serialize,
     {
-        self.key = None;
         self.key = Some(input.serialize(KeySerializer)?);
         Ok(())
     }
@@ -174,7 +173,8 @@
     where
         T: serde::ser::Serialize,
     {
-        let res = value.serialize(super::ValueSerializer {});
+        let mut value_serializer = MapValueSerializer::new();
+        let res = value.serialize(&mut value_serializer);
         match res {
             Ok(item) => {
                 let key = self.key.take().unwrap();
@@ -185,7 +185,7 @@
                 self.items.insert(key, kv);
             }
             Err(e) => {
-                if e != Error::UnsupportedNone {
+                if !(e == Error::UnsupportedNone && value_serializer.is_none) {
                     return Err(e);
                 }
             }
@@ -210,7 +210,8 @@
     where
         T: serde::ser::Serialize,
     {
-        let res = value.serialize(super::ValueSerializer {});
+        let mut value_serializer = MapValueSerializer::new();
+        let res = value.serialize(&mut value_serializer);
         match res {
             Ok(item) => {
                 let kv = crate::table::TableKeyValue::new(
@@ -220,7 +221,7 @@
                 self.items.insert(crate::InternalString::from(key), kv);
             }
             Err(e) => {
-                if e != Error::UnsupportedNone {
+                if !(e == Error::UnsupportedNone && value_serializer.is_none) {
                     return Err(e);
                 }
             }
@@ -403,3 +404,261 @@
         Err(Error::DateInvalid)
     }
 }
+
+#[derive(Default)]
+struct MapValueSerializer {
+    is_none: bool,
+}
+
+impl MapValueSerializer {
+    fn new() -> Self {
+        Self { is_none: false }
+    }
+}
+
+impl serde::ser::Serializer for &mut MapValueSerializer {
+    type Ok = crate::Value;
+    type Error = Error;
+    type SerializeSeq = super::SerializeValueArray;
+    type SerializeTuple = super::SerializeValueArray;
+    type SerializeTupleStruct = super::SerializeValueArray;
+    type SerializeTupleVariant = super::SerializeTupleVariant;
+    type SerializeMap = super::SerializeMap;
+    type SerializeStruct = super::SerializeMap;
+    type SerializeStructVariant = super::SerializeStructVariant;
+
+    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_bool(v)
+    }
+
+    fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i8(v)
+    }
+
+    fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i16(v)
+    }
+
+    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i32(v)
+    }
+
+    fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_i64(v)
+    }
+
+    fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u8(v)
+    }
+
+    fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u16(v)
+    }
+
+    fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u32(v)
+    }
+
+    fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_u64(v)
+    }
+
+    fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_f32(v)
+    }
+
+    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_f64(v)
+    }
+
+    fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_char(v)
+    }
+
+    fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_str(v)
+    }
+
+    fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_bytes(value)
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        self.is_none = true;
+        Err(Error::UnsupportedNone)
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        ValueSerializer::new().serialize_some(value)
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit()
+    }
+
+    fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit_struct(name)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+    ) -> Result<Self::Ok, Self::Error> {
+        ValueSerializer::new().serialize_unit_variant(name, variant_index, variant)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        name: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        ValueSerializer::new().serialize_newtype_struct(name, value)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        value: &T,
+    ) -> Result<Self::Ok, Self::Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        ValueSerializer::new().serialize_newtype_variant(name, variant_index, variant, value)
+    }
+
+    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        ValueSerializer::new().serialize_seq(len)
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        ValueSerializer::new().serialize_tuple(len)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        ValueSerializer::new().serialize_tuple_struct(name, len)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        ValueSerializer::new().serialize_tuple_variant(name, variant_index, variant, len)
+    }
+
+    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        ValueSerializer::new().serialize_map(len)
+    }
+
+    fn serialize_struct(
+        self,
+        name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        ValueSerializer::new().serialize_struct(name, len)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        name: &'static str,
+        variant_index: u32,
+        variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        ValueSerializer::new().serialize_struct_variant(name, variant_index, variant, len)
+    }
+}
+
+pub type SerializeTupleVariant = SerializeVariant<SerializeValueArray>;
+pub type SerializeStructVariant = SerializeVariant<SerializeMap>;
+
+pub struct SerializeVariant<T> {
+    variant: &'static str,
+    inner: T,
+}
+
+impl SerializeVariant<SerializeValueArray> {
+    pub(crate) fn tuple(variant: &'static str, len: usize) -> Self {
+        Self {
+            variant,
+            inner: SerializeValueArray::with_capacity(len),
+        }
+    }
+}
+
+impl SerializeVariant<SerializeMap> {
+    pub(crate) fn struct_(variant: &'static str, len: usize) -> Self {
+        Self {
+            variant,
+            inner: SerializeMap::table_with_capacity(len),
+        }
+    }
+}
+
+impl serde::ser::SerializeTupleVariant for SerializeVariant<SerializeValueArray> {
+    type Ok = crate::Value;
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: serde::ser::Serialize,
+    {
+        serde::ser::SerializeSeq::serialize_element(&mut self.inner, value)
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        let inner = serde::ser::SerializeSeq::end(self.inner)?;
+        let mut items = crate::table::KeyValuePairs::new();
+        let kv = crate::table::TableKeyValue::new(
+            crate::Key::new(self.variant),
+            crate::Item::Value(inner),
+        );
+        items.insert(crate::InternalString::from(self.variant), kv);
+        Ok(crate::Value::InlineTable(crate::InlineTable::with_pairs(
+            items,
+        )))
+    }
+}
+
+impl serde::ser::SerializeStructVariant for SerializeVariant<SerializeMap> {
+    type Ok = crate::Value;
+    type Error = Error;
+
+    #[inline]
+    fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error>
+    where
+        T: serde::ser::Serialize + ?Sized,
+    {
+        serde::ser::SerializeStruct::serialize_field(&mut self.inner, key, value)
+    }
+
+    #[inline]
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        let inner = serde::ser::SerializeStruct::end(self.inner)?;
+        let mut items = crate::table::KeyValuePairs::new();
+        let kv = crate::table::TableKeyValue::new(
+            crate::Key::new(self.variant),
+            crate::Item::Value(inner),
+        );
+        items.insert(crate::InternalString::from(self.variant), kv);
+        Ok(crate::Value::InlineTable(crate::InlineTable::with_pairs(
+            items,
+        )))
+    }
+}
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
index 2c31020..ba31708 100644
--- a/src/ser/mod.rs
+++ b/src/ser/mod.rs
@@ -24,7 +24,7 @@
     OutOfRange(Option<&'static str>),
     /// `None` could not be serialized to TOML
     UnsupportedNone,
-    /// Key was not convertable to `String` for serializing to TOML
+    /// Key was not convertible to `String` for serializing to TOML
     KeyNotString,
     /// A serialized date was invalid
     DateInvalid,
@@ -84,6 +84,7 @@
 /// Serialization can fail if `T`'s implementation of `Serialize` decides to
 /// fail, if `T` contains a map with non-string keys, or if `T` attempts to
 /// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
+#[cfg(feature = "display")]
 pub fn to_vec<T: ?Sized>(value: &T) -> Result<Vec<u8>, Error>
 where
     T: serde::ser::Serialize,
@@ -127,6 +128,7 @@
 /// let toml = toml_edit::ser::to_string(&config).unwrap();
 /// println!("{}", toml)
 /// ```
+#[cfg(feature = "display")]
 pub fn to_string<T: ?Sized>(value: &T) -> Result<String, Error>
 where
     T: serde::ser::Serialize,
@@ -138,6 +140,7 @@
 ///
 /// This is identical to `to_string` except the output string has a more
 /// "pretty" output. See `ValueSerializer::pretty` for more details.
+#[cfg(feature = "display")]
 pub fn to_string_pretty<T: ?Sized>(value: &T) -> Result<String, Error>
 where
     T: serde::ser::Serialize,
diff --git a/src/ser/value.rs b/src/ser/value.rs
index d29390a..47a17a3 100644
--- a/src/ser/value.rs
+++ b/src/ser/value.rs
@@ -60,10 +60,10 @@
     type SerializeSeq = super::SerializeValueArray;
     type SerializeTuple = super::SerializeValueArray;
     type SerializeTupleStruct = super::SerializeValueArray;
-    type SerializeTupleVariant = super::SerializeValueArray;
+    type SerializeTupleVariant = super::SerializeTupleVariant;
     type SerializeMap = super::SerializeMap;
     type SerializeStruct = super::SerializeMap;
-    type SerializeStructVariant = serde::ser::Impossible<Self::Ok, Self::Error>;
+    type SerializeStructVariant = super::SerializeStructVariant;
 
     fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
         Ok(v.into())
@@ -108,7 +108,17 @@
         self.serialize_f64(v as f64)
     }
 
-    fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
+    fn serialize_f64(self, mut v: f64) -> Result<Self::Ok, Self::Error> {
+        // Discard sign of NaN when serialized using Serde.
+        //
+        // In all likelihood the sign of NaNs is not meaningful in the user's
+        // program. Ending up with `-nan` in the TOML document would usually be
+        // surprising and undesirable, when the sign of the NaN was not
+        // intentionally controlled by the caller, or may even be
+        // nondeterministic if it comes from arithmetic operations or a cast.
+        if v.is_nan() {
+            v = v.copysign(1.0);
+        }
         Ok(v.into())
     }
 
@@ -205,10 +215,10 @@
         self,
         _name: &'static str,
         _variant_index: u32,
-        _variant: &'static str,
+        variant: &'static str,
         len: usize,
     ) -> Result<Self::SerializeTupleVariant, Self::Error> {
-        self.serialize_seq(Some(len))
+        Ok(super::SerializeTupleVariant::tuple(variant, len))
     }
 
     fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
@@ -233,11 +243,11 @@
 
     fn serialize_struct_variant(
         self,
-        name: &'static str,
+        _name: &'static str,
         _variant_index: u32,
-        _variant: &'static str,
-        _len: usize,
+        variant: &'static str,
+        len: usize,
     ) -> Result<Self::SerializeStructVariant, Self::Error> {
-        Err(Error::UnsupportedType(Some(name)))
+        Ok(super::SerializeStructVariant::struct_(variant, len))
     }
 }
diff --git a/src/table.rs b/src/table.rs
index 45d6d61..cc0d2b2 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -70,10 +70,10 @@
         values
     }
 
-    fn append_values<'s, 'c>(
+    fn append_values<'s>(
         &'s self,
         parent: &[&'s Key],
-        values: &'c mut Vec<(Vec<&'s Key>, &'s Value)>,
+        values: &mut Vec<(Vec<&'s Key>, &'s Value)>,
     ) {
         for value in self.items.values() {
             let mut path = parent.to_vec();
@@ -165,11 +165,15 @@
     /// In the document above, tables `target` and `target."x86_64/windows.json"` are implicit.
     ///
     /// ```
+    /// # #[cfg(feature = "parse")] {
+    /// # #[cfg(feature = "display")] {
     /// use toml_edit::Document;
     /// let mut doc = "[a]\n[a.b]\n".parse::<Document>().expect("invalid toml");
     ///
     /// doc["a"].as_table_mut().unwrap().set_implicit(true);
     /// assert_eq!(doc.to_string(), "[a.b]\n");
+    /// # }
+    /// # }
     /// ```
     pub fn set_implicit(&mut self, implicit: bool) {
         self.implicit = implicit;
@@ -214,14 +218,28 @@
         &self.decor
     }
 
-    /// Returns the decor associated with a given key of the table.
-    pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
-        self.items.get_mut(key).map(|kv| &mut kv.key.decor)
+    /// Returns an accessor to a key's formatting
+    pub fn key(&self, key: &str) -> Option<&'_ Key> {
+        self.items.get(key).map(|kv| &kv.key)
+    }
+
+    /// Returns an accessor to a key's formatting
+    pub fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> {
+        self.items.get_mut(key).map(|kv| kv.key.as_mut())
     }
 
     /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
+    pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
+        #![allow(deprecated)]
+        self.items.get_mut(key).map(|kv| kv.key.leaf_decor_mut())
+    }
+
+    /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
     pub fn key_decor(&self, key: &str) -> Option<&Decor> {
-        self.items.get(key).map(|kv| &kv.key.decor)
+        #![allow(deprecated)]
+        self.items.get(key).map(|kv| kv.key.leaf_decor())
     }
 
     /// Returns the location within the original document
@@ -413,15 +431,15 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for Table {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        use crate::encode::Encode;
         let children = self.get_values();
         // print table body
         for (key_path, value) in children {
-            key_path.as_slice().encode(f, None, DEFAULT_KEY_DECOR)?;
+            crate::encode::encode_key_path_ref(&key_path, f, None, DEFAULT_KEY_DECOR)?;
             write!(f, "=")?;
-            value.encode(f, None, DEFAULT_VALUE_DECOR)?;
+            crate::encode::encode_value(value, f, None, DEFAULT_VALUE_DECOR)?;
             writeln!(f)?;
         }
         Ok(())
@@ -471,13 +489,14 @@
 pub(crate) type KeyValuePairs = IndexMap<InternalString, TableKeyValue>;
 
 fn decorate_table(table: &mut Table) {
-    for (key_decor, value) in table
+    for (mut key, value) in table
         .items
         .iter_mut()
-        .filter(|&(_, ref kv)| kv.value.is_value())
-        .map(|(_, kv)| (&mut kv.key.decor, kv.value.as_value_mut().unwrap()))
+        .filter(|(_, kv)| kv.value.is_value())
+        .map(|(_, kv)| (kv.key.as_mut(), kv.value.as_value_mut().unwrap()))
     {
-        key_decor.clear();
+        key.leaf_decor_mut().clear();
+        key.dotted_decor_mut().clear();
         value.decor_mut().clear();
     }
 }
@@ -557,9 +576,15 @@
     /// Check if this is a wrapper for dotted keys, rather than a standard table
     fn is_dotted(&self) -> bool;
 
+    /// Returns an accessor to a key's formatting
+    fn key(&self, key: &str) -> Option<&'_ Key>;
+    /// Returns an accessor to a key's formatting
+    fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>>;
     /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
     fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor>;
     /// Returns the decor associated with a given key of the table.
+    #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")]
     fn key_decor(&self, key: &str) -> Option<&Decor>;
 }
 
@@ -617,10 +642,18 @@
         self.set_dotted(yes)
     }
 
+    fn key(&self, key: &str) -> Option<&'_ Key> {
+        self.key(key)
+    }
+    fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> {
+        self.key_mut(key)
+    }
     fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> {
+        #![allow(deprecated)]
         self.key_decor_mut(key)
     }
     fn key_decor(&self, key: &str) -> Option<&Decor> {
+        #![allow(deprecated)]
         self.key_decor(key)
     }
 }
diff --git a/src/value.rs b/src/value.rs
index f10da9a..f416434 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -4,7 +4,6 @@
 use toml_datetime::*;
 
 use crate::key::Key;
-use crate::parser;
 use crate::repr::{Decor, Formatted};
 use crate::{Array, InlineTable, InternalString, RawString};
 
@@ -190,10 +189,12 @@
     /// Sets the prefix and the suffix for value.
     /// # Example
     /// ```rust
+    /// # #[cfg(feature = "display")] {
     /// let mut v = toml_edit::Value::from(42);
     /// assert_eq!(&v.to_string(), "42");
     /// let d = v.decorated(" ", " ");
     /// assert_eq!(&d.to_string(), " 42 ");
+    /// # }
     /// ```
     pub fn decorated(mut self, prefix: impl Into<RawString>, suffix: impl Into<RawString>) -> Self {
         self.decorate(prefix, suffix);
@@ -231,12 +232,13 @@
     }
 }
 
+#[cfg(feature = "parse")]
 impl FromStr for Value {
     type Err = crate::TomlError;
 
     /// Parses a value from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        parser::parse_value(s)
+        crate::parser::parse_value(s)
     }
 }
 
@@ -284,6 +286,7 @@
 
 impl From<f64> for Value {
     fn from(f: f64) -> Self {
+        // Preserve sign of NaN. It may get written to TOML as `-nan`.
         Value::Float(Formatted::new(f))
     }
 }
@@ -346,9 +349,10 @@
     }
 }
 
+#[cfg(feature = "display")]
 impl std::fmt::Display for Value {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        crate::encode::Encode::encode(self, f, None, ("", ""))
+        crate::encode::encode_value(self, f, None, ("", ""))
     }
 }
 
@@ -360,6 +364,8 @@
 pub(crate) const DEFAULT_LEADING_VALUE_DECOR: (&str, &str) = ("", "");
 
 #[cfg(test)]
+#[cfg(feature = "parse")]
+#[cfg(feature = "display")]
 mod tests {
     use super::*;
 
diff --git a/src/visit.rs b/src/visit.rs
index 1bc640a..6d7be0b 100644
--- a/src/visit.rs
+++ b/src/visit.rs
@@ -43,6 +43,7 @@
 //! This visitor stores every string in the document.
 //!
 //! ```
+//! # #[cfg(feature = "parse")] {
 //! # use toml_edit::*;
 //! use toml_edit::visit::*;
 //!
@@ -67,10 +68,11 @@
 //! visitor.visit_document(&document);
 //!
 //! assert_eq!(visitor.strings, vec!["sky-castle", "surrounds-you"]);
+//! # }
 //! ```
 //!
 //! For a more complex example where the visitor has internal state, see `examples/visit.rs`
-//! [on GitHub](https://github.com/ordian/toml_edit/blob/master/examples/visit.rs).
+//! [on GitHub](https://github.com/toml-rs/toml/blob/main/crates/toml_edit/examples/visit.rs).
 
 use crate::{
     Array, ArrayOfTables, Datetime, Document, Formatted, InlineTable, Item, Table, TableLike, Value,
diff --git a/src/visit_mut.rs b/src/visit_mut.rs
index 2c2af97..c823cfb 100644
--- a/src/visit_mut.rs
+++ b/src/visit_mut.rs
@@ -45,6 +45,8 @@
 //! 2 decimal points.
 //!
 //! ```
+//! # #[cfg(feature = "parse")] {
+//! # #[cfg(feature = "display")] {
 //! # use toml_edit::*;
 //! use toml_edit::visit_mut::*;
 //!
@@ -80,10 +82,12 @@
 //! "#;
 //!
 //! assert_eq!(format!("{}", document), output);
+//! # }
+//! # }
 //! ```
 //!
 //! For a more complex example where the visitor has internal state, see `examples/visit.rs`
-//! [on GitHub](https://github.com/ordian/toml_edit/blob/master/examples/visit.rs).
+//! [on GitHub](https://github.com/toml-rs/toml/blob/main/crates/toml_edit/examples/visit.rs).
 
 use crate::{
     Array, ArrayOfTables, Datetime, Document, Formatted, InlineTable, Item, KeyMut, Table,
diff --git a/tests/decoder_compliance.rs b/tests/decoder_compliance.rs
index 0f0b350..37a9335 100644
--- a/tests/decoder_compliance.rs
+++ b/tests/decoder_compliance.rs
@@ -3,15 +3,7 @@
 fn main() {
     let decoder = decoder::Decoder;
     let mut harness = toml_test_harness::DecoderHarness::new(decoder);
-    harness
-        .ignore([
-            "valid/spec/float-0.toml", // Test issue; `Decoder` turns `6.626e-34` into `0.0`
-            // Unreleased
-            "valid/string/escape-esc.toml",
-            "valid/string/hex-escape.toml",
-            "valid/datetime/no-seconds.toml",
-            "valid/inline-table/newline.toml",
-        ])
-        .unwrap();
+    harness.version("1.0.0");
+    harness.ignore([]).unwrap();
     harness.test();
 }
diff --git a/tests/encoder_compliance.rs b/tests/encoder_compliance.rs
index ad65d75..355c540 100644
--- a/tests/encoder_compliance.rs
+++ b/tests/encoder_compliance.rs
@@ -5,10 +5,6 @@
     let encoder = encoder::Encoder;
     let decoder = decoder::Decoder;
     let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder);
-    harness
-        .ignore([
-            "valid/spec/float-0.toml", // Test issue; `Decoder` turns `6.626e-34` into `0.0`
-        ])
-        .unwrap();
+    harness.version("1.0.0");
     harness.test();
 }
diff --git a/tests/fixtures/invalid/array/array.stderr b/tests/fixtures/invalid/array/array.stderr
new file mode 100644
index 0000000..6cb810d
--- /dev/null
+++ b/tests/fixtures/invalid/array/array.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 21
+  |
+1 | double-comma-1 = [1,,2]
+  |                     ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/double-comma-1.stderr b/tests/fixtures/invalid/array/double-comma-1.stderr
index 543e1b6..6cb810d 100644
--- a/tests/fixtures/invalid/array/double-comma-1.stderr
+++ b/tests/fixtures/invalid/array/double-comma-1.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 12
+TOML parse error at line 1, column 21
   |
-1 | array = [1,,2]
-  |            ^
+1 | double-comma-1 = [1,,2]
+  |                     ^
 invalid array
 expected `]`
diff --git a/tests/fixtures/invalid/array/double-comma-2.stderr b/tests/fixtures/invalid/array/double-comma-2.stderr
index 694d7ec..60ab5b5 100644
--- a/tests/fixtures/invalid/array/double-comma-2.stderr
+++ b/tests/fixtures/invalid/array/double-comma-2.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 14
+TOML parse error at line 1, column 23
   |
-1 | array = [1,2,,]
-  |              ^
+1 | double-comma-2 = [1,2,,]
+  |                       ^
 invalid array
 expected `]`
diff --git a/tests/fixtures/invalid/array/extend-defined-aot.stderr b/tests/fixtures/invalid/array/extend-defined-aot.stderr
new file mode 100644
index 0000000..970e0ec
--- /dev/null
+++ b/tests/fixtures/invalid/array/extend-defined-aot.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 3, column 1
+  |
+3 | arr.val1=1
+  | ^
+duplicate key `val1`
diff --git a/tests/fixtures/invalid/array/missing-separator-1.stderr b/tests/fixtures/invalid/array/missing-separator-1.stderr
new file mode 100644
index 0000000..1202bb1
--- /dev/null
+++ b/tests/fixtures/invalid/array/missing-separator-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 14
+  |
+1 | arrr = [true false]
+  |              ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/missing-separator.stderr b/tests/fixtures/invalid/array/missing-separator-2.stderr
similarity index 100%
rename from tests/fixtures/invalid/array/missing-separator.stderr
rename to tests/fixtures/invalid/array/missing-separator-2.stderr
diff --git a/tests/fixtures/invalid/array/no-close.stderr b/tests/fixtures/invalid/array/no-close-1.stderr
similarity index 78%
rename from tests/fixtures/invalid/array/no-close.stderr
rename to tests/fixtures/invalid/array/no-close-1.stderr
index a4f0a88..6f0a3e8 100644
--- a/tests/fixtures/invalid/array/no-close.stderr
+++ b/tests/fixtures/invalid/array/no-close-1.stderr
@@ -1,6 +1,6 @@
 TOML parse error at line 1, column 24
   |
-1 | long_array = [ 1, 2, 3
+1 | no-close-1 = [ 1, 2, 3
   |                        ^
 invalid array
 expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-2.stderr b/tests/fixtures/invalid/array/no-close-2.stderr
index 81ae5a9..96c4d64 100644
--- a/tests/fixtures/invalid/array/no-close-2.stderr
+++ b/tests/fixtures/invalid/array/no-close-2.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 11
+TOML parse error at line 1, column 18
   |
-1 | x = [42 #
-  |           ^
+1 | no-close-2 = [1,
+  |                  ^
 invalid array
 expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-3.stderr b/tests/fixtures/invalid/array/no-close-3.stderr
new file mode 100644
index 0000000..b84e3b9
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-3.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 21
+  |
+1 | no-close-3 = [42 #]
+  |                     ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-4.stderr b/tests/fixtures/invalid/array/no-close-4.stderr
new file mode 100644
index 0000000..ecf4224
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-4.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 25
+  |
+1 | no-close-4 = [{ key = 42
+  |                         ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/array/no-close-5.stderr b/tests/fixtures/invalid/array/no-close-5.stderr
new file mode 100644
index 0000000..c5e9674
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-5.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 27
+  |
+1 | no-close-5 = [{ key = 42}
+  |                           ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-6.stderr b/tests/fixtures/invalid/array/no-close-6.stderr
new file mode 100644
index 0000000..2512a43
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-6.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 26
+  |
+1 | no-close-6 = [{ key = 42 #}]
+  |                          ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/array/no-close-7.stderr b/tests/fixtures/invalid/array/no-close-7.stderr
new file mode 100644
index 0000000..a49e988
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-7.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 30
+  |
+1 | no-close-7 = [{ key = 42} #]
+  |                              ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-8.stderr b/tests/fixtures/invalid/array/no-close-8.stderr
new file mode 100644
index 0000000..0e25a0b
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-close-8.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 16
+  |
+1 | no-close-8 = [
+  |                ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-close-table.stderr b/tests/fixtures/invalid/array/no-close-table-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/array/no-close-table.stderr
rename to tests/fixtures/invalid/array/no-close-table-1.stderr
diff --git a/tests/fixtures/invalid/array/no-comma-1.stderr b/tests/fixtures/invalid/array/no-comma-1.stderr
new file mode 100644
index 0000000..e50fb69
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-comma-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 20
+  |
+1 | no-comma-1 = [true false]
+  |                    ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-comma-2.stderr b/tests/fixtures/invalid/array/no-comma-2.stderr
new file mode 100644
index 0000000..b34d709
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-comma-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 18
+  |
+1 | no-comma-2 = [ 1 2 3 ]
+  |                  ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/no-comma-3.stderr b/tests/fixtures/invalid/array/no-comma-3.stderr
new file mode 100644
index 0000000..c7cbec4
--- /dev/null
+++ b/tests/fixtures/invalid/array/no-comma-3.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 22
+  |
+1 | no-comma-3 = [ 1 #,]
+  |                      ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/only-comma-1.stderr b/tests/fixtures/invalid/array/only-comma-1.stderr
new file mode 100644
index 0000000..3616ca9
--- /dev/null
+++ b/tests/fixtures/invalid/array/only-comma-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 17
+  |
+1 | only-comma-1 = [,]
+  |                 ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/array/only-comma-2.stderr b/tests/fixtures/invalid/array/only-comma-2.stderr
new file mode 100644
index 0000000..8284be5
--- /dev/null
+++ b/tests/fixtures/invalid/array/only-comma-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 17
+  |
+1 | only-comma-2 = [,,]
+  |                 ^
+invalid array
+expected `]`
diff --git a/tests/fixtures/invalid/bool/almost-false-with-extra.stderr b/tests/fixtures/invalid/bool/almost-false-with-extra.stderr
index cd6c1cd..1517084 100644
--- a/tests/fixtures/invalid/bool/almost-false-with-extra.stderr
+++ b/tests/fixtures/invalid/bool/almost-false-with-extra.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = falsify
-  |     ^
+1 | almost-false-with-extra = falsify
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/almost-false.stderr b/tests/fixtures/invalid/bool/almost-false.stderr
index 550020b..ba22196 100644
--- a/tests/fixtures/invalid/bool/almost-false.stderr
+++ b/tests/fixtures/invalid/bool/almost-false.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = fals
-  |     ^
+1 | almost-false            = fals
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/almost-true-with-extra.stderr b/tests/fixtures/invalid/bool/almost-true-with-extra.stderr
index c75c553..23e230e 100644
--- a/tests/fixtures/invalid/bool/almost-true-with-extra.stderr
+++ b/tests/fixtures/invalid/bool/almost-true-with-extra.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = truthy
-  |     ^
+1 | almost-true-with-extra  = truthy
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/almost-true.stderr b/tests/fixtures/invalid/bool/almost-true.stderr
index 0c97e00..21bdf37 100644
--- a/tests/fixtures/invalid/bool/almost-true.stderr
+++ b/tests/fixtures/invalid/bool/almost-true.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = tru
-  |     ^
+1 | almost-true             = tru
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/bool.stderr b/tests/fixtures/invalid/bool/bool.stderr
new file mode 100644
index 0000000..1517084
--- /dev/null
+++ b/tests/fixtures/invalid/bool/bool.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 27
+  |
+1 | almost-false-with-extra = falsify
+  |                           ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/capitalized-false.stderr b/tests/fixtures/invalid/bool/capitalized-false.stderr
new file mode 100644
index 0000000..fd318ca
--- /dev/null
+++ b/tests/fixtures/invalid/bool/capitalized-false.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 28
+  |
+1 | capitalized-false        = False
+  |                            ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/capitalized-true.stderr b/tests/fixtures/invalid/bool/capitalized-true.stderr
new file mode 100644
index 0000000..cb32d8e
--- /dev/null
+++ b/tests/fixtures/invalid/bool/capitalized-true.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 28
+  |
+1 | capitalized-true         = True
+  |                            ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/just-f.stderr b/tests/fixtures/invalid/bool/just-f.stderr
index ed2b9f0..cc4fb78 100644
--- a/tests/fixtures/invalid/bool/just-f.stderr
+++ b/tests/fixtures/invalid/bool/just-f.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = f
-  |     ^
+1 | just-f                  = f
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/just-t.stderr b/tests/fixtures/invalid/bool/just-t.stderr
index 2c8b6a5..f2d889b 100644
--- a/tests/fixtures/invalid/bool/just-t.stderr
+++ b/tests/fixtures/invalid/bool/just-t.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = t
-  |     ^
+1 | just-t                  = t
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/mixed-case-false.stderr b/tests/fixtures/invalid/bool/mixed-case-false.stderr
new file mode 100644
index 0000000..d96dc60
--- /dev/null
+++ b/tests/fixtures/invalid/bool/mixed-case-false.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 27
+  |
+1 | mixed-case-false        = falsE
+  |                           ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/mixed-case-true.stderr b/tests/fixtures/invalid/bool/mixed-case-true.stderr
new file mode 100644
index 0000000..e7f8289
--- /dev/null
+++ b/tests/fixtures/invalid/bool/mixed-case-true.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 27
+  |
+1 | mixed-case-true         = trUe
+  |                           ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/mixed-case.stderr b/tests/fixtures/invalid/bool/mixed-case.stderr
index b7c6192..994020e 100644
--- a/tests/fixtures/invalid/bool/mixed-case.stderr
+++ b/tests/fixtures/invalid/bool/mixed-case.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 9
+TOML parse error at line 1, column 27
   |
-1 | valid = False
-  |         ^
+1 | mixed-case              = valid   = False
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/starting-same-false.stderr b/tests/fixtures/invalid/bool/starting-same-false.stderr
index b332089..1cc00d5 100644
--- a/tests/fixtures/invalid/bool/starting-same-false.stderr
+++ b/tests/fixtures/invalid/bool/starting-same-false.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 10
+TOML parse error at line 1, column 32
   |
-1 | a = falsey
-  |          ^
+1 | starting-same-false     = falsey
+  |                                ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/bool/starting-same-true.stderr b/tests/fixtures/invalid/bool/starting-same-true.stderr
index 6053103..a7cc00b 100644
--- a/tests/fixtures/invalid/bool/starting-same-true.stderr
+++ b/tests/fixtures/invalid/bool/starting-same-true.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 9
+TOML parse error at line 1, column 31
   |
-1 | a = truer
-  |         ^
+1 | starting-same-true      = truer
+  |                               ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/bool/wrong-case-false.stderr b/tests/fixtures/invalid/bool/wrong-case-false.stderr
index f67444c..fd15caa 100644
--- a/tests/fixtures/invalid/bool/wrong-case-false.stderr
+++ b/tests/fixtures/invalid/bool/wrong-case-false.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | b = FALSE
-  |     ^
+1 | wrong-case-false        = FALSE
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/bool/wrong-case-true.stderr b/tests/fixtures/invalid/bool/wrong-case-true.stderr
index 82bb619..bc60a4c 100644
--- a/tests/fixtures/invalid/bool/wrong-case-true.stderr
+++ b/tests/fixtures/invalid/bool/wrong-case-true.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 5
+TOML parse error at line 1, column 27
   |
-1 | a = TRUE
-  |     ^
+1 | wrong-case-true         = TRUE
+  |                           ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/control/bare-formfeed.stderr b/tests/fixtures/invalid/control/bare-formfeed.stderr
index 313274a..449ec87 100644
--- a/tests/fixtures/invalid/control/bare-formfeed.stderr
+++ b/tests/fixtures/invalid/control/bare-formfeed.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 17
+TOML parse error at line 1, column 21
   |
-1 | bare-formfeed = 
-  |                 ^
+1 | bare-formfeed     = 
+  |                     ^
 invalid string
 expected `"`, `'`
diff --git a/tests/fixtures/invalid/control/bare-null.stderr b/tests/fixtures/invalid/control/bare-null.stderr
index cd5e936..9c8231d 100644
--- a/tests/fixtures/invalid/control/bare-null.stderr
+++ b/tests/fixtures/invalid/control/bare-null.stderr
Binary files differ
diff --git a/tests/fixtures/invalid/control/comment-cr.stderr b/tests/fixtures/invalid/control/comment-cr.stderr
index fb262e5..54e42df 100644
--- a/tests/fixtures/invalid/control/comment-cr.stderr
+++ b/tests/fixtures/invalid/control/comment-cr.stderr
@@ -1,6 +1,6 @@
-TOML parse error at line 1, column 45
+TOML parse error at line 1, column 47
   |
-1 | comment-cr = "Carriage return in comment" # 
+1 | comment-cr   = "Carriage return in comment" # 
 a=1
-  |                                             ^
+  |                                               ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/control/comment-del.stderr b/tests/fixtures/invalid/control/comment-del.stderr
index 3d25d68..5eae7a4 100644
--- a/tests/fixtures/invalid/control/comment-del.stderr
+++ b/tests/fixtures/invalid/control/comment-del.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 24
+TOML parse error at line 1, column 27
   |
-1 | comment-del = "0x7f" # 
-  |                        ^
+1 | comment-del  = "0x7f"   # 
+  |                           ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/control/comment-ff.stderr b/tests/fixtures/invalid/control/comment-ff.stderr
new file mode 100644
index 0000000..6f54974
--- /dev/null
+++ b/tests/fixtures/invalid/control/comment-ff.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 27
+  |
+1 | comment-ff   = "0x7f"   # 
+  |                           ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/control/comment-lf.stderr b/tests/fixtures/invalid/control/comment-lf.stderr
index 1613710..3eacd62 100644
--- a/tests/fixtures/invalid/control/comment-lf.stderr
+++ b/tests/fixtures/invalid/control/comment-lf.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 25
+TOML parse error at line 1, column 27
   |
-1 | comment-lf = "ctrl-P" # 
-  |                         ^
+1 | comment-lf   = "ctrl-P" # 
+  |                           ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/control/comment-null.stderr b/tests/fixtures/invalid/control/comment-null.stderr
index 4955b9d..ba5bc80 100644
--- a/tests/fixtures/invalid/control/comment-null.stderr
+++ b/tests/fixtures/invalid/control/comment-null.stderr
Binary files differ
diff --git a/tests/fixtures/invalid/control/comment-us.stderr b/tests/fixtures/invalid/control/comment-us.stderr
index b48d4f3..f34076e 100644
--- a/tests/fixtures/invalid/control/comment-us.stderr
+++ b/tests/fixtures/invalid/control/comment-us.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 25
+TOML parse error at line 1, column 27
   |
-1 | comment-us = "ctrl-_" # 
-  |                         ^
+1 | comment-us   = "ctrl-_" # 
+  |                           ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/control/control.stderr b/tests/fixtures/invalid/control/control.stderr
index 486aacf..f214b54 100644
--- a/tests/fixtures/invalid/control/control.stderr
+++ b/tests/fixtures/invalid/control/control.stderr
@@ -1,6 +1,5 @@
-TOML parse error at line 9, column 22
+TOML parse error at line 9, column 1
   |
-9 | string-null = "null\x00"
-  |                      ^
-invalid escape sequence
-expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"`
+9 | comment-cr   = "Carriage return in comment" # \x0da=1
+  | ^
+duplicate key `comment-cr` in document root
diff --git a/tests/fixtures/invalid/control/multi-cr.stderr b/tests/fixtures/invalid/control/multi-cr.stderr
new file mode 100644
index 0000000..d5c749c
--- /dev/null
+++ b/tests/fixtures/invalid/control/multi-cr.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 21
+  |
+1 | multi-cr   = """null
+"""
+  |                     ^
+invalid multiline basic string
diff --git a/tests/fixtures/invalid/control/multi-del.stderr b/tests/fixtures/invalid/control/multi-del.stderr
index 62702da..a1b54b7 100644
--- a/tests/fixtures/invalid/control/multi-del.stderr
+++ b/tests/fixtures/invalid/control/multi-del.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 20
+TOML parse error at line 1, column 21
   |
-1 | multi-del = """null"""
-  |                    ^
+1 | multi-del  = """null"""
+  |                     ^
 invalid multiline basic string
diff --git a/tests/fixtures/invalid/control/multi-lf.stderr b/tests/fixtures/invalid/control/multi-lf.stderr
index 7b7a138..a0a8f7b 100644
--- a/tests/fixtures/invalid/control/multi-lf.stderr
+++ b/tests/fixtures/invalid/control/multi-lf.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 19
+TOML parse error at line 1, column 21
   |
-1 | multi-lf = """null"""
-  |                   ^
+1 | multi-lf   = """null"""
+  |                     ^
 invalid multiline basic string
diff --git a/tests/fixtures/invalid/control/multi-us.stderr b/tests/fixtures/invalid/control/multi-us.stderr
index cf8e732..ac556b2 100644
--- a/tests/fixtures/invalid/control/multi-us.stderr
+++ b/tests/fixtures/invalid/control/multi-us.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 19
+TOML parse error at line 1, column 21
   |
-1 | multi-us = """null"""
-  |                   ^
+1 | multi-us   = """null"""
+  |                     ^
 invalid multiline basic string
diff --git a/tests/fixtures/invalid/control/rawmulti-cd.stderr b/tests/fixtures/invalid/control/rawmulti-cd.stderr
new file mode 100644
index 0000000..1027009
--- /dev/null
+++ b/tests/fixtures/invalid/control/rawmulti-cd.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 24
+  |
+1 | rawmulti-cd   = '''null
+'''
+  |                        ^
+invalid multiline literal string
diff --git a/tests/fixtures/invalid/control/rawmulti-del.stderr b/tests/fixtures/invalid/control/rawmulti-del.stderr
index 3beeae0..7972a2b 100644
--- a/tests/fixtures/invalid/control/rawmulti-del.stderr
+++ b/tests/fixtures/invalid/control/rawmulti-del.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 23
+TOML parse error at line 1, column 24
   |
-1 | rawmulti-del = '''null'''
-  |                       ^
+1 | rawmulti-del  = '''null'''
+  |                        ^
 invalid multiline literal string
diff --git a/tests/fixtures/invalid/control/rawmulti-lf.stderr b/tests/fixtures/invalid/control/rawmulti-lf.stderr
index 40782a2..1898ec8 100644
--- a/tests/fixtures/invalid/control/rawmulti-lf.stderr
+++ b/tests/fixtures/invalid/control/rawmulti-lf.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 22
+TOML parse error at line 1, column 24
   |
-1 | rawmulti-lf = '''null'''
-  |                      ^
+1 | rawmulti-lf   = '''null'''
+  |                        ^
 invalid multiline literal string
diff --git a/tests/fixtures/invalid/control/rawmulti-us.stderr b/tests/fixtures/invalid/control/rawmulti-us.stderr
index d413d54..4c412f7 100644
--- a/tests/fixtures/invalid/control/rawmulti-us.stderr
+++ b/tests/fixtures/invalid/control/rawmulti-us.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 22
+TOML parse error at line 1, column 24
   |
-1 | rawmulti-us = '''null'''
-  |                      ^
+1 | rawmulti-us   = '''null'''
+  |                        ^
 invalid multiline literal string
diff --git a/tests/fixtures/invalid/control/rawstring-cr.stderr b/tests/fixtures/invalid/control/rawstring-cr.stderr
new file mode 100644
index 0000000..bc8a1a2
--- /dev/null
+++ b/tests/fixtures/invalid/control/rawstring-cr.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 23
+  |
+1 | rawstring-cr   = 'null
+'
+  |                       ^
+invalid literal string
diff --git a/tests/fixtures/invalid/control/rawstring-del.stderr b/tests/fixtures/invalid/control/rawstring-del.stderr
index 640ba46..e80e322 100644
--- a/tests/fixtures/invalid/control/rawstring-del.stderr
+++ b/tests/fixtures/invalid/control/rawstring-del.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 22
+TOML parse error at line 1, column 23
   |
-1 | rawstring-del = 'null'
-  |                      ^
+1 | rawstring-del  = 'null'
+  |                       ^
 invalid literal string
diff --git a/tests/fixtures/invalid/control/rawstring-lf.stderr b/tests/fixtures/invalid/control/rawstring-lf.stderr
index e6499b6..b8874c6 100644
--- a/tests/fixtures/invalid/control/rawstring-lf.stderr
+++ b/tests/fixtures/invalid/control/rawstring-lf.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 21
+TOML parse error at line 1, column 23
   |
-1 | rawstring-lf = 'null'
-  |                     ^
+1 | rawstring-lf   = 'null'
+  |                       ^
 invalid literal string
diff --git a/tests/fixtures/invalid/control/rawstring-us.stderr b/tests/fixtures/invalid/control/rawstring-us.stderr
index 492cdf7..9cf3fae 100644
--- a/tests/fixtures/invalid/control/rawstring-us.stderr
+++ b/tests/fixtures/invalid/control/rawstring-us.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 21
+TOML parse error at line 1, column 23
   |
-1 | rawstring-us = 'null'
-  |                     ^
+1 | rawstring-us   = 'null'
+  |                       ^
 invalid literal string
diff --git a/tests/fixtures/invalid/control/string-bs.stderr b/tests/fixtures/invalid/control/string-bs.stderr
index 556ba1d..3f7b5ee 100644
--- a/tests/fixtures/invalid/control/string-bs.stderr
+++ b/tests/fixtures/invalid/control/string-bs.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 23
+TOML parse error at line 1, column 25
   |
-1 | string-bs = "backspace"
-  |                       ^
+1 | string-bs   = "backspace"
+  |                         ^
 invalid basic string
diff --git a/tests/fixtures/invalid/control/string-cr.stderr b/tests/fixtures/invalid/control/string-cr.stderr
new file mode 100644
index 0000000..0eff4c2
--- /dev/null
+++ b/tests/fixtures/invalid/control/string-cr.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 20
+  |
+1 | string-cr   = "null
+"
+  |                    ^
+invalid basic string
diff --git a/tests/fixtures/invalid/control/string-del.stderr b/tests/fixtures/invalid/control/string-del.stderr
index 85d7af3..b6ace6b 100644
--- a/tests/fixtures/invalid/control/string-del.stderr
+++ b/tests/fixtures/invalid/control/string-del.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 19
+TOML parse error at line 1, column 20
   |
-1 | string-del = "null"
-  |                   ^
+1 | string-del  = "null"
+  |                    ^
 invalid basic string
diff --git a/tests/fixtures/invalid/control/string-lf.stderr b/tests/fixtures/invalid/control/string-lf.stderr
index fbf0d1a..c3b141e 100644
--- a/tests/fixtures/invalid/control/string-lf.stderr
+++ b/tests/fixtures/invalid/control/string-lf.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 18
+TOML parse error at line 1, column 20
   |
-1 | string-lf = "null"
-  |                  ^
+1 | string-lf   = "null"
+  |                    ^
 invalid basic string
diff --git a/tests/fixtures/invalid/control/string-us.stderr b/tests/fixtures/invalid/control/string-us.stderr
index 8278e57..26f2f6a 100644
--- a/tests/fixtures/invalid/control/string-us.stderr
+++ b/tests/fixtures/invalid/control/string-us.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 18
+TOML parse error at line 1, column 20
   |
-1 | string-us = "null"
-  |                  ^
+1 | string-us   = "null"
+  |                    ^
 invalid basic string
diff --git a/tests/fixtures/invalid/datetime/feb-29.stderr b/tests/fixtures/invalid/datetime/feb-29.stderr
new file mode 100644
index 0000000..63fac67
--- /dev/null
+++ b/tests/fixtures/invalid/datetime/feb-29.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 29
+  |
+1 | "not a leap year" = 2100-02-29T15:15:15Z
+  |                             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/datetime/feb-30.stderr b/tests/fixtures/invalid/datetime/feb-30.stderr
new file mode 100644
index 0000000..2b5212a
--- /dev/null
+++ b/tests/fixtures/invalid/datetime/feb-30.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 44
+  |
+1 | "only 28 or 29 days in february" = 1988-02-30T15:15:15Z
+  |                                            ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/datetime/no-leads-month.stderr b/tests/fixtures/invalid/datetime/no-leads-month.stderr
new file mode 100644
index 0000000..3e0ccf7
--- /dev/null
+++ b/tests/fixtures/invalid/datetime/no-leads-month.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 17
+  |
+2 | no-leads = 1987-7-05T17:45:00Z
+  |                 ^
+invalid date-time
diff --git a/tests/fixtures/invalid/datetime/time-no-leads.stderr b/tests/fixtures/invalid/datetime/time-no-leads.stderr
index 7a98902..012f62f 100644
--- a/tests/fixtures/invalid/datetime/time-no-leads.stderr
+++ b/tests/fixtures/invalid/datetime/time-no-leads.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 2, column 6
+TOML parse error at line 2, column 15
   |
-2 | d = 1:32:00
-  |      ^
+2 | d = 2023-10-01T1:32:00Z
+  |               ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/datetime/y10k.stderr b/tests/fixtures/invalid/datetime/y10k.stderr
new file mode 100644
index 0000000..dc5cfc5
--- /dev/null
+++ b/tests/fixtures/invalid/datetime/y10k.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 10000-01-01 00:00:00z
+  |          ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/encoding/bad-codepoint.stderr b/tests/fixtures/invalid/encoding/bad-codepoint.stderr
new file mode 100644
index 0000000..89ef2c7
--- /dev/null
+++ b/tests/fixtures/invalid/encoding/bad-codepoint.stderr
@@ -0,0 +1 @@
+invalid utf-8 sequence of 1 bytes from index 29
\ No newline at end of file
diff --git a/tests/fixtures/invalid/encoding/utf16.stderr b/tests/fixtures/invalid/encoding/utf16-comment.stderr
similarity index 100%
rename from tests/fixtures/invalid/encoding/utf16.stderr
rename to tests/fixtures/invalid/encoding/utf16-comment.stderr
Binary files differ
diff --git a/tests/fixtures/invalid/encoding/utf16-key.stderr b/tests/fixtures/invalid/encoding/utf16-key.stderr
new file mode 100644
index 0000000..16bedc0
--- /dev/null
+++ b/tests/fixtures/invalid/encoding/utf16-key.stderr
Binary files differ
diff --git a/tests/fixtures/invalid/float/exp-point-3.stderr b/tests/fixtures/invalid/float/exp-point-3.stderr
new file mode 100644
index 0000000..873791c
--- /dev/null
+++ b/tests/fixtures/invalid/float/exp-point-3.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 17
+  |
+1 | exp-point-3 = 3.e+20
+  |                 ^
+invalid floating-point number
+expected digit
diff --git a/tests/fixtures/invalid/float/exp-trailing-us-1.stderr b/tests/fixtures/invalid/float/exp-trailing-us-1.stderr
new file mode 100644
index 0000000..254bdf2
--- /dev/null
+++ b/tests/fixtures/invalid/float/exp-trailing-us-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 23
+  |
+1 | exp-trailing-us-1 = 1_e2
+  |                       ^
+invalid integer
+expected digit
diff --git a/tests/fixtures/invalid/float/exp-trailing-us-2.stderr b/tests/fixtures/invalid/float/exp-trailing-us-2.stderr
new file mode 100644
index 0000000..b2986ad
--- /dev/null
+++ b/tests/fixtures/invalid/float/exp-trailing-us-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 25
+  |
+1 | exp-trailing-us-2 = 1.2_e2
+  |                         ^
+invalid floating-point number
+expected digit, digit
diff --git a/tests/fixtures/invalid/float/exp-trailing-us.stderr b/tests/fixtures/invalid/float/exp-trailing-us.stderr
index 9a28184..b7e2ad9 100644
--- a/tests/fixtures/invalid/float/exp-trailing-us.stderr
+++ b/tests/fixtures/invalid/float/exp-trailing-us.stderr
@@ -1,5 +1,6 @@
-TOML parse error at line 1, column 21
+TOML parse error at line 1, column 24
   |
-1 | exp-trailing-us = 1e_23_
-  |                     ^
+1 | exp-trailing-us = 1e23_
+  |                        ^
 invalid floating-point number
+expected digit
diff --git a/tests/fixtures/invalid/float/inf-capital.stderr b/tests/fixtures/invalid/float/inf-capital.stderr
new file mode 100644
index 0000000..ca7a1b1
--- /dev/null
+++ b/tests/fixtures/invalid/float/inf-capital.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 5
+  |
+1 | v = Inf
+  |     ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/float/nan-capital.stderr b/tests/fixtures/invalid/float/nan-capital.stderr
new file mode 100644
index 0000000..ae3b4d9
--- /dev/null
+++ b/tests/fixtures/invalid/float/nan-capital.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 5
+  |
+1 | v = NaN
+  |     ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/float/trailing-us-exp-1.stderr b/tests/fixtures/invalid/float/trailing-us-exp-1.stderr
new file mode 100644
index 0000000..cf0fabc
--- /dev/null
+++ b/tests/fixtures/invalid/float/trailing-us-exp-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 23
+  |
+1 | trailing-us-exp-1 = 1_e2
+  |                       ^
+invalid integer
+expected digit
diff --git a/tests/fixtures/invalid/float/trailing-us-exp-2.stderr b/tests/fixtures/invalid/float/trailing-us-exp-2.stderr
new file mode 100644
index 0000000..83ff120
--- /dev/null
+++ b/tests/fixtures/invalid/float/trailing-us-exp-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 25
+  |
+1 | trailing-us-exp-2 = 1.2_e2
+  |                         ^
+invalid floating-point number
+expected digit, digit
diff --git a/tests/fixtures/invalid/float/trailing-us-exp.stderr b/tests/fixtures/invalid/float/trailing-us-exp.stderr
deleted file mode 100644
index 811f951..0000000
--- a/tests/fixtures/invalid/float/trailing-us-exp.stderr
+++ /dev/null
@@ -1,6 +0,0 @@
-TOML parse error at line 2, column 21
-  |
-2 | trailing-us-exp = 1_e2
-  |                     ^
-invalid integer
-expected digit
diff --git a/tests/fixtures/invalid/inline-table/bad-key-syntax.stderr b/tests/fixtures/invalid/inline-table/bad-key-syntax.stderr
new file mode 100644
index 0000000..c8ce45a
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/bad-key-syntax.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 14
+  |
+1 | tbl = { a = 1, [b] }
+  |              ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/duplicate-key.stderr b/tests/fixtures/invalid/inline-table/duplicate-key-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table/duplicate-key.stderr
rename to tests/fixtures/invalid/inline-table/duplicate-key-1.stderr
diff --git a/tests/fixtures/invalid/inline-table/duplicate-key-2.stderr b/tests/fixtures/invalid/inline-table/duplicate-key-2.stderr
new file mode 100644
index 0000000..91b41fd
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/duplicate-key-2.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 11
+  |
+1 | table1 = { table2.dupe = 1, table2.dupe = 2 }
+  |           ^
+duplicate key `dupe`
diff --git a/tests/fixtures/invalid/inline-table/duplicate-key-3.stderr b/tests/fixtures/invalid/inline-table/duplicate-key-3.stderr
new file mode 100644
index 0000000..79a8041
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/duplicate-key-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | tbl = { fruit = { apple.color = "red" }, fruit.apple.texture = { smooth = true } }
+  |        ^
+duplicate key `fruit`
diff --git a/tests/fixtures/invalid/inline-table/duplicate-key-4.stderr b/tests/fixtures/invalid/inline-table/duplicate-key-4.stderr
new file mode 100644
index 0000000..7b86044
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/duplicate-key-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | tbl = { a.b = "a_b", a.b.c = "a_b_c" }
+  |        ^
+dotted key `a.b` attempted to extend non-table type (string)
diff --git a/tests/fixtures/invalid/inline-table/empty.stderr b/tests/fixtures/invalid/inline-table/empty-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table/empty.stderr
rename to tests/fixtures/invalid/inline-table/empty-1.stderr
diff --git a/tests/fixtures/invalid/inline-table/empty-2.stderr b/tests/fixtures/invalid/inline-table/empty-2.stderr
new file mode 100644
index 0000000..ea51c18
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/empty-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 6
+  |
+1 | t = {,
+  |      ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/empty-3.stderr b/tests/fixtures/invalid/inline-table/empty-3.stderr
new file mode 100644
index 0000000..ca4a29a
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/empty-3.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 6
+  |
+1 | t = {
+  |      ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/no-close-1.stderr b/tests/fixtures/invalid/inline-table/no-close-1.stderr
new file mode 100644
index 0000000..d371b65
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/no-close-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 4
+  |
+1 | a={
+  |    ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/no-close-2.stderr b/tests/fixtures/invalid/inline-table/no-close-2.stderr
new file mode 100644
index 0000000..3b42d78
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/no-close-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 7
+  |
+1 | a={b=1
+  |       ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/no-comma.stderr b/tests/fixtures/invalid/inline-table/no-comma-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table/no-comma.stderr
rename to tests/fixtures/invalid/inline-table/no-comma-1.stderr
diff --git a/tests/fixtures/invalid/inline-table/no-comma-2.stderr b/tests/fixtures/invalid/inline-table/no-comma-2.stderr
new file mode 100644
index 0000000..128ac53
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/no-comma-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 31
+  |
+1 | arrr = { comma-missing = true valid-toml = false }
+  |                               ^
+invalid inline table
+expected `}`
diff --git a/tests/fixtures/invalid/inline-table/overwrite.stderr b/tests/fixtures/invalid/inline-table/overwrite-01.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table/overwrite.stderr
rename to tests/fixtures/invalid/inline-table/overwrite-01.stderr
diff --git a/tests/fixtures/invalid/inline-table/add.stderr b/tests/fixtures/invalid/inline-table/overwrite-02.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table/add.stderr
rename to tests/fixtures/invalid/inline-table/overwrite-02.stderr
diff --git a/tests/fixtures/invalid/inline-table/overwrite-03.stderr b/tests/fixtures/invalid/inline-table/overwrite-03.stderr
new file mode 100644
index 0000000..c1ab1d7
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-03.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 1
+  |
+2 | a.b = 2
+  | ^
+dotted key `a` attempted to extend non-table type (inline table)
diff --git a/tests/fixtures/invalid/inline-table/overwrite-04.stderr b/tests/fixtures/invalid/inline-table/overwrite-04.stderr
new file mode 100644
index 0000000..993e04e
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-04.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 1
+  |
+3 | [[inline-t.nest]]
+  | ^
+invalid table header
+dotted key `inline-t` attempted to extend non-table type (inline table)
diff --git a/tests/fixtures/invalid/inline-table/overwrite-05.stderr b/tests/fixtures/invalid/inline-table/overwrite-05.stderr
new file mode 100644
index 0000000..bf1953d
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-05.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 1
+  |
+3 | [inline-t.nest]
+  | ^
+invalid table header
+dotted key `inline-t` attempted to extend non-table type (inline table)
diff --git a/tests/fixtures/invalid/inline-table/overwrite-06.stderr b/tests/fixtures/invalid/inline-table/overwrite-06.stderr
new file mode 100644
index 0000000..1e5d4cd
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-06.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 6
+  |
+1 | a = { b = 1, b.c = 2 }
+  |      ^
+dotted key `b` attempted to extend non-table type (integer)
diff --git a/tests/fixtures/invalid/inline-table/overwrite-07.stderr b/tests/fixtures/invalid/inline-table/overwrite-07.stderr
new file mode 100644
index 0000000..9e5efba
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-07.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | tab = { inner.table = [{}], inner.table.val = "bad" }
+  |        ^
+dotted key `inner.table` attempted to extend non-table type (array)
diff --git a/tests/fixtures/invalid/inline-table/overwrite-08.stderr b/tests/fixtures/invalid/inline-table/overwrite-08.stderr
new file mode 100644
index 0000000..8322330
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-08.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | tab = { inner = { dog = "best" }, inner.cat = "worst" }
+  |        ^
+duplicate key `inner`
diff --git a/tests/fixtures/invalid/inline-table/overwrite-09.stderr b/tests/fixtures/invalid/inline-table/overwrite-09.stderr
new file mode 100644
index 0000000..9d68dbb
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-09.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 5, column 1
+  |
+5 | nested.inline-t.nest = 2
+  | ^
+duplicate key `nested`
diff --git a/tests/fixtures/invalid/inline-table/overwrite-10.stderr b/tests/fixtures/invalid/inline-table/overwrite-10.stderr
new file mode 100644
index 0000000..18af75a
--- /dev/null
+++ b/tests/fixtures/invalid/inline-table/overwrite-10.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 4, column 6
+  |
+4 | a = {b.a = 1, b = 2, b.c = 3}
+  |      ^
+duplicate key `b`
diff --git a/tests/fixtures/invalid/integer/invalid-hex-1.stderr b/tests/fixtures/invalid/integer/invalid-hex-1.stderr
new file mode 100644
index 0000000..b899e6a
--- /dev/null
+++ b/tests/fixtures/invalid/integer/invalid-hex-1.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 22
+  |
+1 | invalid-hex-1 = 0xaafz
+  |                      ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/integer/invalid-hex-2.stderr b/tests/fixtures/invalid/integer/invalid-hex-2.stderr
new file mode 100644
index 0000000..4df3e2f
--- /dev/null
+++ b/tests/fixtures/invalid/integer/invalid-hex-2.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 19
+  |
+1 | invalid-hex-2 = 0xgabba00f1
+  |                   ^
+invalid hexadecimal integer
diff --git a/tests/fixtures/invalid/integer/leading-us-bin.stderr b/tests/fixtures/invalid/integer/leading-us-bin.stderr
index c893e75..c979c19 100644
--- a/tests/fixtures/invalid/integer/leading-us-bin.stderr
+++ b/tests/fixtures/invalid/integer/leading-us-bin.stderr
@@ -1,6 +1,6 @@
 TOML parse error at line 1, column 18
   |
-1 | leading-us-bin = _0o1
+1 | leading-us-bin = _0b1
   |                  ^
 invalid integer
 expected leading digit
diff --git a/tests/fixtures/invalid/integer/leading-us-hex.stderr b/tests/fixtures/invalid/integer/leading-us-hex.stderr
index 12eb8e6..1824a8b 100644
--- a/tests/fixtures/invalid/integer/leading-us-hex.stderr
+++ b/tests/fixtures/invalid/integer/leading-us-hex.stderr
@@ -1,6 +1,6 @@
 TOML parse error at line 1, column 18
   |
-1 | leading-us-hex = _0o1
+1 | leading-us-hex = _0x1
   |                  ^
 invalid integer
 expected leading digit
diff --git a/tests/fixtures/invalid/integer/negative-oct.stderr b/tests/fixtures/invalid/integer/negative-oct.stderr
index fcf3140..9e922c6 100644
--- a/tests/fixtures/invalid/integer/negative-oct.stderr
+++ b/tests/fixtures/invalid/integer/negative-oct.stderr
@@ -1,5 +1,5 @@
 TOML parse error at line 1, column 18
   |
-1 | negative-oct = -0o99
+1 | negative-oct = -0o755
   |                  ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/integer/positive-oct.stderr b/tests/fixtures/invalid/integer/positive-oct.stderr
index cc09466..30d2ab3 100644
--- a/tests/fixtures/invalid/integer/positive-oct.stderr
+++ b/tests/fixtures/invalid/integer/positive-oct.stderr
@@ -1,5 +1,5 @@
 TOML parse error at line 1, column 18
   |
-1 | positive-oct = +0o99
+1 | positive-oct = +0o755
   |                  ^
 expected newline, `#`
diff --git a/tests/fixtures/invalid/key/dotted-redefine-table-1.stderr b/tests/fixtures/invalid/key/dotted-redefine-table-1.stderr
new file mode 100644
index 0000000..f51a108
--- /dev/null
+++ b/tests/fixtures/invalid/key/dotted-redefine-table-1.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 1
+  |
+2 | a.b = true
+  | ^
+dotted key `a` attempted to extend non-table type (boolean)
diff --git a/tests/fixtures/invalid/key/dotted-redefine-table.stderr b/tests/fixtures/invalid/key/dotted-redefine-table-2.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/dotted-redefine-table.stderr
rename to tests/fixtures/invalid/key/dotted-redefine-table-2.stderr
diff --git a/tests/fixtures/invalid/key/duplicate-keys-1.stderr b/tests/fixtures/invalid/key/duplicate-keys-1.stderr
new file mode 100644
index 0000000..c58dd3c
--- /dev/null
+++ b/tests/fixtures/invalid/key/duplicate-keys-1.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 1
+  |
+2 | name = "Pradyun"
+  | ^
+duplicate key `name` in document root
diff --git a/tests/fixtures/invalid/key/duplicate-keys.stderr b/tests/fixtures/invalid/key/duplicate-keys-2.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/duplicate-keys.stderr
rename to tests/fixtures/invalid/key/duplicate-keys-2.stderr
diff --git a/tests/fixtures/invalid/key/duplicate-keys-3.stderr b/tests/fixtures/invalid/key/duplicate-keys-3.stderr
new file mode 100644
index 0000000..94a019f
--- /dev/null
+++ b/tests/fixtures/invalid/key/duplicate-keys-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 1
+  |
+2 | "spelling" = "favourite"
+  | ^
+duplicate key `spelling` in document root
diff --git a/tests/fixtures/invalid/key/duplicate-keys-4.stderr b/tests/fixtures/invalid/key/duplicate-keys-4.stderr
new file mode 100644
index 0000000..2bd02ab
--- /dev/null
+++ b/tests/fixtures/invalid/key/duplicate-keys-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 1
+  |
+2 | 'spelling' = "favourite"
+  | ^
+duplicate key `spelling` in document root
diff --git a/tests/fixtures/invalid/key/duplicate.stderr b/tests/fixtures/invalid/key/duplicate.stderr
deleted file mode 100644
index 8c7d7b7..0000000
--- a/tests/fixtures/invalid/key/duplicate.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-TOML parse error at line 3, column 1
-  |
-3 | name = "Pradyun"
-  | ^
-duplicate key `name` in document root
diff --git a/tests/fixtures/invalid/key/end-in-escape.stderr b/tests/fixtures/invalid/key/end-in-escape.stderr
new file mode 100644
index 0000000..5916980
--- /dev/null
+++ b/tests/fixtures/invalid/key/end-in-escape.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 30
+  |
+1 | "backslash is the last char\
+  |                              ^
+invalid escape sequence
+expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"`
diff --git a/tests/fixtures/invalid/key/newline.stderr b/tests/fixtures/invalid/key/newline-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/newline.stderr
rename to tests/fixtures/invalid/key/newline-1.stderr
diff --git a/tests/fixtures/invalid/key/newline-2.stderr b/tests/fixtures/invalid/key/newline-2.stderr
new file mode 100644
index 0000000..ac205e1
--- /dev/null
+++ b/tests/fixtures/invalid/key/newline-2.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | "quoted
+  |        ^
+invalid basic string
diff --git a/tests/fixtures/invalid/key/newline-3.stderr b/tests/fixtures/invalid/key/newline-3.stderr
new file mode 100644
index 0000000..ee7c519
--- /dev/null
+++ b/tests/fixtures/invalid/key/newline-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 8
+  |
+1 | 'quoted
+  |        ^
+invalid literal string
diff --git a/tests/fixtures/invalid/key/multiline.stderr b/tests/fixtures/invalid/key/newline-4.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/multiline.stderr
rename to tests/fixtures/invalid/key/newline-4.stderr
diff --git a/tests/fixtures/invalid/key/newline-5.stderr b/tests/fixtures/invalid/key/newline-5.stderr
new file mode 100644
index 0000000..18e3ab3
--- /dev/null
+++ b/tests/fixtures/invalid/key/newline-5.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 3
+  |
+1 | '''long
+  |   ^
+expected `.`, `=`
diff --git a/tests/fixtures/invalid/key/two-equals.stderr b/tests/fixtures/invalid/key/two-equals-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/two-equals.stderr
rename to tests/fixtures/invalid/key/two-equals-1.stderr
diff --git a/tests/fixtures/invalid/key/two-equals2.stderr b/tests/fixtures/invalid/key/two-equals-2.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/two-equals2.stderr
rename to tests/fixtures/invalid/key/two-equals-2.stderr
diff --git a/tests/fixtures/invalid/key/two-equals3.stderr b/tests/fixtures/invalid/key/two-equals-3.stderr
similarity index 100%
rename from tests/fixtures/invalid/key/two-equals3.stderr
rename to tests/fixtures/invalid/key/two-equals-3.stderr
diff --git a/tests/fixtures/invalid/key/without-value-5.stderr b/tests/fixtures/invalid/key/without-value-5.stderr
new file mode 100644
index 0000000..711eab1
--- /dev/null
+++ b/tests/fixtures/invalid/key/without-value-5.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 6
+  |
+1 | fs.fw
+  |      ^
+expected `.`, `=`
diff --git a/tests/fixtures/invalid/key/without-value-6.stderr b/tests/fixtures/invalid/key/without-value-6.stderr
new file mode 100644
index 0000000..c380d70
--- /dev/null
+++ b/tests/fixtures/invalid/key/without-value-6.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 8
+  |
+1 | fs.fw =
+  |        ^
+invalid string
+expected `"`, `'`
diff --git a/tests/fixtures/invalid/key/without-value-7.stderr b/tests/fixtures/invalid/key/without-value-7.stderr
new file mode 100644
index 0000000..8565837
--- /dev/null
+++ b/tests/fixtures/invalid/key/without-value-7.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 3
+  |
+1 | fs.
+  |   ^
+expected `.`, `=`
diff --git a/tests/fixtures/invalid/local-date/feb-29.stderr b/tests/fixtures/invalid/local-date/feb-29.stderr
new file mode 100644
index 0000000..b0ae76d
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/feb-29.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 29
+  |
+1 | "not a leap year" = 2100-02-29
+  |                             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/feb-30.stderr b/tests/fixtures/invalid/local-date/feb-30.stderr
new file mode 100644
index 0000000..7f64348
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/feb-30.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 44
+  |
+1 | "only 28 or 29 days in february" = 1988-02-30
+  |                                            ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/mday-over.stderr b/tests/fixtures/invalid/local-date/mday-over.stderr
new file mode 100644
index 0000000..f2d1876
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/mday-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 13
+  |
+3 | d = 2006-01-32
+  |             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/mday-under.stderr b/tests/fixtures/invalid/local-date/mday-under.stderr
new file mode 100644
index 0000000..fab958d
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/mday-under.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 13
+  |
+3 | d = 2006-01-00
+  |             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/month-over.stderr b/tests/fixtures/invalid/local-date/month-over.stderr
new file mode 100644
index 0000000..ec6d090
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/month-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 2006-13-01
+  |          ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/month-under.stderr b/tests/fixtures/invalid/local-date/month-under.stderr
new file mode 100644
index 0000000..3e8324a
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/month-under.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 2007-00-01
+  |          ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-date/no-leads-with-milli.stderr b/tests/fixtures/invalid/local-date/no-leads-with-milli.stderr
new file mode 100644
index 0000000..e0e3046
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/no-leads-with-milli.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 22
+  |
+2 | with-milli = 1987-07-5
+  |                      ^
+invalid date-time
diff --git a/tests/fixtures/invalid/local-date/no-leads.stderr b/tests/fixtures/invalid/local-date/no-leads.stderr
new file mode 100644
index 0000000..e7b5d69
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/no-leads.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 17
+  |
+2 | no-leads = 1987-7-05
+  |                 ^
+invalid date-time
diff --git a/tests/fixtures/invalid/datetime/trailing-t.stderr b/tests/fixtures/invalid/local-date/trailing-t.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime/trailing-t.stderr
rename to tests/fixtures/invalid/local-date/trailing-t.stderr
diff --git a/tests/fixtures/invalid/local-date/y10k.stderr b/tests/fixtures/invalid/local-date/y10k.stderr
new file mode 100644
index 0000000..244266e
--- /dev/null
+++ b/tests/fixtures/invalid/local-date/y10k.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 10000-01-01
+  |          ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-datetime/feb-29.stderr b/tests/fixtures/invalid/local-datetime/feb-29.stderr
new file mode 100644
index 0000000..8aac712
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/feb-29.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 29
+  |
+1 | "not a leap year" = 2100-02-29T15:15:15
+  |                             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/feb-30.stderr b/tests/fixtures/invalid/local-datetime/feb-30.stderr
new file mode 100644
index 0000000..6175c5e
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/feb-30.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 44
+  |
+1 | "only 28 or 29 days in february" = 1988-02-30T15:15:15
+  |                                            ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/hour-over.stderr b/tests/fixtures/invalid/local-datetime/hour-over.stderr
new file mode 100644
index 0000000..bc255ed
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/hour-over.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 15
+  |
+2 | d = 2006-01-01T24:00:00
+  |               ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-datetime/mday-over.stderr b/tests/fixtures/invalid/local-datetime/mday-over.stderr
new file mode 100644
index 0000000..0556e3a
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/mday-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 13
+  |
+3 | d = 2006-01-32T00:00:00
+  |             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/mday-under.stderr b/tests/fixtures/invalid/local-datetime/mday-under.stderr
new file mode 100644
index 0000000..036e963
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/mday-under.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 13
+  |
+3 | d = 2006-01-00T00:00:00
+  |             ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/minute-over.stderr b/tests/fixtures/invalid/local-datetime/minute-over.stderr
new file mode 100644
index 0000000..039fc6e
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/minute-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 19
+  |
+2 | d = 2006-01-01T00:60:00
+  |                   ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/month-over.stderr b/tests/fixtures/invalid/local-datetime/month-over.stderr
new file mode 100644
index 0000000..0d2dca2
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/month-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 2006-13-01T00:00:00
+  |          ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/month-under.stderr b/tests/fixtures/invalid/local-datetime/month-under.stderr
new file mode 100644
index 0000000..6ce30cb
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/month-under.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 2007-00-01T00:00:00
+  |          ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/no-leads-with-milli.stderr b/tests/fixtures/invalid/local-datetime/no-leads-with-milli.stderr
new file mode 100644
index 0000000..5ec75e9
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/no-leads-with-milli.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 22
+  |
+2 | with-milli = 1987-07-5T17:45:00.12
+  |                      ^
+invalid date-time
diff --git a/tests/fixtures/invalid/local-datetime/no-leads.stderr b/tests/fixtures/invalid/local-datetime/no-leads.stderr
new file mode 100644
index 0000000..7d2c879
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/no-leads.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 17
+  |
+2 | no-leads = 1987-7-05T17:45:00
+  |                 ^
+invalid date-time
diff --git a/tests/fixtures/invalid/local-datetime/no-secs.stderr b/tests/fixtures/invalid/local-datetime/no-secs.stderr
new file mode 100644
index 0000000..7585e1b
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/no-secs.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 27
+  |
+2 | no-secs = 1987-07-05T17:45
+  |                           ^
+invalid date-time
diff --git a/tests/fixtures/invalid/local-datetime/no-t.stderr b/tests/fixtures/invalid/local-datetime/no-t.stderr
new file mode 100644
index 0000000..0f520a2
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/no-t.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 18
+  |
+2 | no-t = 1987-07-0517:45:00
+  |                  ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-datetime/second-over.stderr b/tests/fixtures/invalid/local-datetime/second-over.stderr
new file mode 100644
index 0000000..63d0007
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/second-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 22
+  |
+3 | d = 2006-01-01T00:00:61
+  |                      ^
+invalid date-time
+value is out of range
diff --git a/tests/fixtures/invalid/local-datetime/time-no-leads.stderr b/tests/fixtures/invalid/local-datetime/time-no-leads.stderr
new file mode 100644
index 0000000..012f62f
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/time-no-leads.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 15
+  |
+2 | d = 2023-10-01T1:32:00Z
+  |               ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-datetime/y10k.stderr b/tests/fixtures/invalid/local-datetime/y10k.stderr
new file mode 100644
index 0000000..2d4015d
--- /dev/null
+++ b/tests/fixtures/invalid/local-datetime/y10k.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 10
+  |
+2 | d = 10000-01-01 00:00:00
+  |          ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-time/hour-over.stderr b/tests/fixtures/invalid/local-time/hour-over.stderr
new file mode 100644
index 0000000..1f669e3
--- /dev/null
+++ b/tests/fixtures/invalid/local-time/hour-over.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 7
+  |
+2 | d = 24:00:00
+  |       ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/local-time/minute-over.stderr b/tests/fixtures/invalid/local-time/minute-over.stderr
new file mode 100644
index 0000000..efb676f
--- /dev/null
+++ b/tests/fixtures/invalid/local-time/minute-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 8
+  |
+2 | d = 00:60:00
+  |        ^
+invalid time
+value is out of range
diff --git a/tests/fixtures/invalid/local-time/no-secs.stderr b/tests/fixtures/invalid/local-time/no-secs.stderr
new file mode 100644
index 0000000..2fa8ba3
--- /dev/null
+++ b/tests/fixtures/invalid/local-time/no-secs.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 16
+  |
+2 | no-secs = 17:45
+  |                ^
+invalid time
diff --git a/tests/fixtures/invalid/local-time/second-over.stderr b/tests/fixtures/invalid/local-time/second-over.stderr
new file mode 100644
index 0000000..a608f89
--- /dev/null
+++ b/tests/fixtures/invalid/local-time/second-over.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 11
+  |
+3 | d = 00:00:61
+  |           ^
+invalid time
+value is out of range
diff --git a/tests/fixtures/invalid/datetime/time-no-leads-2.stderr b/tests/fixtures/invalid/local-time/time-no-leads-2.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime/time-no-leads-2.stderr
rename to tests/fixtures/invalid/local-time/time-no-leads-2.stderr
diff --git a/tests/fixtures/invalid/local-time/time-no-leads.stderr b/tests/fixtures/invalid/local-time/time-no-leads.stderr
new file mode 100644
index 0000000..7a98902
--- /dev/null
+++ b/tests/fixtures/invalid/local-time/time-no-leads.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 6
+  |
+2 | d = 1:32:00
+  |      ^
+expected newline, `#`
diff --git a/tests/fixtures/invalid/string/bad-codepoint.stderr b/tests/fixtures/invalid/string/bad-codepoint.stderr
deleted file mode 100644
index 4061c79..0000000
--- a/tests/fixtures/invalid/string/bad-codepoint.stderr
+++ /dev/null
@@ -1,6 +0,0 @@
-TOML parse error at line 1, column 76
-  |
-1 | invalid-codepoint = "This string contains a non scalar unicode codepoint \uD801"
-  |                                                                            ^
-invalid unicode 4-digit hex code
-value is out of range
diff --git a/tests/fixtures/invalid/string/bad-escape-3.stderr b/tests/fixtures/invalid/string/bad-escape-3.stderr
new file mode 100644
index 0000000..ea10886
--- /dev/null
+++ b/tests/fixtures/invalid/string/bad-escape-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 16
+  |
+1 | backslash = "\"
+  |                ^
+invalid basic string
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-1.stderr b/tests/fixtures/invalid/string/bad-uni-esc-1.stderr
index a9e439b..71d8ed3 100644
--- a/tests/fixtures/invalid/string/bad-uni-esc-1.stderr
+++ b/tests/fixtures/invalid/string/bad-uni-esc-1.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 13
+TOML parse error at line 1, column 23
   |
-1 | str = "val\ue"
-  |             ^
+1 | bad-uni-esc-1 = "val\ue"
+  |                       ^
 invalid unicode 4-digit hex code
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-2.stderr b/tests/fixtures/invalid/string/bad-uni-esc-2.stderr
index 87c8681..06138c1 100644
--- a/tests/fixtures/invalid/string/bad-uni-esc-2.stderr
+++ b/tests/fixtures/invalid/string/bad-uni-esc-2.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 13
+TOML parse error at line 1, column 23
   |
-1 | str = "val\Ux"
-  |             ^
+1 | bad-uni-esc-2 = "val\Ux"
+  |                       ^
 invalid unicode 8-digit hex code
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-3.stderr b/tests/fixtures/invalid/string/bad-uni-esc-3.stderr
index 61f8ded..c82355c 100644
--- a/tests/fixtures/invalid/string/bad-uni-esc-3.stderr
+++ b/tests/fixtures/invalid/string/bad-uni-esc-3.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 13
+TOML parse error at line 1, column 23
   |
-1 | str = "val\U0000000"
-  |             ^
+1 | bad-uni-esc-3 = "val\U0000000"
+  |                       ^
 invalid unicode 8-digit hex code
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-4.stderr b/tests/fixtures/invalid/string/bad-uni-esc-4.stderr
index 1a781d9..71fb48c 100644
--- a/tests/fixtures/invalid/string/bad-uni-esc-4.stderr
+++ b/tests/fixtures/invalid/string/bad-uni-esc-4.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 13
+TOML parse error at line 1, column 23
   |
-1 | str = "val\U0000"
-  |             ^
+1 | bad-uni-esc-4 = "val\U0000"
+  |                       ^
 invalid unicode 8-digit hex code
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-5.stderr b/tests/fixtures/invalid/string/bad-uni-esc-5.stderr
index 88773ca..d79798f 100644
--- a/tests/fixtures/invalid/string/bad-uni-esc-5.stderr
+++ b/tests/fixtures/invalid/string/bad-uni-esc-5.stderr
@@ -1,5 +1,5 @@
-TOML parse error at line 1, column 13
+TOML parse error at line 1, column 23
   |
-1 | str = "val\Ugggggggg"
-  |             ^
+1 | bad-uni-esc-5 = "val\Ugggggggg"
+  |                       ^
 invalid unicode 8-digit hex code
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-6.stderr b/tests/fixtures/invalid/string/bad-uni-esc-6.stderr
new file mode 100644
index 0000000..7eb5ff0
--- /dev/null
+++ b/tests/fixtures/invalid/string/bad-uni-esc-6.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 72
+  |
+1 | bad-uni-esc-6 = "This string contains a non scalar unicode codepoint \uD801"
+  |                                                                        ^
+invalid unicode 4-digit hex code
+value is out of range
diff --git a/tests/fixtures/invalid/string/bad-uni-esc-7.stderr b/tests/fixtures/invalid/string/bad-uni-esc-7.stderr
new file mode 100644
index 0000000..c53ef12
--- /dev/null
+++ b/tests/fixtures/invalid/string/bad-uni-esc-7.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 20
+  |
+1 | bad-uni-esc-7 = "\uabag"
+  |                    ^
+invalid unicode 4-digit hex code
diff --git a/tests/fixtures/invalid/string/multiline-bad-escape-4.stderr b/tests/fixtures/invalid/string/multiline-bad-escape-4.stderr
new file mode 100644
index 0000000..d662a4d
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-bad-escape-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 21
+  |
+1 | backslash = """\"""
+  |                     ^
+invalid multiline basic string
diff --git a/tests/fixtures/invalid/string/multiline-escape-space.stderr b/tests/fixtures/invalid/string/multiline-escape-space-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/string/multiline-escape-space.stderr
rename to tests/fixtures/invalid/string/multiline-escape-space-1.stderr
diff --git a/tests/fixtures/invalid/string/multiline-escape-space-2.stderr b/tests/fixtures/invalid/string/multiline-escape-space-2.stderr
new file mode 100644
index 0000000..b5ed276
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-escape-space-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 4, column 7
+  |
+4 | gee \   """
+  |       ^
+invalid escape sequence
+expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"`
diff --git a/tests/fixtures/invalid/string/multiline-lit-no-close-1.stderr b/tests/fixtures/invalid/string/multiline-lit-no-close-1.stderr
new file mode 100644
index 0000000..1731932
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-lit-no-close-1.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 2, column 20
+  |
+2 |     this will fail
+  |                    ^
+invalid multiline literal string
diff --git a/tests/fixtures/invalid/string/multiline-lit-no-close-2.stderr b/tests/fixtures/invalid/string/multiline-lit-no-close-2.stderr
new file mode 100644
index 0000000..347c6de
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-lit-no-close-2.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 7
+  |
+1 | x='''
+  |       ^
+invalid multiline literal string
diff --git a/tests/fixtures/invalid/string/multiline-lit-no-close-3.stderr b/tests/fixtures/invalid/string/multiline-lit-no-close-3.stderr
new file mode 100644
index 0000000..b6d5ec5
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-lit-no-close-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 4, column 7
+  |
+4 | eteta
+  |       ^
+invalid multiline literal string
diff --git a/tests/fixtures/invalid/string/multiline-lit-no-close-4.stderr b/tests/fixtures/invalid/string/multiline-lit-no-close-4.stderr
new file mode 100644
index 0000000..5dea96d
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-lit-no-close-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 3, column 8
+  |
+3 | gee ''
+  |        ^
+invalid multiline literal string
diff --git a/tests/fixtures/invalid/string/multiline-no-close.stderr b/tests/fixtures/invalid/string/multiline-no-close-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/string/multiline-no-close.stderr
rename to tests/fixtures/invalid/string/multiline-no-close-1.stderr
diff --git a/tests/fixtures/invalid/string/multiline-no-close-3.stderr b/tests/fixtures/invalid/string/multiline-no-close-3.stderr
new file mode 100644
index 0000000..eff9fe4
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-no-close-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 4, column 7
+  |
+4 | eteta
+  |       ^
+invalid multiline basic string
diff --git a/tests/fixtures/invalid/string/multiline-no-close-4.stderr b/tests/fixtures/invalid/string/multiline-no-close-4.stderr
new file mode 100644
index 0000000..e19582f
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-no-close-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 3, column 8
+  |
+3 | gee ""
+  |        ^
+invalid multiline basic string
diff --git a/tests/fixtures/invalid/string/multiline-no-close-5.stderr b/tests/fixtures/invalid/string/multiline-no-close-5.stderr
new file mode 100644
index 0000000..00c1735
--- /dev/null
+++ b/tests/fixtures/invalid/string/multiline-no-close-5.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 3, column 8
+  |
+3 | gee\	 
+  |        ^
+invalid multiline basic string
diff --git a/tests/fixtures/invalid/string/no-close.stderr b/tests/fixtures/invalid/string/no-close-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/string/no-close.stderr
rename to tests/fixtures/invalid/string/no-close-1.stderr
diff --git a/tests/fixtures/invalid/string/no-close-2.stderr b/tests/fixtures/invalid/string/no-close-2.stderr
new file mode 100644
index 0000000..a80fe60
--- /dev/null
+++ b/tests/fixtures/invalid/string/no-close-2.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 29
+  |
+1 | "a-string".must-be = "closed
+  |                             ^
+invalid basic string
diff --git a/tests/fixtures/invalid/string/no-close-3.stderr b/tests/fixtures/invalid/string/no-close-3.stderr
new file mode 100644
index 0000000..029b40a
--- /dev/null
+++ b/tests/fixtures/invalid/string/no-close-3.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 42
+  |
+1 | no-ending-quote = 'One time, at band camp
+  |                                          ^
+invalid literal string
diff --git a/tests/fixtures/invalid/string/no-close-4.stderr b/tests/fixtures/invalid/string/no-close-4.stderr
new file mode 100644
index 0000000..9e47ef7
--- /dev/null
+++ b/tests/fixtures/invalid/string/no-close-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 29
+  |
+1 | 'a-string'.must-be = 'closed
+  |                             ^
+invalid literal string
diff --git a/tests/fixtures/invalid/string/bad-hex-esc.stderr b/tests/fixtures/invalid/string/string.stderr
similarity index 100%
rename from tests/fixtures/invalid/string/bad-hex-esc.stderr
rename to tests/fixtures/invalid/string/string.stderr
diff --git a/tests/fixtures/invalid/table/append-to-array-with-dotted-keys.stderr b/tests/fixtures/invalid/table/append-to-array-with-dotted-keys.stderr
new file mode 100644
index 0000000..0fb4b2e
--- /dev/null
+++ b/tests/fixtures/invalid/table/append-to-array-with-dotted-keys.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 4, column 1
+  |
+4 | b.y = 2
+  | ^
+duplicate key `y`
diff --git a/tests/fixtures/invalid/table/array-missing-bracket.stderr b/tests/fixtures/invalid/table/array-no-close-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/table/array-missing-bracket.stderr
rename to tests/fixtures/invalid/table/array-no-close-1.stderr
diff --git a/tests/fixtures/invalid/table/array-no-close-2.stderr b/tests/fixtures/invalid/table/array-no-close-2.stderr
new file mode 100644
index 0000000..a723c14
--- /dev/null
+++ b/tests/fixtures/invalid/table/array-no-close-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 26
+  |
+1 | [[closing-bracket.missing]
+  |                          ^
+invalid table header
+expected `.`, `]]`
diff --git a/tests/fixtures/invalid/table/duplicate-key-dotted-array.stderr b/tests/fixtures/invalid/table/duplicate-key-dotted-array.stderr
new file mode 100644
index 0000000..0cf32ab
--- /dev/null
+++ b/tests/fixtures/invalid/table/duplicate-key-dotted-array.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 4, column 1
+  |
+4 | [[fruit.apple]]
+  | ^
+invalid table header
+duplicate key `apple` in table `fruit`
diff --git a/tests/fixtures/invalid/table/no-close-1.stderr b/tests/fixtures/invalid/table/no-close-1.stderr
new file mode 100644
index 0000000..2e184fa
--- /dev/null
+++ b/tests/fixtures/invalid/table/no-close-1.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 8
+  |
+1 | [where will it end
+  |        ^
+invalid table header
+expected `.`, `]`
diff --git a/tests/fixtures/invalid/table/no-close-2.stderr b/tests/fixtures/invalid/table/no-close-2.stderr
new file mode 100644
index 0000000..a7adf38
--- /dev/null
+++ b/tests/fixtures/invalid/table/no-close-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 25
+  |
+1 | [closing-bracket.missingö
+  |                         ^
+invalid table header
+expected `.`, `]`
diff --git a/tests/fixtures/invalid/table/quoted-no-close.stderr b/tests/fixtures/invalid/table/no-close-3.stderr
similarity index 100%
rename from tests/fixtures/invalid/table/quoted-no-close.stderr
rename to tests/fixtures/invalid/table/no-close-3.stderr
diff --git a/tests/fixtures/invalid/table/no-close-4.stderr b/tests/fixtures/invalid/table/no-close-4.stderr
new file mode 100644
index 0000000..022f1fa
--- /dev/null
+++ b/tests/fixtures/invalid/table/no-close-4.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 1, column 2
+  |
+1 | [
+  |  ^
+invalid key
diff --git a/tests/fixtures/invalid/table/no-close-5.stderr b/tests/fixtures/invalid/table/no-close-5.stderr
new file mode 100644
index 0000000..38c7bee
--- /dev/null
+++ b/tests/fixtures/invalid/table/no-close-5.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 1, column 11
+  |
+1 | [fwfw.wafw
+  |           ^
+invalid table header
+expected `.`, `]`
diff --git a/tests/fixtures/invalid/table/overwrite-array-in-parent.stderr b/tests/fixtures/invalid/table/overwrite-array-in-parent.stderr
new file mode 100644
index 0000000..8a774ff
--- /dev/null
+++ b/tests/fixtures/invalid/table/overwrite-array-in-parent.stderr
@@ -0,0 +1,5 @@
+TOML parse error at line 4, column 1
+  |
+4 | arr = 2
+  | ^
+duplicate key `arr` in table `parent-table`
diff --git a/tests/fixtures/invalid/table/overwrite-bool-with-array.stderr b/tests/fixtures/invalid/table/overwrite-bool-with-array.stderr
new file mode 100644
index 0000000..f52c773
--- /dev/null
+++ b/tests/fixtures/invalid/table/overwrite-bool-with-array.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 1
+  |
+2 | [[a]]
+  | ^
+invalid table header
+duplicate key `a` in document root
diff --git a/tests/fixtures/invalid/table/overwrite-with-deep-table.stderr b/tests/fixtures/invalid/table/overwrite-with-deep-table.stderr
new file mode 100644
index 0000000..48773b6
--- /dev/null
+++ b/tests/fixtures/invalid/table/overwrite-with-deep-table.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 2, column 1
+  |
+2 | [a.b.c.d]
+  | ^
+invalid table header
+dotted key `a` attempted to extend non-table type (integer)
diff --git a/tests/fixtures/invalid/table/redefine.stderr b/tests/fixtures/invalid/table/redefine-1.stderr
similarity index 100%
rename from tests/fixtures/invalid/table/redefine.stderr
rename to tests/fixtures/invalid/table/redefine-1.stderr
diff --git a/tests/fixtures/invalid/table/redefine-2.stderr b/tests/fixtures/invalid/table/redefine-2.stderr
new file mode 100644
index 0000000..43bbb56
--- /dev/null
+++ b/tests/fixtures/invalid/table/redefine-2.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 1
+  |
+3 | [t1.t2]
+  | ^
+invalid table header
+duplicate key `t2` in table `t1`
diff --git a/tests/fixtures/invalid/table/redefine-3.stderr b/tests/fixtures/invalid/table/redefine-3.stderr
new file mode 100644
index 0000000..63bfc24
--- /dev/null
+++ b/tests/fixtures/invalid/table/redefine-3.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 1
+  |
+3 | [t1.t2.t3]
+  | ^
+invalid table header
+duplicate key `t3` in table `t1.t2`
diff --git a/tests/fixtures/invalid/table/super-twice.stderr b/tests/fixtures/invalid/table/super-twice.stderr
new file mode 100644
index 0000000..b36517a
--- /dev/null
+++ b/tests/fixtures/invalid/table/super-twice.stderr
@@ -0,0 +1,6 @@
+TOML parse error at line 3, column 1
+  |
+3 | [a]
+  | ^
+invalid table header
+duplicate key `a` in document root
diff --git a/tests/testsuite/edit.rs b/tests/testsuite/edit.rs
index 28f73c1..94c20d4 100644
--- a/tests/testsuite/edit.rs
+++ b/tests/testsuite/edit.rs
@@ -853,3 +853,10 @@
 "#,
         );
 }
+
+#[test]
+fn sorting_with_references() {
+    let values = vec!["foo", "qux", "bar"];
+    let mut array = toml_edit::Array::from_iter(values);
+    array.sort_by(|lhs, rhs| lhs.as_str().cmp(&rhs.as_str()));
+}
diff --git a/tests/testsuite/float.rs b/tests/testsuite/float.rs
new file mode 100644
index 0000000..34e792e
--- /dev/null
+++ b/tests/testsuite/float.rs
@@ -0,0 +1,60 @@
+use toml_edit::Document;
+
+macro_rules! float_inf_tests {
+    ($ty:ty) => {{
+        let document = r"
+            # infinity
+            sf1 = inf  # positive infinity
+            sf2 = +inf # positive infinity
+            sf3 = -inf # negative infinity
+
+            # not a number
+            sf4 = nan  # actual sNaN/qNaN encoding is implementation specific
+            sf5 = +nan # same as `nan`
+            sf6 = -nan # valid, actual encoding is implementation specific
+
+            # zero
+            sf7 = +0.0
+            sf8 = -0.0
+        ";
+
+        let document = document.parse::<Document>().unwrap();
+        let float = |k| document[k].as_float().unwrap();
+
+        assert!(float("sf1").is_infinite());
+        assert!(float("sf1").is_sign_positive());
+        assert!(float("sf2").is_infinite());
+        assert!(float("sf2").is_sign_positive());
+        assert!(float("sf3").is_infinite());
+        assert!(float("sf3").is_sign_negative());
+
+        assert!(float("sf4").is_nan());
+        assert!(float("sf4").is_sign_positive());
+        assert!(float("sf5").is_nan());
+        assert!(float("sf5").is_sign_positive());
+        assert!(float("sf6").is_nan());
+        assert!(float("sf6").is_sign_negative());
+
+        assert_eq!(float("sf7"), 0.0);
+        assert!(float("sf7").is_sign_positive());
+        assert_eq!(float("sf8"), 0.0);
+        assert!(float("sf8").is_sign_negative());
+
+        let mut document = Document::new();
+        document["sf4"] = toml_edit::value(f64::NAN.copysign(1.0));
+        document["sf6"] = toml_edit::value(f64::NAN.copysign(-1.0));
+        assert_eq!(
+            document.to_string(),
+            "\
+sf4 = nan
+sf6 = -nan
+"
+        );
+    }};
+}
+
+#[test]
+fn test_float() {
+    float_inf_tests!(f32);
+    float_inf_tests!(f64);
+}
diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs
index 1476c5d..0592f3c 100644
--- a/tests/testsuite/main.rs
+++ b/tests/testsuite/main.rs
@@ -3,6 +3,7 @@
 mod convert;
 mod datetime;
 mod edit;
+mod float;
 mod invalid;
 mod parse;
 mod stackoverflow;
diff --git a/tests/testsuite/parse.rs b/tests/testsuite/parse.rs
index f1c3c27..f22b61d 100644
--- a/tests/testsuite/parse.rs
+++ b/tests/testsuite/parse.rs
@@ -56,11 +56,11 @@
 """"#
     )
     .is_str());
-    assert!(parse_value!(r#"'literal string\ \'"#).is_str());
+    assert!(parse_value!(r"'literal string\ \'").is_str());
     assert!(parse_value!(
-        r#"'''multiline
+        r"'''multiline
 literal \ \
-string'''"#
+string'''"
     )
     .is_str());
     assert!(parse_value!(r#"{ hello = "world", a = 1}"#).is_inline_table());
@@ -1488,3 +1488,23 @@
 
     assert_eq!(doc.to_string(), "aaaaaa = 1\nbbb = 2\n");
 }
+
+#[test]
+fn dotted_key_comment_roundtrip() {
+    let input = r###"
+rust.unsafe_op_in_unsafe_fn = "deny"
+
+rust.explicit_outlives_requirements = "warn"
+# rust.unused_crate_dependencies = "warn"
+
+clippy.cast_lossless = "warn"
+clippy.doc_markdown = "warn"
+clippy.exhaustive_enums = "warn"
+"###;
+    let expected = input;
+
+    let manifest: toml_edit::Document = input.parse().unwrap();
+    let actual = manifest.to_string();
+
+    assert_eq(expected, actual);
+}