Skip to content

Pipe operator, New AST node version #7214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Zend/tests/pipe_operator/ast.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Test that a pipe operator displays as a pipe operator when outputting syntax.
--FILE--
<?php

function _test(int $a): int {
return $a + 1;
}

try {
assert((5 |> '_test') == 99);
} catch (AssertionError $e) {
print $e->getMessage();
}

?>
--EXPECTF--
assert(5 |> '_test' == 99)
51 changes: 51 additions & 0 deletions Zend/tests/pipe_operator/call_by_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
Pipe handles reference variables the same as normal functions.
--FILE--
<?php

function _modify(int &$a): string {
$a += 1;
return "foo";
}

$a = 5;
$res1 = $a |> '_modify';

var_dump($res1);
var_dump($a);

try {
$res2 = 5 |> '_modify';
} catch (Error $e) {
print $e->getMessage() . PHP_EOL;
}

function &return_by_ref(string $s): string {
$ret = $s . ' bar';
return $ret;
}

function receive_by_ref(string &$b): string {
$b .= " baz";
print $b . PHP_EOL;
return $b . ' beep';
}

$a = 'foo';
$res2 = $a |> 'return_by_ref' |> 'receive_by_ref';
var_dump($res2);

try {
$not_defined |> '_modify';
} catch (Error $e) {
print $e->getMessage() . PHP_EOL;
}

?>
--EXPECTF--
string(3) "foo"
int(6)
_modify(): Argument #1 ($a) cannot be passed by reference
foo bar baz
string(16) "foo bar baz beep"
_modify(): Argument #1 ($a) must be of type int, null given, called in %s on line %d
19 changes: 19 additions & 0 deletions Zend/tests/pipe_operator/compound_userland_calls.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Pipe operator chains
--FILE--
<?php

function _test1(int $a): int {
return $a + 1;
}

function _test2(int $a): int {
return $a * 2;
}

$res1 = 5 |> '_test1' |> '_test2';

var_dump($res1);
?>
--EXPECT--
int(12)
35 changes: 35 additions & 0 deletions Zend/tests/pipe_operator/evaluation_order.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Function evaluation order
--FILE--
<?php

function _test1(int $a): int {
print __FUNCTION__ . PHP_EOL;
return $a;
}

function _test2(): callable {
print __FUNCTION__ . PHP_EOL;
return '\_test3';
}

function _test3(int $a): int {
print __FUNCTION__ . PHP_EOL;
return $a;
}

function _test4(int $a): int {
print __FUNCTION__ . PHP_EOL;
return $a;
}

$res1 = 5 |> '_test1' |> _test2() |> '_test4';

var_dump($res1);
?>
--EXPECT--
_test1
_test2
_test3
_test4
int(5)
15 changes: 15 additions & 0 deletions Zend/tests/pipe_operator/function_not_found.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Pipe operator throws normally on missing function
--FILE--
<?php

try {
$res1 = 5 |> '_test';
}
catch (Throwable $e) {
printf("Expected %s thrown, got %s", Error::class, get_class($e));
}

?>
--EXPECT--
Expected Error thrown, got Error
44 changes: 44 additions & 0 deletions Zend/tests/pipe_operator/mixed_callable_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
Pipe operator handles all callable styles
--FILE--
<?php

function _add(int $x, int $y): int {
return $x + $y;
}

function _area(int $x, int $y): int {
return $x * $y;
}

class _Test
{
public function message(string $which): string
{
if ($which == 1) {
return "Hello";
}
else if ($which == 2) {
return "Goodbye";
}
else {
return "World";
}
}
}

$test = new _Test();

$add3 = fn($x) => _add($x, 3);

$res1 = 2
|> [$test, 'message']
|> 'strlen'
|> $add3
|> fn($x) => _area($x, 2)
;

var_dump($res1);
?>
--EXPECT--
int(20)
15 changes: 15 additions & 0 deletions Zend/tests/pipe_operator/optional_parameters.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Pipe operator accepts optional-parameter functions
--FILE--
<?php

function _test(int $a, int $b = 3) {
return $a + $b;
}

$res1 = 5 |> '_test';

var_dump($res1);
?>
--EXPECT--
int(8)
17 changes: 17 additions & 0 deletions Zend/tests/pipe_operator/precedence_addition.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Pipe binds lower than addition
--FILE--
<?php

function _test1(int $a): int {
return $a + 1;
}

$bad_func = null;

$res1 = 5 + 2 |> '_test1';

var_dump($res1);
?>
--EXPECT--
int(8)
17 changes: 17 additions & 0 deletions Zend/tests/pipe_operator/precedence_coalesce.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Pipe binds lower than coalesce
--FILE--
<?php

function _test1(int $a): int {
return $a * 2;
}

$bad_func = null;

$res1 = 5 |> $bad_func ?? '_test1';

var_dump($res1);
?>
--EXPECT--
int(10)
21 changes: 21 additions & 0 deletions Zend/tests/pipe_operator/precedence_ternary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Pipe binds lower than ternary
--FILE--
<?php

function _test1(int $a): int {
return $a + 1;
}

function _test2(int $a): int {
return $a * 2;
}

$bad_func = null;

$res1 = 5 |> $bad_func ? '_test1' : '_test2';

var_dump($res1);
?>
--EXPECT--
int(10)
11 changes: 11 additions & 0 deletions Zend/tests/pipe_operator/simple_builtin_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Pipe operator supports built-in functions
--FILE--
<?php

$res1 = "Hello" |> 'strlen';

var_dump($res1);
?>
--EXPECT--
int(5)
15 changes: 15 additions & 0 deletions Zend/tests/pipe_operator/simple_userland_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Pipe operator supports user-defined functions
--FILE--
<?php

function _test(int $a): int {
return $a + 1;
}

$res1 = 5 |> '_test';

var_dump($res1);
?>
--EXPECT--
int(6)
21 changes: 21 additions & 0 deletions Zend/tests/pipe_operator/too_many_parameters.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Pipe operator fails on multi-parameter functions
--FILE--
<?php

function _test(int $a, int $b) {
return $a + $b;
}


try {
$res1 = 5 |> '_test';
}
catch (Throwable $e) {
printf("Expected %s thrown, got %s", ArgumentCountError::class, get_class($e));
}


?>
--EXPECT--
Expected ArgumentCountError thrown, got ArgumentCountError
20 changes: 20 additions & 0 deletions Zend/tests/pipe_operator/type_mismatch.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Pipe operator respects types
--FILE--
<?php

function _test(int $a, int $b) {
return $a + $b;
}

try {
$res1 = "Hello" |> '_test';
var_dump($res1);
}
catch (Throwable $e) {
printf("Expected %s thrown, got %s", TypeError::class, get_class($e));
}

?>
--EXPECT--
Expected TypeError thrown, got TypeError
21 changes: 21 additions & 0 deletions Zend/tests/pipe_operator/void_return.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Pipe operator fails void return chaining in strict mode
--FILE--
<?php
declare(strict_types=1);

function nonReturnFunction($bar): void {}

try {
$result = "Hello World"
|> 'nonReturnFunction'
|> 'strlen';
var_dump($result);
}
catch (Throwable $e) {
printf("Expected %s thrown, got %s", TypeError::class, get_class($e));
}

?>
--EXPECT--
Expected TypeError thrown, got TypeError
21 changes: 21 additions & 0 deletions Zend/tests/pipe_operator/wrapped_chains.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Pipe operator chains saved as a closure
--FILE--
<?php

function _test1(int $a): int {
return $a + 1;
}

function _test2(int $a): int {
return $a * 2;
}

$func = fn($x) => $x |> '_test1' |> '_test2';

$res1 = $func(5);

var_dump($res1);
?>
--EXPECT--
int(12)
5 changes: 5 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1870,6 +1870,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appends(str, "::$");
zend_ast_export_var(str, ast->child[1], 0, indent);
break;
case ZEND_AST_PIPE:
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
smart_str_appends(str, " |> ");
zend_ast_export_ex(str, ast->child[1], 0, indent);
break;
case ZEND_AST_CALL:
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
smart_str_appendc(str, '(');
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ enum _zend_ast_kind {
ZEND_AST_MATCH,
ZEND_AST_MATCH_ARM,
ZEND_AST_NAMED_ARG,
ZEND_AST_PIPE,

/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down
Loading