fix: reconstruct namespace root of dotted {% set ns.attr %} across scopes#1322
Draft
jasmith-hs wants to merge 1 commit into
Draft
fix: reconstruct namespace root of dotted {% set ns.attr %} across scopes#1322jasmith-hs wants to merge 1 commit into
{% set ns.attr %} across scopes#1322jasmith-hs wants to merge 1 commit into
Conversation
…scopes
In eager execution, a dotted namespace assignment such as `{% set ns.attr = x %}`
registered the root `ns` only as a *set* deferred base. Set bases are deferred
only within the same scope (the `isInSameScope` macro-stack check), so when the
assignment runs across a macro boundary or inside a child interpreter context,
the deferral of `ns` never reached the scope that owns it. Phase 1 then read the
namespace unmutated and produced output that diverged from a full render.
Register the namespace root as a *used* deferred base as well (only when the root
resolves to a `Namespace`), reusing the same unconditional propagation and
value-reconstruction path that already makes `{% do ns.update(...) %}` correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
When eager (two-phase) execution defers a dotted namespace assignment like
{% set ns.attr = value %}, also reconstruct the namespace rootnsin the scope that owns it — matching the behavior that already exists for{% do ns.update(...) %}.Why
Eager execution renders a template in a first phase and defers anything it can't yet resolve into a reconstructed template that a second phase renders with the full context. For this to stay correct, any variable a deferred region mutates has to be deferred/reconstructed in the scope where it lives, or the first phase reads a stale value and the two phases diverge.
This already worked for in-place collection mutation via
{% do %}:{% do list.append(x) %}readslistto call.append, solistis tracked as a used deferred base and is deferred/reconstructed unconditionally up the context chain.Dotted namespace assignment behaved differently.
{% set ns.attr = x %}trackednsonly as a set deferred base, and set bases are deferred only whenisInSameScopeis true (the same macro-stack). A plain deferred{% if %}or{% for %}shares the macro stack, so that case worked. But when the assignment runs across a macro boundary — or inside a child interpreter context, which is how some host frameworks pre-render fragments — the deferral ofnsnever reached the scope wherenswas defined. The first phase then read the namespace as unmutated, so a later{{ ns.attr }}(or{% if ns.attr %}) resolved against the wrong value and the reconstructed template dropped the mutation entirely.Fix
When the left-hand side of a
{% set %}is a dotted assignment whose root resolves to aNamespace, register that root as a used deferred word in addition to the existing set word. Mutatingns.attrinherently readsns, so treating the root as a used base is semantically honest, and it reuses the existing, proven propagation/reconstruction path rather than widening the scope gate for all set tags. Theinstanceof Namespaceguard leaves plain-map dotted keys ({% set someMap.field = x %}, which jinjava stores as a literal"someMap.field"key) untouched.Tests
EagerSetTagTest: the namespace root is registered as a used deferred word for both inline and block dotted sets; a non-namespace map root is not registered (guard); a namespace holding a non-serializable attribute value does not throw during reconstruction.EagerTest: end-to-end reconstruction + second-pass parity for a dotted namespace set inside a macro (the cross-scope case that regressed) and inside a{% for %}loop, each asserting the eager result matches a plain non-eager render.PR description authored by Claude Code.