Skip to content

[Yaml] Do not treat quoted << keys as merge keys - #66621

Merged
nicolas-grekas merged 1 commit into
symfony:6.4from
kevin9327:yaml-quoted-merge-key
Oct 5, 2026
Merged

nicolas-grekas merged 1 commit into
symfony:6.4from
kevin9327:yaml-quoted-merge-key

Conversation

@kevin9327

@kevin9327 kevin9327 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Q A
Branch? 6.4
Bug fix? yes
New feature? no
Deprecations? no
Issues -
License MIT

In YAML, only a plain << key is a merge key. '<<' and "<<" are ordinary string keys. The parser treated quoted ones as merge keys too. As a result, the Dumper's own output can't always be read back, even though the Dumper already quotes the key:

$yaml = Yaml::dump(['<<' => 'foo']);   // "'<<': foo\n"
Yaml::parse($yaml);
// ParseException: YAML merge keys used with a scalar value instead of an array

Yaml::parse(Yaml::dump(['<<' => 'foo'], 0)); // "{ '<<': foo }"
// TypeError: Unsupported operand types: array + string

Yaml::parse("'<<': { a: 1 }");
// ['a' => 1] instead of ['<<' => ['a' => 1]]

The second example also shows that in inline mappings, a real merge key with a scalar value ({ <<: foo }), or a sequence of scalars ({ <<: [foo] }), crashed with a TypeError. The block parser already throws a ParseException in those cases, and now the inline parser does the same.

In Parser, the merge-key check now looks at the raw key text ('<<' === $values['key']). In Inline::parseMapping(), it uses the existing $isKeyQuoted. All the '<<' === $key checks go through the new $isMergeKey variable.

I found this by round-tripping random arrays through Yaml::dump() and Yaml::parse(). With the patch applied, a few hundred thousand cases on 6.4 came back identical. The new tests in InlineTest, ParserTest and DumperTest fail without the fix, and the Yaml test suite passes with it.

This also means that a quoted '<<' or "<<" key with a mapping value, which was merged before, is now kept as a key named <<, as YAML 1.1 and PyYAML do. So is !!str <<.

Merge-up to 8.2: in Parser.php, keep the $isMergeKey lines and drop the u modifier of #^&(?P<ref>[^ ]+)#, as 8.2 does. On 7.4, 8.1 and 8.2, DumperTest conflicts because both sides add a method at the end of the class: keep both.

Only a plain `<<` key is a merge key: a quoted or `!!str` one is now kept as a key named `<<`, so the output of the Dumper parses back.
In flow mappings, a merge key with a scalar value now throws a ParseException instead of a TypeError, and merge keys work with PARSE_OBJECT_FOR_MAP.
@symfony symfony deleted a comment from carsonbot Oct 5, 2026
@nicolas-grekas

Copy link
Copy Markdown
Member

I updated the PR: a << key followed by a tab before its colon is a merge key again, merge keys in flow mappings now work with PARSE_OBJECT_FOR_MAP (the new checks reported a mapping as a scalar there), and !!str << is covered.

@nicolas-grekas

Copy link
Copy Markdown
Member

Thank you @kevin9327.

@nicolas-grekas
nicolas-grekas merged commit 82fbe9c into symfony:6.4 Oct 5, 2026
13 of 14 checks passed
This was referenced Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants