Skip to content

Commit 4ff9bc2

Browse files
fix: avoid duplicate table header when adding a key to an out-of-order table
Adding a plain key to an out-of-order table whose concrete `[x]` part is declared after its sub-tables (the "defining a super-table afterward is ok" spec form) wrote the value into the header-less super part, forcing it to render a second `[x]` header next to the existing concrete one. The dumped output then had a duplicate header and no longer parsed. Route the value into the existing concrete (non-super) part instead, so the header that already exists gains the key and no second header is emitted.
1 parent 43668dd commit 4ff9bc2

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

‎tests/test_toml_document.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,20 @@ def test_valid_out_of_order_independent_tables() -> None:
643643
assert doc.as_string() == "[a]\nx=1\n[zz]\n[a.b]\nc=1\n"
644644

645645

646+
def test_set_value_on_out_of_order_table_with_empty_concrete_part() -> None:
647+
# A super table defined after its sub-table (the "defining a super-table
648+
# afterward is ok" spec example) leaves an empty concrete `[x]` part.
649+
# Adding a plain value must land in that concrete part, not turn the
650+
# header-less super part into a second `[x]` header -- which produced
651+
# output with a duplicate header that no longer parsed.
652+
doc = parse("[x.y.z.w]\n\n[x]\n")
653+
doc["x"]["c"] = 3
654+
655+
assert doc["x"]["c"] == 3
656+
assert doc.as_string() == "[x.y.z.w]\n\n[x]\nc = 3\n"
657+
assert parse(doc.as_string()).unwrap() == {"x": {"y": {"z": {"w": {}}}, "c": 3}}
658+
659+
646660
def test_out_of_order_table_merges_aot_fragments() -> None:
647661
# https://github.com/python-poetry/tomlkit/issues/505
648662
content = """\

‎tomlkit/container.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,18 @@ def _is_table_or_aot(it: Any) -> bool:
11681168
table[key] = value
11691169
break
11701170
else:
1171-
self._tables[0][key] = value
1171+
# No part holds a plain value yet, so the chosen part must
1172+
# start rendering its own ``[key]`` header. Prefer a part
1173+
# that is not a super table (it already renders that header):
1174+
# turning a header-less super part concrete here would emit a
1175+
# second, duplicate header next to an existing concrete part
1176+
# and produce TOML that no longer parses.
1177+
for table in self._tables:
1178+
if not table.is_super_table():
1179+
table[key] = value
1180+
break
1181+
else:
1182+
self._tables[0][key] = value
11721183
else:
11731184
self._tables[0][key] = value
11741185
else:

0 commit comments

Comments
 (0)