Skip to content

Commit f01ac40

Browse files
committed
Emit error for unparenthesized arrow function in pipe operator
As the parser currently does not explicitly represent parenthesis, use a separate map to track whether arrow functions are parenthesized. Related to #1124.
1 parent 232169f commit f01ac40

File tree

5 files changed

+166
-5
lines changed

5 files changed

+166
-5
lines changed

grammar/php.y

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,10 +1089,18 @@ expr:
10891089
| expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; }
10901090
| expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
10911091
#if PHP8
1092-
| expr T_PIPE expr { $$ = Expr\BinaryOp\Pipe[$1, $3]; }
1092+
| expr T_PIPE expr {
1093+
$$ = Expr\BinaryOp\Pipe[$1, $3];
1094+
$this->checkPipeOperatorParentheses($3);
1095+
}
10931096
#endif
10941097
| expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; }
1095-
| '(' expr ')' { $$ = $2; }
1098+
| '(' expr ')' {
1099+
$$ = $2;
1100+
if ($$ instanceof Expr\ArrowFunction) {
1101+
$this->parenthesizedArrowFunctions->offsetSet($$);
1102+
}
1103+
}
10961104
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
10971105
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
10981106
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }

lib/PhpParser/Parser/Php7.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2445,7 +2445,12 @@ protected function initReduceCallbacks(): void {
24452445
$self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]));
24462446
},
24472447
476 => static function ($self, $stackPos) {
2448-
$self->semValue = $self->semStack[$stackPos-(3-2)];
2448+
2449+
$self->semValue = $self->semStack[$stackPos-(3-2)];
2450+
if ($self->semValue instanceof Expr\ArrowFunction) {
2451+
$self->parenthesizedArrowFunctions->offsetSet($self->semValue);
2452+
}
2453+
24492454
},
24502455
477 => static function ($self, $stackPos) {
24512456
$self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]));

lib/PhpParser/Parser/Php8.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,13 +2440,21 @@ protected function initReduceCallbacks(): void {
24402440
$self->semValue = new Expr\BinaryOp\GreaterOrEqual($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]));
24412441
},
24422442
477 => static function ($self, $stackPos) {
2443-
$self->semValue = new Expr\BinaryOp\Pipe($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]));
2443+
2444+
$self->semValue = new Expr\BinaryOp\Pipe($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]));
2445+
$self->checkPipeOperatorParentheses($self->semStack[$stackPos-(3-3)]);
2446+
24442447
},
24452448
478 => static function ($self, $stackPos) {
24462449
$self->semValue = new Expr\Instanceof_($self->semStack[$stackPos-(3-1)], $self->semStack[$stackPos-(3-3)], $self->getAttributes($self->tokenStartStack[$stackPos-(3-1)], $self->tokenEndStack[$stackPos]));
24472450
},
24482451
479 => static function ($self, $stackPos) {
2449-
$self->semValue = $self->semStack[$stackPos-(3-2)];
2452+
2453+
$self->semValue = $self->semStack[$stackPos-(3-2)];
2454+
if ($self->semValue instanceof Expr\ArrowFunction) {
2455+
$self->parenthesizedArrowFunctions->offsetSet($self->semValue);
2456+
}
2457+
24502458
},
24512459
480 => static function ($self, $stackPos) {
24522460
$self->semValue = new Expr\Ternary($self->semStack[$stackPos-(5-1)], $self->semStack[$stackPos-(5-3)], $self->semStack[$stackPos-(5-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]));

lib/PhpParser/ParserAbstract.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ abstract class ParserAbstract implements Parser {
132132
/** @var \SplObjectStorage<Array_, null>|null Array nodes created during parsing, for postprocessing of empty elements. */
133133
protected ?\SplObjectStorage $createdArrays;
134134

135+
/** @var \SplObjectStorage<Expr\ArrowFunction, null>|null
136+
* Arrow functions that are wrapped in parentheses, to enforce the pipe operator parentheses requirements.
137+
*/
138+
protected ?\SplObjectStorage $parenthesizedArrowFunctions;
139+
135140
/** @var Token[] Tokens for the current parse */
136141
protected array $tokens;
137142
/** @var int Current position in token array */
@@ -182,6 +187,7 @@ public function __construct(Lexer $lexer, ?PhpVersion $phpVersion = null) {
182187
public function parse(string $code, ?ErrorHandler $errorHandler = null): ?array {
183188
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing();
184189
$this->createdArrays = new \SplObjectStorage();
190+
$this->parenthesizedArrowFunctions = new \SplObjectStorage();
185191

186192
$this->tokens = $this->lexer->tokenize($code, $this->errorHandler);
187193
$result = $this->doParse();
@@ -205,6 +211,7 @@ public function parse(string $code, ?ErrorHandler $errorHandler = null): ?array
205211
$this->semStack = [];
206212
$this->semValue = null;
207213
$this->createdArrays = null;
214+
$this->parenthesizedArrowFunctions = null;
208215

209216
if ($result !== null) {
210217
$traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens));
@@ -1237,6 +1244,13 @@ protected function checkConstantAttributes(Const_ $node): void {
12371244
}
12381245
}
12391246

1247+
protected function checkPipeOperatorParentheses(Expr $node): void {
1248+
if ($node instanceof Expr\ArrowFunction && !$this->parenthesizedArrowFunctions->offsetExists($node)) {
1249+
$this->emitError(new Error(
1250+
'Arrow functions on the right hand side of |> must be parenthesized', $node->getAttributes()));
1251+
}
1252+
}
1253+
12401254
/**
12411255
* @param Property|Param $node
12421256
*/

test/code/parser/expr/pipe.test

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ $a |> $b |> $c;
55
$a . $b |> $c . $d;
66
$a |> $b == $c;
77
$c == $a |> $b;
8+
$a |> (fn($x) => $x) |> (fn($y) => $y);
9+
$a |> fn($x) => $x |> fn($y) => $y;
810
-----
11+
Arrow functions on the right hand side of |> must be parenthesized from 7:23 to 7:34
12+
Arrow functions on the right hand side of |> must be parenthesized from 7:7 to 7:34
913
array(
1014
0: Stmt_Expression(
1115
expr: Expr_BinaryOp_Pipe(
@@ -72,4 +76,126 @@ array(
7276
)
7377
)
7478
)
79+
4: Stmt_Expression(
80+
expr: Expr_BinaryOp_Pipe(
81+
left: Expr_BinaryOp_Pipe(
82+
left: Expr_Variable(
83+
name: a
84+
)
85+
right: Expr_ArrowFunction(
86+
attrGroups: array(
87+
)
88+
static: false
89+
byRef: false
90+
params: array(
91+
0: Param(
92+
attrGroups: array(
93+
)
94+
flags: 0
95+
type: null
96+
byRef: false
97+
variadic: false
98+
var: Expr_Variable(
99+
name: x
100+
)
101+
default: null
102+
hooks: array(
103+
)
104+
)
105+
)
106+
returnType: null
107+
expr: Expr_Variable(
108+
name: x
109+
)
110+
)
111+
)
112+
right: Expr_ArrowFunction(
113+
attrGroups: array(
114+
)
115+
static: false
116+
byRef: false
117+
params: array(
118+
0: Param(
119+
attrGroups: array(
120+
)
121+
flags: 0
122+
type: null
123+
byRef: false
124+
variadic: false
125+
var: Expr_Variable(
126+
name: y
127+
)
128+
default: null
129+
hooks: array(
130+
)
131+
)
132+
)
133+
returnType: null
134+
expr: Expr_Variable(
135+
name: y
136+
)
137+
)
138+
)
139+
)
140+
5: Stmt_Expression(
141+
expr: Expr_BinaryOp_Pipe(
142+
left: Expr_Variable(
143+
name: a
144+
)
145+
right: Expr_ArrowFunction(
146+
attrGroups: array(
147+
)
148+
static: false
149+
byRef: false
150+
params: array(
151+
0: Param(
152+
attrGroups: array(
153+
)
154+
flags: 0
155+
type: null
156+
byRef: false
157+
variadic: false
158+
var: Expr_Variable(
159+
name: x
160+
)
161+
default: null
162+
hooks: array(
163+
)
164+
)
165+
)
166+
returnType: null
167+
expr: Expr_BinaryOp_Pipe(
168+
left: Expr_Variable(
169+
name: x
170+
)
171+
right: Expr_ArrowFunction(
172+
attrGroups: array(
173+
)
174+
static: false
175+
byRef: false
176+
params: array(
177+
0: Param(
178+
attrGroups: array(
179+
)
180+
flags: 0
181+
type: null
182+
byRef: false
183+
variadic: false
184+
var: Expr_Variable(
185+
name: y
186+
)
187+
default: null
188+
hooks: array(
189+
)
190+
)
191+
)
192+
returnType: null
193+
expr: Expr_Variable(
194+
name: y
195+
)
196+
)
197+
)
198+
)
199+
)
200+
)
75201
)

0 commit comments

Comments
 (0)