Skip to content

first class callable conversion #7019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 18 commits into from
Closed
4 changes: 3 additions & 1 deletion Zend/Optimizer/optimize_func_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
call--;
if (call_stack[call].func && call_stack[call].opline) {
zend_op *fcall = call_stack[call].opline;
Expand Down Expand Up @@ -216,7 +217,8 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
}

if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
&& call_stack[call].try_inline) {
&& call_stack[call].try_inline
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
}
}
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_call_graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
func_info->flags |= ZEND_FUNC_HAS_CALLS;
if (call_info) {
call_info->caller_call_opline = opline;
Expand Down
5 changes: 5 additions & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "zend_inference.h"
#include "zend_func_info.h"
#include "zend_call_graph.h"
#include "zend_closures.h"
#include "zend_worklist.h"
#include "zend_optimizer_internal.h"

Expand Down Expand Up @@ -3504,6 +3505,10 @@ static zend_always_inline int _zend_update_type_info(
}
}
break;
case ZEND_CALLABLE_CONVERT:
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;
case ZEND_FETCH_CONSTANT:
case ZEND_FETCH_CLASS_CONSTANT:
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);
Expand Down
10 changes: 10 additions & 0 deletions Zend/tests/first_class_callable_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
First Class Callable from Internal
--FILE--
<?php
$sprintf = sprintf(...);

echo $sprintf("Hello %s", "World");
?>
--EXPECT--
Hello World
14 changes: 14 additions & 0 deletions Zend/tests/first_class_callable_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
First Class Callable from User
--FILE--
<?php
function foo() {
return "OK";
}

$foo = foo(...);

echo $foo();
?>
--EXPECT--
OK
17 changes: 17 additions & 0 deletions Zend/tests/first_class_callable_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
First Class Callable from Method
--FILE--
<?php
class Foo {
public function bar() {
echo "OK";
}
}

$foo = new Foo;
$bar = $foo->bar(...);

echo $bar();
?>
--EXPECT--
OK
21 changes: 21 additions & 0 deletions Zend/tests/first_class_callable_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
First Class Callable from Private Scope
--FILE--
<?php
class Foo {
private function method() {
return __METHOD__;
}

public function bar() {
return $this->method(...);
}
}

$foo = new Foo;
$bar = $foo->bar(...);

echo ($bar())();
?>
--EXPECT--
Foo::method
26 changes: 26 additions & 0 deletions Zend/tests/first_class_callable_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
First Class Callable from Magic
--FILE--
<?php
class Foo {
public function __call($method, $args) {
return $method;
}

public static function __callStatic($method, $args) {
return $method;
}
}

$foo = new Foo;
$bar = $foo->anythingInstance(...);

echo $bar() . PHP_EOL;

$qux = Foo::anythingStatic(...);

echo $qux();
?>
--EXPECT--
anythingInstance
anythingStatic
13 changes: 13 additions & 0 deletions Zend/tests/first_class_callable_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
First Class Callable from Closure
--FILE--
<?php
$foo = function(){};
$bar = $foo(...);

if ($foo === $bar) {
echo "OK";
}
?>
--EXPECT--
OK
12 changes: 12 additions & 0 deletions Zend/tests/first_class_callable_007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
First Class Callable from Special Compiler Function
--FILE--
<?php
$strlen = strlen(...);

if ($strlen("Hello World") === 11) {
echo "OK";
}
?>
--EXPECT--
OK
12 changes: 12 additions & 0 deletions Zend/tests/first_class_callable_008.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
First Class Callable from NEW
--FILE--
<?php
class Foo {

}

new Foo(...);
?>
--EXPECTF--
Fatal error: Cannot create Closure for new expression in %s on line 6
14 changes: 14 additions & 0 deletions Zend/tests/first_class_callable_009.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
First Class Callable from Closure::__invoke
--FILE--
<?php
$closure = function() {
return "OK";
};

$foo = $closure->__invoke(...);

echo $foo();
?>
--EXPECT--
OK
18 changes: 18 additions & 0 deletions Zend/tests/first_class_callable_010.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
First Class Callable preserve Called Scope
--FILE--
<?php
class Foo {
public static function method() {
return static::class;
}
}

class Bar extends Foo {}

$bar = Bar::method(...);

echo $bar();
?>
--EXPECT--
Bar
11 changes: 11 additions & 0 deletions Zend/tests/first_class_callable_011.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
First Class Callable Attribute Error
--FILE--
<?php
#[Attribute(...)]
class Foo {

}
?>
--EXPECTF--
Fatal error: Cannot create Closure as attribute argument in %s on line 3
10 changes: 10 additions & 0 deletions Zend/tests/first_class_callable_012.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
First class callable with nullsafe method call
--FILE--
<?php

$foo?->bar(...);

?>
--EXPECTF--
Fatal error: Cannot combine nullsafe operator with Closure creation in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/first_class_callable_013.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
First class callable with nullsafe method call (nested)
--FILE--
<?php

$foo?->foo->bar(...);

?>
--EXPECTF--
Fatal error: Cannot combine nullsafe operator with Closure creation in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/first_class_callable_014.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
First class callable with nullsafe method call (unrelated)
--FILE--
<?php

$foo = null;
var_dump($foo?->foo($foo->bar(...)));

?>
--EXPECT--
NULL
24 changes: 24 additions & 0 deletions Zend/tests/first_class_callable_015.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
First class callables and strict types
--FILE--
<?php
declare(strict_types=1);

function test(int $i) {
var_dump($i);
}

require __DIR__ . '/first_class_callable_015_weak.inc';
require __DIR__ . '/first_class_callable_015_strict.inc';
$fn = test(...);
do_weak_call($fn);
try {
do_strict_call($fn);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECTF--
int(42)
test(): Argument #1 ($i) must be of type int, string given, called in %s on line %d
5 changes: 5 additions & 0 deletions Zend/tests/first_class_callable_015_strict.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php declare(strict_types=1);

function do_strict_call(Closure $fn) {
$fn("42");
}
5 changes: 5 additions & 0 deletions Zend/tests/first_class_callable_015_weak.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

function do_weak_call(Closure $fn) {
$fn("42");
}
25 changes: 25 additions & 0 deletions Zend/tests/first_class_callable_assert.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Acquire callable to assert()
--FILE--
<?php

namespace Foo;

$assert = assert(...);
$assert(1 == 1.0, "Message 1");
try {
$assert(1 == 2.0, "Message 2");
} catch (\AssertionError $e) {
echo $e->getMessage(), "\n";
}

try {
assert(false && strlen(...));
} catch (\AssertionError $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Message 2
assert(false && strlen(...))
53 changes: 53 additions & 0 deletions Zend/tests/first_class_callable_dynamic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
--TEST--
Acquire callable through various dynamic constructs
--FILE--
<?php

class A {
public static function b($x) {
return $x;
}

public function c($x) {
return $x;
}
}

$name = 'strlen';
$fn = $name(...);
var_dump($fn('x'));

$name = ['A', 'b'];
$fn = $name(...);
var_dump($fn(2));

$name = [new A, 'c'];
$fn = $name(...);
var_dump($fn(3));

$name1 = 'A';
$name2 = 'b';
$fn = $name1::$name2(...);
var_dump($fn(4));

$name2 = 'c';
$fn = (new A)->$name2(...);
var_dump($fn(5));

$fn = [A::class, 'b'](...);
var_dump($fn(6));

$o = new stdClass;
$o->prop = A::b(...);
($o->prop)(7);

$nam

?>
--EXPECT--
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
Loading