Fix TOML roundtrip: use TomlInline flag instead of FlowStyle to preserve inline tables

FlowStyle affected YAML decode output (causing inline tables to appear as
YAML flow mappings). Replace it with a new TOML-specific TomlInline bool
on CandidateNode that:
- Is set by the TOML decoder for inline tables (not FlowStyle)
- Is copied by UpdateAttributesFrom so it survives DeeplyAssign merges
- Is checked by the TOML encoder alongside FlowStyle (for YAML flow maps)
- Has no effect on the YAML encoder, preserving existing TOML→YAML output

TOML roundtrip tests are restored to their original expected values (inline
tables stay inline, table sections stay as sections).

Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/f59bdf62-6d16-4664-991b-38eb87c9d81c

Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-08 07:06:48 +00:00 committed by GitHub
parent b99f4174ec
commit d1ffec12e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 21 additions and 54 deletions

View File

@ -100,6 +100,9 @@ type CandidateNode struct {
// For formats like HCL and TOML: indicates that child entries should be emitted as separate blocks/tables
// rather than consolidated into nested mappings (default behaviour)
EncodeSeparate bool
// For TOML: indicates that this mapping was originally a TOML inline table and should be
// re-encoded as an inline table rather than a separate table section.
TomlInline bool
}
func (n *CandidateNode) CreateChild() *CandidateNode {
@ -412,6 +415,7 @@ func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
IsMapKey: n.IsMapKey,
EncodeSeparate: n.EncodeSeparate,
TomlInline: n.TomlInline,
}
if cloneContent {
@ -467,6 +471,8 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
// Preserve EncodeSeparate flag for format-specific encoding hints
n.EncodeSeparate = other.EncodeSeparate
// Preserve TomlInline flag for TOML inline table round-trips
n.TomlInline = other.TomlInline
// merge will pickup the style of the new thing
// when autocreating nodes

View File

@ -150,10 +150,10 @@ func (dec *tomlDecoder) createInlineTableMap(tomlNode *toml.Node) (*CandidateNod
}
return &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Style: FlowStyle,
Content: content,
Kind: MappingNode,
Tag: "!!map",
TomlInline: true,
Content: content,
}, nil
}

View File

@ -153,9 +153,7 @@ yq '.' sample.toml
```
will output
```yaml
[name]
first = "Tom"
last = "Preston-Werner"
name = { first = "Tom", last = "Preston-Werner" }
```
## Roundtrip: table section
@ -374,10 +372,7 @@ dob = 1979-05-27T07:32:00-08:00
enabled = true
ports = [8000, 8001, 8002]
data = [["delta", "phi"], [3.14]]
[database.temp_targets]
cpu = 79.5
case = 72.0
temp_targets = { cpu = 79.5, case = 72.0 }
# [servers] yq can't do this one yet
[servers.alpha]

View File

@ -162,10 +162,9 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
// Regular array attribute
return te.writeArrayAttribute(w, path[len(path)-1], node)
case MappingNode:
// Use inline table syntax only for nodes explicitly marked with flow/inline style
// (e.g. TOML inline tables or YAML flow mappings). All other mappings become
// readable TOML table sections.
if node.Style&FlowStyle != 0 {
// Use inline table syntax for nodes explicitly marked as TOML inline tables
// or YAML flow mappings. All other mappings become readable TOML table sections.
if node.TomlInline || node.Style&FlowStyle != 0 {
return te.writeInlineTableAttribute(w, path[len(path)-1], node)
}
return te.encodeSeparateMapping(w, path, node)
@ -428,7 +427,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate
// It emits the table header for this mapping if it has any content, then processes children.
func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *CandidateNode) error {
// Check if this mapping has any non-mapping, non-array-of-tables children (i.e., attributes).
// Flow-style (inline) mapping children also count as attributes since they render as key = { ... }.
// TomlInline mapping children also count as attributes since they render as key = { ... }.
hasAttrs := false
for i := 0; i < len(m.Content); i += 2 {
v := m.Content[i+1]
@ -436,7 +435,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
hasAttrs = true
break
}
if v.Kind == MappingNode && v.Style&FlowStyle != 0 {
if v.Kind == MappingNode && (v.TomlInline || v.Style&FlowStyle != 0) {
hasAttrs = true
break
}
@ -598,13 +597,13 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
}
}
// Finally, child mappings: flow-style (inline) ones become inline table attributes,
// Finally, child mappings: TomlInline or flow-style ones become inline table attributes,
// while all others are emitted as separate sub-table sections.
for i := 0; i < len(m.Content); i += 2 {
k := m.Content[i].Value
v := m.Content[i+1]
if v.Kind == MappingNode {
if v.Style&FlowStyle != 0 {
if v.TomlInline || v.Style&FlowStyle != 0 {
if err := te.writeInlineTableAttribute(w, k, v); err != nil {
return err
}

View File

@ -182,12 +182,6 @@ var expectedSampleWithHeader = `servers:
var rtInlineTableAttr = `name = { first = "Tom", last = "Preston-Werner" }
`
// Inline tables are converted to readable table sections on encode
var rtInlineTableAttrEncoded = `[name]
first = "Tom"
last = "Preston-Werner"
`
var rtTableSection = `[owner.contact]
name = "Tom"
age = 36
@ -269,33 +263,6 @@ ip = "10.0.0.2"
role = "backend"
`
// Inline table temp_targets is expanded to a readable sub-table when re-encoding
var sampleFromWebEncoded = `# This is a TOML document
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [8000, 8001, 8002]
data = [["delta", "phi"], [3.14]]
[database.temp_targets]
cpu = 79.5
case = 72.0
# [servers] yq can't do this one yet
[servers.alpha]
ip = "10.0.0.1"
role = "frontend"
[servers.beta]
ip = "10.0.0.2"
role = "backend"
`
var subArrays = `
[[array]]
@ -539,7 +506,7 @@ var tomlScenarios = []formatScenario{
description: "Roundtrip: inline table attribute",
input: rtInlineTableAttr,
expression: ".",
expected: rtInlineTableAttrEncoded,
expected: rtInlineTableAttr,
scenarioType: "roundtrip",
},
{
@ -638,7 +605,7 @@ var tomlScenarios = []formatScenario{
description: "Roundtrip: sample from web",
input: sampleFromWeb,
expression: ".",
expected: sampleFromWebEncoded,
expected: sampleFromWeb,
scenarioType: "roundtrip",
},
{