Skip to content

Replay scope-deriving mutators in NodeCallbackScope answers - #6259

Merged
ondrejmirtes merged 1 commit into
2.2.xfrom
node-callback-scope-derived-ops
Aug 24, 2026
Merged

Replay scope-deriving mutators in NodeCallbackScope answers#6259
ondrejmirtes merged 1 commit into
2.2.xfrom
node-callback-scope-derived-ops

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

NodeCallbackScope answers getType()/getNativeType() from the asked node's stored before-scope, which knows nothing about what a rule derived locally from the callback scope. filterByTruthyValue() and filterByFalseyValue() were already recorded and replayed onto the before-scope, but assignExpression(), assignVariable() and invalidateExpression() were not — a rule assigning a more specific type onto its scope read the original stored type right back. The recorded filters were also lost when the derivation chain passed through one of the unrecorded mutators.

This is a regression against 2.2.8 (pre-NodeCallbackScope) hit in the wild: ShipMonk's InputDocumentor assigns caller-side constant argument types onto the scope of a callee body it re-analyses via NodeScopeResolver::processNodes(), then expects getType() on an argument variable to see the constant — on current 2.2.x it saw the generalized string and aborted the analysis with "Internal error: Unexpected argument analysed …".

The fix records every scope-deriving call in one ordered list (replacing the separate truthy/falsey lists, whose replay was order-lossy between the two kinds) and replays it in call order in preprocessScope(). Only top-level calls are recorded: a mutator implemented via other mutators (assignVariable() calls assignExpression()) resets the list to its caller's view and appends itself, and the replay re-runs the composition.

The new test fails before the fix with all four probes reporting the stale generalized types (the filter-then-assign probe also demonstrates the filter loss: string|null).

🤖 Generated with Claude Code

https://claude.ai/code/session_01K2oPPwjrXy88MUsYekCFQ6

NodeCallbackScope answers getType()/getNativeType() from the asked
node's stored before-scope, which knows nothing about what a rule
derived locally from the callback scope. filterByTruthyValue() and
filterByFalseyValue() were already recorded and replayed onto the
before-scope, but assignExpression(), assignVariable() and
invalidateExpression() were not — a rule assigning a more specific
type onto its scope read the original stored type right back. The
recorded filters were also lost when the derivation chain passed
through one of the unrecorded mutators.

Record every scope-deriving call in one ordered list (replacing the
separate truthy/falsey lists, whose replay was order-lossy between the
two kinds) and replay it in call order in preprocessScope(). Only
top-level calls are recorded: a mutator implemented via other mutators
resets the list to its caller's view and appends itself, and the
replay re-runs the composition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K2oPPwjrXy88MUsYekCFQ6
@ondrejmirtes
ondrejmirtes merged commit 4db7884 into 2.2.x Aug 24, 2026
186 of 188 checks passed
@ondrejmirtes
ondrejmirtes deleted the node-callback-scope-derived-ops branch August 24, 2026 20:57
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