Skip to content

fix: resolve substitutions that pass through object concatenations - #880

Open
kastoestoramadus wants to merge 9 commits into
lightbend:mainfrom
kastoestoramadus:fix/substitution-through-concatenation
Open

kastoestoramadus wants to merge 9 commits into
lightbend:mainfrom
kastoestoramadus:fix/substitution-through-concatenation

Conversation

@kastoestoramadus

@kastoestoramadus kastoestoramadus commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Draft stack, merge order #878 → #879 → #880 → #881 → #882; each branch contains the ones before it.

TLDR: Substitutions through object concatenations no longer resolve unrelated siblings or falsely cycle. Fixes #586.

Child restrictions are kept and object pieces are marked for look-back; includes the nested correction from #879.

Tests in their own commit before the fix; 3 of 132 fail without it.

Generated with GLM-5.3-Flash/effort not exposed through Claude Code
Revised with gpt-6.1-sol/m through Codex

kastoestoramadus and others added 2 commits October 5, 2026 16:43
…end#751)

Resolving the issue lightbend#751 config crashes with BugOrBroken
'SimpleConfigObject.replaceChild did not find SimpleConfigList' when a
duplicate key with a substitution (a delayed merge) sits inside a list
that is a piece of a value concatenation. Add regression tests for the
optional and the defined strict substitution variants.

Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>
…htbend#751)

Resolving a delayed merge inside a container that is a piece of a value
concatenation threw BugOrBroken 'SimpleConfigObject.replaceChild did not
find ...' (issues lightbend#751, also hit by lightbend#339's crash): the concatenation was
never pushed onto ResolveSource's parent chain, so when the delayed merge
replaced itself within its parent, the walk up the chain skipped the
concatenation and tried to replace the container directly in its
grandparent, where it cannot be found.

ConfigConcatenation implements Container with working replaceChild and
hasDescendant; resolveSubstitutions now pushes itself onto the source's
parent chain like other containers do.

Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>
kastoestoramadus and others added 3 commits October 6, 2026 07:10
A self-referential list concatenation in a field of an object merged
over a ${d} substitution must look back at the earlier definition of
the field (lightbend#728), with one or several appended
elements. A sibling field of a merge piece must keep seeing the
piece's final value. Genuine cycles must keep being reported as
cycles: a = ${a}, mutually referring fields inside one object, and an
object or array with a substitution inside it on a field merged over
a substitution.

Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>
…ieces

d { x = [] }, c : ${d}, c { x : ${c.x}[1, 2] } fails with a false
"was part of a cycle of substitutions" (lightbend#728), and so does any
self-referential field inside a piece of a delayed merge stack.

HOCON.md requires that on a cycle a substitution "looks back" in the
merge stack. That look back is implemented by resolving the merge
against the pieces below the one being resolved, but only when the
piece itself is unmergeable (a substitution or a concatenation). When
the self-referential field sits inside a mergeable piece, a lookup of
the field's path re-enters the whole merge, restarts the stack at the
top piece, re-resolves the in-flight substitution, and the cycle
marker reports a cycle.

While a mergeable piece of the stack is being resolved it is marked on
the ResolveContext. If a lookup restricted to a leaf child of the
marked piece re-enters a value that is still on the resolve stack, the
leaf is dropped from the piece and the merge supplies the value from
the pieces below it, as the look back rule requires. The drop applies
only to the marked piece itself and only to leaves whose value is a
substitution or a value concatenation, so plain self-referential
objects and arrays keep reporting unbreakable cycles, as HOCON.md
prescribes.

Resolutions re-entered while the original is still on the resolve
stack are no longer memoized, since their result depends on that
in-flight look-back state.

Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>
…iece

Skipping the memo whenever a value was re-entered made every ${x} lookup
into a still-resolving root walk the whole root again, so resolving N
fields that refer to one key went from linear to quadratic (40,000 fields:
1.5 s -> 22.5 s). The result only depends on in-flight state while a merge
stack piece is marked, so skip the memo only then.

Co-Authored-By: sonnet-5.5/m through Claude Code <noreply@anthropic.com>
@kastoestoramadus

kastoestoramadus commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

TLDR: Position 3 of 5 in the stack #878 → #879 → #880 → #881 → #882; see the comment on #878 before rebasing or pushing.

kastoestoramadus and others added 4 commits October 10, 2026 19:06
Co-Authored-By: gpt-6.1-sol/m through Codex <noreply@openai.com>
Co-Authored-By: gpt-6.1-sol/m through Codex <noreply@openai.com>
Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>

Co-Authored-By: gpt-6.1-sol/m through Codex <noreply@openai.com>
Co-Authored-By: GLM-5.3-Flash/effort not exposed through Claude Code <noreply@anthropic.com>

Co-Authored-By: gpt-6.1-sol/m through Codex <noreply@openai.com>
@kastoestoramadus
kastoestoramadus force-pushed the fix/substitution-through-concatenation branch from ac20f4a to eac548e Compare October 10, 2026 17:16
@kastoestoramadus
kastoestoramadus marked this pull request as ready for review October 10, 2026 17:43

This branch has not been deployed

No deployments
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.

Mixin substitutions affect resolution inside of the object being mixed into, causing errors

1 participant