Skip to content

Commit 2da898c

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 2da898c

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+
subKey := key[len(array.rkey)+1:] // Remove the array prefix and dot
216+
actualKey = fmt.Sprintf("%s.%d.%s", array.rkey, len(array.list.Elts)-1, subKey)
217+
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)