Skip to content

Commit 90b1fed

Browse files
committed
Support using throw as an expression
To maximize backward compatibility, continue using ThrowStatement for throw being the first keyword in a statement. For https://wiki.php.net/rfc/throw_expression which is currently in the voting phase.
1 parent c5e2bf5 commit 90b1fed

15 files changed

+488
-38
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node\Expression;
8+
9+
use Microsoft\PhpParser\Node\Expression;
10+
use Microsoft\PhpParser\Token;
11+
12+
class ThrowExpression extends Expression {
13+
/** @var Token */
14+
public $throwKeyword;
15+
/** @var Expression */
16+
public $expression;
17+
18+
const CHILD_NAMES = [
19+
'throwKeyword',
20+
'expression',
21+
];
22+
}

src/Node/Statement/ThrowStatement.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Microsoft\PhpParser\Node\StatementNode;
1111
use Microsoft\PhpParser\Token;
1212

13+
// TODO: Remove this and replace with ThrowExpression in a backwards incompatible major release
1314
class ThrowStatement extends StatementNode {
1415
/** @var Token */
1516
public $throwKeyword;

src/Parser.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
ScopedPropertyAccessExpression,
4444
SubscriptExpression,
4545
TernaryExpression,
46+
ThrowExpression,
4647
UnaryExpression,
4748
UnaryOpExpression,
4849
UnsetIntrinsicExpression,
@@ -1762,6 +1763,8 @@ private function parseUnaryExpressionOrHigher($parentNode) {
17621763
case TokenKind::RequireKeyword:
17631764
case TokenKind::RequireOnceKeyword:
17641765
return $this->parseScriptInclusionExpression($parentNode);
1766+
case TokenKind::ThrowKeyword: // throw-statement will become an expression in php 8.0
1767+
return $this->parseThrowExpression($parentNode);
17651768
}
17661769

17671770
$expression = $this->parsePrimaryExpression($parentNode);
@@ -2223,6 +2226,16 @@ private function parseThrowStatement($parentNode) {
22232226
return $throwStatement;
22242227
}
22252228

2229+
private function parseThrowExpression($parentNode) {
2230+
$throwExpression = new ThrowExpression();
2231+
$throwExpression->parent = $parentNode;
2232+
$throwExpression->throwKeyword = $this->eat1(TokenKind::ThrowKeyword);
2233+
// TODO error for failures to parse expressions when not optional
2234+
$throwExpression->expression = $this->parseExpression($throwExpression);
2235+
2236+
return $throwExpression;
2237+
}
2238+
22262239
private function parseTryStatement($parentNode) {
22272240
$tryStatement = new TryStatement();
22282241
$tryStatement->parent = $parentNode;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<?php
2-
2+
// This becomes syntactically valid in php 8.0 for throw expressions. Only the inner throw is executed.
33
throw throw $e;
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1 @@
1-
[
2-
{
3-
"kind": 0,
4-
"message": "'Expression' expected.",
5-
"start": 12,
6-
"length": 0
7-
},
8-
{
9-
"kind": 0,
10-
"message": "';' expected.",
11-
"start": 12,
12-
"length": 0
13-
}
14-
]
1+
[]

tests/cases/parser/throwStatement5.php.tree

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,19 @@
1818
"textLength": 5
1919
},
2020
"expression": {
21-
"error": "MissingToken",
22-
"kind": "Expression",
23-
"textLength": 0
24-
},
25-
"semicolon": {
26-
"error": "MissingToken",
27-
"kind": "SemicolonToken",
28-
"textLength": 0
29-
}
30-
}
31-
},
32-
{
33-
"ThrowStatement": {
34-
"throwKeyword": {
35-
"kind": "ThrowKeyword",
36-
"textLength": 5
37-
},
38-
"expression": {
39-
"Variable": {
40-
"dollar": null,
41-
"name": {
42-
"kind": "VariableName",
43-
"textLength": 2
21+
"ThrowExpression": {
22+
"throwKeyword": {
23+
"kind": "ThrowKeyword",
24+
"textLength": 5
25+
},
26+
"expression": {
27+
"Variable": {
28+
"dollar": null,
29+
"name": {
30+
"kind": "VariableName",
31+
"textLength": 2
32+
}
33+
}
4434
}
4535
}
4636
},
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
// This is surprisingly parsed by php 8 as throw(new RuntimeException() && printf("Meant to be unreachable"))
3+
throw new RuntimeException() && printf("Meant to be unreachable but isn't\n");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{
2+
"SourceFileNode": {
3+
"statementList": [
4+
{
5+
"InlineHtml": {
6+
"scriptSectionEndTag": null,
7+
"text": null,
8+
"scriptSectionStartTag": {
9+
"kind": "ScriptSectionStartTag",
10+
"textLength": 6
11+
}
12+
}
13+
},
14+
{
15+
"ThrowStatement": {
16+
"throwKeyword": {
17+
"kind": "ThrowKeyword",
18+
"textLength": 5
19+
},
20+
"expression": {
21+
"BinaryExpression": {
22+
"leftOperand": {
23+
"ObjectCreationExpression": {
24+
"newKeword": {
25+
"kind": "NewKeyword",
26+
"textLength": 3
27+
},
28+
"classTypeDesignator": {
29+
"QualifiedName": {
30+
"globalSpecifier": null,
31+
"relativeSpecifier": null,
32+
"nameParts": [
33+
{
34+
"kind": "Name",
35+
"textLength": 16
36+
}
37+
]
38+
}
39+
},
40+
"openParen": {
41+
"kind": "OpenParenToken",
42+
"textLength": 1
43+
},
44+
"argumentExpressionList": null,
45+
"closeParen": {
46+
"kind": "CloseParenToken",
47+
"textLength": 1
48+
},
49+
"classBaseClause": null,
50+
"classInterfaceClause": null,
51+
"classMembers": null
52+
}
53+
},
54+
"operator": {
55+
"kind": "AmpersandAmpersandToken",
56+
"textLength": 2
57+
},
58+
"rightOperand": {
59+
"CallExpression": {
60+
"callableExpression": {
61+
"QualifiedName": {
62+
"globalSpecifier": null,
63+
"relativeSpecifier": null,
64+
"nameParts": [
65+
{
66+
"kind": "Name",
67+
"textLength": 6
68+
}
69+
]
70+
}
71+
},
72+
"openParen": {
73+
"kind": "OpenParenToken",
74+
"textLength": 1
75+
},
76+
"argumentExpressionList": {
77+
"ArgumentExpressionList": {
78+
"children": [
79+
{
80+
"ArgumentExpression": {
81+
"byRefToken": null,
82+
"dotDotDotToken": null,
83+
"expression": {
84+
"StringLiteral": {
85+
"startQuote": null,
86+
"children": {
87+
"kind": "StringLiteralToken",
88+
"textLength": 37
89+
},
90+
"endQuote": null
91+
}
92+
}
93+
}
94+
}
95+
]
96+
}
97+
},
98+
"closeParen": {
99+
"kind": "CloseParenToken",
100+
"textLength": 1
101+
}
102+
}
103+
}
104+
}
105+
},
106+
"semicolon": {
107+
"kind": "SemicolonToken",
108+
"textLength": 1
109+
}
110+
}
111+
}
112+
],
113+
"endOfFileToken": {
114+
"kind": "EndOfFileToken",
115+
"textLength": 0
116+
}
117+
}
118+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
// $value is non-nullable.
3+
$value = $nullableValue ?? throw new InvalidArgumentException();
4+
5+
// $value is truthy.
6+
$value = $falsableValue ?: throw new InvalidArgumentException();

0 commit comments

Comments
 (0)