summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-05-02 14:57:16 +0200
committerNikita Popov <nikita.ppv@gmail.com>2019-05-02 15:04:03 +0200
commitf3e5bbe6f37ce52a9ecd42812389e6aaf3aa2892 (patch)
tree4d7baab746e966624ee4557c2153e5099cbbcbd3 /Zend
parenteaab0a2b6f6f61bc0b8c42579a74a626b179a070 (diff)
downloadphp-git-f3e5bbe6f37ce52a9ecd42812389e6aaf3aa2892.tar.gz
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>
Diffstat (limited to 'Zend')
-rw-r--r--Zend/tests/arg_unpack/many_args.phpt4
-rw-r--r--Zend/tests/arrow_functions/001.phpt45
-rw-r--r--Zend/tests/arrow_functions/002.phpt13
-rw-r--r--Zend/tests/arrow_functions/003.phpt21
-rw-r--r--Zend/tests/arrow_functions/004.phpt13
-rw-r--r--Zend/tests/arrow_functions/005.phpt54
-rw-r--r--Zend/tests/arrow_functions/006.phpt44
-rw-r--r--Zend/tests/arrow_functions/007.phpt14
-rw-r--r--Zend/tests/arrow_functions/008.phpt28
-rw-r--r--Zend/tests/generators/bug65035.phpt4
-rw-r--r--Zend/tests/grammar/semi_reserved_001.phpt3
-rw-r--r--Zend/tests/grammar/semi_reserved_002.phpt3
-rw-r--r--Zend/tests/grammar/semi_reserved_003.phpt3
-rw-r--r--Zend/tests/grammar/semi_reserved_004.phpt3
-rw-r--r--Zend/tests/grammar/semi_reserved_005.phpt3
-rw-r--r--Zend/tests/this_as_lexical_var_error.phpt2
-rw-r--r--Zend/tests/variadic/optional_params.phpt14
-rw-r--r--Zend/zend_ast.c18
-rw-r--r--Zend/zend_ast.h5
-rw-r--r--Zend/zend_compile.c143
-rw-r--r--Zend/zend_compile.h6
-rw-r--r--Zend/zend_language_parser.y39
-rw-r--r--Zend/zend_language_scanner.l4
-rw-r--r--Zend/zend_vm_def.h5
-rw-r--r--Zend/zend_vm_execute.h5
25 files changed, 452 insertions, 44 deletions
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--
<?php
-function fn(...$args) {
+function f(...$args) {
var_dump(count($args));
}
$array = array_fill(0, 10000, 42);
-fn(...$array, ...$array);
+f(...$array, ...$array);
?>
--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--
+<?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)
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--
+<?php
+
+$b = 1;
+
+var_dump((fn() => $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--
+<?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
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--
+<?php
+
+// This should work, but *not* generate a binding for $GLOBALS
+$a = 123;
+$fn = fn() => $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--
+<?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)
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--
+<?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
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--
+<?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
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--
+<?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)
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
<?php
function gen() {
- fn();
+ f();
yield;
}
-function fn() {
+function f() {
exit('Done');
}
diff --git a/Zend/tests/grammar/semi_reserved_001.phpt b/Zend/tests/grammar/semi_reserved_001.phpt
index 68b35d3f22..bbb8ccdede 100644
--- a/Zend/tests/grammar/semi_reserved_001.phpt
+++ b/Zend/tests/grammar/semi_reserved_001.phpt
@@ -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
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
<?php
class Foo {
- public function fn() {
+ public function f() {
return function() use ($this) {};
}
}
diff --git a/Zend/tests/variadic/optional_params.phpt b/Zend/tests/variadic/optional_params.phpt
index 6cb516fa0c..ba800032d7 100644
--- a/Zend/tests/variadic/optional_params.phpt
+++ b/Zend/tests/variadic/optional_params.phpt
@@ -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--
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($$); } <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:
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);
}
+<ST_IN_SCRIPTING>"fn" {
+ RETURN_TOKEN(T_FN);
+}
+
<ST_IN_SCRIPTING>"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();
}