fix(parser): reset dotted-key and array records per array-of-tables element

Assisted-by: GLM 5.3
This commit is contained in:
2026-09-17 23:05:06 +02:00
parent 696f117c22
commit 830f840f44
3 changed files with 43 additions and 10 deletions
+4
View File
@@ -46,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
check covered `[table]` headers and dotted keys but not the intermediate check covered `[table]` headers and dotted keys but not the intermediate
steps of an array-of-tables header, so such a document silently extended the steps of an array-of-tables header, so such a document silently extended the
inline table. inline table.
- A new element of an array of tables starts a fresh scope for dotted-key paths
and nested arrays of tables: `[[a]]`, `b.c = 1`, `[[a]]`, `[a.b]` parses, as
the TOML examples in the spec shape it. The records of the previous element
falsely rejected the same paths in the next one.
- A float with an exponent marker but no digits (`1e`, `0.0E`) is rejected; - A float with an exponent marker but no digits (`1e`, `0.0E`) is rejected;
the exponent requires at least one digit. the exponent requires at least one digit.
- A date-time offset outside 00:00 through 23:59 is rejected; such offsets - A date-time offset outside 00:00 through 23:59 is rejected; such offsets
+30
View File
@@ -498,6 +498,36 @@ func TestRejectsInlineTableExtension(t *testing.T) {
} }
} }
// A new element of an array of tables starts a fresh scope: sub-table headers,
// nested arrays of tables, and dotted-key paths recorded for the previous
// element must not block the same paths in the next one.
func TestArrayOfTablesFreshScopePerElement(t *testing.T) {
cases := map[string]string{
"nested array of tables": "[[a]]\n[[a.b]]\nx = 1\n[[a]]\n[a.b]\ny = 2\n",
"dotted key": "[[a]]\nb.c = 1\n[[a]]\n[a.b]\nd = 2\n",
}
for name, doc := range cases {
tree, err := Parse([]byte(doc))
if err != nil {
t.Errorf("%s: %v", name, err)
continue
}
elements := tree["a"].([]map[string]any)
if len(elements) != 2 {
t.Errorf("%s: len(a) = %d, want 2", name, len(elements))
}
}
// Within one element the redefinition rules keep applying.
for name, doc := range map[string]string{
"header over dotted in one element": "[[a]]\nb.c = 1\n[a.b]\nd = 2\n",
"table over nested array": "[[a]]\n[[a.b]]\n[a.b]\nx = 1\n",
} {
if _, err := Parse([]byte(doc)); err == nil {
t.Errorf("%s: expected an error, got none", name)
}
}
}
func TestRejectsSpecInvalid(t *testing.T) { func TestRejectsSpecInvalid(t *testing.T) {
cases := map[string]string{ cases := map[string]string{
"single-digit hour": "a = 2023-10-01T1:32:00Z\n", "single-digit hour": "a = 2023-10-01T1:32:00Z\n",
+9 -10
View File
@@ -274,18 +274,17 @@ func (p *parser) freezeInline(path []string, val any) {
} }
} }
// resetScopeUnder forgets the header and freeze records nested under key, which // resetScopeUnder forgets the definition records nested under key, which
// belong to the previous element of an array of tables. // belong to the previous element of an array of tables: headers, frozen
// inline tables, dotted-key paths, and nested arrays of tables all start
// fresh in the new element.
func (p *parser) resetScopeUnder(key []string) { func (p *parser) resetScopeUnder(key []string) {
prefix := pathKey(key) + "\x00" prefix := pathKey(key) + "\x00"
for k := range p.headers { for _, m := range []map[string]bool{p.headers, p.frozen, p.dotted, p.arrays} {
if strings.HasPrefix(k, prefix) { for k := range m {
delete(p.headers, k) if strings.HasPrefix(k, prefix) {
} delete(m, k)
} }
for k := range p.frozen {
if strings.HasPrefix(k, prefix) {
delete(p.frozen, k)
} }
} }
} }