Skip to content

Commit d0b09a7

Browse files
krakjoenikic
andcommitted
Add first-class callables
Support acquiring a Closure to a callable using the syntax func(...), $obj->method(...), etc. This is essentially a shortcut for Closure::fromCallable(). RFC: https://wiki.php.net/rfc/first_class_callable_syntax Closes GH-7019. Co-Authored-By: Nikita Popov <nikita.ppv@gmail.com>
1 parent 42cb5b5 commit d0b09a7

40 files changed

+1078
-515
lines changed

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ PHP 8.1 UPGRADE NOTES
197197
default values, static variable and global constant initializers, as well
198198
as attribute arguments.
199199
RFC: https://wiki.php.net/rfc/new_in_initializers
200+
. Closures for callables can now be created using the syntax `myFunc(...)`,
201+
which is the same as `Closure::fromCallable('myFunc')`. Yes, the `...` is
202+
part of the syntax, not an omission.
203+
RFC: https://wiki.php.net/rfc/first_class_callable_syntax
200204
. File uploads now provide an additional full_path key, which contains the
201205
full path (rather than just the basename) of the uploaded file. This is
202206
intended for use in conjunction with "upload webkitdirectory".

Zend/Optimizer/optimize_func_calls.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
188188
case ZEND_DO_ICALL:
189189
case ZEND_DO_UCALL:
190190
case ZEND_DO_FCALL_BY_NAME:
191+
case ZEND_CALLABLE_CONVERT:
191192
call--;
192193
if (call_stack[call].func && call_stack[call].opline) {
193194
zend_op *fcall = call_stack[call].opline;
@@ -216,7 +217,8 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
216217
}
217218

218219
if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
219-
&& call_stack[call].try_inline) {
220+
&& call_stack[call].try_inline
221+
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
220222
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
221223
}
222224
}

Zend/Optimizer/zend_call_graph.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_
106106
case ZEND_DO_ICALL:
107107
case ZEND_DO_UCALL:
108108
case ZEND_DO_FCALL_BY_NAME:
109+
case ZEND_CALLABLE_CONVERT:
109110
func_info->flags |= ZEND_FUNC_HAS_CALLS;
110111
if (call_info) {
111112
call_info->caller_call_opline = opline;

Zend/Optimizer/zend_inference.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "zend_inference.h"
2222
#include "zend_func_info.h"
2323
#include "zend_call_graph.h"
24+
#include "zend_closures.h"
2425
#include "zend_worklist.h"
2526
#include "zend_optimizer_internal.h"
2627

@@ -3504,6 +3505,10 @@ static zend_always_inline int _zend_update_type_info(
35043505
}
35053506
}
35063507
break;
3508+
case ZEND_CALLABLE_CONVERT:
3509+
UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
3510+
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
3511+
break;
35073512
case ZEND_FETCH_CONSTANT:
35083513
case ZEND_FETCH_CLASS_CONSTANT:
35093514
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
First Class Callable from Internal
3+
--FILE--
4+
<?php
5+
$sprintf = sprintf(...);
6+
7+
echo $sprintf("Hello %s", "World");
8+
?>
9+
--EXPECT--
10+
Hello World
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
First Class Callable from User
3+
--FILE--
4+
<?php
5+
function foo() {
6+
return "OK";
7+
}
8+
9+
$foo = foo(...);
10+
11+
echo $foo();
12+
?>
13+
--EXPECT--
14+
OK
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
First Class Callable from Method
3+
--FILE--
4+
<?php
5+
class Foo {
6+
public function bar() {
7+
echo "OK";
8+
}
9+
}
10+
11+
$foo = new Foo;
12+
$bar = $foo->bar(...);
13+
14+
echo $bar();
15+
?>
16+
--EXPECT--
17+
OK
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
First Class Callable from Private Scope
3+
--FILE--
4+
<?php
5+
class Foo {
6+
private function method() {
7+
return __METHOD__;
8+
}
9+
10+
public function bar() {
11+
return $this->method(...);
12+
}
13+
}
14+
15+
$foo = new Foo;
16+
$bar = $foo->bar(...);
17+
18+
echo ($bar())();
19+
?>
20+
--EXPECT--
21+
Foo::method
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
First Class Callable from Magic
3+
--FILE--
4+
<?php
5+
class Foo {
6+
public function __call($method, $args) {
7+
return $method;
8+
}
9+
10+
public static function __callStatic($method, $args) {
11+
return $method;
12+
}
13+
}
14+
15+
$foo = new Foo;
16+
$bar = $foo->anythingInstance(...);
17+
18+
echo $bar() . PHP_EOL;
19+
20+
$qux = Foo::anythingStatic(...);
21+
22+
echo $qux();
23+
?>
24+
--EXPECT--
25+
anythingInstance
26+
anythingStatic
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
First Class Callable from Closure
3+
--FILE--
4+
<?php
5+
$foo = function(){};
6+
$bar = $foo(...);
7+
8+
if ($foo === $bar) {
9+
echo "OK";
10+
}
11+
?>
12+
--EXPECT--
13+
OK

0 commit comments

Comments
 (0)