Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
55ed6b9
Partial application
arnaud-lb Aug 5, 2025
cb3d555
Remove unnecessary change
arnaud-lb Jan 6, 2026
d1ede81
Skip preload test on Windows
arnaud-lb Jan 6, 2026
1eaa604
Comments
arnaud-lb Jan 6, 2026
aa28413
Improve type coherency
arnaud-lb Jan 6, 2026
8ec7ba4
Clarify
arnaud-lb Jan 6, 2026
ed51bff
Useless memset
arnaud-lb Jan 6, 2026
75662c7
Move include
arnaud-lb Jan 7, 2026
d749512
Rename tests
arnaud-lb Jan 7, 2026
8587256
Fix test
arnaud-lb Jan 7, 2026
dd593f9
Better type
arnaud-lb Jan 7, 2026
73ac5a7
Generated file
arnaud-lb Jan 7, 2026
9f48051
Consistency
arnaud-lb Jan 8, 2026
91b8a26
Fix test name
arnaud-lb Jan 8, 2026
d93bbe9
Improve tests
arnaud-lb Jan 8, 2026
fafb938
Improve tests
arnaud-lb Jan 15, 2026
2d9be7c
Improve tests
arnaud-lb Jan 15, 2026
d5434c1
Update comment, add tests
arnaud-lb Jan 15, 2026
31c8d8e
Improve test
arnaud-lb Jan 16, 2026
8080d35
Avoid void cast
arnaud-lb Jan 16, 2026
99d573d
Fix tests
arnaud-lb Jan 16, 2026
6353c1c
constify
arnaud-lb Jan 16, 2026
7c5f0c9
inline
arnaud-lb Jan 16, 2026
b697abd
Useless condition
arnaud-lb Jan 16, 2026
bf6a416
const
arnaud-lb Jan 16, 2026
20f755a
Simplify
arnaud-lb Jan 16, 2026
c09ff68
fix build
arnaud-lb Jan 16, 2026
a833b15
Fix test on x32
arnaud-lb Jan 16, 2026
0a2d4dd
Support PFAs in array_map() optimization
arnaud-lb Jan 19, 2026
b8fdf74
Reuse zp_argument_error()
arnaud-lb Jan 19, 2026
9bfedc6
WS
arnaud-lb Jan 19, 2026
063a2fa
Improve tests
arnaud-lb Jan 20, 2026
35f3df6
Add tests
arnaud-lb Jan 20, 2026
d2eba44
Fix tests
arnaud-lb Jan 21, 2026
7368f41
Fix arg name
arnaud-lb Jan 21, 2026
5e40c40
Add tests
arnaud-lb Feb 14, 2026
5b844c6
Fix tests after rebasing
arnaud-lb Feb 14, 2026
5e10990
Fix tests
arnaud-lb Feb 16, 2026
ca590ae
Improve tests
arnaud-lb Feb 16, 2026
75a13bb
CS
arnaud-lb Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_PLACEHOLDER:
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
opline->result.num = cache_size;
Expand All @@ -747,6 +748,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
cache_size += sizeof(void *);
}
break;
case ZEND_CALLABLE_CONVERT_PARTIAL:
opline->op1.num = cache_size;
cache_size += 2 * sizeof(void *);
break;
}
opline++;
}
Expand Down
6 changes: 4 additions & 2 deletions Zend/Optimizer/optimize_func_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
call--;
if (call_stack[call].func && call_stack[call].opline) {
zend_op *fcall = call_stack[call].opline;
Expand Down Expand Up @@ -225,13 +226,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
* At this point we also know whether or not the result of
* the DO opcode is used, allowing to optimize calls to
* ZEND_ACC_NODISCARD functions. */
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
if (opline->opcode != ZEND_CALLABLE_CONVERT && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline));
}

if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
&& call_stack[call].try_inline
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
&& opline->opcode != ZEND_CALLABLE_CONVERT
&& opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
}
}
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/zend_call_graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
func_info->flags |= ZEND_FUNC_HAS_CALLS;
if (call_info) {
call_info->caller_call_opline = opline;
Expand All @@ -142,6 +143,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_USER:
case ZEND_SEND_PLACEHOLDER:
if (call_info) {
if (opline->op2_type == IS_CONST) {
call_info->named_args = true;
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -3906,6 +3906,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
break;
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
break;
Expand Down

This file was deleted.

This file was deleted.

35 changes: 35 additions & 0 deletions Zend/tests/partial_application/assert.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
PFA of assert() behaves like a dynamic call to assert()
--FILE--
<?php

try {
echo "# Static call:\n";
assert(false);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}

try {
echo "# Dynamic call:\n";
(function ($f) { $f(false); })('assert');
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
}

try {
echo "# PFA call:\n";
$f = assert(?);
$f(false);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
}

?>
--EXPECT--
# Static call:
AssertionError: assert(false)
# Dynamic call:
AssertionError: (no message)
# PFA call:
AssertionError: (no message)
88 changes: 88 additions & 0 deletions Zend/tests/partial_application/attributes_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
--TEST--
PFA inherits NoDiscard and SensitiveParameter attributes
--FILE--
<?php

#[Attribute]
class Test {}

#[NoDiscard] #[Test]
function f($a, #[SensitiveParameter] $b, #[Test] ...$c) {
}

function dump_attributes($function) {
$r = new ReflectionFunction($function);
var_dump($r->getAttributes());

foreach ($r->getParameters() as $i => $p) {
echo "Parameter $i:\n";
var_dump($p->getAttributes());
}
}

echo "# Orig attributes:\n";

dump_attributes('f');

$f = f(1, ?, ?, ...);

echo "# PFA attributes:\n";

dump_attributes($f);

?>
--EXPECTF--
# Orig attributes:
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(4) "Test"
}
}
Parameter 0:
array(0) {
}
Parameter 1:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(18) "SensitiveParameter"
}
}
Parameter 2:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(4) "Test"
}
}
# PFA attributes:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
}
Parameter 0:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(18) "SensitiveParameter"
}
}
Parameter 1:
array(0) {
}
Parameter 2:
array(0) {
}
19 changes: 19 additions & 0 deletions Zend/tests/partial_application/attributes_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
PFA preserves #[SensitiveParameter]
--FILE--
<?php

function f($a, #[SensitiveParameter] $b, $c, #[SensitiveParameter] ...$d) {
throw new Exception();
}

$f = f(1, ?, ?, ?, ...)('normal param', 3, 'reified variadic', 'variadic');

?>
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 %s(%d): f(1, Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#1 %s(%d): {closure:pfa:%s:7}(Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#2 {main}
thrown in %s on line %d
16 changes: 16 additions & 0 deletions Zend/tests/partial_application/attributes_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
PFA preserves #[NoDiscard]
--FILE--
<?php

#[NoDiscard] function f($a) {
}

$f = f(?);
$f(1);

(void) $f(1);

?>
--EXPECTF--
Warning: The return value of function {closure:%s}() should either be used or intentionally ignored by casting it as (void) in %s on line 7
50 changes: 50 additions & 0 deletions Zend/tests/partial_application/clone.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
clone() can be partially applied
--FILE--
<?php

class C {
public function __construct(
public mixed $a,
public mixed $b,
) { }
}

$clone = clone(?);
var_dump($clone(new C(1, 2)));

$clone = clone(...);
var_dump($clone(new C(3, 4)));

$clone = clone(new C(5, 6), ?);
var_dump($clone(['a' => 7]));

$clone = clone(?, ['a' => 8]);
var_dump($clone(new C(9, 10)));

?>
--EXPECTF--
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
object(C)#%d (2) {
["a"]=>
int(3)
["b"]=>
int(4)
}
object(C)#%d (2) {
["a"]=>
int(7)
["b"]=>
int(6)
}
object(C)#%d (2) {
["a"]=>
int(8)
["b"]=>
int(10)
}
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: multiple variadic placeholders
--FILE--
<?php
foo(..., ...);
?>
--EXPECTF--
Fatal error: Variadic placeholder may only appear once in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last
--FILE--
<?php
foo(..., ?);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: placeholders can not appear after named args
--FILE--
<?php
foo(n: 5, ?);
?>
--EXPECTF--
Fatal error: Cannot use positional argument after named argument in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last, including after named args
--FILE--
<?php
foo(..., n: 5);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last, including after positional args
--FILE--
<?php
foo(..., $a);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/partial_application/compile_errors_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: can not use unpacking in PFA, including with variadic placeholdres
--FILE--
<?php
foo(...["foo" => "bar"], ...);
?>
--EXPECTF--
Fatal error: Cannot combine partial application and unpacking in %s on line %d
Loading