Repository: toml-rs/toml
Pull request #617 (2023-10-03 00:13:01)

fix(parser): Failed on mixed inline tables


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 1ea0930c..f82556f1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -893,6 +893,7 @@ dependencies = [
  "serde_json",
  "serde_spanned",
  "snapbox",
+ "toml-test-data",
  "toml-test-harness",
  "toml_datetime",
  "toml_edit",
@@ -900,9 +901,9 @@ dependencies = [
 
 [[package]]
 name = "toml-test"
-version = "0.3.4"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37351256790aa1dbd6d60f4ff08e55e7f372e292f3e9040d6e077463d9a779c3"
+checksum = "a47dfa19ca3d6591b3151156884e0345e646307299a111391c54b0a7bd33bb04"
 dependencies = [
  "chrono",
  "serde",
@@ -911,18 +912,18 @@ dependencies = [
 
 [[package]]
 name = "toml-test-data"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93f351b6d6005ee802b0d4a53ca1cdf05636f441df4d299e62cba57f1da52646"
+checksum = "4340f0f6c69efad9058211393c1a2d959f48f01c291ae6b21ffdf14a7eeba503"
 dependencies = [
  "include_dir",
 ]
 
 [[package]]
 name = "toml-test-harness"
-version = "0.4.3"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e00fda5710922fe6b3005bf6a5050c303d6f9625249c37b7386e8818f4af675"
+checksum = "7346ccc45965d33ad1a15e558bc67f8c7a35a72d882f646c342fea9227390296"
 dependencies = [
  "ignore",
  "libtest-mimic",
diff --git a/crates/toml/Cargo.toml b/crates/toml/Cargo.toml
index 42dce811..1568e4ee 100644
--- a/crates/toml/Cargo.toml
+++ b/crates/toml/Cargo.toml
@@ -49,7 +49,8 @@ serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["ser
 [dev-dependencies]
 serde = { version = "1.0.160", features = ["derive"] }
 serde_json = "1.0.96"
-toml-test-harness = "0.4.3"
+toml-test-harness = "0.4.5"
+toml-test-data = "1.3.1"
 snapbox = "0.4.11"
 
 [[test]]
diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml
index 992a7f67..03107fdd 100644
--- a/crates/toml_edit/Cargo.toml
+++ b/crates/toml_edit/Cargo.toml
@@ -46,8 +46,8 @@ serde_spanned = { version = "0.6.3", path = "../serde_spanned", features = ["ser
 
 [dev-dependencies]
 serde_json = "1.0.96"
-toml-test-harness = "0.4.3"
-toml-test-data = "1.3.0"
+toml-test-harness = "0.4.5"
+toml-test-data = "1.3.1"
 libtest-mimic = "0.6.0"
 snapbox = { version = "0.4.11", features = ["harness"] }
 
diff --git a/crates/toml_edit/src/inline_table.rs b/crates/toml_edit/src/inline_table.rs
index 3dc6c0c0..6009267e 100644
--- a/crates/toml_edit/src/inline_table.rs
+++ b/crates/toml_edit/src/inline_table.rs
@@ -11,6 +11,8 @@ use crate::{InternalString, Item, KeyMut, RawString, Table, Value};
 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>>,
@@ -133,6 +135,32 @@ impl InlineTable {
         }
     }
 
+    /// 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.
+    ///
+    /// ```
+    /// 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;
diff --git a/crates/toml_edit/src/parser/inline_table.rs b/crates/toml_edit/src/parser/inline_table.rs
index 994e0033..f7cf2e9c 100644
--- a/crates/toml_edit/src/parser/inline_table.rs
+++ b/crates/toml_edit/src/parser/inline_table.rs
@@ -44,6 +44,16 @@ fn table_from_pairs(
 
     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 @@ fn descend_path<'a>(
     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 => {


Test patch:
diff --git a/crates/toml/tests/decoder_compliance.rs b/crates/toml/tests/decoder_compliance.rs
index 5d4fc2a4..4e9e5b4c 100644
--- a/crates/toml/tests/decoder_compliance.rs
+++ b/crates/toml/tests/decoder_compliance.rs
@@ -4,16 +4,8 @@ mod decoder;
 fn main() {
     let decoder = decoder::Decoder;
     let mut harness = toml_test_harness::DecoderHarness::new(decoder);
-    harness
-        .ignore([
-            "valid/spec/float-0.toml",
-            // Unreleased
-            "valid/string/escape-esc.toml",
-            "valid/string/hex-escape.toml",
-            "valid/datetime/no-seconds.toml",
-            "valid/inline-table/newline.toml",
-        ])
-        .unwrap();
+    harness.ignore(["valid/spec/float-0.toml"]).unwrap();
+    harness.version("1.0.0");
     harness.test();
 }
 
diff --git a/crates/toml/tests/encoder_compliance.rs b/crates/toml/tests/encoder_compliance.rs
index 38072483..8702b3af 100644
--- a/crates/toml/tests/encoder_compliance.rs
+++ b/crates/toml/tests/encoder_compliance.rs
@@ -7,6 +7,7 @@ fn main() {
     let decoder = decoder::Decoder;
     let mut harness = toml_test_harness::EncoderHarness::new(encoder, decoder);
     harness.ignore(["valid/spec/float-0.toml"]).unwrap();
+    harness.version("1.0.0");
     harness.test();
 }
 
diff --git a/crates/toml_edit/tests/decoder_compliance.rs b/crates/toml_edit/tests/decoder_compliance.rs
index 0f0b3501..cab69a48 100644
--- a/crates/toml_edit/tests/decoder_compliance.rs
+++ b/crates/toml_edit/tests/decoder_compliance.rs
@@ -3,15 +3,7 @@ mod decoder;
 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.ignore(["valid/spec/float-0.toml"]).unwrap();
+    harness.version("1.0.0");
     harness.test();
 }
diff --git a/crates/toml_edit/tests/fixtures/invalid/encoding/bad-codepoint.stderr b/crates/toml_edit/tests/fixtures/invalid/encoding/bad-codepoint.stderr
new file mode 100644
index 00000000..89ef2c79
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/encoding/bad-codepoint.stderr
@@ -0,0 +1,1 @@
+invalid utf-8 sequence of 1 bytes from index 29
\ No newline at end of file
diff --git a/crates/toml_edit/tests/fixtures/invalid/float/exp-trailing-us.stderr b/crates/toml_edit/tests/fixtures/invalid/float/exp-trailing-us.stderr
index 9a281849..b7e2ad9c 100644
--- a/crates/toml_edit/tests/fixtures/invalid/float/exp-trailing-us.stderr
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/float/inf-capital.stderr b/crates/toml_edit/tests/fixtures/invalid/float/inf-capital.stderr
new file mode 100644
index 00000000..ca7a1b1b
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/float/nan-capital.stderr b/crates/toml_edit/tests/fixtures/invalid/float/nan-capital.stderr
new file mode 100644
index 00000000..ae3b4d95
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp-1.stderr b/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp-1.stderr
new file mode 100644
index 00000000..cf0fabc7
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp-2.stderr b/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp-2.stderr
new file mode 100644
index 00000000..83ff1208
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp.stderr b/crates/toml_edit/tests/fixtures/invalid/float/trailing-us-exp.stderr
deleted file mode 100644
index 811f951e..00000000
--- a/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/inline-table/bad-key-syntax.stderr b/crates/toml_edit/tests/fixtures/invalid/inline-table/bad-key-syntax.stderr
new file mode 100644
index 00000000..c8ce45a6
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/inline-table/dotted-key-conflict.stderr b/crates/toml_edit/tests/fixtures/invalid/inline-table/dotted-key-conflict.stderr
new file mode 100644
index 00000000..7b860448
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/inline-table/dotted-key-conflict.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/crates/toml_edit/tests/fixtures/invalid/inline-table/nested_key_conflict.stderr b/crates/toml_edit/tests/fixtures/invalid/inline-table/nested_key_conflict.stderr
new file mode 100644
index 00000000..79a80410
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/inline-table/nested_key_conflict.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/crates/toml_edit/tests/fixtures/invalid/table/append-to-array-with-dotted-keys.stderr b/crates/toml_edit/tests/fixtures/invalid/table/append-to-array-with-dotted-keys.stderr
new file mode 100644
index 00000000..0fb4b2eb
--- /dev/null
+++ b/crates/toml_edit/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/crates/toml_edit/tests/fixtures/invalid/table/duplicate-key-dotted-array.stderr b/crates/toml_edit/tests/fixtures/invalid/table/duplicate-key-dotted-array.stderr
new file mode 100644
index 00000000..0cf32abe
--- /dev/null
+++ b/crates/toml_edit/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`