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:
Nikita Popov 2019-05-02 14:57:16 +02:00
parent eaab0a2b6f
commit f3e5bbe6f3
27 changed files with 465 additions and 44 deletions

View File

@ -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();

View File

@ -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--

View 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)

View 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)

View 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

View 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)

View 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)

View 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

View 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

View 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)

View File

@ -4,11 +4,11 @@ Bug #65035: yield / exit segfault
<?php
function gen() {
fn();
f();
yield;
}
function fn() {
function f() {
exit('Done');
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -4,7 +4,7 @@ Cannot use $this as lexical variable
<?php
class Foo {
public function fn() {
public function f() {
return function() use ($this) {};
}
}

View File

@ -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--

View File

@ -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);

View File

@ -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;
}

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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";