Invoke intermediate Callables in dotted-name resolution#312
Conversation
Agent-Id: agent-afae2211-1cf8-460a-8aea-733b035005fa Linked-Note-Id: 804110b6-0ff6-4285-ad35-33931c601c67
Agent-Id: agent-afae2211-1cf8-460a-8aea-733b035005fa Linked-Note-Id: 804110b6-0ff6-4285-ad35-33931c601c67
Agent-Id: agent-afae2211-1cf8-460a-8aea-733b035005fa
Agent-Id: agent-af8bcf7c-4595-4b36-a217-2b132bb9a1eb Linked-Note-Id: 507df405-bb15-40b9-bef8-4ad8222e7ae4
🤖 Augment PR SummarySummary: This PR updates dotted-name section rendering so Changes:
Technical Notes: Intermediate scopes are only pushed when the resolved wrapper indicates an actual dotted traversal (to preserve literal-key semantics) and wrapper caching/guards are intended to handle shape changes. 🤖 Was this summary useful? React with 👍 or 👎 |
| } | ||
|
|
||
| private void addIntermediateScopes(List<Object> scopes) { | ||
| Wrapper resolvedWrapper = oh.find(name, scopes); |
There was a problem hiding this comment.
addIntermediateScopes calls oh.find(name, scopes) (and then resolves intermediateBindings) which re-runs dotted-name resolution and can invoke intermediate getters/Callables extra times, so {{#foo.bar}} may not fully match {{#foo}}{{#bar}} when intermediates have side effects. Consider adding a regression test with a counting intermediate callable/getter to ensure dotted vs nested sections have equivalent invocation behavior.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Addressed in cc69244 and 8f4f4df. IterableCode now resolves reflection-backed dotted sections from one fresh find result, and ReflectionWrapper.callDotted walks that wrapper chain once to collect the intermediate scopes and final value together. This removes the per-prefix binding evaluations and preserves the literal-key gate because a literal dotted key has no intermediate wrapper chain. The counting regression records the fixed overhead precisely: the dotted form invokes the intermediate 2 times (lookup plus resolution), while the nested form invokes it 1 time. mvn -q -pl compiler -am test passes all 288 tests with 0 failures/errors.
There was a problem hiding this comment.
After reviewing the Mustache spec, I removed intermediate scope pushing entirely. addIntermediateScopes and the scope replay path are gone, so this invocation-count concern no longer applies. Intermediate Callable values are now invoked only as part of dotted-name resolution.
Agent-Id: agent-7d67dd5b-4ec4-4525-aaa5-f1f08c624495
Agent-Id: agent-7d67dd5b-4ec4-4525-aaa5-f1f08c624495
Agent-Id: agent-8def19d3-9b6b-4877-bbce-d3164fc53f9e
Summary
Dotted-name resolution now invokes intermediate
Callablevalues so a chain can continue into their results.Intermediate parts participate in resolution only. They are not pushed onto the context stack; per the Mustache spec, a section pushes only the final resolved element.
Existing behavior for broken chains, literal
"foo.bar"keys, context precedence, and inverted sections is unchanged.Rendering example
Template:
View:
{ foo: (Callable) () -> Map(bar -> (Function<String,String>) text -> "baz") }Before: renders
""because the chain stops at theCallable.After: renders
"baz".Implementation
ReflectionObjectHandlerinserts a self-guardingCALLABLE_WRAPPERinto dotted-name wrapper chains. Initial resolution and cachedGuardedBindingreplay can therefore invoke an intermediateCallablebefore resolving the next segment.IterableCodekeeps the normal section path and pushes only the final resolved value.Tests
DotNotationTestcovers Callable roots and mid-chain Callables, broken chains, literal dotted keys (including a"foo.bar"/"foo"collision), inverted sections, and final-only scope behavior.{{#a.b}}{{x}}{{/a.b}}rendersROOTrather than the intermediatea.xvalue.mvn -q -pl compiler -am testpasses: 285 tests, 0 failures or errors, 8 skipped, includingSpecTestandFullSpecTest.Also adds
AGENTS.mdrepository guidelines.