Feed engine gatherers from NodeScopeResolver frames instead of wrapping node callbacks - #6258
Merged
Merged
Conversation
…ng node callbacks GatheringNodeCallback existed so engine-feeding gatherers (return statements, execution ends, impure points, invalidate expressions) could ride the node-callback channel while only the rule-facing remainder got the storage-backed scope - callNodeCallback() unwrapped the chain per emission, and VirtualAssignNodeCallback had to rebuild chains so a filter could not starve a gatherer. With everything synchronous the split is unnecessary: gatherers live as frames on NodeScopeResolver, fed the raw walk scope directly by callNodeCallback() and by replayRecording() for replayed emissions (previously a replayed gatherer saw the storage-backed scope - the raw scope is state-identical and consistent with live emissions). The nine construction sites push a frame around their body walk and pass the rule-facing callback through untouched. Fresh walks extensions start mid-analysis (processNodes()/processStmtNodes()) suspend the frames, so a rule-started re-walk cannot feed the interrupted walk's gatherers - matching the old behavior where such walks carried no wrapper. The ShallowNodeCallback marker went with it: nothing has consulted it since the fiber era. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 24, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 24, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 30, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 30, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 31, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Aug 31, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Sep 1, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
ondrejmirtes
added a commit
that referenced
this pull request
Sep 2, 2026
The rebase over the merged gathering cleanup (#6258) kept the branch's wrapper-based gatherer sites while the base deleted GatheringNodeCallback; this converts them to the upstream frame mechanism - pushNodeGatherer()/popNodeGatherer() on NodeScopeResolver, fed the raw walk scope by callNodeCallback() and per replayed pair by replayRecording() - woven into the branch's shapes: the per-body storages of method/function bodies, the ambient storage pushes, and the scope-bound replayRecording() signature. Gatherer bodies are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8
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.
Post-fiber cleanup: with node callbacks synchronous since #6248, the gatherer/rule-facing callback split no longer needs to ride the callback channel.
GatheringNodeCallbackpaired an engine-feeding gatherer (return statements, execution ends, impure points, invalidate expressions) with the rule-facing callback;callNodeCallback()unwrapped the chain per emission so gatherers got the raw walk scope, andVirtualAssignNodeCallbackhad to rebuild chains so its filter could not starve a gatherer.Now gatherers live as frames on
NodeScopeResolver:callNodeCallback()feeds every frame the raw walk scope directly — same per-emission cost profile as the old unwrap, noNodeCallbackScopeconstruction for engine code.replayRecording()feeds frames for replayed emissions too. This also fixes a quirk: a gatherer reached through a replayed recording previously observed the storage-backed scope instead of the raw one (output-neutral either way — verified).new,array_walkargs) push a frame around their body walk and pass the rule-facing callback through untouched.processNodes()/processStmtNodes()) suspend the frames, matching the old behavior where such walks carried no wrapper.VirtualAssignNodeCallback::create()loses the chain-rebuild; theShallowNodeCallbackmarker interface goes too — nothing has consulted it since the fiber era.Analysis output is byte-identical on full self-analysis vs the base; full test suite,
make phpstan, and CS green. No turbo-shadowed classes touched.🤖 Generated with Claude Code
https://claude.ai/code/session_01GnwgpaeUXRkgSDyg95tfK8