Summary
An array-of-tables header closed by a single ] is accepted when it is the last
thing in the document. [[a] parses as { a: [ {} ] } instead of throwing. One
character of trailing content is also silently discarded, so [[a]x parses the
same way.
Version
- smol-toml 1.8.0 (also reproduces on 1.7.1)
- Node v22.17.0
Reproduction
import { parse } from 'smol-toml'
parse('[[a]') // => { a: [ {} ] } expected: throw
parse('[[a]x') // => { a: [ {} ] } expected: throw ('x' is dropped)
Why it is invalid
TOML 1.1.0's grammar requires two closing brackets:
array-table = array-table-open key array-table-close
array-table-open = %x5B.5B ws ; [[ Double left square bracket
array-table-close = ws %x5D.5D ; ]] Double right square bracket
array-table-close is %x5D.5D, so [[a] cannot derive from array-table.
Observed behaviour
| input |
1.8.0 |
expected |
[[a] |
accepts → {a:[{}]} |
reject |
[[a]x |
accepts → {a:[{}]}, x discarded |
reject |
[[a]xyz |
rejects |
reject |
[[a]]x |
rejects |
reject |
[[a] + newline + b = 1 |
rejects |
reject |
[a]x |
rejects |
reject |
Likely cause
In parse.js:
let k = parseKey(ctx, ']'); // consumes through the first ']'
if (isTableArray) {
if (toml.charCodeAt(ctx.p - 1) !== 0x5d /* ] */) {
throw new TomlError('expected end of table declaration', { ... });
}
ctx.p++;
}
The guard tests ctx.p - 1, which is the ] that parseKey has already
consumed and so is always 0x5d — the condition can never fire. The second
bracket is therefore never required, and ctx.p++ advances past whatever
happens to be there.
That accounts for the whole table: at EOF the increment is a no-op ([[a]), with
exactly one trailing character it swallows it ([[a]x), and with more the
leftovers produce an unrelated error ([[a]xyz).
Testing charCodeAt(ctx.p) for the closing bracket before advancing looks like
the intended check.
Why the test suite doesn't catch it
smol-toml passes all five existing toml-test cases for unclosed table headers
(invalid/table/array-no-close-01..04 and nested-brackets-close). Every one
of them has a line following the header, so none exercises the end-of-input path.
I've opened toml-lang/toml-test#205 with the two EOF cases ([[a] and [[a]x).
Other implementations
All reject [[a]:
- BurntSushi/toml (Go) v1.6.0 —
expected end of table array name delimiter ']'
- CPython
tomllib (3.14.7) — raises
- Rust
toml 1.1.4+spec-1.1.0 — rejects
How this was found
Generated by a conformance-suite generator that derives one test per named error
branch of an executable TOML 1.1.0 semantics.
Summary
An array-of-tables header closed by a single
]is accepted when it is the lastthing in the document.
[[a]parses as{ a: [ {} ] }instead of throwing. Onecharacter of trailing content is also silently discarded, so
[[a]xparses thesame way.
Version
Reproduction
Why it is invalid
TOML 1.1.0's grammar requires two closing brackets:
array-table-closeis%x5D.5D, so[[a]cannot derive fromarray-table.Observed behaviour
[[a]{a:[{}]}[[a]x{a:[{}]},xdiscarded[[a]xyz[[a]]x[[a]+ newline +b = 1[a]xLikely cause
In
parse.js:The guard tests
ctx.p - 1, which is the]thatparseKeyhas alreadyconsumed and so is always
0x5d— the condition can never fire. The secondbracket is therefore never required, and
ctx.p++advances past whateverhappens to be there.
That accounts for the whole table: at EOF the increment is a no-op (
[[a]), withexactly one trailing character it swallows it (
[[a]x), and with more theleftovers produce an unrelated error (
[[a]xyz).Testing
charCodeAt(ctx.p)for the closing bracket before advancing looks likethe intended check.
Why the test suite doesn't catch it
smol-toml passes all five existing
toml-testcases for unclosed table headers(
invalid/table/array-no-close-01..04andnested-brackets-close). Every oneof them has a line following the header, so none exercises the end-of-input path.
I've opened toml-lang/toml-test#205 with the two EOF cases (
[[a]and[[a]x).Other implementations
All reject
[[a]:expected end of table array name delimiter ']'tomllib(3.14.7) — raisestoml1.1.4+spec-1.1.0 — rejectsHow this was found
Generated by a conformance-suite generator that derives one test per named error
branch of an executable TOML 1.1.0 semantics.