Skip to content

Pipe operator #17118

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

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9585c04
Implement pipe operator.
Crell Apr 11, 2020
529ba11
Include FCC in tested callable formats.
Crell Dec 13, 2024
0988adc
Move the code generation from the lexer to a compile function, courte…
Crell Dec 30, 2024
6612361
Fix pipe error output.
Crell Dec 30, 2024
499b111
Remove dead comment.
Crell Dec 30, 2024
b04e422
Wrap pipe LHS in a QM_ASSIGN opcode to implicitly block references.
Crell Jan 14, 2025
4dbf197
Add test to check behaviour of prefer-by-ref parameters
Girgias Feb 8, 2025
97db4bc
Use echo instead.
Crell Feb 9, 2025
f07fea2
Don't use printf, either.
Crell Feb 9, 2025
1443311
Correct addition-precedence test so it would be a different output if…
Crell Feb 9, 2025
3b6a9b3
Tweak style.
Crell Feb 10, 2025
46be67d
Expand ternary test.
Crell Feb 10, 2025
77af087
Add a test for comparison operators.
Crell Feb 10, 2025
d395181
Update comparison precedence test.
Crell Feb 10, 2025
c8e0051
Whitespace fixes.
Crell Feb 10, 2025
4ddf259
Add single quotes for consistency.
Crell Feb 13, 2025
cf26b9f
Increase precedence of pipe operator.
Crell Feb 26, 2025
c02447c
Remove dead code.
Crell Feb 27, 2025
ec94530
Add test for generator and pipe behavior.
Crell Mar 6, 2025
f1cb86d
Add regenerated file.
Crell Mar 6, 2025
0d9e407
Add test for complex ordering.
Crell Mar 6, 2025
c932837
Add test for exceptions.
Crell Mar 6, 2025
d608250
Use BINARY_OP macro for printing Pipe AST.
Crell Mar 6, 2025
241dda7
Improve tests for AST printing, still doesn't work.
Crell Mar 18, 2025
63c7b63
Improved optimzations and namespace handling, courtesy Arnaud.
Crell Mar 18, 2025
d7b7a99
Improve docs.
Crell Mar 18, 2025
82bae3c
Ensure higher order functions stil work.
Crell Mar 18, 2025
0d432c6
AST: Update priorities for ZEND_AST_PIPE
arnaud-lb Mar 18, 2025
93ea77b
Remove vestigial comment.
Crell Mar 18, 2025
d53cade
Add test for namespaced functions.
Crell Mar 18, 2025
7b13753
Remove dead code
Crell Mar 20, 2025
036cf3c
More namespace tests.
Crell Mar 20, 2025
cfa3e3f
Add test for static method FCC.
Crell Mar 20, 2025
2ba6512
Add more AST examples.
Crell Mar 20, 2025
7b6365d
Optimize static method calls in pipes.
Crell Mar 20, 2025
ba646f5
Typo fix.
Crell Mar 20, 2025
9915924
Add tests for pipe optimizations.
Crell Mar 20, 2025
ffa7735
Make test output more portable.
Crell May 28, 2025
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
109 changes: 109 additions & 0 deletions Zend/tests/pipe_operator/ast.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
--TEST--
A pipe operator displays as a pipe operator when outputting syntax, with correct parens.
--FILE--
<?php

print "Concat, which binds higher\n";

try {
assert(false && foo() . bar() |> baz() . quux());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && (foo() . bar()) |> baz() . quux());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() . (bar() |> baz()) . quux());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() . bar() |> (baz() . quux()));
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && (foo() . bar() |> baz()) . quux());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() . (bar() |> baz() . quux()));
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

print "<, which binds lower\n";

try {
assert(false && foo() < bar() |> baz());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && (foo() < bar()) |> baz());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() < (bar() |> baz()));
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() |> bar() < baz());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && (foo() |> bar()) < baz());
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert(false && foo() |> (bar() < baz()));
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}



print "misc examples\n";

try {
assert(false && foo() |> (bar() |> baz(...)));
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
Concat, which binds higher
assert(false && foo() . bar() |> baz() . quux())
assert(false && foo() . bar() |> baz() . quux())
assert(false && foo() . (bar() |> baz()) . quux())
assert(false && foo() . bar() |> baz() . quux())
assert(false && (foo() . bar() |> baz()) . quux())
assert(false && foo() . (bar() |> baz() . quux()))
<, which binds lower
assert(false && foo() < bar() |> baz())
assert(false && (foo() < bar()) |> baz())
assert(false && foo() < bar() |> baz())
assert(false && foo() |> bar() < baz())
assert(false && foo() |> bar() < baz())
assert(false && foo() |> (bar() < baz()))
misc examples
assert(false && foo() |> (bar() |> baz(...)))
37 changes: 37 additions & 0 deletions Zend/tests/pipe_operator/call_by_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
Pipe operator rejects by-reference functions.
--FILE--
<?php

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

function _append(array &$a): string {
$a['bar'] = 'beep';
}

// Simple variables
try {
$a = 5;
$res1 = $a |> _modify(...);
var_dump($res1);
} catch (\Error $e) {
echo $e->getMessage(), PHP_EOL;
}

// Complex variables.
try {
$a = ['foo' => 'beep'];
$res2 = $a |> _append(...);
var_dump($res2);
} catch (\Error $e) {
echo $e->getMessage(), PHP_EOL;
}


?>
--EXPECTF--
_modify(): Argument #1 ($a) could not be passed by reference
_append(): Argument #1 ($a) could not be passed by reference
17 changes: 17 additions & 0 deletions Zend/tests/pipe_operator/call_prefer_by_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Pipe operator accepts prefer-by-reference functions.
--FILE--
<?php

$a = ['hello', 'world'];

try {
$r = $a |> array_multisort(...);
var_dump($r);
} catch (\Error $e) {
echo $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
bool(true)
20 changes: 20 additions & 0 deletions Zend/tests/pipe_operator/complex_ordering.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Functions are executed in the expected order
--FILE--
<?php

function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
function quux($in) { echo __FUNCTION__, PHP_EOL; return $in; }

$result = foo()
|> (bar() ? baz(...) : quux(...))
|> var_dump(...);

?>
--EXPECT--
foo
bar
quux
int(1)
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)
37 changes: 37 additions & 0 deletions Zend/tests/pipe_operator/exception_interruption.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
A pipe interrupted by an exception, to demonstrate correct order of execution.
--FILE--
<?php

function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
function quux($in) { echo __FUNCTION__, PHP_EOL; throw new \Exception('Oops'); }

try {
$result = foo()
|> (bar() ? baz(...) : quux(...))
|> var_dump(...);
}
catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

try {
$result = foo()
|> (throw new Exception('Break'))
|> (bar() ? baz(...) : quux(...))
|> var_dump(...);
}
catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECTF--
foo
bar
quux
Exception: Oops
foo
Exception: Break
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) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
Error: Call to undefined function _test()
30 changes: 30 additions & 0 deletions Zend/tests/pipe_operator/generators.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
Generators
--FILE--
<?php

function producer(): \Generator {
yield 1;
yield 2;
yield 3;
}

function map_incr(iterable $it): \Generator {
foreach ($it as $val) {
yield $val +1;
}
}

$result = producer() |> map_incr(...) |> iterator_to_array(...);

var_dump($result);
?>
--EXPECT--
array(3) {
[0]=>
int(2)
[1]=>
int(3)
[2]=>
int(4)
}
62 changes: 62 additions & 0 deletions Zend/tests/pipe_operator/mixed_callable_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--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";
}
}
}

class StaticTest {
public static function oneMore(int $x): int {
return $x + 1;
}
}

function _double(int $x): int {
return $x * 2;
}

function multiplier(int $x): \Closure
{
return fn($y) => $x * $y;
}

$test = new _Test();

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

$res1 = 2
|> [$test, 'message']
|> 'strlen'
|> $add3
|> fn($x) => _area($x, 2)
|> _double(...)
|> multiplier(3)
|> StaticTest::oneMore(...)
;

var_dump($res1);
?>
--EXPECT--
int(121)
22 changes: 22 additions & 0 deletions Zend/tests/pipe_operator/namespaced_functions.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Pipe operator handles all callable styles
--FILE--
<?php

namespace Beep {
function test(int $x) {
echo $x, PHP_EOL;
}
}

namespace Bar {
use function \Beep\test;

5 |> test(...);

5 |> \Beep\test(...);
}
?>
--EXPECT--
5
5
Loading