From f3e5bbe6f37ce52a9ecd42812389e6aaf3aa2892 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 2 May 2019 14:57:16 +0200 Subject: [PATCH] Implement arrow functions Per RFC: https://wiki.php.net/rfc/arrow_functions_v2 Co-authored-by: Levi Morrison Co-authored-by: Bob Weinand --- UPGRADING | 11 ++ Zend/tests/arg_unpack/many_args.phpt | 4 +- Zend/tests/arrow_functions/001.phpt | 45 +++++++ Zend/tests/arrow_functions/002.phpt | 13 ++ Zend/tests/arrow_functions/003.phpt | 21 ++++ Zend/tests/arrow_functions/004.phpt | 13 ++ Zend/tests/arrow_functions/005.phpt | 54 ++++++++ Zend/tests/arrow_functions/006.phpt | 44 +++++++ Zend/tests/arrow_functions/007.phpt | 14 +++ Zend/tests/arrow_functions/008.phpt | 28 +++++ Zend/tests/generators/bug65035.phpt | 4 +- Zend/tests/grammar/semi_reserved_001.phpt | 3 + Zend/tests/grammar/semi_reserved_002.phpt | 3 + Zend/tests/grammar/semi_reserved_003.phpt | 3 + Zend/tests/grammar/semi_reserved_004.phpt | 3 + Zend/tests/grammar/semi_reserved_005.phpt | 3 + Zend/tests/this_as_lexical_var_error.phpt | 2 +- Zend/tests/variadic/optional_params.phpt | 14 +-- Zend/zend_ast.c | 18 ++- Zend/zend_ast.h | 5 + Zend/zend_compile.c | 143 ++++++++++++++++++++-- Zend/zend_compile.h | 6 +- Zend/zend_language_parser.y | 39 ++++-- Zend/zend_language_scanner.l | 4 + Zend/zend_vm_def.h | 5 +- Zend/zend_vm_execute.h | 5 +- ext/tokenizer/tokenizer_data.c | 2 + 27 files changed, 465 insertions(+), 44 deletions(-) create mode 100644 Zend/tests/arrow_functions/001.phpt create mode 100644 Zend/tests/arrow_functions/002.phpt create mode 100644 Zend/tests/arrow_functions/003.phpt create mode 100644 Zend/tests/arrow_functions/004.phpt create mode 100644 Zend/tests/arrow_functions/005.phpt create mode 100644 Zend/tests/arrow_functions/006.phpt create mode 100644 Zend/tests/arrow_functions/007.phpt create mode 100644 Zend/tests/arrow_functions/008.phpt diff --git a/UPGRADING b/UPGRADING index ba0d5b4ed5..c25a4ea3b6 100644 --- a/UPGRADING +++ b/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(); diff --git a/Zend/tests/arg_unpack/many_args.phpt b/Zend/tests/arg_unpack/many_args.phpt index 0ef5a30d6d..e1609d8e1e 100644 --- a/Zend/tests/arg_unpack/many_args.phpt +++ b/Zend/tests/arg_unpack/many_args.phpt @@ -3,12 +3,12 @@ Argument unpacking with many arguments --FILE-- --EXPECT-- diff --git a/Zend/tests/arrow_functions/001.phpt b/Zend/tests/arrow_functions/001.phpt new file mode 100644 index 0000000000..27b2557b99 --- /dev/null +++ b/Zend/tests/arrow_functions/001.phpt @@ -0,0 +1,45 @@ +--TEST-- +Basic arrow function functionality check +--FILE-- + 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) diff --git a/Zend/tests/arrow_functions/002.phpt b/Zend/tests/arrow_functions/002.phpt new file mode 100644 index 0000000000..52c8020c19 --- /dev/null +++ b/Zend/tests/arrow_functions/002.phpt @@ -0,0 +1,13 @@ +--TEST-- +Arrow functions implicit use must be throwing notices only upon actual use +--FILE-- + $b + $c)()); + +?> +--EXPECTF-- +Notice: Undefined variable: c in %s on line %d +int(1) diff --git a/Zend/tests/arrow_functions/003.phpt b/Zend/tests/arrow_functions/003.phpt new file mode 100644 index 0000000000..5e77743fad --- /dev/null +++ b/Zend/tests/arrow_functions/003.phpt @@ -0,0 +1,21 @@ +--TEST-- +Variable-variables inside arrow functions +--FILE-- + $$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 diff --git a/Zend/tests/arrow_functions/004.phpt b/Zend/tests/arrow_functions/004.phpt new file mode 100644 index 0000000000..51467dae1d --- /dev/null +++ b/Zend/tests/arrow_functions/004.phpt @@ -0,0 +1,13 @@ +--TEST-- +Auto-globals in arrow functions +--FILE-- + $GLOBALS['a']; +var_dump($fn()); + +?> +--EXPECT-- +int(123) diff --git a/Zend/tests/arrow_functions/005.phpt b/Zend/tests/arrow_functions/005.phpt new file mode 100644 index 0000000000..7371be57bb --- /dev/null +++ b/Zend/tests/arrow_functions/005.phpt @@ -0,0 +1,54 @@ +--TEST-- +Arrow function $this binding +--FILE-- + 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) diff --git a/Zend/tests/arrow_functions/006.phpt b/Zend/tests/arrow_functions/006.phpt new file mode 100644 index 0000000000..16427146a8 --- /dev/null +++ b/Zend/tests/arrow_functions/006.phpt @@ -0,0 +1,44 @@ +--TEST-- +Arrow functions syntax variations +--FILE-- + $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 diff --git a/Zend/tests/arrow_functions/007.phpt b/Zend/tests/arrow_functions/007.phpt new file mode 100644 index 0000000000..0fcc4930d1 --- /dev/null +++ b/Zend/tests/arrow_functions/007.phpt @@ -0,0 +1,14 @@ +--TEST-- +Pretty printing for arrow functions +--FILE-- + 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 diff --git a/Zend/tests/arrow_functions/008.phpt b/Zend/tests/arrow_functions/008.phpt new file mode 100644 index 0000000000..df3fe5dba7 --- /dev/null +++ b/Zend/tests/arrow_functions/008.phpt @@ -0,0 +1,28 @@ +--TEST-- +Yield inside arrow functions +--FILE-- + 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) diff --git a/Zend/tests/generators/bug65035.phpt b/Zend/tests/generators/bug65035.phpt index 18276cc23a..cc7f5b68a3 100644 --- a/Zend/tests/generators/bug65035.phpt +++ b/Zend/tests/generators/bug65035.phpt @@ -4,11 +4,11 @@ Bug #65035: yield / exit segfault 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 diff --git a/Zend/tests/grammar/semi_reserved_002.phpt b/Zend/tests/grammar/semi_reserved_002.phpt index 4aa0f27e5e..59cd97479e 100644 --- a/Zend/tests/grammar/semi_reserved_002.phpt +++ b/Zend/tests/grammar/semi_reserved_002.phpt @@ -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 diff --git a/Zend/tests/grammar/semi_reserved_003.phpt b/Zend/tests/grammar/semi_reserved_003.phpt index 8e459a636f..360647135b 100644 --- a/Zend/tests/grammar/semi_reserved_003.phpt +++ b/Zend/tests/grammar/semi_reserved_003.phpt @@ -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 diff --git a/Zend/tests/grammar/semi_reserved_004.phpt b/Zend/tests/grammar/semi_reserved_004.phpt index 5a625a608b..2ab1f3139e 100644 --- a/Zend/tests/grammar/semi_reserved_004.phpt +++ b/Zend/tests/grammar/semi_reserved_004.phpt @@ -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 diff --git a/Zend/tests/grammar/semi_reserved_005.phpt b/Zend/tests/grammar/semi_reserved_005.phpt index 2cf7709b15..3e74024ecc 100644 --- a/Zend/tests/grammar/semi_reserved_005.phpt +++ b/Zend/tests/grammar/semi_reserved_005.phpt @@ -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 diff --git a/Zend/tests/this_as_lexical_var_error.phpt b/Zend/tests/this_as_lexical_var_error.phpt index d0e0106dae..6b75933937 100644 --- a/Zend/tests/this_as_lexical_var_error.phpt +++ b/Zend/tests/this_as_lexical_var_error.phpt @@ -4,7 +4,7 @@ Cannot use $this as lexical variable --EXPECT-- diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 2cacd53287..2e57e07ab5 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -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); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 499b8b4191..fd6dd1677a 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -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; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0ef5469083..6e9b42151f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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: diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 93cddfa806..e03408a572 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -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) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index af5ce87fa2..55376ede3d 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %destructor { zend_ast_destroy($$); } %destructor { if ($$) zend_string_release_ex($$, 0); } +%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 array_pair non_empty_array_pair_list array_pair_list possible_array_pair %type isset_variable type return_type type_expr %type identifier +%type inline_function -%type returns_ref function is_reference is_variadic variable_modifiers +%type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier %type class_modifiers class_modifier use_type backup_fn_flags +%type backup_lex_pos %type 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: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index b1e25ebae5..a75e113541 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1268,6 +1268,10 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN(T_EXIT); } +"fn" { + RETURN_TOKEN(T_FN); +} + "function" { RETURN_TOKEN(T_FUNCTION); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9f2fba4fbf..286227fa34 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d4aac71756..e64eefe255 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -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(); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 8b5c6be945..5048ef732f 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -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"; -- 2.40.0