Skip to content

Commit 1616f33

Browse files
arnaud-lbkrakjoe
andcommitted
Partial application
RFC: https://wiki.php.net/rfc/partial_function_application_v2 Co-authored-by: Joe Watkins <[email protected]>
1 parent 7dd5f31 commit 1616f33

File tree

145 files changed

+7301
-667
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+7301
-667
lines changed

Zend/Optimizer/compact_literals.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
735735
case ZEND_SEND_VAR_NO_REF_EX:
736736
case ZEND_SEND_REF:
737737
case ZEND_SEND_FUNC_ARG:
738+
case ZEND_SEND_PLACEHOLDER:
738739
case ZEND_CHECK_FUNC_ARG:
739740
if (opline->op2_type == IS_CONST) {
740741
opline->result.num = cache_size;
@@ -747,6 +748,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
747748
cache_size += sizeof(void *);
748749
}
749750
break;
751+
case ZEND_CALLABLE_CONVERT_PARTIAL:
752+
opline->op1.num = cache_size;
753+
cache_size += 2 * sizeof(void *);
754+
break;
750755
}
751756
opline++;
752757
}

Zend/Optimizer/optimize_func_calls.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
193193
case ZEND_DO_UCALL:
194194
case ZEND_DO_FCALL_BY_NAME:
195195
case ZEND_CALLABLE_CONVERT:
196+
case ZEND_CALLABLE_CONVERT_PARTIAL:
196197
call--;
197198
if (call_stack[call].func && call_stack[call].opline) {
198199
zend_op *fcall = call_stack[call].opline;
@@ -225,13 +226,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
225226
* At this point we also know whether or not the result of
226227
* the DO opcode is used, allowing to optimize calls to
227228
* ZEND_ACC_NODISCARD functions. */
228-
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
229+
if (opline->opcode != ZEND_CALLABLE_CONVERT && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
229230
opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline));
230231
}
231232

232233
if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
233234
&& call_stack[call].try_inline
234-
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
235+
&& opline->opcode != ZEND_CALLABLE_CONVERT
236+
&& opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
235237
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
236238
}
237239
}

Zend/Optimizer/zend_call_graph.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
128128
case ZEND_DO_UCALL:
129129
case ZEND_DO_FCALL_BY_NAME:
130130
case ZEND_CALLABLE_CONVERT:
131+
case ZEND_CALLABLE_CONVERT_PARTIAL:
131132
func_info->flags |= ZEND_FUNC_HAS_CALLS;
132133
if (call_info) {
133134
call_info->caller_call_opline = opline;
@@ -144,11 +145,15 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
144145
case ZEND_SEND_VAR_NO_REF:
145146
case ZEND_SEND_VAR_NO_REF_EX:
146147
case ZEND_SEND_USER:
148+
case ZEND_SEND_PLACEHOLDER:
147149
if (call_info) {
148150
if (opline->op2_type == IS_CONST) {
149151
call_info->named_args = true;
150152
break;
151153
}
154+
if (opline->opcode == ZEND_SEND_PLACEHOLDER && opline->op1.num == _IS_PLACEHOLDER_VARIADIC) {
155+
break;
156+
}
152157

153158
uint32_t num = opline->op2.num;
154159
if (num > 0) {

Zend/Optimizer/zend_inference.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,7 @@ static zend_always_inline zend_result _zend_update_type_info(
39063906
}
39073907
break;
39083908
case ZEND_CALLABLE_CONVERT:
3909+
case ZEND_CALLABLE_CONVERT_PARTIAL:
39093910
UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
39103911
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
39113912
break;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
PFA of assert() behaves like a dynamic call to assert()
3+
--FILE--
4+
<?php
5+
6+
try {
7+
echo "# Static call:\n";
8+
assert(false);
9+
} catch (Error $e) {
10+
echo $e::class, ": ", $e->getMessage(), "\n";
11+
}
12+
13+
try {
14+
echo "# Dynamic call:\n";
15+
(function ($f) { $f(false); })('assert');
16+
} catch (Error $e) {
17+
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
18+
}
19+
20+
try {
21+
echo "# PFA call:\n";
22+
$f = assert(?);
23+
$f(false);
24+
} catch (Error $e) {
25+
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
26+
}
27+
28+
?>
29+
--EXPECT--
30+
# Static call:
31+
AssertionError: assert(false)
32+
# Dynamic call:
33+
AssertionError: (no message)
34+
# PFA call:
35+
AssertionError: (no message)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
--TEST--
2+
PFA inherits NoDiscard and SensitiveParameter attributes
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class Test {}
8+
9+
#[NoDiscard] #[Test]
10+
function f($a, #[SensitiveParameter] $b, #[Test] ...$c) {
11+
}
12+
13+
function dump_attributes($function) {
14+
$r = new ReflectionFunction($function);
15+
var_dump($r->getAttributes());
16+
17+
foreach ($r->getParameters() as $i => $p) {
18+
echo "Parameter $i:\n";
19+
var_dump($p->getAttributes());
20+
}
21+
}
22+
23+
echo "# Orig attributes:\n";
24+
25+
dump_attributes('f');
26+
27+
$f = f(1, ?, ?, ...);
28+
29+
echo "# PFA attributes:\n";
30+
31+
dump_attributes($f);
32+
33+
?>
34+
--EXPECTF--
35+
# Orig attributes:
36+
array(2) {
37+
[0]=>
38+
object(ReflectionAttribute)#%d (1) {
39+
["name"]=>
40+
string(9) "NoDiscard"
41+
}
42+
[1]=>
43+
object(ReflectionAttribute)#%d (1) {
44+
["name"]=>
45+
string(4) "Test"
46+
}
47+
}
48+
Parameter 0:
49+
array(0) {
50+
}
51+
Parameter 1:
52+
array(1) {
53+
[0]=>
54+
object(ReflectionAttribute)#%d (1) {
55+
["name"]=>
56+
string(18) "SensitiveParameter"
57+
}
58+
}
59+
Parameter 2:
60+
array(1) {
61+
[0]=>
62+
object(ReflectionAttribute)#%d (1) {
63+
["name"]=>
64+
string(4) "Test"
65+
}
66+
}
67+
# PFA attributes:
68+
array(1) {
69+
[0]=>
70+
object(ReflectionAttribute)#%d (1) {
71+
["name"]=>
72+
string(9) "NoDiscard"
73+
}
74+
}
75+
Parameter 0:
76+
array(1) {
77+
[0]=>
78+
object(ReflectionAttribute)#%d (1) {
79+
["name"]=>
80+
string(18) "SensitiveParameter"
81+
}
82+
}
83+
Parameter 1:
84+
array(0) {
85+
}
86+
Parameter 2:
87+
array(0) {
88+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
PFA preserves #[SensitiveParameter]
3+
--FILE--
4+
<?php
5+
6+
function f($a, #[SensitiveParameter] $b, $c, #[SensitiveParameter] ...$d) {
7+
throw new Exception();
8+
}
9+
10+
$f = f(1, ?, ?, ?, ...)('normal param', 3, 'reified variadic', 'variadic');
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: Uncaught Exception in %s:%d
15+
Stack trace:
16+
#0 %s(%d): f(1, Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
17+
#1 %s(%d): {closure:pfa:%s:7}(Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
18+
#2 {main}
19+
thrown in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
PFA preserves #[NoDiscard]
3+
--FILE--
4+
<?php
5+
6+
#[NoDiscard] function f($a) {
7+
}
8+
9+
$f = f(?);
10+
$f(1);
11+
12+
(void) $f(1);
13+
14+
?>
15+
--EXPECTF--
16+
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
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--TEST--
2+
clone() can be partially applied
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public function __construct(
8+
public mixed $a,
9+
public mixed $b,
10+
) { }
11+
}
12+
13+
$clone = clone(?);
14+
var_dump($clone(new C(1, 2)));
15+
16+
$clone = clone(...);
17+
var_dump($clone(new C(3, 4)));
18+
19+
$clone = clone(new C(5, 6), ?);
20+
var_dump($clone(['a' => 7]));
21+
22+
$clone = clone(?, ['a' => 8]);
23+
var_dump($clone(new C(9, 10)));
24+
25+
?>
26+
--EXPECTF--
27+
object(C)#%d (2) {
28+
["a"]=>
29+
int(1)
30+
["b"]=>
31+
int(2)
32+
}
33+
object(C)#%d (2) {
34+
["a"]=>
35+
int(3)
36+
["b"]=>
37+
int(4)
38+
}
39+
object(C)#%d (2) {
40+
["a"]=>
41+
int(7)
42+
["b"]=>
43+
int(6)
44+
}
45+
object(C)#%d (2) {
46+
["a"]=>
47+
int(8)
48+
["b"]=>
49+
int(10)
50+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
PFA compile errors: multiple variadic placeholders
3+
--FILE--
4+
<?php
5+
foo(..., ...);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Variadic placeholder may only appear once in %s on line %d

0 commit comments

Comments
 (0)