Process call arguments in a single pass with type-driven acceptor selection - #6158
Merged
Conversation
…ection processArgs() now receives the callee's variants instead of a pre-selected acceptor. Arguments are processed closures-last, their types gathered on the arg-to-arg evolving scope, and the resolved ParametersAcceptor is selected from those gathered types (returned via the new ArgsResult). A closure/arrow argument resolves its parameter metadata from the sibling args processed before it, so a generic callable(T) parameter is already resolved when the closure body is analysed - without reading any argument's type before that argument is processed. Callers combine the variants into a structural acceptor (ParametersAcceptorSelector::combineVariantsForNormalization()) for argument normalization and throw/impure points, and read the resolved acceptor off ArgsResult for the explicit-never terminating check and the generic-sensitive @phpstan-self-out / remembered-call-value types. Closure::bind's bind scope became a deferred factory, evaluated once the bound this/scope arguments have been processed. The intrinsic argument overrides (array_map/filter/walk/find, curl_setopt, implode, Closure::bind) are applied once at the head of processArgs(), so the parameter pushed on the in-function-call stack while an argument is processed is the overridden one, exactly as when the callers pre-selected via selectFromArgs(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
Pins $this inside a closure bound to a maybe-null, a non-null and a definite-null $newThis, in both flavours - a maybe-null bind keeps $this nullable rather than falling back to the unbound scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
ondrejmirtes
force-pushed
the
single-pass-process-args
branch
from
July 30, 2026 20:40
4420672 to
47c744f
Compare
Restores the fast path selectFromArgs() used to take: with one acceptor, no templates and no named-argument variants, the gathered arg types can never influence the selection, so the (already-overridden) acceptor is the resolved acceptor without running selectFromTypes(). The parameter template traversals are hoisted out of the per-argument loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
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.
Extracted from the resolve-type-rewrite branch:
processArgs()no longer receives a pre-selectedParametersAcceptor— it receives the callee's variants and selects the acceptor itself, from argument types gathered while the arguments are processed.What changes
callable(T)parameter or an intrinsic argument override is evaluated before the closure body is analysed.processArgs(). Argument types are gathered on the arg-to-arg evolving scope; a closure/arrow argument resolves its parameter metadata from the args gathered so far (padded to the full count withmixed, keeping count-keyed by-ref/variadic variants stable, e.g.sscanf). The post-loop resolved acceptor is returned via the newArgsResultvalue object. No argument's type is read before that argument is processed.ParametersAcceptorSelector::combineVariantsForNormalization()for argument normalization and structural metadata, and read the resolved acceptor offArgsResultfor the explicit-never terminating check (conditional-returnneveronly resolves once actual argument types are folded in) and the generic-sensitive@phpstan-self-out/ remembered-call-value types.Closure::bind's bind scope became a deferred factory, evaluated once the bound$this/scope arguments have been processed — sound now that they precede the closure in processing order.array_map/array_filter/array_walk/array_find,curl_setopt,implode,Closure::bind) are applied once at the head ofprocessArgs(), so the parameter pushed on the in-function-call stack while an argument is processed is the overridden one — exactly as when the callers pre-selected viaselectFromArgs().All suites pass unchanged: NodeScopeResolverTest 1693/1693, full suite 17809 tests,
make phpstan,make cs.🤖 Generated with Claude Code
https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7