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
d7716ad
Implement pipe operator.
Crell Apr 11, 2020
0f8b8b5
Include FCC in tested callable formats.
Crell Dec 13, 2024
69a6feb
Move the code generation from the lexer to a compile function, courte…
Crell Dec 30, 2024
0b40c32
Fix pipe error output.
Crell Dec 30, 2024
80ba4df
Remove dead comment.
Crell Dec 30, 2024
c8c0365
Wrap pipe LHS in a QM_ASSIGN opcode to implicitly block references.
Crell Jan 14, 2025
48bb0de
Add test to check behaviour of prefer-by-ref parameters
Girgias Feb 8, 2025
f9c4217
Merge pull request #1 from Girgias/funsies-additions
Crell Feb 10, 2025
bbb3fd6
Use echo instead.
Crell Feb 9, 2025
0794003
Don't use printf, either.
Crell Feb 9, 2025
594af65
Correct addition-precedence test so it would be a different output if…
Crell Feb 9, 2025
37d75e2
Tweak style.
Crell Feb 10, 2025
ec75335
Expand ternary test.
Crell Feb 10, 2025
d836c17
Add a test for comparison operators.
Crell Feb 10, 2025
8957cdd
Update comparison precedence test.
Crell Feb 10, 2025
26629a8
Whitespace fixes.
Crell Feb 10, 2025
ceb4589
Add single quotes for consistency.
Crell Feb 13, 2025
e7890e3
Increase precedence of pipe operator.
Crell Feb 26, 2025
392d8b2
Remove dead code.
Crell Feb 27, 2025
c15c082
Add test for generator and pipe behavior.
Crell Mar 6, 2025
9cd04b4
Add regenerated file.
Crell Mar 6, 2025
8cf43d8
Add test for complex ordering.
Crell Mar 6, 2025
a264deb
Add test for exceptions.
Crell Mar 6, 2025
d5a5dc8
Use BINARY_OP macro for printing Pipe AST.
Crell Mar 6, 2025
94f737e
Improve tests for AST printing, still doesn't work.
Crell Mar 18, 2025
ef2d1a0
Improved optimzations and namespace handling, courtesy Arnaud.
Crell Mar 18, 2025
59873a7
Improve docs.
Crell Mar 18, 2025
2beea97
Ensure higher order functions stil work.
Crell Mar 18, 2025
5700cb2
AST: Update priorities for ZEND_AST_PIPE
arnaud-lb Mar 18, 2025
f7027e7
Remove vestigial comment.
Crell Mar 18, 2025
ee3d107
Add test for namespaced functions.
Crell Mar 18, 2025
10fd080
Remove dead code
Crell Mar 20, 2025
55df27c
More namespace tests.
Crell Mar 20, 2025
d275216
Add test for static method FCC.
Crell Mar 20, 2025
38f1309
Add more AST examples.
Crell Mar 20, 2025
d1115a8
Optimize static method calls in pipes.
Crell Mar 20, 2025
d717f25
Typo fix.
Crell Mar 20, 2025
9f9e054
Add tests for pipe optimizations.
Crell Mar 20, 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
Loading