Skip to content

Commit 5c4c79e

Browse files
committed
first class callable conversion
1 parent 7bc8f2c commit 5c4c79e

17 files changed

+730
-520
lines changed
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 "OK";
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+
OK
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 "OK CALL\n";
8+
}
9+
10+
public static function __callStatic($method, $args) {
11+
return "OK CALL STATIC\n";
12+
}
13+
}
14+
15+
$foo = new Foo;
16+
$bar = $foo->anything(...);
17+
18+
echo $bar();
19+
20+
$qux = Foo::anything(...);
21+
22+
echo $qux();
23+
?>
24+
--EXPECT--
25+
OK CALL
26+
OK CALL STATIC
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
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
First Class Callable from Special Compiler Function
3+
--FILE--
4+
<?php
5+
$strlen = strlen(...);
6+
7+
if ($strlen("Hello World") === 11) {
8+
echo "OK";
9+
}
10+
?>
11+
--EXPECT--
12+
OK

Zend/zend_ast.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ enum _zend_ast_kind {
7070
ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,
7171
ZEND_AST_TYPE,
7272
ZEND_AST_CONSTANT_CLASS,
73+
ZEND_AST_CALLABLE_CONVERT,
7374

7475
/* 1 child node */
7576
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,

Zend/zend_closures.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,8 @@ static zend_object *zend_closure_clone(zend_object *zobject) /* {{{ */
486486

487487
int zend_closure_get_closure(zend_object *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr, bool check_only) /* {{{ */
488488
{
489-
zend_closure *closure = (zend_closure *)obj;
489+
zend_closure *closure = (zend_closure*)obj;
490+
490491
*fptr_ptr = &closure->func;
491492
*ce_ptr = closure->called_scope;
492493

@@ -734,6 +735,36 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_clas
734735
}
735736
/* }}} */
736737

738+
void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* {{{ */
739+
zval instance;
740+
zend_internal_function trampoline;
741+
zend_function *mptr = call->func;
742+
743+
if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) {
744+
RETURN_OBJ(ZEND_CLOSURE_OBJECT(mptr));
745+
}
746+
747+
if (mptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
748+
memset(&trampoline, 0, sizeof(zend_internal_function));
749+
trampoline.type = ZEND_INTERNAL_FUNCTION;
750+
trampoline.fn_flags = mptr->common.fn_flags & ZEND_ACC_STATIC;
751+
trampoline.handler = zend_closure_call_magic;
752+
trampoline.function_name = mptr->common.function_name;
753+
trampoline.scope = mptr->common.scope;
754+
755+
zend_free_trampoline(mptr);
756+
mptr = (zend_function *) &trampoline;
757+
}
758+
759+
if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_THIS) {
760+
ZVAL_OBJ(&instance, Z_OBJ(call->This));
761+
762+
zend_create_fake_closure(return_value, mptr, mptr->common.scope, mptr->common.scope, &instance);
763+
} else {
764+
zend_create_fake_closure(return_value, mptr, mptr->common.scope, mptr->common.scope, NULL);
765+
}
766+
} /* }}} */
767+
737768
void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var) /* {{{ */
738769
{
739770
zend_closure *closure = (zend_closure *) Z_OBJ_P(closure_zv);

Zend/zend_closures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ BEGIN_EXTERN_C()
2929
void zend_register_closure_ce(void);
3030
void zend_closure_bind_var(zval *closure_zv, zend_string *var_name, zval *var);
3131
void zend_closure_bind_var_ex(zval *closure_zv, uint32_t offset, zval *val);
32+
void zend_closure_from_frame(zval *closure_zv, zend_execute_data *frame);
3233

3334
extern ZEND_API zend_class_entry *zend_ce_closure;
3435

Zend/zend_compile.c

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3678,25 +3678,39 @@ void zend_compile_call_common(znode *result, zend_ast *args_ast, zend_function *
36783678
{
36793679
zend_op *opline;
36803680
uint32_t opnum_init = get_next_op_number() - 1;
3681-
uint32_t arg_count;
3682-
bool may_have_extra_named_args;
36833681

3684-
arg_count = zend_compile_args(args_ast, fbc, &may_have_extra_named_args);
3682+
if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) {
3683+
bool may_have_extra_named_args;
3684+
uint32_t arg_count = zend_compile_args(args_ast, fbc, &may_have_extra_named_args);
36853685

3686-
zend_do_extended_fcall_begin();
3686+
zend_do_extended_fcall_begin();
36873687

3688-
opline = &CG(active_op_array)->opcodes[opnum_init];
3689-
opline->extended_value = arg_count;
3688+
opline = &CG(active_op_array)->opcodes[opnum_init];
3689+
opline->extended_value = arg_count;
36903690

3691-
if (opline->opcode == ZEND_INIT_FCALL) {
3692-
opline->op1.num = zend_vm_calc_used_stack(arg_count, fbc);
3693-
}
3691+
if (opline->opcode == ZEND_INIT_FCALL) {
3692+
opline->op1.num = zend_vm_calc_used_stack(arg_count, fbc);
3693+
}
3694+
3695+
opline = zend_emit_op(result, zend_get_call_op(opline, fbc), NULL, NULL);
3696+
if (may_have_extra_named_args) {
3697+
opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS;
3698+
}
3699+
zend_do_extended_fcall_end();
3700+
} else {
3701+
opline = &CG(active_op_array)->opcodes[opnum_init];
3702+
opline->extended_value = 0;
3703+
3704+
if (opline->opcode == ZEND_NEW) {
3705+
zend_error_noreturn(E_COMPILE_ERROR, "cannot create Closure from call to constructor");
3706+
}
36943707

3695-
opline = zend_emit_op(result, zend_get_call_op(opline, fbc), NULL, NULL);
3696-
if (may_have_extra_named_args) {
3697-
opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS;
3708+
if (opline->opcode == ZEND_INIT_FCALL) {
3709+
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
3710+
}
3711+
3712+
opline = zend_emit_op(result, ZEND_CALLABLE_CONVERT, NULL, NULL);
36983713
}
3699-
zend_do_extended_fcall_end();
37003714
}
37013715
/* }}} */
37023716

@@ -4437,7 +4451,8 @@ void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
44374451
fbc = zend_hash_find_ptr(CG(function_table), lcname);
44384452

44394453
/* Special assert() handling should apply independently of compiler flags. */
4440-
if (fbc && zend_string_equals_literal(lcname, "assert")) {
4454+
if ((args_ast->kind != ZEND_AST_CALLABLE_CONVERT) &&
4455+
fbc && zend_string_equals_literal(lcname, "assert")) {
44414456
zend_compile_assert(result, zend_ast_get_list(args_ast), lcname, fbc);
44424457
zend_string_release(lcname);
44434458
zval_ptr_dtor(&name_node.u.constant);
@@ -4454,7 +4469,8 @@ void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
44544469
return;
44554470
}
44564471

4457-
if (zend_try_compile_special_func(result, lcname,
4472+
if ((args_ast->kind != ZEND_AST_CALLABLE_CONVERT) &&
4473+
zend_try_compile_special_func(result, lcname,
44584474
zend_ast_get_list(args_ast), fbc, type) == SUCCESS
44594475
) {
44604476
zend_string_release_ex(lcname, 0);

Zend/zend_language_parser.y

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ return_type:
827827
argument_list:
828828
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
829829
| '(' non_empty_argument_list possible_comma ')' { $$ = $2; }
830+
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create(ZEND_AST_CALLABLE_CONVERT); }
830831
;
831832

832833
non_empty_argument_list:

Zend/zend_vm_def.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9217,6 +9217,24 @@ ZEND_VM_HANDLER(167, ZEND_COPY_TMP, TMPVAR, UNUSED)
92179217
ZEND_VM_NEXT_OPCODE();
92189218
}
92199219

9220+
ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED)
9221+
{
9222+
USE_OPLINE
9223+
zend_execute_data *call = EX(call);
9224+
9225+
zend_closure_from_frame(EX_VAR(opline->result.var), call);
9226+
9227+
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
9228+
OBJ_RELEASE(Z_OBJ(call->This));
9229+
}
9230+
9231+
EX(call) = call->prev_execute_data;
9232+
9233+
zend_vm_stack_free_call_frame(call);
9234+
9235+
ZEND_VM_NEXT_OPCODE();
9236+
}
9237+
92209238
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_JMP, (OP_JMP_ADDR(op, op->op1) > op), ZEND_JMP_FORWARD, JMP_ADDR, ANY)
92219239
{
92229240
USE_OPLINE

0 commit comments

Comments
 (0)