Render an emptied array of tables as an empty array - #567
Conversation
_render_aot looped over the AoT body to emit [[key]] headers, so an array of tables with no elements left rendered as nothing at all and the key was dropped from the output. Fall back to the inline `key = []` form, matching what an empty array built through the API already renders as. TOML only reads bare key/value pairs before the first table header, so these keys are hoisted there; left in body order an emptied array of tables sitting after a table would be parsed back as a key of that table. Non-empty arrays of tables are unaffected and still render in place.
davidpavlovschi
left a comment
There was a problem hiding this comment.
The empty fallback breaks nested arrays of tables because it keeps the full header prefix even though the fallback now renders as a key/value inside the current table scope. Minimal case: parse [t] followed by [[t.a]], pop the only t.a element, then serialize. This head emits [t] / t.a = [], which reparses as t.t.a instead of t.a. The same duplication occurs at deeper tables and inside an AoT element. Please add a nested regression that asserts doc.unwrap() equals parse(doc.as_string()).unwrap(). For an empty AoT rendered inside [t], the key/value fallback needs to be a = [] in that scope, not t.a = []. I ran the full suite on this head as well: 1,054 passed, 1 skipped; the current tests cover only root-level empty AoTs.
Summary
Addresses the array-of-tables half of #553.
When every element is removed from an array of tables, there is no
[[key]]header left to render, and_render_aotlooped over an empty body and emitted nothing — so the key vanished from the output entirely:This renders the inline
a = []form instead, which is what tomlkit already produces for an empty array built through the API (doc["a"] = []).The ordering part
Rendering
a = []in body order is not enough. TOML only reads bare key/value pairs before the first table header, so an emptied array of tables sitting after one would be read back as a key of that table:So empty arrays of tables are hoisted to just before the first table header. Non-empty ones are untouched and still render in place, byte for byte.
Scope: the other half of the issue
The issue also covers
doc["x"]["y"].pop("z")leaving an empty super table that renders as nothing. I have not fixed that here, because the current behaviour is pinned by two existing tests —test_delete_out_of_order_table_keyandtest_overwriting_out_of_order_tableboth assert that a super table emptied bydeldisappears from the output rather than rendering its own header.Making emptied super tables render
[x.y]breaks both. That looked like a deliberate call rather than an oversight, so I left it alone rather than rewriting your tests to suit. Happy to follow up if you'd like that changed — it is a small diff once the intended behaviour is settled.Tests
Four tests added, covering the bare case, the hoisting case, an emptied array preceded by a scalar, and a control asserting non-empty arrays of tables still round-trip unchanged.
Revert-verified: with
container.pyreverted, the first three fail and the control still passes.Full suite on Windows / Python 3.12: 1055 passed (1051 on a clean tree, plus these 4).
ruff checkandruff format --checkare clean on both changed files.