Hi Tim,
On Sun, Oct 12, 2025 at 8:36 PM Tim Düsterhus <[email protected]> wrote:
> Relatedly to the stack trace example and also to the “Evaluation order”
> I'd be curious how pre-filled parameters are implemented technically.
> For pre-filled variables, the regular variable capturing logic would
> work, but it doesn't for function returns. Is it effectively creating a
> temporary variable under the hood? Meaning is:
>
> $partial = speak(?, getArg());
>
> desugared into:
>
> $pfa_tmp_2 = getArg();
> $partial = fn (string $who) => speak($who, $pfa_tmp2);
>
> ?
Pre-filled parameters are passed to the Closure via used vars, but
there is no requirement for them to be in a CV slot so we don't need
to create a temporary variable. We can bind used vars to any zval
directly.
The code
$partial = speak(?, getArg());
Is compiled to
INIT_FCALL speak
SEND_PLACEHOLDER
INIT_FCALL getArg
T1 = DO_FCALL
SEND T1
CALLABLE_CONVERT_PARTIAL
The CALLABLE_CONVERT_PARTIAL opcode generates the AST for
function (string $who) use ($msg) {
return \speak($who, $msg);
};
and compiles it (this is cached in SHM). It then instantiates the
Closure, and binds $msg to ZEND_CALL_ARG(2).
A few other values are passed via used vars, such as the object if
it's an instance method call, or the underlying function if it's a
closure.
> - How does it interact with
> `ReflectionFunctionAbstract::getClosureUsedVariables()`?
> - Same question for other scope introspection functionality (e.g. Xdebug).
getClosureUsedVariables() and other introspection functionality will
report any used var as usual. Used vars are named after the parameter
if there is no collision. But this should not be considered API as
this may change. For instance, I want to optimize-out used vars for
parameters that are pre-filled with a literal. It would be possible to
hide used vars of a PFA in Reflection.
Best Regards,
Arnaud