Skip to content

Commit f65bf45

Browse files
arnaud-lbkrakjoe
andcommitted
Partial application
Co-authored-by: Joe Watkins <[email protected]>
1 parent 469aa25 commit f65bf45

File tree

125 files changed

+5882
-675
lines changed

Some content is hidden

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

125 files changed

+5882
-675
lines changed

Zend/Optimizer/compact_literals.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,12 +734,17 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
734734
case ZEND_SEND_VAR_NO_REF_EX:
735735
case ZEND_SEND_REF:
736736
case ZEND_SEND_FUNC_ARG:
737+
case ZEND_SEND_PLACEHOLDER:
737738
case ZEND_CHECK_FUNC_ARG:
738739
if (opline->op2_type == IS_CONST) {
739740
opline->result.num = cache_size;
740741
cache_size += 2 * sizeof(void *);
741742
}
742743
break;
744+
case ZEND_CALLABLE_CONVERT_PARTIAL:
745+
opline->extended_value = cache_size;
746+
cache_size += 3 * sizeof(void *);
747+
break;
743748
}
744749
opline++;
745750
}

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: 2 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,6 +145,7 @@ 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 = 1;

Zend/Optimizer/zend_inference.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3905,6 +3905,7 @@ static zend_always_inline zend_result _zend_update_type_info(
39053905
}
39063906
break;
39073907
case ZEND_CALLABLE_CONVERT:
3908+
case ZEND_CALLABLE_CONVERT_PARTIAL:
39083909
UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
39093910
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
39103911
break;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Closure application copies attributes
3+
--FILE--
4+
<?php
5+
6+
#[Attribute]
7+
class Test {}
8+
9+
#[NoDiscard]
10+
function f($a, #[SensitiveParameter] $b, #[Test] ...$c) {
11+
}
12+
13+
function dump_attributes($function) {
14+
echo "Function attributes:\n";
15+
$r = new ReflectionFunction($function);
16+
var_dump($r->getAttributes());
17+
18+
foreach ($r->getParameters() as $i => $p) {
19+
echo "Parameter $i:\n";
20+
var_dump($p->getAttributes());
21+
}
22+
}
23+
24+
dump_attributes('f');
25+
26+
$f = f(1, ?, ?, ...);
27+
28+
dump_attributes($f);
29+
30+
?>
31+
--EXPECTF--
32+
Function attributes:
33+
array(1) {
34+
[0]=>
35+
object(ReflectionAttribute)#%d (1) {
36+
["name"]=>
37+
string(9) "NoDiscard"
38+
}
39+
}
40+
Parameter 0:
41+
array(0) {
42+
}
43+
Parameter 1:
44+
array(1) {
45+
[0]=>
46+
object(ReflectionAttribute)#%d (1) {
47+
["name"]=>
48+
string(18) "SensitiveParameter"
49+
}
50+
}
51+
Parameter 2:
52+
array(1) {
53+
[0]=>
54+
object(ReflectionAttribute)#%d (1) {
55+
["name"]=>
56+
string(4) "Test"
57+
}
58+
}
59+
Function attributes:
60+
array(1) {
61+
[0]=>
62+
object(ReflectionAttribute)#%d (1) {
63+
["name"]=>
64+
string(9) "NoDiscard"
65+
}
66+
}
67+
Parameter 0:
68+
array(1) {
69+
[0]=>
70+
object(ReflectionAttribute)#%d (1) {
71+
["name"]=>
72+
string(18) "SensitiveParameter"
73+
}
74+
}
75+
Parameter 1:
76+
array(1) {
77+
[0]=>
78+
object(ReflectionAttribute)#%d (1) {
79+
["name"]=>
80+
string(4) "Test"
81+
}
82+
}
83+
Parameter 2:
84+
array(1) {
85+
[0]=>
86+
object(ReflectionAttribute)#%d (1) {
87+
["name"]=>
88+
string(4) "Test"
89+
}
90+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
Closure application preserves #[SensitiveParameter]
3+
--FILE--
4+
<?php
5+
6+
function f($a, #[SensitiveParameter] $b, $c) {
7+
throw new Exception();
8+
}
9+
10+
echo "# During partial application:\n";
11+
12+
try {
13+
$f = f(1, 'sensitive');
14+
} catch (Error $e) {
15+
echo $e, "\n\n";
16+
}
17+
18+
echo "# In trampoline:\n";
19+
20+
try {
21+
$f = f(1, ?, ?)('sensitive');
22+
} catch (Error $e) {
23+
echo $e, "\n\n";
24+
}
25+
26+
echo "# In execution:\n";
27+
28+
try {
29+
$f = f(1, ?, ?)('sensitive', 3);
30+
} catch (Exception $e) {
31+
echo $e, "\n";
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
# During partial application:
37+
ArgumentCountError: Too few arguments to function f(), 2 passed in %s on line %d and exactly 3 expected in %s:%d
38+
Stack trace:
39+
#0 %s(%d): f(1, Object(SensitiveParameterValue))
40+
#1 {main}
41+
42+
# In trampoline:
43+
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected in %s:%d
44+
Stack trace:
45+
#0 %s(%d): {closure:%s}(Object(SensitiveParameterValue))
46+
#1 {main}
47+
48+
# In execution:
49+
Exception in %s:%d
50+
Stack trace:
51+
#0 %s(%d): f(1, Object(SensitiveParameterValue), 3)
52+
#1 %s(%d): {closure:%s}(Object(SensitiveParameterValue), 3)
53+
#2 {main}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Closure application 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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Closure application preserves #[Deprecated]
3+
--FILE--
4+
<?php
5+
6+
#[Deprecated] function f($a) {
7+
}
8+
9+
$f = f(?);
10+
$f(1);
11+
12+
?>
13+
--EXPECTF--
14+
Deprecated: Function {closure:%s}() is deprecated in %s on line 7
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
Closure application clone
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $a;
8+
public $b;
9+
}
10+
11+
$clone = clone(?);
12+
var_dump($clone(new C));
13+
14+
$clone = clone(...);
15+
var_dump($clone(new C));
16+
17+
$clone = clone(new C, ?);
18+
var_dump($clone(['a' => 1]));
19+
20+
$clone = clone(?, ['a' => 1]);
21+
var_dump($clone(new C));
22+
23+
?>
24+
--EXPECTF--
25+
object(C)#%d (2) {
26+
["a"]=>
27+
NULL
28+
["b"]=>
29+
NULL
30+
}
31+
object(C)#%d (2) {
32+
["a"]=>
33+
NULL
34+
["b"]=>
35+
NULL
36+
}
37+
object(C)#%d (2) {
38+
["a"]=>
39+
int(1)
40+
["b"]=>
41+
NULL
42+
}
43+
object(C)#%d (2) {
44+
["a"]=>
45+
int(1)
46+
["b"]=>
47+
NULL
48+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Closure application compile errors: multiple ...
3+
--FILE--
4+
<?php
5+
foo(..., ...);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Variadic placeholder may only appear once in %s on line %d
9+

0 commit comments

Comments
 (0)