Skip to content

Commit

Permalink
Implement arrow functions
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
3 people committed May 2, 2019
1 parent eaab0a2 commit f3e5bbe
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 44 deletions.
11 changes: 11 additions & 0 deletions UPGRADING
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions Zend/tests/arg_unpack/many_args.phpt
Expand Up @@ -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--
Expand Down
45 changes: 45 additions & 0 deletions 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)
13 changes: 13 additions & 0 deletions 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)
21 changes: 21 additions & 0 deletions 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
13 changes: 13 additions & 0 deletions 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)
54 changes: 54 additions & 0 deletions 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)
44 changes: 44 additions & 0 deletions 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
14 changes: 14 additions & 0 deletions 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
28 changes: 28 additions & 0 deletions 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)
4 changes: 2 additions & 2 deletions Zend/tests/generators/bug65035.phpt
Expand Up @@ -4,11 +4,11 @@ Bug #65035: yield / exit segfault
<?php

function gen() {
fn();
f();
yield;
}

function fn() {
function f() {
exit('Done');
}

Expand Down
3 changes: 3 additions & 0 deletions Zend/tests/grammar/semi_reserved_001.phpt
Expand Up @@ -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; }
Expand Down Expand Up @@ -135,6 +136,7 @@ $obj->continue();
$obj->switch();
$obj->yield();
$obj->function();
$obj->fn();
$obj->if();
$obj->endswitch();
$obj->finally();
Expand Down Expand Up @@ -213,6 +215,7 @@ Obj::continue
Obj::switch
Obj::yield
Obj::function
Obj::fn
Obj::if
Obj::endswitch
Obj::finally
Expand Down
3 changes: 3 additions & 0 deletions Zend/tests/grammar/semi_reserved_002.phpt
Expand Up @@ -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; }
Expand Down Expand Up @@ -133,6 +134,7 @@ Obj::continue();
Obj::switch();
Obj::yield();
Obj::function();
Obj::fn();
Obj::if();
Obj::endswitch();
Obj::finally();
Expand Down Expand Up @@ -211,6 +213,7 @@ Obj::continue
Obj::switch
Obj::yield
Obj::function
Obj::fn
Obj::if
Obj::endswitch
Obj::finally
Expand Down
3 changes: 3 additions & 0 deletions Zend/tests/grammar/semi_reserved_003.phpt
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -217,6 +219,7 @@ continue
switch
yield
function
fn
if
endswitch
finally
Expand Down
3 changes: 3 additions & 0 deletions Zend/tests/grammar/semi_reserved_004.phpt
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -213,6 +215,7 @@ continue
switch
yield
function
fn
if
endswitch
finally
Expand Down

0 comments on commit f3e5bbe

Please sign in to comment.