Skip to content

test(conformance): an object concatenated over a substitution is a merge - #28

Merged
lejmr merged 4 commits into
mainfrom
conformance/concatenation-is-a-merge
Oct 24, 2026
Merged

lejmr merged 4 commits into
mainfrom
conformance/concatenation-is-a-merge

Conversation

@lejmr

@lejmr lejmr commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

The spec defines object concatenation as merging (array-and-object-concatenation.6) and lists a : { b : 1 } { c : 2 } beside two fields a : { b : 1 }, a : { c : 2 } as the same value. So top : ${x} { arr += four } is top : ${x} and top { arr += four }, and the suite had no case where that matters: nothing referred to the object from inside a ${x} { ... }. lightbend/config#882 brought it up.

Eight cases, each citing array-and-object-concatenation.6:

case input spec zig-hocon Java 1.4.9
plus-equals/009 top : ${x} { arr += four } [one, four] [four] [four] (#608)
plus-equals/010 y : ${x} { l += 2 }, z : ${y} { l += 3 } z.l = [1, 2, 3] [3] [3]
plus-equals/011 top : ${x} { a { b { c += 2 } } } [1, 2] [2] [2]
plus-equals/012 y = ${x} { l = [1, 2] }, y.l += 3 [1, 2, 3] ✓ [3] (#375)
self-referential/027 c : ${d} { x : ${c.x} [1, 2] } [0, 1, 2] cycle cycle (#728)
self-referential/028 c : ${d} ${e} { x : ${c.x} [1], w : ${e.z} } x = [0, 1], w = 5 cycle cycle
self-referential/029 a = ${mixin} { y = ${a.hello} } y = world cycle cycle (#586)
self-referential/030 top = ${x} { n = 2, seen = ${top.n} } seen = 2 cycle cycle
  • 009–012, 027, 028 are self-references: the overriding field looks back at the overridden one (examples-of-self-referential-substitutions.3), and here the overridden one comes from ${x}.
  • 029, 030 are not self-referential (an object with a substitution inside is not, self-referential-substitutions.8), so they look forward and see the merged object (examples-of-self-referential-substitutions.14, .16).

zig-hocon gets every one of these right written as two fields and fails 7 of the 8 in one value: 447/454. Java 402/454 against the spec (all 8 are java: diverges or unsupported), pyhocon 318/454. The other four reports are behind as before.

A second commit fixes zig-hocon (454/454).

Layers merge before they are resolved

The same review turned up a neighbour (lightbend/config#838, #812): the layers of a key merge first and are resolved after, so a field an upper layer hides is never evaluated (examples-of-self-referential-substitutions.8), and a reference below looks forward at the merged value (.16).

case input spec zig-hocon before Java 1.4.9
self-referential/031 p : { a : ${y} }, p : ${x}, x : { a : 42 } p.a = 42 Unresolved ${y} ✓ (#838)
self-referential/032 child = ${base} { foo = ${missing} }, child.foo = x foo = x Unresolved Unresolved (#812)
self-referential/033 bar : { foo : 42, baz : ${bar.foo} }, bar : ${r}, r = { foo : 43 } baz = 43 42 ✓

A third and fourth commit add the cases and the fix (mergedBelow in src/Value/Resolver/stack.zig): the PR merges at 457/457. Java 404/457 against the spec, pyhocon 320/457.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CbBo1aotFTDLY2gery7qYD

lejmr and others added 4 commits October 24, 2026 08:28
`top : ${x} { ... }` is a merge (array-and-object-concatenation.6), the same as `top : ${x}` and `top { ... }` written as two fields, so inside it:

- a self-reference, `+=` or the long form, looks back at the field ${x} gives (examples-of-self-referential-substitutions.3): plus-equals-field-separator/009–012, self-referential-examples/027–028
- any other reference to the object looks forward and sees the merged value (.14, .16): self-referential-examples/029–030

typesafe/config drops the inherited element of a += (lightbend/config#375, #608, fix proposed in #882) and reports the rest as cycles (#586, #728). zig-hocon gets the two-field form right and fails 7 of the 8 in one value: 447/454.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbBo1aotFTDLY2gery7qYD
`top = ${x} { ... }` was one value joined once its parts were resolved, so nothing inside the object saw what ${x} gives: a += there dropped the inherited list, a self-reference had nothing below, and ${top.n} closed a cycle. The object is now resolved as a layer over the parts in front of it, as if written as a field of its own (spec L398), so it goes through the same code as `top = ${x}` and `top { ... }`. Next to a list or text, or under `.keep` while ${x} waits, it is still a concatenation.

zig-hocon passes all 454 cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbBo1aotFTDLY2gery7qYD
- self-referential-examples/031: an object a substitution gives on top hides a field of the object below it, so a substitution there is never evaluated (examples-of-self-referential-substitutions.8; lightbend/config#838)
- self-referential-examples/032: the same inside `${base} { ... }`, hidden by a later `child.foo = x` (lightbend/config#812, Java evaluates it)
- self-referential-examples/033: a reference in the object below looks forward at the merged value (.16, .17)

zig-hocon resolved the layer below first and fails all three: 454/457.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbBo1aotFTDLY2gery7qYD
An object layer, or an object a substitution gives, was merged onto the layers below it only after they were resolved, so a field it hides was evaluated anyway (an error for `p : { a : ${y} }`, `p : ${x}` with x.a set) and a reference below saw the old value. `mergedBelow` now merges the objects among the layers as written and resolves only what decides whether they merge, the refs; the merged object is resolved once, on top.

zig-hocon passes all 457 cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbBo1aotFTDLY2gery7qYD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant