Skip to content

Commit 10b0a94

Browse files
committed
encoding/toml: fix decoding subtable duplicate keys
Previously, subtables with duplicate keys fail to be decoded even though they represent valid TOML. Instead we should track seenTableKeys by the array index to ensure duplication is local to the given object Fixes #3609 Signed-off-by: Lucas Charles <[email protected]>
1 parent dffc5ce commit 10b0a94

File tree

2 files changed

+56
-7
lines changed

2 files changed

+56
-7
lines changed

encoding/toml/decode.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,28 @@ func (d *Decoder) nextRootNode(tnode *toml.Node) error {
202202
case toml.Table:
203203
// Tables always begin a new line.
204204
key, keyElems := d.decodeKey("", tnode.Key())
205+
206+
// Check if this table is a subtable of an existing array element
207+
array := d.findArrayPrefix(key)
208+
var actualKey string
209+
if array != nil { // [last_array.new_table]
210+
if array.rkey == key {
211+
return d.nodeErrf(tnode.Child(), "cannot redeclare table array %q as a table", key)
212+
}
213+
// For subtables within array elements, we need to use the current array element's key
214+
// to avoid false duplicate key errors between different array elements
215+
arrayElementKey := array.rkey + "." + strconv.Itoa(len(array.list.Elts)-1)
216+
subKey := key[len(array.rkey)+1:] // Remove the array prefix and dot
217+
actualKey = arrayElementKey + "." + subKey
218+
} else {
219+
actualKey = key
220+
}
221+
205222
// All table keys must be unique, including for the top-level table.
206-
if d.seenTableKeys[key] {
223+
if d.seenTableKeys[actualKey] {
207224
return d.nodeErrf(tnode.Child(), "duplicate key: %s", key)
208225
}
209-
d.seenTableKeys[key] = true
226+
d.seenTableKeys[actualKey] = true
210227

211228
// We want a multi-line struct with curly braces,
212229
// just like TOML's tables are on multiple lines.
@@ -215,11 +232,7 @@ func (d *Decoder) nextRootNode(tnode *toml.Node) error {
215232
Lbrace: token.NoPos.WithRel(token.Blank),
216233
Rbrace: token.NoPos.WithRel(token.Newline),
217234
}
218-
array := d.findArrayPrefix(key)
219235
if array != nil { // [last_array.new_table]
220-
if array.rkey == key {
221-
return d.nodeErrf(tnode.Child(), "cannot redeclare table array %q as a table", key)
222-
}
223236
subKeyElems := keyElems[array.level:]
224237
topField, leafField := d.inlineFields(subKeyElems, token.Newline)
225238
array.lastTable.Elts = append(array.lastTable.Elts, topField)
@@ -229,7 +242,7 @@ func (d *Decoder) nextRootNode(tnode *toml.Node) error {
229242
d.topFile.Elts = append(d.topFile.Elts, topField)
230243
leafField.Value = d.currentTable
231244
}
232-
d.currentTableKey = key
245+
d.currentTableKey = actualKey
233246

234247
case toml.ArrayTable:
235248
// Table array elements always begin a new line.

encoding/toml/decode_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,42 @@ line two.\
643643
},
644644
]
645645
`,
646+
}, {
647+
name: "ArrayTablesSubtableDuplicateKey",
648+
input: `
649+
[[foo]]
650+
[foo.subtable]
651+
name = "bar"
652+
[[foo]]
653+
[foo.subtable]
654+
name = "bar"
655+
`,
656+
wantCUE: `
657+
foo: [
658+
{
659+
subtable: {
660+
name: "bar"
661+
}
662+
},
663+
{
664+
subtable: {
665+
name: "bar"
666+
}
667+
}
668+
]
669+
`,
670+
}, {
671+
name: "ArrayTablesSubtableActualDuplicate",
672+
input: `
673+
[[foo]]
674+
[foo.subtable]
675+
name = "bar"
676+
[foo.subtable]
677+
`,
678+
wantErr: `
679+
duplicate key: foo.subtable:
680+
test.toml:4:2
681+
`,
646682
}, {
647683
name: "ArrayTablesNested",
648684
input: `

0 commit comments

Comments
 (0)