Skip to content

Pipe operator #5425

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 1 commit 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)
20 changes: 20 additions & 0 deletions Zend/tests/pipe_operator/call_by_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Pipe operator accepts by-reference functions
--FILE--
<?php

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

//try {
$a = 5;
$res1 = $a |> '_modify';

var_dump($res1);
var_dump($a);
?>
--EXPECT--
string(3) "foo"
int(6)
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) {
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)
15 changes: 11 additions & 4 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1861,10 +1861,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
zend_ast_export_var(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, '(');
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
if (ast->attr & ZEND_CALL_SYNTAX_PIPE) {
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appends(str, " |> ");
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
}
else {
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
smart_str_appendc(str, '(');
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
}
break;
case ZEND_AST_CLASS_CONST:
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
/* These should not clash with ZEND_ACC_(PUBLIC|PROTECTED|PRIVATE) */
#define ZEND_PARAM_REF (1<<3)
#define ZEND_PARAM_VARIADIC (1<<4)
#define ZEND_CALL_SYNTAX_PIPE (1u << 2u)

#define ZEND_NAME_FQ 0
#define ZEND_NAME_NOT_FQ 1
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%precedence T_DOUBLE_ARROW
%precedence T_YIELD_FROM
%precedence '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
%left T_PIPE
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
Expand Down Expand Up @@ -231,6 +232,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_COALESCE "'??'"
%token T_POW "'**'"
%token T_POW_EQUAL "'**='"
%token T_PIPE "|>"
%token T_BAD_CHARACTER "invalid character"

/* Token used to force a parse error from the lexer */
Expand Down Expand Up @@ -1114,6 +1116,8 @@ expr:
{ $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $1, $3); }
| expr T_IS_NOT_EQUAL expr
{ $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); }
| expr T_PIPE expr
{ $$ = zend_ast_create(ZEND_AST_CALL, $3, zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1) ); $$->attr = ZEND_CALL_SYNTAX_PIPE; }
Comment on lines +1119 to +1120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikic Is there something we could/should do with CG(zend_lineno) here? I don't know how the global is really used, so I don't know if this would have any positive impact, or if it should be elsewhere on the line (and I didn't try compiling this):

	|	expr T_PIPE { $<num>$ = CG(zend_lineno); } expr
			{ $$ = zend_ast_create(ZEND_AST_CALL, $4, zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1) ); $$->attr = ZEND_CALL_SYNTAX_PIPE; }

Was trying to think of how we can make it as helpful as possible for debuggers, profilers, etc to have accurate line numbers.

| expr '<' expr
{ $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); }
| expr T_IS_SMALLER_OR_EQUAL expr
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_language_scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
RETURN_TOKEN(T_COALESCE_EQUAL);
}

<ST_IN_SCRIPTING>"|>" {
RETURN_TOKEN(T_PIPE);
}

<ST_IN_SCRIPTING>"||" {
RETURN_TOKEN(T_BOOLEAN_OR);
}
Expand Down
2 changes: 2 additions & 0 deletions ext/tokenizer/tokenizer_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_PIPE", T_PIPE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT);
}
Expand Down Expand Up @@ -317,6 +318,7 @@ char *get_token_type_name(int token_type)
case T_COALESCE: return "T_COALESCE";
case T_POW: return "T_POW";
case T_POW_EQUAL: return "T_POW_EQUAL";
case T_PIPE: return "T_PIPE";
case T_BAD_CHARACTER: return "T_BAD_CHARACTER";

}
Expand Down