Render each run of a split array of tables where it was written - #601
Open
hxperl wants to merge 1 commit into
Open
Render each run of a split array of tables where it was written#601hxperl wants to merge 1 commit into
hxperl wants to merge 1 commit into
Conversation
A document may interrupt an array of tables with an unrelated table and
then continue it:
[[fruit]]
name = "apple"
[settings]
color = true
[[fruit]]
name = "banana"
The parser collects only contiguous `[[fruit]]` headers into one AoT, so
the second run arrives at `Container.append` as a separate AoT under a
key that already exists. That branch merged its elements into the first
AoT and returned, discarding where the run appeared, and rendering then
emitted the whole array at the first run's position — moving `[settings]`
below `[[fruit]] name = "banana"` on a plain parse/dump round-trip.
Keep the elements in the single AoT, so `doc["fruit"]` is still the whole
array, and record the later run with an `_AoTContinuation` marker in the
body. Rendering splits the AoT across its runs at the recorded positions.
Elements appended after parsing render with the last run, and a run whose
elements have all been deleted renders nothing.
test_parse_aot_without_ending_newline came from python-poetry#422 (a missing final
newline corrupting the dump) and pinned the reordered output of the day
as its expectation; it now asserts the document is preserved, which is
what that fix was after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyaZ48KuBpkPpfPui9xCUK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tomlkit.dumps(tomlkit.parse(s))does not returnswhen a document interrupts an array of tables with an unrelated table and then continues it. The intervening table is moved below the whole array.I did not find an existing issue for this, so there is no issue number to reference.
Reproduction
On
master(4b38bec):[settings]has moved, and the blank line before it is gone. Nothing was modified between the parse and the dump. The document is valid TOML 1.0.0 — a table header may interrupt an array of tables and a later[[fruit]]continues the same array — and the parse is correct:doc["fruit"]is the two-element array. Only the rendering is wrong.With this branch the input is reproduced byte for byte.
Cause
Parser._parse_aotgathers only contiguous[[fruit]]headers, so the second run reachesContainer.appendas a separateAoTunder a key that is already present. That branch merged the new elements into the existingAoTand returned:Once merged, nothing recorded that a run had been written further down, and
as_stringrendered the entire array at the first run's body position.Approach
The elements stay in one
AoT, sodoc["fruit"]is still the whole array and every existing accessor,unwrap()and mutation path is unchanged. What is added is a marker in the container body,_AoTContinuation, recording that a later run was written at that point and which elements it began with.as_stringrenders theAoTup to the first marker, and each marker renders its own slice in place.The alternative I tried first was the one the out-of-order table path uses: give the run its own body entry, make
_map[key]a tuple, and merge the runs on access. It fixes the rendering just as well, butdoc["fruit"]then has to be a merged copy, anddoc["fruit"].append(table)silently does nothing. That seemed worse than the bug, so I went with the marker. If you would rather have the split represented in_map— in line with how out-of-order tables work — I am happy to redo it that way; it would want an AoT-shaped proxy to keep mutation working.Behaviour of the marker after parsing:
There are tests for the first and second of these.
On the changed test
test_parse_aot_without_ending_newlineasserted the reordered output. It came from #422 / #381, which was about a missing final newline corrupting the dump, and the expectation simply captured what the renderer did at the time. It now asserts that the document is preserved; thedoc == {...}half of the test is unchanged and still passes.What I verified
1062 passed. CI will need to confirm the other versions and Linux.ruff checkandmypyreport exactly the same pre-existing findings before and after the change (3 and 97 respectively, none in the lines touched).[t],[t.sub]and[[arr]]headers): before, 464 documents did not round-trip and every one was this bug; after, none.copy.deepcopyandpickleround-trips of a split array (the marker needed a_getstate, andContainer.__setstate__recomputes the flag).Rendering does one extra pass over the body to locate markers. It is guarded by a flag set only when a split array is actually parsed, so documents without one — nearly all of them — are untouched.
Adjacent, not fixed here
The same fuzzing shows a sub-table of an array element is still pulled up out of order:
dumps as
[[arr]]/[arr.sub]/[t2]. That is a different code path (theis_super_table()branch just above the one changed here), so I left it alone rather than widen this PR. Happy to open an issue for it.Agent Drafting Metadata
dumps(parse(s)) == s, not from an issue report. Diagnosis, the choice between the two approaches, the patch and the tests were drafted with the agent and reviewed and run locally by me; all output quoted above is from real runs on this branch.