Partial function application (#20848)

RFC: https://wiki.php.net/rfc/partial_function_application_v2

Co-authored-by: Joe Watkins <krakjoe@php.net>
This commit is contained in:
Arnaud Le BlancandJoe Watkins authored and GitHub committed 2026-07-17 10:09:22 +02:00
1 parent 47355da494
commit b5fba859ea
161 files changed
+8058 -698

No files matched your search

+3
View File
@@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.6.0alpha3
- Core:
. Implemented partial function application RFC. (Arnaud)
- GMP:
. Fixed GMP power and shift operators to reject GMP right operands outside
the unsigned long range instead of silently truncating them. (Weilin Du)
+2
View File
@@ -235,6 +235,8 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/debugable-enums
. #[\Override] can now be applied to class constants, including enum cases.
RFC: https://wiki.php.net/rfc/override_constants
. Implemented partial function application
RFC: https://wiki.php.net/rfc/partial_function_application_v2
- Curl:
. curl_getinfo() return array now includes a new size_delivered key, which
+4
View File
@@ -132,6 +132,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. zend_argument_error_variadic() now takes a new 'function' parameters.
. Added zend_argument_error_ex(), zend_argument_type_error_ex(),
zend_argument_value_error_ex().
. Added zend_ast_dup().
. Added zend_compile_ast().
. Added zend_check_type_ex().
. Added zend_create_partial_closure().
========================
2. Build system changes
+5
View File
@@ -733,6 +733,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
case ZEND_SEND_FUNC_ARG:
case ZEND_SEND_PLACEHOLDER:
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
opline->result.num = cache_size;
@@ -745,6 +746,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
cache_size += sizeof(void *);
}
break;
case ZEND_CALLABLE_CONVERT_PARTIAL:
opline->op1.num = cache_size;
cache_size += 2 * sizeof(void *);
break;
}
opline++;
}
+4 -2
View File
@@ -191,6 +191,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
call--;
if (call_stack[call].func && call_stack[call].opline) {
zend_op *fcall = call_stack[call].opline;
@@ -223,13 +224,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
* At this point we also know whether or not the result of
* the DO opcode is used, allowing to optimize calls to
* ZEND_ACC_NODISCARD functions. */
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
if (opline->opcode != ZEND_CALLABLE_CONVERT && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline));
}
if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
&& call_stack[call].try_inline
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
&& opline->opcode != ZEND_CALLABLE_CONVERT
&& opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
}
}
+2
View File
@@ -124,6 +124,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
func_info->flags |= ZEND_FUNC_HAS_CALLS;
if (call_info) {
call_info->caller_call_opline = opline;
@@ -140,6 +141,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_USER:
case ZEND_SEND_PLACEHOLDER:
if (call_info) {
if (opline->op2_type == IS_CONST) {
call_info->named_args = true;
+1
View File
@@ -3903,6 +3903,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
break;
case ZEND_CALLABLE_CONVERT:
case ZEND_CALLABLE_CONVERT_PARTIAL:
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;
@@ -1,10 +0,0 @@
--TEST--
First class callable error: more than one argument
--FILE--
<?php
foo(1, ...);
?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
@@ -1,10 +0,0 @@
--TEST--
First class callable error: non-variadic placeholder
--FILE--
<?php
foo(?);
?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
@@ -0,0 +1,45 @@
--TEST--
PFA of assert() behaves like a dynamic call to assert()
--FILE--
<?php
try {
echo "# Static call:\n";
assert(false);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
echo "# Dynamic call:\n";
(function ($f) { $f(false); })('assert');
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
}
try {
echo "# PFA call:\n";
$f = assert(?);
$f(false);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
}
try {
echo "# Upper-case assert():\n";
$f = ASSERT(?);
$f(false);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
}
?>
--EXPECT--
# Static call:
AssertionError: assert(false)
# Dynamic call:
AssertionError: (no message)
# PFA call:
AssertionError: (no message)
# Upper-case assert():
AssertionError: (no message)
@@ -0,0 +1,88 @@
--TEST--
PFA inherits NoDiscard and SensitiveParameter attributes
--FILE--
<?php
#[Attribute]
class Test {}
#[NoDiscard] #[Test]
function f($a, #[SensitiveParameter] $b, #[Test] ...$c) {
}
function dump_attributes($function) {
$r = new ReflectionFunction($function);
var_dump($r->getAttributes());
foreach ($r->getParameters() as $i => $p) {
echo "Parameter $i:\n";
var_dump($p->getAttributes());
}
}
echo "# Orig attributes:\n";
dump_attributes('f');
$f = f(1, ?, ?, ...);
echo "# PFA attributes:\n";
dump_attributes($f);
?>
--EXPECTF--
# Orig attributes:
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(4) "Test"
}
}
Parameter 0:
array(0) {
}
Parameter 1:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(18) "SensitiveParameter"
}
}
Parameter 2:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(4) "Test"
}
}
# PFA attributes:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
}
Parameter 0:
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(18) "SensitiveParameter"
}
}
Parameter 1:
array(0) {
}
Parameter 2:
array(0) {
}
@@ -0,0 +1,19 @@
--TEST--
PFA preserves #[SensitiveParameter]
--FILE--
<?php
function f($a, #[SensitiveParameter] $b, $c, #[SensitiveParameter] ...$d) {
throw new Exception();
}
$f = f(1, ?, ?, ?, ...)('normal param', 3, 'reified variadic', 'variadic');
?>
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 %s(%d): f(1, Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#1 %s(%d): {closure:pfa:%s:7}(Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
#2 {main}
thrown in %s on line %d
@@ -0,0 +1,16 @@
--TEST--
PFA preserves #[NoDiscard]
--FILE--
<?php
#[NoDiscard] function f($a) {
}
$f = f(?);
$f(1);
(void) $f(1);
?>
--EXPECTF--
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
+50
View File
@@ -0,0 +1,50 @@
--TEST--
clone() can be partially applied
--FILE--
<?php
class C {
public function __construct(
public mixed $a,
public mixed $b,
) { }
}
$clone = clone(?);
var_dump($clone(new C(1, 2)));
$clone = clone(...);
var_dump($clone(new C(3, 4)));
$clone = clone(new C(5, 6), ?);
var_dump($clone(['a' => 7]));
$clone = clone(?, ['a' => 8]);
var_dump($clone(new C(9, 10)));
?>
--EXPECTF--
object(C)#%d (2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
object(C)#%d (2) {
["a"]=>
int(3)
["b"]=>
int(4)
}
object(C)#%d (2) {
["a"]=>
int(7)
["b"]=>
int(6)
}
object(C)#%d (2) {
["a"]=>
int(8)
["b"]=>
int(10)
}
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: multiple variadic placeholders
--FILE--
<?php
foo(..., ...);
?>
--EXPECTF--
Fatal error: Variadic placeholder may only appear once in %s on line %d
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last
--FILE--
<?php
foo(..., ?);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: placeholders can not appear after named args
--FILE--
<?php
foo(n: 5, ?);
?>
--EXPECTF--
Fatal error: Cannot use positional argument after named argument in %s on line %d
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last, including after named args
--FILE--
<?php
foo(..., n: 5);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: variadic placeholder must be last, including after positional args
--FILE--
<?php
foo(..., $a);
?>
--EXPECTF--
Fatal error: Variadic placeholder must be last in %s on line %d
@@ -0,0 +1,8 @@
--TEST--
PFA compile errors: can not use unpacking in PFA, including with variadic placeholders
--FILE--
<?php
foo(...["foo" => "bar"], ...);
?>
--EXPECTF--
Fatal error: Cannot combine partial application and unpacking in %s on line %d
@@ -0,0 +1,14 @@
--TEST--
PFAs may emit deprecation warnings
--FILE--
<?php
#[Deprecated] function f($a) {
}
$f = f(?);
$f(1);
?>
--EXPECTF--
Deprecated: Function f() is deprecated in %s on line %d
@@ -0,0 +1,62 @@
--TEST--
PFA errors: PFA instantiation follows the usual argument count validation
--FILE--
<?php
function foo($a, $b, $c) {
}
class C {
function f($a, $b, $c) {
}
}
try {
foo(?);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
try {
foo(?, ?, ?, ?);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
try {
$c = new C();
$c->f(?);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
try {
property_exists(?);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
try {
usleep(?, ?);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
try {
foo(?, ?, ?, ?, ...);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
/* It is allowed to specify less than the number of required params, when there
* is a variadic placeholder */
foo(?, ...);
?>
--EXPECT--
ArgumentCountError: Partial application of foo() expects exactly 3 arguments, 1 given
ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given
ArgumentCountError: Partial application of C::f() expects exactly 3 arguments, 1 given
ArgumentCountError: Partial application of property_exists() expects exactly 2 arguments, 1 given
ArgumentCountError: Partial application of usleep() expects at most 1 arguments, 2 given
ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given
@@ -0,0 +1,15 @@
--TEST--
PFA errors: named parameter that resolve to the position of a placeholder is an error
--FILE--
<?php
function foo($a) {
}
try {
foo(?, a: 1);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
?>
--EXPECT--
Error: Named parameter $a overwrites previous placeholder
@@ -0,0 +1,74 @@
--TEST--
PFA errors: PFA call follows the usual argument count validation
--FILE--
<?php
function foo($a, ...$b) {
}
function bar($a, $b, $c) {}
$foo = foo(?);
try {
$foo();
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$foo = foo(?, ?);
try {
$foo(1);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$bar = bar(?, ?, ...);
try {
$bar(1);
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
class Foo {
public function bar($a, ...$b) {}
}
$foo = new Foo;
$bar = $foo->bar(?);
try {
$bar();
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$repeat = str_repeat('a', ...);
try {
$repeat();
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$usleep = usleep(?);
try {
$usleep();
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$usleep(1, 2);
?>
--EXPECTF--
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 3 expected
ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
@@ -0,0 +1,16 @@
--TEST--
PFA errors: not specifying a required param is an error
--FILE--
<?php
function f($a, $b, $c = null) {
}
$f = f(1, c: ?);
?>
--EXPECTF--
Fatal error: Uncaught ArgumentCountError: f(): Argument #2 ($b) not passed in %s:6
Stack trace:
#0 {main}
thrown in %s on line %d
@@ -0,0 +1,20 @@
--TEST--
PFA errors: Can not fetch default parameter value for Closure::__invoke()
--FILE--
<?php
$f = function ($a, $b, $c = null) {};
$g = $f->__invoke(0, 0, ?);
echo "No error\n";
try {
$g = $f->__invoke(0, 0, ...);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
No error
ArgumentCountError: Closure::__invoke(): Argument #3 ($c) must be passed explicitly, because the default value is not known
@@ -0,0 +1,45 @@
--TEST--
PFA errors: Some internal function parameters have UNKNOWN default value
--FILE--
<?php
$f = array_keys(?, 42);
var_dump($f([41, 42, 43]));
$f = array_keys(?, '42', true);
var_dump($f([41, 42, 43]));
$f = array_keys(?, '42', strict: ?);
var_dump($f([41, 42, 43], false));
var_dump($f([41, 42, 43], true));
try {
// fn (array $array) => array_keys($array, ???, true)
$f = array_keys(?, strict: true);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
// fn (array $array, mixed $filter_value = ???) => array_keys($array, $filter_value, true)
$f = array_keys(?, strict: true, ...);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
array(0) {
}
array(1) {
[0]=>
int(1)
}
array(0) {
}
ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known
ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known
@@ -0,0 +1,14 @@
--TEST--
PFA AST export
--INI--
assert.exception=1
--FILE--
<?php
try {
assert(0 && foo(?) && foo(new stdClass, bar: 1, ...));
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
?>
--EXPECT--
AssertionError: assert(0 && foo(?) && foo(new stdClass(), bar: 1, ...))
@@ -0,0 +1,49 @@
--TEST--
PFA extra named parameters are forwarded to the actual function
--FILE--
<?php
function foo(...$args) {
var_dump($args);
}
$foo = foo(foo: "foo", ...);
$bar = $foo(bar: "bar", ...);
$baz = $bar(baz: "baz", ...);
$baz();
$foo = foo(...);
$bar = $foo(bar: "bar", ...);
$baz = $bar(baz: "baz", ...);
$baz();
$foo = foo(foo: "foo", ...);
$foo(bar: "bar");
?>
--EXPECT--
array(3) {
["foo"]=>
string(3) "foo"
["bar"]=>
string(3) "bar"
["baz"]=>
string(3) "baz"
}
array(2) {
["bar"]=>
string(3) "bar"
["baz"]=>
string(3) "baz"
}
array(2) {
["foo"]=>
string(3) "foo"
["bar"]=>
string(3) "bar"
}
@@ -0,0 +1,40 @@
--TEST--
Partial application function name
--FILE--
<?php
function g($a) {}
class C {
static function m() {
echo "# From a method:\n";
var_dump((new ReflectionFunction(g(?)))->getName());
}
}
function f() {
echo "# From a function:\n";
var_dump((new ReflectionFunction(g(?)))->getName());
echo "# Declared on same line:\n";
[$a, $b] = [g(?), g(?)];
var_dump((new ReflectionFunction($a))->getName(), (new ReflectionFunction($b))->getName());
}
f();
C::m();
echo "# From global scope:\n";
var_dump((new ReflectionFunction(g(?)))->getName());
?>
--EXPECTF--
# From a function:
string(20) "{closure:pfa:f():14}"
# Declared on same line:
string(20) "{closure:pfa:f():17}"
string(20) "{closure:pfa:f():17}"
# From a method:
string(22) "{closure:pfa:C::m():8}"
# From global scope:
string(%d) "{closure:pfa:%sfunction_name.php:25}"
@@ -0,0 +1,21 @@
--TEST--
Closure application fuzz 001
--FILE--
<?php
$closure = function($a, $b) {};
echo (string) new ReflectionFunction($closure(1, ?));
?>
--EXPECTF--
Closure [ <user> function {closure:%s:%d} ] {
@@ %s 4 - 4
- Bound Variables [2] {
Variable #0 [ $fn ]
Variable #1 [ $a ]
}
- Parameters [1] {
Parameter #0 [ <required> $b ]
}
}
@@ -0,0 +1,20 @@
--TEST--
Closure application fuzz 003
--FILE--
<?php
class Foo {
function __call($name, $args) {
var_dump($args);
}
}
$foo = new Foo;
$bar = $foo->method(1, ...);
$bar(2);
?>
--EXPECT--
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
@@ -0,0 +1,19 @@
--TEST--
Closure application fuzz 004
--FILE--
<?php
function foo($a, $b) {
return $a + $b;
}
$foo = foo(b: 10, ...);
try {
$foo->__invoke(UNDEFINED);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Undefined constant "UNDEFINED"
@@ -0,0 +1,16 @@
--TEST--
PFA fuzz 005
--FILE--
<?php
function foo(int $day = 1, int $month = 1, int $yearh = 1, int $year = 2005) {
}
$foo = foo(month: 12, ...);
$bar = $foo(year: 2016, ...);
?>
==DONE==
--EXPECT--
==DONE==
@@ -0,0 +1,16 @@
--TEST--
PFA fuzz 006
--FILE--
<?php
function foo(int $day = 1, int $month = 1, int $year = 2005) {
var_dump($day);
}
$foo = foo(month: 12, ...);
$foo(year: 2025);
?>
--EXPECT--
int(1)
@@ -0,0 +1,19 @@
--TEST--
PFA fuzz 007
--FILE--
<?php
function foo(int $day = 1, int $month = UNDEFINED, int $year = 2005) {
}
$foo = foo(year: 2006, ...);
try {
$foo(2);
} catch (\Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Undefined constant "UNDEFINED"
+25
View File
@@ -0,0 +1,25 @@
--TEST--
Parent property hook call can not be partially applied
--FILE--
<?php
class C {
public $a {
set($value) { var_dump($value); }
}
}
class D extends C {
public $a {
set($value) {
$f = parent::$a::set(?);
}
}
}
$d = new D();
$d->a = 1;
?>
--EXPECTF--
Fatal error: Cannot create Closure for parent property hook call in %s on line %d
@@ -0,0 +1,43 @@
--TEST--
PFA: instance polymorphism
--FILE--
<?php
class P {
public function m(string $a): void {
echo __METHOD__, PHP_EOL;
var_dump($a);
}
public function get() {
/* The method is resolved before creating the PFA, and captured by the
* PFA. We use the signature of the resolved method. */
return $this->m(?);
}
}
class C extends P {
public function m(string|array $b): void {
echo __METHOD__, PHP_EOL;
var_dump($b);
}
}
for ($i = 0; $i < 2; $i++) {
(new P())->get()(a: 'a');
(new C())->get()(b: []);
}
?>
--EXPECT--
P::m
string(1) "a"
C::m
array(0) {
}
P::m
string(1) "a"
C::m
array(0) {
}
@@ -0,0 +1,51 @@
--TEST--
__invoke() can be partially applied
--FILE--
<?php
class C {
public function __invoke(int $a, object $b): C {
var_dump($a, $b);
return $this;
}
}
$c = new C();
$f = $c(?, ...);
echo (string) new ReflectionFunction($f), "\n";
$f = $c(?, new stdClass);
echo (string) new ReflectionFunction($f), "\n";
$f(1);
?>
--EXPECTF--
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s.php 11 - 11
- Parameters [2] {
Parameter #0 [ <required> int $a ]
Parameter #1 [ <required> object $b ]
}
- Return [ C ]
}
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s.php 15 - 15
- Bound Variables [1] {
Variable #0 [ $b ]
}
- Parameters [1] {
Parameter #0 [ <required> int $a ]
}
- Return [ C ]
}
int(1)
object(stdClass)#%d (0) {
}
@@ -0,0 +1,9 @@
--TEST--
PFA JIT 001
--FILE--
<?php
(function ($a,$b) { var_dump($a, $b); })(1, ...)(2);
?>
--EXPECT--
int(1)
int(2)
@@ -0,0 +1,81 @@
--TEST--
__call() can be partially applied
--FILE--
<?php
class Foo {
public function __call($method, $arguments) {
printf("%s::%s\n", __CLASS__, $method);
var_dump(...$arguments);
}
}
$foo = new Foo;
$bar = $foo->method(?);
echo (string) new ReflectionFunction($bar);
try {
$bar();
} catch (Error $ex) {
printf("%s: %s\n", $ex::class, $ex->getMessage());
}
$bar(1, 2);
$bar(1);
$bar = $foo->method(?, ...);
echo (string) new ReflectionFunction($bar);
$bar(10);
$bar(10, 20);
$bar = $foo->method(new Foo, ...);
echo (string) new ReflectionFunction($bar);
$bar(100);
?>
--EXPECTF--
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 12 - 12
- Parameters [1] {
Parameter #0 [ <required> mixed $arguments0 ]
}
}
ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
Foo::method
int(1)
Foo::method
int(1)
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 25 - 25
- Parameters [2] {
Parameter #0 [ <required> mixed $arguments0 ]
Parameter #1 [ <optional> mixed ...$arguments ]
}
}
Foo::method
int(10)
Foo::method
int(10)
int(20)
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 32 - 32
- Bound Variables [1] {
Variable #0 [ $arguments2 ]
}
- Parameters [1] {
Parameter #0 [ <optional> mixed ...$arguments ]
}
}
Foo::method
object(Foo)#%d (0) {
}
int(100)
@@ -0,0 +1,72 @@
--TEST--
__callStatic() can be partially applied
--FILE--
<?php
class Foo {
public static function __callStatic($method, $arguments) {
printf("%s::%s\n", __CLASS__, $method);
var_dump(...$arguments);
}
}
$bar = Foo::method(?);
echo (string) new ReflectionFunction($bar);
$bar(1);
$bar(1, 2);
$bar = Foo::method(?, ...);
echo (string) new ReflectionFunction($bar);
$bar(10);
$bar(10, 20);
$bar = Foo::method(new Foo,...);
echo (string) new ReflectionFunction($bar);
$bar(100);
?>
--EXPECTF--
Closure [ <user> static public method {closure:%s:%d} ] {
@@ %s 10 - 10
- Parameters [1] {
Parameter #0 [ <required> mixed $arguments0 ]
}
}
Foo::method
int(1)
Foo::method
int(1)
Closure [ <user> static public method {closure:%s:%d} ] {
@@ %s 17 - 17
- Parameters [2] {
Parameter #0 [ <required> mixed $arguments0 ]
Parameter #1 [ <optional> mixed ...$arguments ]
}
}
Foo::method
int(10)
Foo::method
int(10)
int(20)
Closure [ <user> static public method {closure:%s:%d} ] {
@@ %s 24 - 24
- Bound Variables [1] {
Variable #0 [ $arguments2 ]
}
- Parameters [1] {
Parameter #0 [ <optional> mixed ...$arguments ]
}
}
Foo::method
object(Foo)#%d (0) {
}
int(100)
@@ -0,0 +1,13 @@
--TEST--
PFA magic trampoline release (result unused)
--FILE--
<?php
class Foo {
static function __callStatic($name, $args) {}
}
Foo::method(?);
echo "OK";
?>
--EXPECT--
OK
@@ -0,0 +1,14 @@
--TEST--
PFA magic trampoline release (result used)
--FILE--
<?php
class Foo {
static function __callStatic($name, $args) {}
}
$a = Foo::method(?);
(void)Foo::method(?);
echo "OK";
?>
--EXPECT--
OK
@@ -0,0 +1,29 @@
--TEST--
PFA magic null ptr deref in arginfo
--FILE--
<?php
class Foo {
function __call($name, $args) {
}
}
$foo = new Foo;
$bar = $foo->method(?);
var_dump($bar);
?>
--EXPECTF--
object(Closure)#%d (%d) {
["name"]=>
string(%d) "{closure:%s}"
["file"]=>
string(%d) "%smagic_005.php"
["line"]=>
int(7)
["this"]=>
object(Foo)#%d (0) {
}
["parameter"]=>
array(1) {
["$arguments0"]=>
string(10) "<required>"
}
}
@@ -0,0 +1,20 @@
--TEST--
PFA magic varargs
--FILE--
<?php
class Foo {
function __call($name, $args) {
var_dump($args);
}
}
$foo = new Foo;
$bar = $foo->method(1,...);
$bar(2);
?>
--EXPECT--
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
@@ -0,0 +1,121 @@
--TEST--
PFA supports named placeholders
--FILE--
<?php
class A {}
class B {}
class C {}
function foo($a = 1, $b = 2, $c = 3) {
var_dump($a, $b, $c);
}
echo "# Case 1\n";
$c = foo(b: ?);
echo (string) new ReflectionFunction($c);
$c(new B);
echo "# Case 2\n";
$c = $c(?);
echo (string) new ReflectionFunction($c);
$c(new B);
echo "# Case 3\n";
$c = foo(?, ?);
try {
$c = $c(b: ?);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Case 4\n";
function bar($a = 1, $b = 2, ...$c) {
var_dump($a, $b, $c);
}
$d = bar(b: ?, ...);
echo (string) new ReflectionFunction($d);
$d(new B, new A, new C);
echo "# Case 5\n";
try {
$d = bar(?, a: ?);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Case 6\n";
try {
$d = bar(c: ?, ...);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
# Case 1
Closure [ <user> static function {closure:%s:%d} ] {
@@ %snamed_placeholders.php 13 - 13
- Parameters [1] {
Parameter #0 [ <required> $b ]
}
}
int(1)
object(B)#%d (0) {
}
int(3)
# Case 2
Closure [ <user> static function {closure:%s:%d} ] {
@@ %snamed_placeholders.php 21 - 21
- Bound Variables [1] {
Variable #0 [ $fn ]
}
- Parameters [1] {
Parameter #0 [ <required> $b ]
}
}
int(1)
object(B)#%d (0) {
}
int(3)
# Case 3
ArgumentCountError: {closure:pfa:%s:29}(): Argument #1 ($a) not passed
# Case 4
Closure [ <user> static function {closure:%s:%d} ] {
@@ %snamed_placeholders.php 42 - 42
- Parameters [3] {
Parameter #0 [ <required> $b ]
Parameter #1 [ <optional> $a = 1 ]
Parameter #2 [ <optional> ...$c ]
}
}
object(A)#%d (0) {
}
object(B)#%d (0) {
}
array(1) {
[0]=>
object(C)#%d (0) {
}
}
# Case 5
Error: Named parameter $a overwrites previous placeholder
# Case 6
Error: Cannot use named placeholder for unknown or variadic parameter $c
@@ -0,0 +1,30 @@
--TEST--
Inline cache can not be used for ZEND_ACC_NEVER_CACHE functions
--FILE--
<?php
$fns = [
'return function ($x) { var_dump("A"); };',
'return function ($x) { var_dump("B"); };',
];
function f($f) {
$f(0);
}
foreach ($fns as $f) {
f(eval($f));
}
foreach ($fns as $f) {
$f = eval($f);
// Closure::__invoke() is ZEND_ACC_NEVER_CACHE
f($f->__invoke(?));
}
?>
--EXPECT--
string(1) "A"
string(1) "B"
string(1) "A"
string(1) "B"
@@ -0,0 +1,46 @@
--TEST--
Functions that can not be called dynamically, can not be partially applied
--FILE--
<?php
function _func_get_arg() {
return func_get_arg(?);
}
function _compact() {
return compact(?);
}
function _extract() {
return extract(?);
}
function _func_get_args() {
return func_get_args(?);
}
function _func_num_args() {
return func_num_args(?);
}
function _get_defined_vars() {
return get_defined_vars(?);
}
foreach (['_func_get_arg', '_compact', '_extract', '_func_get_args', '_func_num_args', '_get_defined_vars'] as $func) {
try {
$func();
echo "$func\n";
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
}
?>
--EXPECT--
Error: Cannot call func_get_arg() dynamically
Error: Cannot call compact() dynamically
Error: Cannot call extract() dynamically
Error: Cannot call func_get_args() dynamically
Error: Cannot call func_num_args() dynamically
Error: Cannot call get_defined_vars() dynamically
@@ -0,0 +1,34 @@
--TEST--
PFA support observers
--EXTENSIONS--
zend_test
--INI--
zend_test.observer.enabled=1
zend_test.observer.show_output=1
zend_test.observer.observe_all=1
--FILE--
<?php
function f($a, $b) {
var_dump($a, $b);
}
$f = f(1, ...);
$f(2);
?>
--EXPECTF--
<!-- init '%sobservers.php' -->
<file '%sobservers.php'>
<!-- init {closure:%s}() -->
<{closure:%s}>
<!-- init f() -->
<f>
<!-- init var_dump() -->
<var_dump>
int(1)
int(2)
</var_dump>
</f>
</{closure:%s:%d}>
</file '%sobservers.php'>
@@ -0,0 +1,77 @@
--TEST--
PFA optional placeholder: ? always generates required params; ... inherits optionality
--FILE--
<?php
function foo(int $a = 1, string $b = 'default', float $c = 3.14) {
return [$a, $b, $c];
}
echo "# ? makes targeted params required regardless of original optionality\n";
$f = foo(?);
var_dump((new ReflectionFunction($f))->getParameters()[0]->isOptional());
$f = foo(?, ?);
$params = (new ReflectionFunction($f))->getParameters();
var_dump($params[0]->isOptional(), $params[1]->isOptional());
$f = foo(?, ?, ?);
$params = (new ReflectionFunction($f))->getParameters();
var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
echo "# ... inherits optionality from the original function\n";
$f = foo(?, ...);
$params = (new ReflectionFunction($f))->getParameters();
var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
$f = foo(?, ?, ...);
$params = (new ReflectionFunction($f))->getParameters();
var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
echo "# Calling works: ? params are required, unspecified optional params use their defaults\n";
$f = foo(?, ?);
var_dump($f(10, 'hello'));
echo "# Named ? placeholders are also required\n";
function bar(int $x = 0, int $y = 0) {
return [$x, $y];
}
$f = bar(y: ?);
var_dump((new ReflectionFunction($f))->getParameters()[0]->isOptional());
var_dump($f(42));
?>
--EXPECT--
# ? makes targeted params required regardless of original optionality
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
# ... inherits optionality from the original function
bool(false)
bool(true)
bool(true)
bool(false)
bool(false)
bool(true)
# Calling works: ? params are required, unspecified optional params use their defaults
array(3) {
[0]=>
int(10)
[1]=>
string(5) "hello"
[2]=>
float(3.14)
}
# Named ? placeholders are also required
bool(false)
array(2) {
[0]=>
int(0)
[1]=>
int(42)
}
@@ -0,0 +1,202 @@
--TEST--
Named parameters define the order of parameters in a PFA
--FILE--
<?php
function f($a, $b, $c, $d = null) {
return [$a, $b, $c, $d];
}
function g($a, $b, $c, ...$d) {
return [$a, $b, $c, $d];
}
echo "# All named\n";
$a = f(d: ?, c: ?, b: ?, a: ?);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2, 3, 4));
echo "# Some named: Positional first, then named in specified order\n";
$a = f(?, ?, d: ?, c: ?);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2, 3, 4));
echo "# Some named, one unspecified\n";
$a = f(?, c: ?, b: ?);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2, 3, 4));
echo "# Some named, some implicit added by '...'\n";
$a = f(c: ?, b: ?, ...);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2, 3, 4));
echo "# Some named, some implicit added by '...' on variadic function\n";
$a = g(c: ?, b: ?, ...);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2, 3, 4, 5, 6));
echo "# Some prebound, some named\n";
$a = f(-1, c: ?, d: -2, b: ?);
echo new ReflectionFunction($a), "\n";
var_dump($a(1, 2));
echo "# Some named, some required missing\n";
try {
$a = f(b: ?, c: ?);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
# All named
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 13 - 13
- Parameters [4] {
Parameter #0 [ <required> $d ]
Parameter #1 [ <required> $c ]
Parameter #2 [ <required> $b ]
Parameter #3 [ <required> $a ]
}
}
array(4) {
[0]=>
int(4)
[1]=>
int(3)
[2]=>
int(2)
[3]=>
int(1)
}
# Some named: Positional first, then named in specified order
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 19 - 19
- Parameters [4] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <required> $d ]
Parameter #3 [ <required> $c ]
}
}
array(4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(4)
[3]=>
int(3)
}
# Some named, one unspecified
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 25 - 25
- Parameters [3] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $c ]
Parameter #2 [ <required> $b ]
}
}
array(4) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(2)
[3]=>
NULL
}
# Some named, some implicit added by '...'
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 31 - 31
- Parameters [4] {
Parameter #0 [ <required> $c ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <required> $a ]
Parameter #3 [ <optional> $d = NULL ]
}
}
array(4) {
[0]=>
int(3)
[1]=>
int(2)
[2]=>
int(1)
[3]=>
int(4)
}
# Some named, some implicit added by '...' on variadic function
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 37 - 37
- Parameters [4] {
Parameter #0 [ <required> $c ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <required> $a ]
Parameter #3 [ <optional> ...$d ]
}
}
array(4) {
[0]=>
int(3)
[1]=>
int(2)
[2]=>
int(1)
[3]=>
array(3) {
[0]=>
int(4)
[1]=>
int(5)
[2]=>
int(6)
}
}
# Some prebound, some named
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sparam_reorder.php 43 - 43
- Bound Variables [2] {
Variable #0 [ $a ]
Variable #1 [ $d ]
}
- Parameters [2] {
Parameter #0 [ <required> $c ]
Parameter #1 [ <required> $b ]
}
}
array(4) {
[0]=>
int(-1)
[1]=>
int(2)
[2]=>
int(1)
[3]=>
int(-2)
}
# Some named, some required missing
ArgumentCountError: f(): Argument #1 ($a) not passed
@@ -0,0 +1,47 @@
--TEST--
PFA optimization: PFA with single placeholder arg can be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a) {
var_dump($a);
}
}
1 |> foo(?);
?>
--EXPECTF--
$_main:
; (lines=9, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_001.php:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 1 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 DO_FCALL_BY_NAME
0008 RETURN int(1)
foo:
; (lines=5, args=1, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_001.php:4-6
0000 CV0($a) = RECV 1
0001 INIT_FCALL 1 %d string("var_dump")
0002 SEND_VAR CV0($a) 1
0003 DO_ICALL
0004 RETURN null
int(1)
@@ -0,0 +1,51 @@
--TEST--
PFA pipe optimization: PFA with only one placeholder can be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
2 |> foo(1, ?);
?>
--EXPECTF--
$_main:
; (lines=10, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_002.php:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 2 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(2) 2
0008 DO_FCALL_BY_NAME
0009 RETURN int(1)
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_002.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
int(1)
int(2)
@@ -0,0 +1,51 @@
--TEST--
PFA pipe optimization: PFA with only one placeholder can be optimized (placeholder first)
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
2 |> foo(?, 1);
?>
--EXPECTF--
$_main:
; (lines=10, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_003.php:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 2 string("foo")
0006 SEND_VAL_EX int(2) 1
0007 SEND_VAL_EX int(1) 2
0008 DO_FCALL_BY_NAME
0009 RETURN int(1)
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_003.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
int(2)
int(1)
@@ -0,0 +1,88 @@
--TEST--
PFA pipe optimization: PFA with multiple placeholders can not be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
try {
2 |> foo(?, ?);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
$_main:
; (lines=22, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_004.php:1-16
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 2 string("foo")
0006 SEND_PLACEHOLDER 1
0007 SEND_PLACEHOLDER 2
0008 T1 = CALLABLE_CONVERT_PARTIAL %d
0009 INIT_DYNAMIC_CALL 1 T1
0010 SEND_VAL_EX int(2) 1
0011 DO_FCALL
0012 RETURN int(1)
0013 CV0($e) = CATCH string("Throwable")
0014 T1 = FETCH_CLASS_NAME CV0($e)
0015 ECHO T1
0016 ECHO string(": ")
0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
0018 T1 = DO_FCALL
0019 ECHO T1
0020 ECHO string("\n")
0021 RETURN int(1)
EXCEPTION TABLE:
0005, 0013, -, -
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_004.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
$_main:
; (lines=3, args=0, vars=0, tmps=%d)
; (after optimizer)
; %s:1-10
0000 T0 = DECLARE_LAMBDA_FUNCTION 0
0001 FREE T0
0002 RETURN int(1)
{closure:%s:%d}:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %s:10-10
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("foo")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 T2 = DO_UCALL
0006 RETURN T2
ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
@@ -0,0 +1,51 @@
--TEST--
PFA pipe optimization: PFA with only one placeholder can be optimized (variadic)
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
2 |> foo(1, ...);
?>
--EXPECTF--
$_main:
; (lines=10, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_005.php:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 2 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(2) 2
0008 DO_FCALL_BY_NAME
0009 RETURN int(1)
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_005.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
int(1)
int(2)
@@ -0,0 +1,55 @@
--TEST--
PFA pipe optimization: PFA with only one placeholder can be optimized (named)
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b = null, $c = null) {
var_dump($a, $b, $c);
}
}
2 |> foo(1, c: ?);
?>
--EXPECTF--
$_main:
; (lines=11, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_006.php:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 1 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(2) string("c")
0008 CHECK_UNDEF_ARGS
0009 DO_FCALL_BY_NAME
0010 RETURN int(1)
foo:
; (lines=9, args=3, vars=3, tmps=%d)
; (after optimizer)
; %spipe_optimization_006.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV_INIT 2 null
0002 CV2($c) = RECV_INIT 3 null
0003 INIT_FCALL 3 %d string("var_dump")
0004 SEND_VAR CV0($a) 1
0005 SEND_VAR CV1($b) 2
0006 SEND_VAR CV2($c) 3
0007 DO_ICALL
0008 RETURN null
int(1)
NULL
int(2)
@@ -0,0 +1,88 @@
--TEST--
PFA pipe optimization: PFA with multiple placeholders can not be optimized (named)
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
try {
2 |> foo(a: ?, b: ?);
} catch (\Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
$_main:
; (lines=22, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_007.php:1-16
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 0 string("foo")
0006 SEND_PLACEHOLDER string("a")
0007 SEND_PLACEHOLDER string("b")
0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...)
0009 INIT_DYNAMIC_CALL 1 T1
0010 SEND_VAL_EX int(2) 1
0011 DO_FCALL
0012 RETURN int(1)
0013 CV0($e) = CATCH string("Throwable")
0014 T1 = FETCH_CLASS_NAME CV0($e)
0015 ECHO T1
0016 ECHO string(": ")
0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
0018 T1 = DO_FCALL
0019 ECHO T1
0020 ECHO string("\n")
0021 RETURN int(1)
EXCEPTION TABLE:
0005, 0013, -, -
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_007.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
$_main:
; (lines=3, args=0, vars=0, tmps=%d)
; (after optimizer)
; %s:1-10
0000 T0 = DECLARE_LAMBDA_FUNCTION 0
0001 FREE T0
0002 RETURN int(1)
{closure:%s:%d}:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %s:10-10
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("foo")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 T2 = DO_UCALL
0006 RETURN T2
ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
@@ -0,0 +1,99 @@
--TEST--
PFA pipe optimization: PFA with both a variadic placeholder and named arg can not be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
try {
2 |> foo(a: 1, ...);
} catch (\Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
$_main:
; (lines=18, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_008.php:1-16
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 0 string("foo")
0006 SEND_VAL_EX int(1) string("a")
0007 T1 = CALLABLE_CONVERT_PARTIAL %d
0008 INIT_DYNAMIC_CALL 1 T1
0009 SEND_VAL_EX int(2) 1
0010 DO_FCALL
0011 RETURN int(1)
0012 CV0($e) = CATCH string("Throwable")
0013 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
0014 T1 = DO_FCALL
0015 ECHO T1
0016 ECHO string("\n")
0017 RETURN int(1)
EXCEPTION TABLE:
0005, 0012, -, -
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_008.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
$_main:
; (lines=4, args=0, vars=1, tmps=%d)
; (after optimizer)
; %s:1-10
0000 T1 = DECLARE_LAMBDA_FUNCTION 0
0001 BIND_LEXICAL T1 CV0($a)
0002 FREE T1
0003 RETURN int(1)
LIVE RANGES:
1: 0001 - 0002 (tmp/var)
{closure:%s:%d}:
; (lines=18, args=1, vars=2, tmps=%d)
; (after optimizer)
; %s:10-10
0000 CV0($b) = RECV 1
0001 BIND_STATIC CV1($a)
0002 T3 = FUNC_NUM_ARGS
0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1)
0004 JMPZ T2 0010
0005 INIT_FCALL 2 %d string("foo")
0006 SEND_VAR CV1($a) 1
0007 SEND_VAR CV0($b) 2
0008 T2 = DO_UCALL
0009 RETURN T2
0010 INIT_FCALL 2 %d string("foo")
0011 SEND_VAR CV1($a) 1
0012 SEND_VAR CV0($b) 2
0013 T2 = FUNC_GET_ARGS int(1)
0014 SEND_UNPACK T2
0015 CHECK_UNDEF_ARGS
0016 T2 = DO_UCALL
0017 RETURN T2
int(1)
int(2)
@@ -0,0 +1,102 @@
--TEST--
PFA pipe optimization: Evaluation order
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b, $c) {
var_dump($a, $b, $c);
}
function lhs() {
echo __FUNCTION__, "\n";
return 0;
}
function arg1() {
echo __FUNCTION__, "\n";
return 1;
}
function arg2() {
echo __FUNCTION__, "\n";
return 2;
}
}
lhs() |> foo(arg1(), ?, arg2());
?>
--EXPECTF--
$_main:
; (lines=20, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_009.php:1-24
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0008
0004 DECLARE_FUNCTION string("foo") 0
0005 DECLARE_FUNCTION string("lhs") 1
0006 DECLARE_FUNCTION string("arg1") 2
0007 DECLARE_FUNCTION string("arg2") 3
0008 INIT_FCALL_BY_NAME 0 string("lhs")
0009 T0 = DO_FCALL_BY_NAME
0010 INIT_FCALL_BY_NAME 3 string("foo")
0011 INIT_FCALL_BY_NAME 0 string("arg1")
0012 V1 = DO_FCALL_BY_NAME
0013 SEND_VAR_NO_REF_EX V1 1
0014 SEND_VAL_EX T0 2
0015 INIT_FCALL_BY_NAME 0 string("arg2")
0016 V0 = DO_FCALL_BY_NAME
0017 SEND_VAR_NO_REF_EX V0 3
0018 DO_FCALL_BY_NAME
0019 RETURN int(1)
LIVE RANGES:
0: 0010 - 0014 (tmp/var)
foo:
; (lines=9, args=3, vars=3, tmps=%d)
; (after optimizer)
; %spipe_optimization_009.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 CV2($c) = RECV 3
0003 INIT_FCALL 3 %d string("var_dump")
0004 SEND_VAR CV0($a) 1
0005 SEND_VAR CV1($b) 2
0006 SEND_VAR CV2($c) 3
0007 DO_ICALL
0008 RETURN null
lhs:
; (lines=2, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_009.php:7-10
0000 ECHO string("lhs\n")
0001 RETURN int(0)
arg1:
; (lines=2, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_009.php:11-14
0000 ECHO string("arg1\n")
0001 RETURN int(1)
arg2:
; (lines=2, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spipe_optimization_009.php:15-18
0000 ECHO string("arg2\n")
0001 RETURN int(2)
lhs
arg1
arg2
int(1)
int(0)
int(2)
@@ -0,0 +1,58 @@
--TEST--
PFA pipe optimization: References
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo(&$a, $b) {
var_dump($a, $b);
$a = 2;
}
}
1 |> foo($a, ?);
var_dump($a);
?>
--EXPECTF--
$_main:
; (lines=13, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_010.php:1-14
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 2 string("foo")
0006 SEND_VAR_EX CV0($a) 1
0007 SEND_VAL_EX int(1) 2
0008 DO_FCALL_BY_NAME
0009 INIT_FCALL 1 %d string("var_dump")
0010 SEND_VAR CV0($a) 1
0011 DO_ICALL
0012 RETURN int(1)
foo:
; (lines=8, args=2, vars=2, tmps=%d)
; (after optimizer)
; %spipe_optimization_010.php:4-7
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 ASSIGN CV0($a) int(2)
0007 RETURN null
NULL
int(1)
int(2)
@@ -0,0 +1,98 @@
--TEST--
PFA pipe optimization: Evaluation order
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b, $c) {
var_dump($a, $b, $c);
}
function arg1() {
global $a;
$a = 2;
echo __FUNCTION__, "\n";
return 1;
}
function arg2() {
global $a;
$a = 3;
echo __FUNCTION__, "\n";
return 2;
}
}
$a = 0;
$a |> foo(arg1(), ?, arg2());
?>
--EXPECTF--
$_main:
; (lines=19, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_011.php:1-25
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0007
0004 DECLARE_FUNCTION string("foo") 0
0005 DECLARE_FUNCTION string("arg1") %d
0006 DECLARE_FUNCTION string("arg2") %d
0007 ASSIGN CV0($a) int(0)
0008 T1 = QM_ASSIGN CV0($a)
0009 INIT_FCALL_BY_NAME 3 string("foo")
0010 INIT_FCALL_BY_NAME 0 string("arg1")
0011 V2 = DO_FCALL_BY_NAME
0012 SEND_VAR_NO_REF_EX V2 1
0013 SEND_VAL_EX T1 2
0014 INIT_FCALL_BY_NAME 0 string("arg2")
0015 V1 = DO_FCALL_BY_NAME
0016 SEND_VAR_NO_REF_EX V1 3
0017 DO_FCALL_BY_NAME
0018 RETURN int(1)
LIVE RANGES:
1: 0009 - 0013 (tmp/var)
foo:
; (lines=9, args=3, vars=3, tmps=%d)
; (after optimizer)
; %spipe_optimization_011.php:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 CV2($c) = RECV 3
0003 INIT_FCALL 3 %d string("var_dump")
0004 SEND_VAR CV0($a) 1
0005 SEND_VAR CV1($b) 2
0006 SEND_VAR CV2($c) 3
0007 DO_ICALL
0008 RETURN null
arg1:
; (lines=4, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_011.php:7-12
0000 BIND_GLOBAL CV0($a) string("a")
0001 ASSIGN CV0($a) int(2)
0002 ECHO string("arg1\n")
0003 RETURN int(1)
arg2:
; (lines=4, args=0, vars=1, tmps=%d)
; (after optimizer)
; %spipe_optimization_011.php:13-18
0000 BIND_GLOBAL CV0($a) string("a")
0001 ASSIGN CV0($a) int(3)
0002 ECHO string("arg2\n")
0003 RETURN int(2)
arg1
arg2
int(1)
int(0)
int(2)
@@ -0,0 +1,52 @@
--TEST--
PFA optimization: PFA with named args and placeholders can be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
1 |> foo(?, b: 2);
?>
--EXPECTF--
$_main:
; (lines=11, args=0, vars=0, tmps=%d)
; (after optimizer)
; %s:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 1 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(2) string("b")
0008 CHECK_UNDEF_ARGS
0009 DO_FCALL_BY_NAME
0010 RETURN int(1)
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %s:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
int(1)
int(2)
@@ -0,0 +1,87 @@
--TEST--
PFA optimization: PFA with named args and a variadic placeholder can not be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b) {
var_dump($a, $b);
}
}
1 |> foo(b: 2, ...);
?>
--EXPECTF--
$_main:
; (lines=12, args=0, vars=0, tmps=%d)
; (after optimizer)
; %s:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 0 string("foo")
0006 SEND_VAL_EX int(2) string("b")
0007 T0 = CALLABLE_CONVERT_PARTIAL 3
0008 INIT_DYNAMIC_CALL 1 T0
0009 SEND_VAL_EX int(1) 1
0010 DO_FCALL
0011 RETURN int(1)
foo:
; (lines=7, args=2, vars=2, tmps=%d)
; (after optimizer)
; %s:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV 2
0002 INIT_FCALL 2 %d string("var_dump")
0003 SEND_VAR CV0($a) 1
0004 SEND_VAR CV1($b) 2
0005 DO_ICALL
0006 RETURN null
$_main:
; (lines=4, args=0, vars=1, tmps=%d)
; (after optimizer)
; %s:1-9
0000 T1 = DECLARE_LAMBDA_FUNCTION 0
0001 BIND_LEXICAL T1 CV0($b)
0002 FREE T1
0003 RETURN int(1)
LIVE RANGES:
1: 0001 - 0002 (tmp/var)
{closure:pfa:%s:9}:
; (lines=18, args=1, vars=2, tmps=%d)
; (after optimizer)
; %s:9-9
0000 CV0($a) = RECV 1
0001 BIND_STATIC CV1($b)
0002 T3 = FUNC_NUM_ARGS
0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1)
0004 JMPZ T2 0010
0005 INIT_FCALL 2 %d string("foo")
0006 SEND_VAR CV0($a) 1
0007 SEND_VAR CV1($b) 2
0008 T2 = DO_UCALL
0009 RETURN T2
0010 INIT_FCALL 2 %d string("foo")
0011 SEND_VAR CV0($a) 1
0012 SEND_VAR CV1($b) 2
0013 T2 = FUNC_GET_ARGS int(1)
0014 SEND_UNPACK T2
0015 CHECK_UNDEF_ARGS
0016 T2 = DO_UCALL
0017 RETURN T2
int(1)
int(2)
@@ -0,0 +1,68 @@
--TEST--
PFA pipe optimization: PFA with unknown named parameter can be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b = null, $c = null) {
var_dump($a, $b, $c);
}
}
try {
2 |> foo(1, unknown: ?);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
$_main:
; (lines=20, args=0, vars=1, tmps=%d)
; (after optimizer)
; %s:1-16
0000 INIT_FCALL 0 %d string("time")
0001 T2 = DO_ICALL
0002 T1 = IS_SMALLER int(0) T2
0003 JMPZ T1 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 1 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(2) string("unknown")
0008 CHECK_UNDEF_ARGS
0009 DO_FCALL_BY_NAME
0010 RETURN int(1)
0011 CV0($e) = CATCH string("Error")
0012 T1 = FETCH_CLASS_NAME CV0($e)
0013 ECHO T1
0014 ECHO string(": ")
0015 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
0016 T1 = DO_FCALL
0017 ECHO T1
0018 ECHO string("\n")
0019 RETURN int(1)
EXCEPTION TABLE:
0005, 0011, -, -
foo:
; (lines=9, args=3, vars=3, tmps=%d)
; (after optimizer)
; %s:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV_INIT 2 null
0002 CV2($c) = RECV_INIT 3 null
0003 INIT_FCALL 3 %d string("var_dump")
0004 SEND_VAR CV0($a) 1
0005 SEND_VAR CV1($b) 2
0006 SEND_VAR CV2($c) 3
0007 DO_ICALL
0008 RETURN null
Error: Unknown named parameter $unknown
@@ -0,0 +1,55 @@
--TEST--
PFA pipe optimization: PFA with skipped optional parameter can be optimized
--EXTENSIONS--
opcache
--INI--
opcache.opt_debug_level=0x20000
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=
opcache.file_cache_only=0
--FILE--
<?php
if (time() > 0) {
function foo($a, $b = null, $c = null) {
var_dump($a, $b, $c);
}
}
3 |> foo(1, c: ?);
?>
--EXPECTF--
$_main:
; (lines=11, args=0, vars=0, tmps=%d)
; (after optimizer)
; %s:1-12
0000 INIT_FCALL 0 %d string("time")
0001 T1 = DO_ICALL
0002 T0 = IS_SMALLER int(0) T1
0003 JMPZ T0 0005
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 1 string("foo")
0006 SEND_VAL_EX int(1) 1
0007 SEND_VAL_EX int(3) string("c")
0008 CHECK_UNDEF_ARGS
0009 DO_FCALL_BY_NAME
0010 RETURN int(1)
foo:
; (lines=9, args=3, vars=3, tmps=%d)
; (after optimizer)
; %s:4-6
0000 CV0($a) = RECV 1
0001 CV1($b) = RECV_INIT 2 null
0002 CV2($c) = RECV_INIT 3 null
0003 INIT_FCALL 3 %d string("var_dump")
0004 SEND_VAR CV0($a) 1
0005 SEND_VAR CV1($b) 2
0006 SEND_VAR CV2($c) 3
0007 DO_ICALL
0008 RETURN null
int(1)
NULL
int(3)
@@ -0,0 +1,13 @@
<?php
function f($abc) {
return $abc + 1;
}
function test() {
return f(?);
}
var_dump(test()(1));
?>
@@ -0,0 +1,19 @@
--TEST--
PFA preloading
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.preload={PWD}/preloading.inc
--SKIPIF--
<?php
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
?>
--FILE--
<?php
var_dump(test()(1));
?>
--EXPECT--
int(2)
int(2)
@@ -0,0 +1,59 @@
--TEST--
PFA can only be rebound to an instanceof $this
--FILE--
<?php
class C {
function f($a) { var_dump($this); }
static function g($a) { var_dump(static::class); }
}
class SubClass extends C {}
class Unrelated {}
$c = new C;
$f = $c->f(?);
$g = $c->g(?);
echo "# Can be rebound to \$this of the same class:\n";
$f->bindTo(new C)(1);
echo "# Can be rebound to \$this of a sub-class:\n";
$f->bindTo(new SubClass)(1);
echo "# Cannot be rebound to an unrelated class:\n";
try {
$f->bindTo(new Unrelated)(1);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Cannot unbind \$this on instance method:\n";
try {
$f->bindTo(null)(1);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Can unbind \$this on static method:\n";
$g->bindTo(null)(1);
?>
--EXPECTF--
# Can be rebound to $this of the same class:
object(C)#%d (0) {
}
# Can be rebound to $this of a sub-class:
object(SubClass)#%d (0) {
}
# Cannot be rebound to an unrelated class:
Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
# Cannot unbind $this on instance method:
Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
# Can unbind $this on static method:
string(1) "C"
@@ -0,0 +1,45 @@
--TEST--
PFA scope cannot be rebound
--FILE--
<?php
class C {
function f($a) { var_dump(static::class); }
}
class SubClass extends C {}
function g($a) { var_dump(__FUNCTION__); }
$c = new C;
$f = $c->f(?);
$g = g(?);
echo "# Can be rebound to the same scope:\n";
$f->bindTo($c, C::class)(1);
echo "# Method cannot be rebound to a different scope:\n";
try {
$f->bindTo($c, SubClass::class)(1);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Function cannot be rebound to a different scope:\n";
try {
$g->bindTo($c, SubClass::class)(1);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECTF--
# Can be rebound to the same scope:
string(1) "C"
# Method cannot be rebound to a different scope:
Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
# Function cannot be rebound to a different scope:
Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
@@ -0,0 +1,69 @@
--TEST--
Rebinding PFA of Closure rebinds inner Closure
--FILE--
<?php
class C {
function getF() {
return function ($a) { var_dump($this, $this === $a); };
}
function getG() {
return static function ($a) { var_dump(static::class, $a); };
}
}
class SubClass extends C {}
class Unrelated {}
$c = new C();
$f = $c->getF()(?);
$g = $c->getG()(?);
echo "# Can be rebound to \$this of the same class:\n";
$d = new C();
$f->bindTo($d)($d);
echo "# Can be rebound to \$this of a sub-class:\n";
$d = new SubClass();
$f->bindTo($d)($d);
echo "# Cannot be rebound to an unrelated class:\n";
try {
$f->bindTo(new Unrelated)($c);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Cannot unbind \$this on non-static closure:\n";
try {
$f->bindTo(null)($c);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# Can unbind \$this on static closure:\n";
$g->bindTo(null)($c);
?>
--EXPECTF--
# Can be rebound to $this of the same class:
object(C)#%d (0) {
}
bool(true)
# Can be rebound to $this of a sub-class:
object(SubClass)#%d (0) {
}
bool(true)
# Cannot be rebound to an unrelated class:
Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
# Cannot unbind $this on non-static closure:
Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
Error: Value of type null is not callable
# Can unbind $this on static closure:
string(1) "C"
object(C)#%d (0) {
}
@@ -0,0 +1,14 @@
--TEST--
PFA compilation warnings are recorded and replayed
--FILE--
<?php
function f(_ $a) {}
$f = f(?);
?>
--EXPECTF--
Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 3
Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 5
@@ -0,0 +1,24 @@
--TEST--
PFA receives by val if the actual function does
--FILE--
<?php
function foo($a, $b) {
$b = 2;
}
$a = ['unchanged because foo() doesn\'t take by reference'];
$b = &$a[0];
$foo = foo(1, ?);
$foo($b);
var_dump($a, $b);
?>
--EXPECT--
array(1) {
[0]=>
&string(49) "unchanged because foo() doesn't take by reference"
}
string(49) "unchanged because foo() doesn't take by reference"
@@ -0,0 +1,24 @@
--TEST--
PFA receives by ref if the actual function does
--FILE--
<?php
function foo($a, &$b) {
$b = 2;
}
$a = ['this will be changed'];
$b = &$a[0];
$foo = foo(1, ?);
$foo($b);
var_dump($a, $b);
?>
--EXPECT--
array(1) {
[0]=>
&int(2)
}
int(2)
@@ -0,0 +1,20 @@
--TEST--
PFA receives by ref if the actual function does
--FILE--
<?php
function foo($a, &$b) {
$a = 2;
}
$foo = foo(1, ?);
try {
$foo(2);
} catch (\Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
{closure:%s}(): Argument #1 ($b) could not be passed by reference
@@ -0,0 +1,42 @@
--TEST--
PFA receives variadic param by ref if the actual function does
--FILE--
<?php
function foo($a, &...$args) {
foreach ($args as &$arg) {
$arg = -$arg;
}
}
$b = 2;
$c = 3;
$d = 4;
$foo = foo(1, $b, ?, ...);
echo (string) new ReflectionFunction($foo), "\n";
$foo($c, $d);
var_dump($b, $c, $d);
?>
--EXPECTF--
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreferences_004.php 13 - 13
- Bound Variables [2] {
Variable #0 [ $a ]
Variable #1 [ $args2 ]
}
- Parameters [2] {
Parameter #0 [ <required> &$args0 ]
Parameter #1 [ <optional> &...$args ]
}
}
int(-2)
int(-3)
int(-4)
@@ -0,0 +1,26 @@
--TEST--
PFA inherits return by ref
--FILE--
<?php
function &foo(&$a) {
return $a;
}
$f = foo(?);
$a = [];
$b = &$f($a);
$a[0] = 1;
var_dump($a, $b);
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
array(1) {
[0]=>
int(1)
}
@@ -0,0 +1,48 @@
--TEST--
PFA reflection: optional parameters
--FILE--
<?php
function foo($a = 1, $b = 5, $c = 10) {
}
$foo = foo(?, ...);
echo (string) new ReflectionFunction($foo);
$foo = foo(?, ?, ...);
echo (string) new ReflectionFunction($foo);
$foo = foo(?, ?, ?);
echo (string) new ReflectionFunction($foo);
?>
--EXPECTF--
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_001.php 6 - 6
- Parameters [3] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <optional> $b = 5 ]
Parameter #2 [ <optional> $c = 10 ]
}
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_001.php 10 - 10
- Parameters [3] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <optional> $c = 10 ]
}
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_001.php 14 - 14
- Parameters [3] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <required> $c ]
}
}
@@ -0,0 +1,57 @@
--TEST--
PFA reflection: variadics
--FILE--
<?php
function foo($a, ...$b) {
var_dump(func_get_args());
}
$foo = foo(?);
echo (string) new ReflectionFunction($foo);
$foo = foo(?, ...);
echo (string) new ReflectionFunction($foo);
$foo = foo(?, ?);
echo (string) new ReflectionFunction($foo);
$foo = foo(?, ?, ?);
echo (string) new ReflectionFunction($foo);
?>
--EXPECTF--
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 6 - 6
- Parameters [1] {
Parameter #0 [ <required> $a ]
}
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 10 - 10
- Parameters [2] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <optional> ...$b ]
}
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 14 - 14
- Parameters [2] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b0 ]
}
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 18 - 18
- Parameters [3] {
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b0 ]
Parameter #2 [ <required> $b1 ]
}
}
@@ -0,0 +1,43 @@
--TEST--
PFA reflection: internal with variadics
--FILE--
<?php
$foo = sprintf(?);
echo (string) new ReflectionFunction($foo);
$foo = sprintf(?, ...);
echo (string) new ReflectionFunction($foo);
$foo = sprintf(?, ?);
echo (string) new ReflectionFunction($foo);
?>
--EXPECTF--
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_003.php 2 - 2
- Parameters [1] {
Parameter #0 [ <required> string $format ]
}
- Return [ string ]
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_003.php 6 - 6
- Parameters [2] {
Parameter #0 [ <required> string $format ]
Parameter #1 [ <optional> mixed ...$values ]
}
- Return [ string ]
}
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreflection_003.php 10 - 10
- Parameters [2] {
Parameter #0 [ <required> string $format ]
Parameter #1 [ <required> mixed $values0 ]
}
- Return [ string ]
}
@@ -0,0 +1,16 @@
--TEST--
PFA reflection: ReflectionFunction::isAnonymous() is true for partials
--FILE--
<?php
var_dump((new ReflectionFunction('sprintf'))->isAnonymous());
var_dump((new ReflectionFunction(function () {}))->isAnonymous());
var_dump((new ReflectionFunction(sprintf(?)))->isAnonymous());
?>
--EXPECT--
bool(false)
bool(true)
bool(true)
@@ -0,0 +1,16 @@
--TEST--
PFA reflection: ReflectionFunction::isClosure() is true for partials
--FILE--
<?php
var_dump((new ReflectionFunction('sprintf'))->isClosure());
var_dump((new ReflectionFunction(function () {}))->isClosure());
var_dump((new ReflectionFunction(sprintf(?)))->isClosure());
?>
--EXPECT--
bool(false)
bool(true)
bool(true)
@@ -0,0 +1,148 @@
--TEST--
PFA supports relative return types
--FILE--
<?php
if (time() > 0) {
trait T {
public function getSelf(object $o): self {
return $o;
}
public function getStatic(object $o): static {
return $o;
}
}
}
class C {
use T;
}
class D extends C {
}
$c = new C;
echo "# C::getSelf():\n";
$self = $c->getSelf(?);
echo (string) new ReflectionFunction($self), "\n";
var_dump($self($c));
var_dump($self(new D));
try {
$self(new stdClass);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
echo "# C::getStatic():\n";
$static = $c->getStatic(?);
echo (string) new ReflectionFunction($static), "\n";
var_dump($static($c));
var_dump($static(new D));
try {
$static(new stdClass);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$d = new D;
echo "# D::getSelf():\n";
$self = $d->getSelf(?);
echo (string) new ReflectionFunction($self), "\n";
var_dump($self($d));
var_dump($self(new C));
try {
$self(new stdClass);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
echo "# D::getStatic():\n";
$static = $d->getStatic(?);
echo (string) new ReflectionFunction($static), "\n";
var_dump($static($d));
try {
var_dump($static(new C));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
$static(new stdClass);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
# C::getSelf():
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 25 - 25
- Parameters [1] {
Parameter #0 [ <required> object $o ]
}
- Return [ self ]
}
object(C)#%d (0) {
}
object(D)#%d (0) {
}
C::getSelf(): Return value must be of type C, stdClass returned
# C::getStatic():
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 39 - 39
- Parameters [1] {
Parameter #0 [ <required> object $o ]
}
- Return [ static ]
}
object(C)#%d (0) {
}
object(D)#%d (0) {
}
C::getStatic(): Return value must be of type C, stdClass returned
# D::getSelf():
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 55 - 55
- Parameters [1] {
Parameter #0 [ <required> object $o ]
}
- Return [ self ]
}
object(D)#%d (0) {
}
object(C)#%d (0) {
}
TypeError: C::getSelf(): Return value must be of type C, stdClass returned
# D::getStatic():
Closure [ <user> public method {closure:%s:%d} ] {
@@ %s 69 - 69
- Parameters [1] {
Parameter #0 [ <required> object $o ]
}
- Return [ static ]
}
object(D)#%d (0) {
}
TypeError: C::getStatic(): Return value must be of type D, C returned
C::getStatic(): Return value must be of type D, stdClass returned
@@ -0,0 +1,20 @@
--TEST--
PFA inherits return type
--FILE--
<?php
function foo($a) : array {}
echo (string) new ReflectionFunction(foo(new stdClass, ...));
?>
--EXPECTF--
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 4 - 4
- Bound Variables [1] {
Variable #0 [ $a ]
}
- Parameters [0] {
}
- Return [ array ]
}
@@ -0,0 +1,76 @@
<?php
function check_equivalence($tests)
{
foreach ($tests as $test => [$pfa, $closure]) {
echo "# ", $test, ": ";
$pfaReflector = new ReflectionFunction($pfa);
$closureReflector = new ReflectionFunction($closure);
try {
if (count($pfaReflector->getParameters()) !== count($closureReflector->getParameters())) {
throw new Exception(sprintf(
"Arity does not match: expected %d, got %d",
count($closureReflector->getParameters()),
count($pfaReflector->getParameters()),
));
}
$it = new MultipleIterator();
$it->attachIterator(new ArrayIterator($pfaReflector->getParameters()));
$it->attachIterator(new ArrayIterator($closureReflector->getParameters()));
foreach ($it as $i => [$pfaParam, $closureParam]) {
[$i] = $i;
if ($pfaParam->getName() !== $closureParam->getName()) {
throw new Exception(sprintf("Name of param %d does not match: %s vs %s",
$i,
$pfaParam->getName(),
$closureParam->getName(),
));
}
if ((string)$pfaParam->getType() !== (string)$closureParam->getType()) {
throw new Exception(sprintf("Type of param %d does not match: %s vs %s",
$i,
$pfaParam->getType(),
$closureParam->getType(),
));
}
if ($pfaParam->isOptional() !== $closureParam->isOptional()) {
throw new Exception(sprintf("Optionalness of param %d does not match: %d vs %d",
$i,
$pfaParam->isOptional(),
$closureParam->isOptional(),
));
}
}
$args = [];
foreach ($pfaReflector->getParameters() as $i => $p) {
$args[] = match ((string) $p->getType()) {
'int' => 100 + $i,
'float' => 100.5 + $i,
'?float' => 100.5 + $i,
'string' => (string) (100 + $i),
'Point' => new Point,
'mixed' => "mixed($i)",
'' => "mixed($i)",
};
}
if ($pfaReflector->getClosureThis() !== $closureReflector->getClosureThis()) {
throw new Exception("\$this differs");
}
if ($pfa(...$args) !== $closure(...$args)) {
throw new Exception("PFA is not equivalent to closure");
}
} catch (Exception $e) {
echo $e->getMessage(), "\n";
echo $pfaReflector;
echo $closureReflector;
return;
}
echo "Ok\n";
}
}
@@ -0,0 +1,25 @@
--TEST--
PFA RFC examples: "Constant expressions" section
--XFAIL--
PFA in constant expressions not implemented yet
--FILE--
<?php
class Foo
{
public function __construct(
private \Closure $callback = bar(1, 2, ?),
) {}
}
class Example
{
public const BASE = 10;
private Closure $arrayToInt = array_map(intval(?, self::BASE), ?);
}
?>
==DONE==
--EXPECTF--
==DONE==
@@ -0,0 +1,25 @@
--TEST--
PFA RFC examples: "Debug output" section
--FILE--
<?php
function f($a, #[SensitiveParameter] $b, $c) {
g();
}
function g() {
throw new Exception();
}
$f = f(?, ?, 3);
$f(1, 2);
?>
--EXPECTF--
Fatal error: Uncaught Exception in %s:%d
Stack trace:
#0 %s(%d): g()
#1 %s(%d): f(1, Object(SensitiveParameterValue), 3)
#2 %s(%d): {closure:pfa:%s}(1, Object(SensitiveParameterValue))
#3 {main}
thrown in %s on line %d
@@ -0,0 +1,34 @@
--TEST--
PFA RFC examples: "Error examples" section
--FILE--
<?php
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {}
class Point {}
$point = new Point();
try {
stuff(?);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
stuff(?, ?, ?, ?, ?, ?);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
try {
stuff(?, ?, 3.5, $point, i: 5);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
ArgumentCountError: Partial application of stuff() expects at least 4 arguments, 1 given
ArgumentCountError: Partial application of stuff() expects at most 5 arguments, 6 given
Error: Named parameter $i overwrites previous placeholder
@@ -0,0 +1,30 @@
--TEST--
PFA RFC examples: "Evaluation order" section
--FILE--
<?php
function getArg() {
print __FUNCTION__ . PHP_EOL;
return 'hi';
}
function speak(string $who, string $msg) {
printf("%s: %s\n", $who, $msg);
}
$arrow = static fn($who) => speak($who, getArg());
print "Arnaud\n";
$arrow('Larry');
$partial = speak(?, getArg());
print "Arnaud\n";
$partial('Larry');
?>
--EXPECT--
Arnaud
getArg
Larry: hi
getArg
Arnaud
Larry: hi
@@ -0,0 +1,54 @@
--TEST--
PFA RFC examples: "Variadics, func_get_args(), and extraneous arguments" section
--FILE--
<?php
require 'rfc_examples.inc';
function foo(int $i, int $j) {
return func_num_args();
}
function foo2(int $i, int $j, ...$extra) {
return func_num_args();
}
$tests = [
'If a PFA call has no ... placeholder, then any extraneous arguments to the resulting closure will be ignored. That is consistent with how manually writing the equivalent closure would behave, and is the same regardless of whether the underlying function is variadic' => [
foo(1, ?),
static function (int $j) { return foo(1, $j); },
],
'If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function' => [
foo(1, ?, ...),
static function (int $j) { return foo(1, $j, ...array_slice(func_get_args(), 1)); },
],
'If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal' => [
foo2(1, ?, ...),
static function (int $j, ...$extra) { return foo2(1, $j, ...$extra); },
],
];
check_equivalence($tests);
echo "# The extra parameter here will be passed to the closure object, which will simply ignore it:\n";
var_dump(foo(1, ?)(4, 'ignore me'));
echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al:\n";
var_dump(foo(1, ?, ...)(4, 'ignore me'));
echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the \$extra array:\n";
var_dump(foo2(1, ?, ...)(4, 'ignore me'));
?>
==DONE==
--EXPECT--
# If a PFA call has no ... placeholder, then any extraneous arguments to the resulting closure will be ignored. That is consistent with how manually writing the equivalent closure would behave, and is the same regardless of whether the underlying function is variadic: Ok
# If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function: Ok
# If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal: Ok
# The extra parameter here will be passed to the closure object, which will simply ignore it:
int(2)
# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al:
int(3)
# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the $extra array:
int(3)
==DONE==
@@ -0,0 +1,14 @@
--TEST--
PFA RFC examples: "Incompatible functions" section
--FILE--
<?php
try {
(function ($f) { $f(?); })("func_get_arg");
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Cannot call func_get_arg() dynamically
@@ -0,0 +1,47 @@
--TEST--
PFA RFC examples: "Magic methods" section
--FILE--
<?php
require 'rfc_examples.inc';
class Foo {
public function __call($method, $args) {
printf("%s::%s\n", __CLASS__, $method);
print_r($args);
}
}
$f = new Foo();
check_equivalence([
'Test 1' => [
$f->method(?, ?),
(function ($f) {
return fn(mixed $arguments0, mixed $arguments1) => $f->method($arguments0, $arguments1);
})($f)->bindTo($f),
],
]);
try {
$f->method(?, ?)(a: 1, b: 2);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
# Test 1: Foo::method
Array
(
[0] => mixed(0)
[1] => mixed(1)
)
Foo::method
Array
(
[0] => mixed(0)
[1] => mixed(1)
)
Ok
Error: Unknown named parameter $a
@@ -0,0 +1,44 @@
--TEST--
PFA RFC examples: "Overview" section
--FILE--
<?php
require 'rfc_examples.inc';
function foo(int $a, int $b, int $c, int $d): int
{
return $a + $b + $c + $d;
}
$tests = [
'Test 1' => [
foo(1, ?, 3, 4),
static fn(int $b): int => foo(1, $b, 3, 4),
],
'Test 2' => [
foo(1, ?, 3, ?),
static fn(int $b, int $d): int => foo(1, $b, 3, $d),
],
'Test 3' => [
foo(1, ...),
static fn(int $b, int $c, int $d): int => foo(1, $b, $c, $d),
],
'Test 4' => [
foo(1, 2, ...),
static fn(int $c, int $d): int => foo(1, 2, $c, $d),
],
'Test 5' => [
foo(1, ?, 3, ...),
static fn(int $b, int $d): int => foo(1, $b, 3, $d),
],
];
check_equivalence($tests);
?>
--EXPECT--
# Test 1: Ok
# Test 2: Ok
# Test 3: Ok
# Test 4: Ok
# Test 5: Ok
@@ -0,0 +1,113 @@
--TEST--
PFA RFC examples: "Scoping" section
--FILE--
<?php
require 'rfc_examples.inc';
function foo(int $i, int $j = 0): string {
return "$i $j";
}
class Foo {
static function bar(int $i, int $j): string {
return "$i $j";
}
}
class C {
function f($a) {
return self::class;
}
}
class CSubClass extends C {
function g($a) {
return self::class;
}
function h($a): \Closure {
return parent::f(?);
}
}
class Unrelated {}
$tests = [
'Static closure 1' => [
foo(?, ?),
static fn(int $i, int $j): string => foo($i, $j),
],
'Static closure 2' => [
Foo::bar(?, ?),
static fn(int $i, int $j): string => Foo::bar($i, $j),
],
'Static closure 3' => [
foo(?, ?)(1, ?),
static fn(int $j): string => foo(1, $j),
],
'Static closure 4' => [
foo(...)(?),
static fn(int $i): string => foo($i, 0),
],
];
check_equivalence($tests);
$c = new C();
$f = $c->f(?);
echo "# Cannot unbind \$this:\n";
var_dump($f->bindTo(null, C::class)); // Warning: Cannot unbind $this of method, this will be an error in PHP 9 (returns null)
echo "# Cannot rebind scope:\n";
var_dump($f->bindTo($c, CSubClass::class)); // Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 (returns null)
echo "# Can rebind \$this with subclass:\n";
var_dump($f->bindTo(new CSubClass, C::class)); // Allowed
echo "# Cannot rebind \$this with unrelated class:\n";
$f = $f->bindTo(new Unrelated, C::class); // Warning: Cannot bind method C::{closure:/path/to/test.php:11}() to object of class Unrelated, this will be an error in PHP 9 (returns null)
echo "# self resolution:\n";
$c = new CSubClass();
var_dump($c->f(?)(1)); // string(1) "C"
var_dump($c->g(?)(1)); // string(9) "CSubClass"
var_dump($c->h(1)(1)); // string(1) "C"
?>
--EXPECTF--
# Static closure 1: Ok
# Static closure 2: Ok
# Static closure 3: Ok
# Static closure 4: Ok
# Cannot unbind $this:
Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
NULL
# Cannot rebind scope:
Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d
NULL
# Can rebind $this with subclass:
object(Closure)#%d (5) {
["name"]=>
string(%d) "{closure:pfa:%s}"
["file"]=>
string(%d) "%s"
["line"]=>
int(53)
["this"]=>
object(CSubClass)#%d (0) {
}
["parameter"]=>
array(1) {
["$a"]=>
string(10) "<required>"
}
}
# Cannot rebind $this with unrelated class:
Warning: Cannot bind method C::{closure:pfa:%s}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
# self resolution:
string(1) "C"
string(9) "CSubClass"
string(1) "C"
@@ -0,0 +1,30 @@
--TEST--
PFA RFC examples: "Placeholder Semantics" section
--FILE--
<?php
require 'rfc_examples.inc';
class Point {}
function foo(int $a = 5, int $b = 1, string ...$c) { }
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {}
$tests = [
'Test 1' => [
foo(?, ?, ?, ?),
static fn(int $a, int $b, string $c0, string $c1) => foo($a, $b, $c0, $c1),
],
'Test 2' => [
stuff(1, ?, p: ?, f: 3.14, ...),
static fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $p, $m),
],
];
check_equivalence($tests);
?>
--EXPECT--
# Test 1: Ok
# Test 2: Ok
@@ -0,0 +1,217 @@
--TEST--
PFA RFC examples: "Placeholder Semantics: Examples" section
--FILE--
<?php
require 'rfc_examples.inc';
class Point {}
function stuff(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array {
return [$i1, $s2, $f3, $p4, $m5];
}
function things(int $i1, ?float $f3 = null, Point ...$points): array {
return [$i1, $f3, $points];
}
function four(int $a, int $b, int $c, int $d): string {
return "$a, $b, $c, $d\n";
}
class E {
private static $cache = [];
public function __construct(private int $x, private int $y) {}
public static function make(int $x, int $y): self {
return self::$cache["$x-$y"] ??= new self($x, $y);
}
public function foo(int $a, int $b, int $c): array {
return [$a, $b, $c];
}
}
class RunMe {
public function __invoke(int $a, int $b): string {
return "$a $b";
}
}
$point = new Point();
$tests = [
'Manually specify the first two values, and pull the rest "as is"' => [
stuff(?, ?, ...),
static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array
=> stuff($i1, $s2, $f3, $p4, $m5),
],
'The degenerate "first class callables" case. (Supported since 8.1)' => [
stuff(...),
static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array
=> stuff($i1, $s2, $f3, $p4, $m5),
],
'Provide some values, require the rest to be provided later (1)' => [
stuff(1, 'hi', ?, ?, ?),
static fn(float $f3, Point $p4, int $m5): array => stuff(1, 'hi', $f3, $p4, $m5),
],
'Provide some values, require the rest to be provided later (2)' => [
stuff(1, 'hi', ...),
static fn(float $f3, Point $p4, int $m5 = 0): array => stuff(1, 'hi', $f3, $p4, $m5),
],
'Provide some values, but not just from the left (1)' => [
stuff(1, ?, 3.5, ?, ?),
static fn(string $s2, Point $p4, int $m5): array => stuff(1, $s2, 3.5, $p4, $m5),
],
'Provide some values, but not just from the left (2)' => [
stuff(1, ?, 3.5, ...),
static fn(string $s2, Point $p4, int $m5 = 0): array => stuff(1, $s2, 3.5, $p4, $m5),
],
'Provide just the last value' => [
stuff(?, ?, ?, ?, 5),
static fn(int $i1, string $s2, float $f3, Point $p4): array
=> stuff($i1, $s2, $f3, $p4, 5),
],
'Not accounting for an optional argument means it will always get its default value' => [
stuff(?, ?, ?, ?),
static fn(int $i1, string $s2, float $f3, Point $p4): array
=> stuff($i1, $s2, $f3, $p4),
],
'Named arguments can be pulled "out of order", and still work (1)' => [
stuff(?, ?, f3: 3.5, p4: $point),
static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point),
],
'Named arguments can be pulled "out of order", and still work (2)' => [
stuff(?, ?, p4: $point, f3: 3.5),
static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point),
],
'But named placeholders adopt the order listed' => [
stuff(s2: ?, i1: ?, p4: ?, f3: 3.5),
static fn(string $s2, int $i1, Point $p4): array => stuff($i1, $s2, 3.5, $p4),
],
'The ... "everything else" placeholder respects named arguments' => [
stuff(?, ?, f3: 3.5, p4: $point, ...),
static fn(int $i1, string $s2, int $m5 = 0): array => stuff($i1, $s2, 3.5, $point, $m5),
],
'Prefill all parameters, making a "delayed call" or "thunk"' => [
stuff(1, 'hi', 3.4, $point, 5, ...),
static fn(): array => stuff(1, 'hi', 3.4, $point, 5),
],
// Variadics
'FCC equivalent. The signature is unchanged' => [
things(...),
static fn(int $i1, ?float $f3 = null, Point ...$points): array => things(...[$i1, $f3, ...$points]),
],
'Provide some values, but allow the variadic to remain variadic' => [
things(1, 3.14, ...),
static fn(Point ...$points): array => things(1, 3.14, ...$points),
],
'In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case' => [
things(?, ?, ?, ?),
static fn(int $i1, ?float $f3, Point $points0, Point $points1): array => things($i1, $f3, $points0, $points1),
],
// Esoteric examples
'Esoteric 1' => [
four(...),
static fn(int $a, int $b, int $c, int $d): string => four($a, $b, $c, $d),
],
'Esoteric 2' => [
four(1, 2, ...),
static fn(int $c, int $d): string => four(1, 2, $c, $d),
],
'Esoteric 3' => [
four(1, 2, 3, ?),
static fn(int $d): string => four(1, 2, 3, $d),
],
'Esoteric 4' => [
four(1, ?, ?, 4),
static fn(int $b, int $c): string => four(1, $b, $c, 4),
],
'Esoteric 5' => [
four(1, 2, 3, 4, ...),
static fn(): string => four(1, 2, 3, 4, ...array_slice(func_get_args(), 4)),
],
'Esoteric 6' => [
four(d: 4, a: 1, ...),
static fn(int $b, int $c): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)),
],
'Esoteric 7' => [
four(c: ?, d: 4, b: ?, a: 1),
static fn(int $c, int $b): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)),
],
// Other callable styles
'This is allowed. Note the method is static, thus the partial closure is static' => [
E::make(1, ?),
static fn(int $y): E => E::make(1, $y),
],
'Note the method is non-static, so the partial closure is non-static' => (function () {
$eMaker = E::make(1, ?);
$e = $eMaker(2);
return [
$e->foo(?, ?, 3),
(function ($e) {
return fn(int $a, int $b): array => $e->foo($a, $b, 3);
})($e)->bindTo($e),
];
})(),
'$c can then be further refined' => (function () {
$eMaker = E::make(1, ?);
$e = $eMaker(2);
$c = $e->foo(?, ?, 3);
return [
$c(1, ?),
(function ($e) {
return fn(int $b): array => $e->foo(1, $b, 3);
})($e)->bindTo($e),
];
})(),
'RunMe' => (function () {
$r = new RunMe();
return [
$r(?, 3),
(function ($r) {
return fn(int $a): string => $r($a, 3);
})($r)->bindTo($r),
];
})(),
];
check_equivalence($tests);
?>
--EXPECT--
# Manually specify the first two values, and pull the rest "as is": Ok
# The degenerate "first class callables" case. (Supported since 8.1): Ok
# Provide some values, require the rest to be provided later (1): Ok
# Provide some values, require the rest to be provided later (2): Ok
# Provide some values, but not just from the left (1): Ok
# Provide some values, but not just from the left (2): Ok
# Provide just the last value: Ok
# Not accounting for an optional argument means it will always get its default value: Ok
# Named arguments can be pulled "out of order", and still work (1): Ok
# Named arguments can be pulled "out of order", and still work (2): Ok
# But named placeholders adopt the order listed: Ok
# The ... "everything else" placeholder respects named arguments: Ok
# Prefill all parameters, making a "delayed call" or "thunk": Ok
# FCC equivalent. The signature is unchanged: Ok
# Provide some values, but allow the variadic to remain variadic: Ok
# In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case: Ok
# Esoteric 1: Ok
# Esoteric 2: Ok
# Esoteric 3: Ok
# Esoteric 4: Ok
# Esoteric 5: Ok
# Esoteric 6: Ok
# Esoteric 7: Ok
# This is allowed. Note the method is static, thus the partial closure is static: Ok
# Note the method is non-static, so the partial closure is non-static: Ok
# $c can then be further refined: Ok
# RunMe: Ok
@@ -0,0 +1,18 @@
--TEST--
PFA supports static methods
--FILE--
<?php
class Foo {
public static function method($a, $b) {
printf("%s\n", __METHOD__);
}
}
$foo = Foo::method(new stdClass, ...);
$bar = $foo(new stdClass, ...);
$bar();
?>
--EXPECTF--
Foo::method
@@ -0,0 +1,45 @@
--TEST--
PFA of static method is static
--FILE--
<?php
class C {
public static function f($a) {
var_dump($a);
}
}
$f = C::f(?);
echo new ReflectionFunction($f), "\n";
$f(1);
$f = (new C)->f(?);
echo new ReflectionFunction($f), "\n";
$f(1);
// Warns
var_dump($f->bindTo(new C));
?>
--EXPECTF--
Closure [ <user> static public method {closure:pfa:%s:9} ] {
@@ %s 9 - 9
- Parameters [1] {
Parameter #0 [ <required> $a ]
}
}
int(1)
Closure [ <user> static public method {closure:pfa:%s:13} ] {
@@ %s 13 - 13
- Parameters [1] {
Parameter #0 [ <required> $a ]
}
}
int(1)
Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
NULL
@@ -0,0 +1,35 @@
--TEST--
PFA of instance method is not static
--FILE--
<?php
class C {
public function f($a) {
var_dump($this, $this === $a);
}
}
$c = new C();
$f = $c->f(?);
echo new ReflectionFunction($f), "\n";
$f($c);
$c2 = new C();
$f->bindTo($c2)($c2);
?>
--EXPECTF--
Closure [ <user> public method {closure:pfa:%s:10} ] {
@@ %s 10 - 10
- Parameters [1] {
Parameter #0 [ <required> $a ]
}
}
object(C)#%d (0) {
}
bool(true)
object(C)#%d (0) {
}
bool(true)
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff. Show more