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 14 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
25 changes: 25 additions & 0 deletions Zend/tests/pipe_operator/ast.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--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) {
echo $e->getMessage(), PHP_EOL;
}

try {
assert((5 |> _test(...)) == 99);
} catch (AssertionError $e) {
echo $e->getMessage(), PHP_EOL;
}

?>
--EXPECTF--
assert(5 |> '_test' == 99)
assert(5 |> _test(...) == 99)
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)
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)
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()
49 changes: 49 additions & 0 deletions Zend/tests/pipe_operator/mixed_callable_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--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";
}
}
}

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

$test = new _Test();

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

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

var_dump($res1);
?>
--EXPECT--
int(40)
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 * 2;
}

$bad_func = null;

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

var_dump($res1);
?>
--EXPECT--
int(14)
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)
17 changes: 17 additions & 0 deletions Zend/tests/pipe_operator/precedence_comparison.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Pipe binds higher than comparison
--FILE--
<?php

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

// This will error without the parens,
// as it will try to compare a closure to an int first.
$res1 = (5 |> _test1(...)) == 10 ;
var_dump($res1);

?>
--EXPECT--
bool(true)
42 changes: 42 additions & 0 deletions Zend/tests/pipe_operator/precedence_ternary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
Pipe binds lower than ternary
--FILE--
<?php

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

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

function _test3(int $a): int {
return $a * 100;
}

// $config is null, so the second function gets used.
$config = null;
$res1 = 5 |> $config ? _test1(...) : _test2(...);
var_dump($res1);

// $config is truthy, so the ternary binds first
// and evaluates to the first function.
$config = _test3(...);
$res2 = 5 |> $config ? _test1(...) : _test2(...);
var_dump($res2);

// Binding the ternary first doesn't make logical sense,
// so the pipe runs first in this case.
$x = true;
$y = 'beep';
$z = 'default';
$ret3 = $x ? $y |> strlen(...) : $z;
var_dump($ret3);


?>
--EXPECT--
int(10)
int(6)
int(4)
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) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}


?>
--EXPECTF--
ArgumentCountError: Too few arguments to function %s, 1 passed in %s on line %s and exactly 2 expected
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) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECTF--
TypeError: _test(): Argument #1 ($a) must be of type int, string given, called in %s on line %d
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) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
TypeError: strlen(): Argument #1 ($string) must be of type string, null given
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)
Loading
Loading