diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py index 7ba61f5e..54ba9fd7 100644 --- a/tests/test_toml_document.py +++ b/tests/test_toml_document.py @@ -643,6 +643,30 @@ def test_valid_out_of_order_independent_tables() -> None: assert doc.as_string() == "[a]\nx=1\n[zz]\n[a.b]\nc=1\n" +def test_out_of_order_table_extended_after_intervening_header() -> None: + # Regression test for a real-world case: an out-of-order table (here + # tool.ruff.lint) gets extended with a further sibling after some + # unrelated header (tool.poetry.source) came in between. The existing + # entry shows up as an OutOfOrderTableProxy rather than a bare Table, and + # the concrete/super type check used to treat that as a type mismatch + # against the new Table candidate, rejecting a document tomllib accepts. + source = ( + "[tool.ruff]\n" + "[tool.ruff.lint.a]\n" + "[tool.ruff.lint]\n" + "[[tool.poetry.source]]\n" + "[tool.ruff.lint.b]\n" + ) + doc = parse(source) + assert doc.unwrap() == { + "tool": { + "ruff": {"lint": {"a": {}, "b": {}}}, + "poetry": {"source": [{}]}, + } + } + assert doc.as_string() == source + + def test_set_value_on_out_of_order_table_with_empty_concrete_part() -> None: # A super table defined after its sub-table (the "defining a super-table # afterward is ok" spec example) leaves an empty concrete `[x]` part. diff --git a/tomlkit/container.py b/tomlkit/container.py index 8ff30d98..d4b3bbb2 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -424,7 +424,16 @@ def _validate_table_candidate(self, current: Table, candidate: Table) -> None: if k in current.value._map: existing = current.value.item(k) - if isinstance(existing, (Table, AoT)) != isinstance(v, (Table, AoT)): + # An out-of-order table already merged under this key shows up + # as an OutOfOrderTableProxy rather than a Table/AoT instance, + # even though it represents one or more concrete tables. Count + # it as table-like here too, or a later fragment of that same + # table gets rejected as a type mismatch against its own kind. + existing_is_table = isinstance( + existing, (Table, AoT, OutOfOrderTableProxy) + ) + candidate_is_table = isinstance(v, (Table, AoT, OutOfOrderTableProxy)) + if existing_is_table != candidate_is_table: raise KeyAlreadyPresent(k) if k.is_dotted(): raise TOMLKitError("Redefinition of an existing table")