mirror of
https://github.com/php/php-src.git
synced 2024-11-27 11:53:33 +08:00
Implement arrow functions
Per RFC: https://wiki.php.net/rfc/arrow_functions_v2 Co-authored-by: Levi Morrison <levim@php.net> Co-authored-by: Bob Weinand <bobwei9@hotmail.com>
This commit is contained in:
parent
eaab0a2b6f
commit
f3e5bbe6f3
11
UPGRADING
11
UPGRADING
@ -24,6 +24,9 @@ PHP 7.4 UPGRADE NOTES
|
||||
- Core:
|
||||
. get_declared_classes() no longer returns anonymous classes that haven't
|
||||
been instantiated yet.
|
||||
. "fn" is now a reserved keyword. In particular it can no longer be used as a
|
||||
function or class name. It can still be used as a method or class constant
|
||||
name.
|
||||
|
||||
- Curl:
|
||||
. Attempting to serialize a CURLFile class will now generate an exception.
|
||||
@ -116,6 +119,14 @@ PHP 7.4 UPGRADE NOTES
|
||||
$user->name can only be assigned strings. For more information see the
|
||||
RFC: https://wiki.php.net/rfc/typed_properties_v2
|
||||
|
||||
. Added support for arrow functions with implicit by-value scope binding.
|
||||
For example:
|
||||
|
||||
$factor = 10;
|
||||
$nums = array_map(fn($num) => $num * $factor, $nums);
|
||||
|
||||
RFC: https://wiki.php.net/rfc/arrow_functions_v2
|
||||
|
||||
. Added support for coalesce assign (??=) operator. For example:
|
||||
|
||||
$array['key'] ??= computeDefault();
|
||||
|
@ -3,12 +3,12 @@ Argument unpacking with many arguments
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function fn(...$args) {
|
||||
function f(...$args) {
|
||||
var_dump(count($args));
|
||||
}
|
||||
|
||||
$array = array_fill(0, 10000, 42);
|
||||
fn(...$array, ...$array);
|
||||
f(...$array, ...$array);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
|
45
Zend/tests/arrow_functions/001.phpt
Normal file
45
Zend/tests/arrow_functions/001.phpt
Normal file
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Basic arrow function functionality check
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$foo = fn() => 1;
|
||||
var_dump($foo());
|
||||
|
||||
$foo = fn($x) => $x;
|
||||
var_dump($foo(2));
|
||||
|
||||
$foo = fn($x, $y) => $x + $y;
|
||||
var_dump($foo(1, 2));
|
||||
|
||||
// Closing over $var
|
||||
$var = 4;
|
||||
$foo = fn() => $var;
|
||||
var_dump($foo());
|
||||
|
||||
// Not closing over $var, it's a parameter
|
||||
$foo = fn($var) => $var;
|
||||
var_dump($foo(5));
|
||||
|
||||
// Close over $var by-value, not by-reference
|
||||
$var = 5;
|
||||
$foo = fn() => ++$var;
|
||||
var_dump($foo());
|
||||
var_dump($var);
|
||||
|
||||
// Nested arrow functions closing over variable
|
||||
$var = 6;
|
||||
var_dump((fn() => fn() => $var)()());
|
||||
var_dump((fn() => function() use($var) { return $var; })()());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(1)
|
||||
int(2)
|
||||
int(3)
|
||||
int(4)
|
||||
int(5)
|
||||
int(6)
|
||||
int(5)
|
||||
int(6)
|
||||
int(6)
|
13
Zend/tests/arrow_functions/002.phpt
Normal file
13
Zend/tests/arrow_functions/002.phpt
Normal file
@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
Arrow functions implicit use must be throwing notices only upon actual use
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$b = 1;
|
||||
|
||||
var_dump((fn() => $b + $c)());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Notice: Undefined variable: c in %s on line %d
|
||||
int(1)
|
21
Zend/tests/arrow_functions/003.phpt
Normal file
21
Zend/tests/arrow_functions/003.phpt
Normal file
@ -0,0 +1,21 @@
|
||||
--TEST--
|
||||
Variable-variables inside arrow functions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$a = 1;
|
||||
$var = "a";
|
||||
$fn = fn() => $$var;
|
||||
var_dump($fn());
|
||||
|
||||
${5} = 2;
|
||||
$fn = fn() => ${5};
|
||||
var_dump($fn());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Notice: Undefined variable: a in %s on line %d
|
||||
NULL
|
||||
|
||||
Notice: Undefined variable: 5 in %s on line %d
|
||||
NULL
|
13
Zend/tests/arrow_functions/004.phpt
Normal file
13
Zend/tests/arrow_functions/004.phpt
Normal file
@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
Auto-globals in arrow functions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// This should work, but *not* generate a binding for $GLOBALS
|
||||
$a = 123;
|
||||
$fn = fn() => $GLOBALS['a'];
|
||||
var_dump($fn());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(123)
|
54
Zend/tests/arrow_functions/005.phpt
Normal file
54
Zend/tests/arrow_functions/005.phpt
Normal file
@ -0,0 +1,54 @@
|
||||
--TEST--
|
||||
Arrow function $this binding
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public function method() {
|
||||
// It would be okay if this is NULL, but the rest should work
|
||||
$fn = fn() => 42;
|
||||
$r = new ReflectionFunction($fn);
|
||||
var_dump($r->getClosureThis());
|
||||
|
||||
$fn = fn() => $this;
|
||||
var_dump($fn());
|
||||
|
||||
$fn = fn() => Test::method2();
|
||||
$fn();
|
||||
|
||||
$fn = fn() => call_user_func('Test::method2');
|
||||
$fn();
|
||||
|
||||
$thisName = "this";
|
||||
$fn = fn() => $$thisName;
|
||||
var_dump($fn());
|
||||
|
||||
$fn = fn() => self::class;
|
||||
var_dump($fn());
|
||||
|
||||
// static can be used to unbind $this
|
||||
$fn = static fn() => isset($this);
|
||||
var_dump($fn());
|
||||
}
|
||||
|
||||
public function method2() {
|
||||
var_dump($this);
|
||||
}
|
||||
}
|
||||
|
||||
(new Test)->method();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(Test)#1 (0) {
|
||||
}
|
||||
object(Test)#1 (0) {
|
||||
}
|
||||
object(Test)#1 (0) {
|
||||
}
|
||||
object(Test)#1 (0) {
|
||||
}
|
||||
object(Test)#1 (0) {
|
||||
}
|
||||
string(4) "Test"
|
||||
bool(false)
|
44
Zend/tests/arrow_functions/006.phpt
Normal file
44
Zend/tests/arrow_functions/006.phpt
Normal file
@ -0,0 +1,44 @@
|
||||
--TEST--
|
||||
Arrow functions syntax variations
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// By-reference argument and return
|
||||
$var = 1;
|
||||
$id = fn&(&$x) => $x;
|
||||
$ref =& $id($var);
|
||||
$ref++;
|
||||
var_dump($var);
|
||||
|
||||
// int argument and return type
|
||||
$var = 10;
|
||||
$int_fn = fn(int $x): int => $x;
|
||||
var_dump($int_fn($var));
|
||||
try {
|
||||
$int_fn("foo");
|
||||
} catch (TypeError $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
$varargs = fn(?int... $args): array => $args;
|
||||
var_dump($varargs(20, null, 30));
|
||||
try {
|
||||
$varargs(40, "foo");
|
||||
} catch (TypeError $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
int(2)
|
||||
int(10)
|
||||
Argument 1 passed to {closure}() must be of the type int, string given, called in %s on line %d
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(20)
|
||||
[1]=>
|
||||
NULL
|
||||
[2]=>
|
||||
int(30)
|
||||
}
|
||||
Argument 2 passed to {closure}() must be of the type int or null, string given, called in %s on line %d
|
14
Zend/tests/arrow_functions/007.phpt
Normal file
14
Zend/tests/arrow_functions/007.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Pretty printing for arrow functions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// TODO We're missing parentheses for the direct call
|
||||
assert((fn() => false)());
|
||||
assert((fn&(int... $args): ?bool => $args[0])(false));
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Warning: assert(): assert(fn() => false()) failed in %s on line %d
|
||||
|
||||
Warning: assert(): assert(fn&(int ...$args): ?bool => $args[0](false)) failed in %s on line %d
|
28
Zend/tests/arrow_functions/008.phpt
Normal file
28
Zend/tests/arrow_functions/008.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Yield inside arrow functions
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// This doesn't make terribly much sense, but it works...
|
||||
|
||||
$fn = fn() => yield 123;
|
||||
foreach ($fn() as $val) {
|
||||
var_dump($val);
|
||||
}
|
||||
|
||||
$fn = fn() => yield from [456, 789];
|
||||
foreach ($fn() as $val) {
|
||||
var_dump($val);
|
||||
}
|
||||
|
||||
$fn = fn() => fn() => yield 987;
|
||||
foreach ($fn()() as $val) {
|
||||
var_dump($val);
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(123)
|
||||
int(456)
|
||||
int(789)
|
||||
int(987)
|
@ -4,11 +4,11 @@ Bug #65035: yield / exit segfault
|
||||
<?php
|
||||
|
||||
function gen() {
|
||||
fn();
|
||||
f();
|
||||
yield;
|
||||
}
|
||||
|
||||
function fn() {
|
||||
function f() {
|
||||
exit('Done');
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ class Obj
|
||||
function switch(){ echo __METHOD__, PHP_EOL; }
|
||||
function yield(){ echo __METHOD__, PHP_EOL; }
|
||||
function function(){ echo __METHOD__, PHP_EOL; }
|
||||
function fn(){ echo __METHOD__, PHP_EOL; }
|
||||
function if(){ echo __METHOD__, PHP_EOL; }
|
||||
function endswitch(){ echo __METHOD__, PHP_EOL; }
|
||||
function finally(){ echo __METHOD__, PHP_EOL; }
|
||||
@ -135,6 +136,7 @@ $obj->continue();
|
||||
$obj->switch();
|
||||
$obj->yield();
|
||||
$obj->function();
|
||||
$obj->fn();
|
||||
$obj->if();
|
||||
$obj->endswitch();
|
||||
$obj->finally();
|
||||
@ -213,6 +215,7 @@ Obj::continue
|
||||
Obj::switch
|
||||
Obj::yield
|
||||
Obj::function
|
||||
Obj::fn
|
||||
Obj::if
|
||||
Obj::endswitch
|
||||
Obj::finally
|
||||
|
@ -56,6 +56,7 @@ class Obj
|
||||
static function switch(){ echo __METHOD__, PHP_EOL; }
|
||||
static function yield(){ echo __METHOD__, PHP_EOL; }
|
||||
static function function(){ echo __METHOD__, PHP_EOL; }
|
||||
static function fn(){ echo __METHOD__, PHP_EOL; }
|
||||
static function if(){ echo __METHOD__, PHP_EOL; }
|
||||
static function endswitch(){ echo __METHOD__, PHP_EOL; }
|
||||
static function finally(){ echo __METHOD__, PHP_EOL; }
|
||||
@ -133,6 +134,7 @@ Obj::continue();
|
||||
Obj::switch();
|
||||
Obj::yield();
|
||||
Obj::function();
|
||||
Obj::fn();
|
||||
Obj::if();
|
||||
Obj::endswitch();
|
||||
Obj::finally();
|
||||
@ -211,6 +213,7 @@ Obj::continue
|
||||
Obj::switch
|
||||
Obj::yield
|
||||
Obj::function
|
||||
Obj::fn
|
||||
Obj::if
|
||||
Obj::endswitch
|
||||
Obj::finally
|
||||
|
@ -56,6 +56,7 @@ class Obj
|
||||
var $switch = 'switch';
|
||||
var $yield = 'yield';
|
||||
var $function = 'function';
|
||||
var $fn = 'fn';
|
||||
var $if = 'if';
|
||||
var $endswitch = 'endswitch';
|
||||
var $finally = 'finally';
|
||||
@ -136,6 +137,7 @@ echo $obj->continue, PHP_EOL;
|
||||
echo $obj->switch, PHP_EOL;
|
||||
echo $obj->yield, PHP_EOL;
|
||||
echo $obj->function, PHP_EOL;
|
||||
echo $obj->fn, PHP_EOL;
|
||||
echo $obj->if, PHP_EOL;
|
||||
echo $obj->endswitch, PHP_EOL;
|
||||
echo $obj->finally, PHP_EOL;
|
||||
@ -217,6 +219,7 @@ continue
|
||||
switch
|
||||
yield
|
||||
function
|
||||
fn
|
||||
if
|
||||
endswitch
|
||||
finally
|
||||
|
@ -56,6 +56,7 @@ class Obj
|
||||
static $switch = 'switch';
|
||||
static $yield = 'yield';
|
||||
static $function = 'function';
|
||||
static $fn = 'fn';
|
||||
static $if = 'if';
|
||||
static $endswitch = 'endswitch';
|
||||
static $finally = 'finally';
|
||||
@ -134,6 +135,7 @@ echo Obj::$continue, PHP_EOL;
|
||||
echo Obj::$switch, PHP_EOL;
|
||||
echo Obj::$yield, PHP_EOL;
|
||||
echo Obj::$function, PHP_EOL;
|
||||
echo Obj::$fn, PHP_EOL;
|
||||
echo Obj::$if, PHP_EOL;
|
||||
echo Obj::$endswitch, PHP_EOL;
|
||||
echo Obj::$finally, PHP_EOL;
|
||||
@ -213,6 +215,7 @@ continue
|
||||
switch
|
||||
yield
|
||||
function
|
||||
fn
|
||||
if
|
||||
endswitch
|
||||
finally
|
||||
|
@ -55,6 +55,7 @@ class Obj
|
||||
const SWITCH = 'switch';
|
||||
const YIELD = 'yield';
|
||||
const FUNCTION = 'function';
|
||||
const FN = 'fn';
|
||||
const IF = 'if';
|
||||
const ENDSWITCH = 'endswitch';
|
||||
const FINALLY = 'finally';
|
||||
@ -131,6 +132,7 @@ echo Obj::CONTINUE, PHP_EOL;
|
||||
echo Obj::SWITCH, PHP_EOL;
|
||||
echo Obj::YIELD, PHP_EOL;
|
||||
echo Obj::FUNCTION, PHP_EOL;
|
||||
echo Obj::FN, PHP_EOL;
|
||||
echo Obj::IF, PHP_EOL;
|
||||
echo Obj::ENDSWITCH, PHP_EOL;
|
||||
echo Obj::FINALLY, PHP_EOL;
|
||||
@ -208,6 +210,7 @@ continue
|
||||
switch
|
||||
yield
|
||||
function
|
||||
fn
|
||||
if
|
||||
endswitch
|
||||
finally
|
||||
|
@ -4,7 +4,7 @@ Cannot use $this as lexical variable
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
public function fn() {
|
||||
public function f() {
|
||||
return function() use ($this) {};
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ Optional parameter before variadic parameter
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function fn($reqParam, $optParam = null, ...$params) {
|
||||
function f($reqParam, $optParam = null, ...$params) {
|
||||
var_dump($reqParam, $optParam, $params);
|
||||
}
|
||||
|
||||
fn(1);
|
||||
fn(1, 2);
|
||||
fn(1, 2, 3);
|
||||
fn(1, 2, 3, 4);
|
||||
fn(1, 2, 3, 4, 5);
|
||||
|
||||
f(1);
|
||||
f(1, 2);
|
||||
f(1, 2, 3);
|
||||
f(1, 2, 3, 4);
|
||||
f(1, 2, 3, 4, 5);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
|
@ -1106,7 +1106,7 @@ static ZEND_COLD void zend_ast_export_var_list(smart_str *str, zend_ast_list *li
|
||||
if (i != 0) {
|
||||
smart_str_appends(str, ", ");
|
||||
}
|
||||
if (list->child[i]->attr) {
|
||||
if (list->child[i]->attr & ZEND_BIND_REF) {
|
||||
smart_str_appendc(str, '&');
|
||||
}
|
||||
smart_str_appendc(str, '$');
|
||||
@ -1341,6 +1341,7 @@ tail_call:
|
||||
/* declaration nodes */
|
||||
case ZEND_AST_FUNC_DECL:
|
||||
case ZEND_AST_CLOSURE:
|
||||
case ZEND_AST_ARROW_FUNC:
|
||||
case ZEND_AST_METHOD:
|
||||
decl = (zend_ast_decl *) ast;
|
||||
if (decl->flags & ZEND_ACC_PUBLIC) {
|
||||
@ -1359,11 +1360,15 @@ tail_call:
|
||||
if (decl->flags & ZEND_ACC_FINAL) {
|
||||
smart_str_appends(str, "final ");
|
||||
}
|
||||
smart_str_appends(str, "function ");
|
||||
if (decl->kind == ZEND_AST_ARROW_FUNC) {
|
||||
smart_str_appends(str, "fn");
|
||||
} else {
|
||||
smart_str_appends(str, "function ");
|
||||
}
|
||||
if (decl->flags & ZEND_ACC_RETURN_REFERENCE) {
|
||||
smart_str_appendc(str, '&');
|
||||
}
|
||||
if (ast->kind != ZEND_AST_CLOSURE) {
|
||||
if (ast->kind != ZEND_AST_CLOSURE && ast->kind != ZEND_AST_ARROW_FUNC) {
|
||||
smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
|
||||
}
|
||||
smart_str_appendc(str, '(');
|
||||
@ -1378,6 +1383,13 @@ tail_call:
|
||||
zend_ast_export_ns_name(str, decl->child[3], 0, indent);
|
||||
}
|
||||
if (decl->child[2]) {
|
||||
if (decl->kind == ZEND_AST_ARROW_FUNC) {
|
||||
ZEND_ASSERT(decl->child[2]->kind == ZEND_AST_RETURN);
|
||||
smart_str_appends(str, " => ");
|
||||
zend_ast_export_ex(str, decl->child[2]->child[0], 0, indent);
|
||||
break;
|
||||
}
|
||||
|
||||
smart_str_appends(str, " {\n");
|
||||
zend_ast_export_stmt(str, decl->child[2], indent + 1);
|
||||
zend_ast_export_indent(str, indent);
|
||||
|
@ -42,6 +42,7 @@ enum _zend_ast_kind {
|
||||
ZEND_AST_CLOSURE,
|
||||
ZEND_AST_METHOD,
|
||||
ZEND_AST_CLASS,
|
||||
ZEND_AST_ARROW_FUNC,
|
||||
|
||||
/* list nodes */
|
||||
ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,
|
||||
@ -281,6 +282,10 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast);
|
||||
typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr);
|
||||
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn);
|
||||
|
||||
static zend_always_inline zend_bool zend_ast_is_special(zend_ast *ast) {
|
||||
return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1;
|
||||
}
|
||||
|
||||
static zend_always_inline zend_bool zend_ast_is_list(zend_ast *ast) {
|
||||
return (ast->kind >> ZEND_AST_IS_LIST_SHIFT) & 1;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ static zend_string *zend_build_runtime_definition_key(zend_string *name, unsigne
|
||||
|
||||
/* NULL, name length, filename length, last accepting char position length */
|
||||
result = zend_string_alloc(1 + ZSTR_LEN(name) + ZSTR_LEN(filename) + char_pos_len, 0);
|
||||
sprintf(ZSTR_VAL(result), "%c%s%s%s", '\0', ZSTR_VAL(name), ZSTR_VAL(filename), char_pos_buf);
|
||||
sprintf(ZSTR_VAL(result), "%c%s%s%s", '\0', ZSTR_VAL(name), ZSTR_VAL(filename), char_pos_buf);
|
||||
return zend_new_interned_string(result);
|
||||
}
|
||||
/* }}} */
|
||||
@ -4049,11 +4049,9 @@ void zend_compile_global_var(zend_ast *ast) /* {{{ */
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void zend_compile_static_var_common(zend_ast *var_ast, zval *value, uint32_t by_ref) /* {{{ */
|
||||
static void zend_compile_static_var_common(zend_string *var_name, zval *value, uint32_t by_ref) /* {{{ */
|
||||
{
|
||||
zend_op *opline;
|
||||
zend_string *var_name = zval_make_interned_string(zend_ast_get_zval(var_ast));
|
||||
|
||||
if (!CG(active_op_array)->static_variables) {
|
||||
if (CG(active_op_array)->scope) {
|
||||
CG(active_op_array)->scope->ce_flags |= ZEND_HAS_STATIC_IN_METHODS;
|
||||
@ -4086,7 +4084,7 @@ void zend_compile_static_var(zend_ast *ast) /* {{{ */
|
||||
ZVAL_NULL(&value_zv);
|
||||
}
|
||||
|
||||
zend_compile_static_var_common(var_ast, &value_zv, ZEND_BIND_REF);
|
||||
zend_compile_static_var_common(zend_ast_get_str(var_ast), &value_zv, ZEND_BIND_REF);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@ -5473,7 +5471,7 @@ static void zend_compile_closure_binding(znode *closure, zend_op_array *op_array
|
||||
for (i = 0; i < list->children; ++i) {
|
||||
zend_ast *var_name_ast = list->child[i];
|
||||
zend_string *var_name = zval_make_interned_string(zend_ast_get_zval(var_name_ast));
|
||||
uint32_t by_ref = var_name_ast->attr;
|
||||
uint32_t mode = var_name_ast->attr;
|
||||
zend_op *opline;
|
||||
zval *value;
|
||||
|
||||
@ -5494,12 +5492,114 @@ static void zend_compile_closure_binding(znode *closure, zend_op_array *op_array
|
||||
opline = zend_emit_op(NULL, ZEND_BIND_LEXICAL, closure, NULL);
|
||||
opline->op2_type = IS_CV;
|
||||
opline->op2.var = lookup_cv(var_name);
|
||||
opline->extended_value = (uint32_t)((char*)value - (char*)op_array->static_variables->arData) | by_ref;
|
||||
opline->extended_value =
|
||||
(uint32_t)((char*)value - (char*)op_array->static_variables->arData) | mode;
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
|
||||
typedef struct {
|
||||
HashTable uses;
|
||||
zend_bool varvars_used;
|
||||
} closure_info;
|
||||
|
||||
static void find_implicit_binds_recursively(closure_info *info, zend_ast *ast) {
|
||||
if (!ast) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ast->kind == ZEND_AST_VAR) {
|
||||
zend_ast *name_ast = ast->child[0];
|
||||
if (name_ast->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(name_ast)) == IS_STRING) {
|
||||
zend_string *name = zend_ast_get_str(name_ast);
|
||||
if (zend_is_auto_global(name)) {
|
||||
/* These is no need to explicitly import auto-globals. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (zend_string_equals_literal(name, "this")) {
|
||||
/* $this does not need to be explicitly imported. */
|
||||
return;
|
||||
}
|
||||
|
||||
zend_hash_add_empty_element(&info->uses, name);
|
||||
} else {
|
||||
info->varvars_used = 1;
|
||||
find_implicit_binds_recursively(info, name_ast);
|
||||
}
|
||||
} else if (zend_ast_is_list(ast)) {
|
||||
zend_ast_list *list = zend_ast_get_list(ast);
|
||||
uint32_t i;
|
||||
for (i = 0; i < list->children; i++) {
|
||||
find_implicit_binds_recursively(info, list->child[i]);
|
||||
}
|
||||
} else if (ast->kind == ZEND_AST_CLOSURE) {
|
||||
/* For normal closures add the use() list. */
|
||||
zend_ast_decl *closure_ast = (zend_ast_decl *) ast;
|
||||
zend_ast *uses_ast = closure_ast->child[1];
|
||||
if (uses_ast) {
|
||||
zend_ast_list *uses_list = zend_ast_get_list(uses_ast);
|
||||
uint32_t i;
|
||||
for (i = 0; i < uses_list->children; i++) {
|
||||
zend_hash_add_empty_element(&info->uses, zend_ast_get_str(uses_list->child[i]));
|
||||
}
|
||||
}
|
||||
} else if (ast->kind == ZEND_AST_ARROW_FUNC) {
|
||||
/* For arrow functions recursively check the expression. */
|
||||
zend_ast_decl *closure_ast = (zend_ast_decl *) ast;
|
||||
find_implicit_binds_recursively(info, closure_ast->child[2]);
|
||||
} else if (!zend_ast_is_special(ast)) {
|
||||
uint32_t i, children = zend_ast_get_num_children(ast);
|
||||
for (i = 0; i < children; i++) {
|
||||
find_implicit_binds_recursively(info, ast->child[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void find_implicit_binds(closure_info *info, zend_ast *params_ast, zend_ast *stmt_ast)
|
||||
{
|
||||
zend_ast_list *param_list = zend_ast_get_list(params_ast);
|
||||
uint32_t i;
|
||||
|
||||
zend_hash_init(&info->uses, param_list->children, NULL, NULL, 0);
|
||||
|
||||
find_implicit_binds_recursively(info, stmt_ast);
|
||||
|
||||
/* Remove variables that are parameters */
|
||||
for (i = 0; i < param_list->children; i++) {
|
||||
zend_ast *param_ast = param_list->child[i];
|
||||
zend_hash_del(&info->uses, zend_ast_get_str(param_ast->child[1]));
|
||||
}
|
||||
}
|
||||
|
||||
static void compile_implicit_lexical_binds(
|
||||
closure_info *info, znode *closure, zend_op_array *op_array)
|
||||
{
|
||||
zend_string *var_name;
|
||||
zend_op *opline;
|
||||
|
||||
/* TODO We might want to use a special binding mode if varvars_used is set. */
|
||||
if (zend_hash_num_elements(&info->uses) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!op_array->static_variables) {
|
||||
op_array->static_variables = zend_new_array(8);
|
||||
}
|
||||
|
||||
ZEND_HASH_FOREACH_STR_KEY(&info->uses, var_name)
|
||||
zval *value = zend_hash_add(
|
||||
op_array->static_variables, var_name, &EG(uninitialized_zval));
|
||||
uint32_t offset = (uint32_t)((char*)value - (char*)op_array->static_variables->arData);
|
||||
|
||||
opline = zend_emit_op(NULL, ZEND_BIND_LEXICAL, closure, NULL);
|
||||
opline->op2_type = IS_CV;
|
||||
opline->op2.var = lookup_cv(var_name);
|
||||
opline->extended_value = offset | ZEND_BIND_IMPLICIT;
|
||||
ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
static void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
|
||||
{
|
||||
zend_op_array *op_array = CG(active_op_array);
|
||||
zend_ast_list *list = zend_ast_get_list(ast);
|
||||
@ -5508,7 +5608,6 @@ void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
|
||||
for (i = 0; i < list->children; ++i) {
|
||||
zend_ast *var_ast = list->child[i];
|
||||
zend_string *var_name = zend_ast_get_str(var_ast);
|
||||
uint32_t by_ref = var_ast->attr;
|
||||
zval zv;
|
||||
ZVAL_NULL(&zv);
|
||||
|
||||
@ -5522,11 +5621,21 @@ void zend_compile_closure_uses(zend_ast *ast) /* {{{ */
|
||||
}
|
||||
}
|
||||
|
||||
zend_compile_static_var_common(var_ast, &zv, by_ref);
|
||||
zend_compile_static_var_common(var_name, &zv, var_ast->attr);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void zend_compile_implicit_closure_uses(closure_info *info)
|
||||
{
|
||||
zend_string *var_name;
|
||||
ZEND_HASH_FOREACH_STR_KEY(&info->uses, var_name)
|
||||
zval zv;
|
||||
ZVAL_NULL(&zv);
|
||||
zend_compile_static_var_common(var_name, &zv, 0);
|
||||
ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body) /* {{{ */
|
||||
{
|
||||
zend_class_entry *ce = CG(active_class_entry);
|
||||
@ -5782,6 +5891,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
|
||||
zend_op_array *orig_op_array = CG(active_op_array);
|
||||
zend_op_array *op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
|
||||
zend_oparray_context orig_oparray_context;
|
||||
closure_info info = {0};
|
||||
|
||||
init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE);
|
||||
|
||||
@ -5795,7 +5905,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
|
||||
if (decl->doc_comment) {
|
||||
op_array->doc_comment = zend_string_copy(decl->doc_comment);
|
||||
}
|
||||
if (decl->kind == ZEND_AST_CLOSURE) {
|
||||
if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) {
|
||||
op_array->fn_flags |= ZEND_ACC_CLOSURE;
|
||||
}
|
||||
|
||||
@ -5804,7 +5914,10 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
|
||||
zend_begin_method_decl(op_array, decl->name, has_body);
|
||||
} else {
|
||||
zend_begin_func_decl(result, op_array, decl, toplevel);
|
||||
if (uses_ast) {
|
||||
if (decl->kind == ZEND_AST_ARROW_FUNC) {
|
||||
find_implicit_binds(&info, params_ast, stmt_ast);
|
||||
compile_implicit_lexical_binds(&info, result, op_array);
|
||||
} else if (uses_ast) {
|
||||
zend_compile_closure_binding(result, op_array, uses_ast);
|
||||
}
|
||||
}
|
||||
@ -5842,7 +5955,10 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
|
||||
zend_mark_function_as_generator();
|
||||
zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
|
||||
}
|
||||
if (uses_ast) {
|
||||
if (decl->kind == ZEND_AST_ARROW_FUNC) {
|
||||
zend_compile_implicit_closure_uses(&info);
|
||||
zend_hash_destroy(&info.uses);
|
||||
} else if (uses_ast) {
|
||||
zend_compile_closure_uses(uses_ast);
|
||||
}
|
||||
zend_compile_stmt(stmt_ast);
|
||||
@ -8408,6 +8524,7 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
|
||||
zend_compile_magic_const(result, ast);
|
||||
return;
|
||||
case ZEND_AST_CLOSURE:
|
||||
case ZEND_AST_ARROW_FUNC:
|
||||
zend_compile_func_decl(result, ast, 0);
|
||||
return;
|
||||
default:
|
||||
|
@ -123,6 +123,7 @@ typedef union _zend_parser_stack_elem {
|
||||
zend_ast *ast;
|
||||
zend_string *str;
|
||||
zend_ulong num;
|
||||
unsigned char *ptr;
|
||||
} zend_parser_stack_elem;
|
||||
|
||||
void zend_compile_top_stmt(zend_ast *ast);
|
||||
@ -982,8 +983,9 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf,
|
||||
#define ZEND_RETURN_VAL 0
|
||||
#define ZEND_RETURN_REF 1
|
||||
|
||||
#define ZEND_BIND_VAL 0
|
||||
#define ZEND_BIND_REF 1
|
||||
#define ZEND_BIND_VAL 0
|
||||
#define ZEND_BIND_REF 1
|
||||
#define ZEND_BIND_IMPLICIT 2
|
||||
|
||||
#define ZEND_RETURNS_FUNCTION (1<<0)
|
||||
#define ZEND_RETURNS_VALUE (1<<1)
|
||||
|
@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%destructor { zend_ast_destroy($$); } <ast>
|
||||
%destructor { if ($$) zend_string_release_ex($$, 0); } <str>
|
||||
|
||||
%precedence PREC_ARROW_FUNCTION
|
||||
%precedence T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE
|
||||
%left T_LOGICAL_OR
|
||||
%left T_LOGICAL_XOR
|
||||
@ -164,6 +165,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%token T_CONTINUE "continue (T_CONTINUE)"
|
||||
%token T_GOTO "goto (T_GOTO)"
|
||||
%token T_FUNCTION "function (T_FUNCTION)"
|
||||
%token T_FN "fn (T_FN)"
|
||||
%token T_CONST "const (T_CONST)"
|
||||
%token T_RETURN "return (T_RETURN)"
|
||||
%token T_TRY "try (T_TRY)"
|
||||
@ -251,11 +253,13 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%type <ast> array_pair non_empty_array_pair_list array_pair_list possible_array_pair
|
||||
%type <ast> isset_variable type return_type type_expr
|
||||
%type <ast> identifier
|
||||
%type <ast> inline_function
|
||||
|
||||
%type <num> returns_ref function is_reference is_variadic variable_modifiers
|
||||
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
|
||||
%type <num> method_modifiers non_empty_member_modifiers member_modifier
|
||||
%type <num> class_modifiers class_modifier use_type backup_fn_flags
|
||||
|
||||
%type <ptr> backup_lex_pos
|
||||
%type <str> backup_doc_comment
|
||||
|
||||
%% /* Rules */
|
||||
@ -271,7 +275,7 @@ reserved_non_modifiers:
|
||||
| T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
|
||||
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK
|
||||
| T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
||||
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C
|
||||
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
@ -982,16 +986,27 @@ expr:
|
||||
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
|
||||
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
|
||||
| T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; }
|
||||
| function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type
|
||||
| inline_function { $$ = $1; }
|
||||
| T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; }
|
||||
;
|
||||
|
||||
|
||||
inline_function:
|
||||
function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type
|
||||
backup_fn_flags '{' inner_statement_list '}' backup_fn_flags
|
||||
{ $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $13, $1, $3,
|
||||
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
|
||||
$5, $7, $11, $8); CG(extra_fn_flags) = $9; }
|
||||
| T_STATIC function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars
|
||||
return_type backup_fn_flags '{' inner_statement_list '}' backup_fn_flags
|
||||
{ $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | $14 | ZEND_ACC_STATIC, $2, $4,
|
||||
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
|
||||
$6, $8, $12, $9); CG(extra_fn_flags) = $10; }
|
||||
$5, $7, $11, $8); CG(extra_fn_flags) = $9; }
|
||||
| fn returns_ref '(' parameter_list ')' return_type backup_doc_comment T_DOUBLE_ARROW backup_fn_flags backup_lex_pos expr backup_fn_flags
|
||||
{ $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $7,
|
||||
zend_string_init("{closure}", sizeof("{closure}") - 1, 0), $4, NULL,
|
||||
zend_ast_create(ZEND_AST_RETURN, $11), $6);
|
||||
((zend_ast_decl *) $$)->lex_pos = $10;
|
||||
CG(extra_fn_flags) = $9; }
|
||||
;
|
||||
|
||||
fn:
|
||||
T_FN { $$ = CG(zend_lineno); }
|
||||
;
|
||||
|
||||
function:
|
||||
@ -1003,7 +1018,11 @@ backup_doc_comment:
|
||||
;
|
||||
|
||||
backup_fn_flags:
|
||||
/* empty */ { $$ = CG(extra_fn_flags); CG(extra_fn_flags) = 0; }
|
||||
%prec PREC_ARROW_FUNCTION /* empty */ { $$ = CG(extra_fn_flags); CG(extra_fn_flags) = 0; }
|
||||
;
|
||||
|
||||
backup_lex_pos:
|
||||
/* empty */ { $$ = LANG_SCNG(yy_text); }
|
||||
;
|
||||
|
||||
returns_ref:
|
||||
|
@ -1268,6 +1268,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
|
||||
RETURN_TOKEN(T_EXIT);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"fn" {
|
||||
RETURN_TOKEN(T_FN);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"function" {
|
||||
RETURN_TOKEN(T_FUNCTION);
|
||||
}
|
||||
|
@ -8072,7 +8072,7 @@ ZEND_VM_HANDLER(182, ZEND_BIND_LEXICAL, TMP, CV, REF)
|
||||
}
|
||||
} else {
|
||||
var = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
|
||||
if (UNEXPECTED(Z_ISUNDEF_P(var))) {
|
||||
if (UNEXPECTED(Z_ISUNDEF_P(var)) && !(opline->extended_value & ZEND_BIND_IMPLICIT)) {
|
||||
SAVE_OPLINE();
|
||||
var = ZVAL_UNDEFINED_OP2();
|
||||
if (UNEXPECTED(EG(exception))) {
|
||||
@ -8083,7 +8083,8 @@ ZEND_VM_HANDLER(182, ZEND_BIND_LEXICAL, TMP, CV, REF)
|
||||
Z_TRY_ADDREF_P(var);
|
||||
}
|
||||
|
||||
zend_closure_bind_var_ex(closure, (opline->extended_value & ~ZEND_BIND_REF), var);
|
||||
zend_closure_bind_var_ex(closure,
|
||||
(opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)), var);
|
||||
ZEND_VM_NEXT_OPCODE();
|
||||
}
|
||||
|
||||
|
@ -20716,7 +20716,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_LEXICAL_SPEC_TMP_CV_HANDL
|
||||
}
|
||||
} else {
|
||||
var = EX_VAR(opline->op2.var);
|
||||
if (UNEXPECTED(Z_ISUNDEF_P(var))) {
|
||||
if (UNEXPECTED(Z_ISUNDEF_P(var)) && !(opline->extended_value & ZEND_BIND_IMPLICIT)) {
|
||||
SAVE_OPLINE();
|
||||
var = ZVAL_UNDEFINED_OP2();
|
||||
if (UNEXPECTED(EG(exception))) {
|
||||
@ -20727,7 +20727,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_LEXICAL_SPEC_TMP_CV_HANDL
|
||||
Z_TRY_ADDREF_P(var);
|
||||
}
|
||||
|
||||
zend_closure_bind_var_ex(closure, (opline->extended_value & ~ZEND_BIND_REF), var);
|
||||
zend_closure_bind_var_ex(closure,
|
||||
(opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT)), var);
|
||||
ZEND_VM_NEXT_OPCODE();
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
|
||||
REGISTER_LONG_CONSTANT("T_CONTINUE", T_CONTINUE, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_GOTO", T_GOTO, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_FUNCTION", T_FUNCTION, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_FN", T_FN, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_CONST", T_CONST, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_RETURN", T_RETURN, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_TRY", T_TRY, CONST_CS | CONST_PERSISTENT);
|
||||
@ -251,6 +252,7 @@ char *get_token_type_name(int token_type)
|
||||
case T_CONTINUE: return "T_CONTINUE";
|
||||
case T_GOTO: return "T_GOTO";
|
||||
case T_FUNCTION: return "T_FUNCTION";
|
||||
case T_FN: return "T_FN";
|
||||
case T_CONST: return "T_CONST";
|
||||
case T_RETURN: return "T_RETURN";
|
||||
case T_TRY: return "T_TRY";
|
||||
|
Loading…
Reference in New Issue
Block a user