diff --git a/tests/test_parser.py b/tests/test_parser.py index 88232e8..15eeafe 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -112,3 +112,18 @@ def test_parser_rejects_tab_in_bare_key(content: str) -> None: parser = Parser(content) with pytest.raises(ParseError): parser.parse() + + +@pytest.mark.parametrize( + "content", + [ + "[a.b]\n[a]\n[a.b]", + "[a.b]\nx = 1\n[a]\n[a.b]\ny = 2", + "[a.b.c]\n[a]\n[a.b.c]", + "[a.b]\n[a.c]\n[a]\n[a.b]", + ], +) +def test_parser_rejects_table_redefined_after_parent(content: str) -> None: + parser = Parser(content) + with pytest.raises(ParseError): + parser.parse() diff --git a/tomlkit/container.py b/tomlkit/container.py index 3f8363d..6113a3b 100644 --- a/tomlkit/container.py +++ b/tomlkit/container.py @@ -339,6 +339,14 @@ def _validate_table_candidate(self, current: Table, candidate: Table) -> None: raise KeyAlreadyPresent(k) if k.is_dotted(): raise TOMLKitError("Redefinition of an existing table") + if isinstance(existing, Table) and isinstance(v, Table): + if not existing.is_super_table() and not v.is_super_table(): + # Both sides are concrete `[table]` definitions of the + # same name; the table is declared twice. + raise KeyAlreadyPresent(k) + # One side is still an implicit/super table, so a duplicate + # (if any) is nested deeper - keep checking the subtree. + self._validate_table_candidate(existing, v) continue if not k.is_dotted():