Skip to content

Commit 56b2e28

Browse files
committed
Fixes microsoft#103: Support parsing multiple exception types
Add a new array `otherQualifiedNameList` alongside qualifiedName to `CatchClause`. (Contains remaining `Token`s and `QualifiedName`s) (This design breaks backwards compatibility as little as possible) - A future backwards incompatible release should merge the two properties into `qualifiedNameList`, or something along those lines. Add a new helper method to parse the catch list. - The new helper is required because `tests/cases/php-langspec/exception_handling/odds_and_ends.php` should not fail because `catch (int $e)` is **syntactically** valid php (in 7.x), i.e. `php --syntax-check` doesn't reject it.
1 parent 31cffdb commit 56b2e28

18 files changed

+454
-3
lines changed

src/Node/CatchClause.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ class CatchClause extends Node {
1616
public $openParen;
1717
/** @var QualifiedName */
1818
public $qualifiedName;
19+
/**
20+
* @var QualifiedName[]|Token[] Remaining tokens and qualified names in the catch clause
21+
* (e.g. `catch (FirstException|SecondException $x)` would contain
22+
* the representation of `|SecondException`)
23+
*
24+
* TODO: In the next backwards incompatible release, replace qualifiedName with qualifiedNameList?
25+
*/
26+
public $otherQualifiedNameList;
1927
/** @var Token */
2028
public $variableName;
2129
/** @var Token */
@@ -27,6 +35,7 @@ class CatchClause extends Node {
2735
'catch',
2836
'openParen',
2937
'qualifiedName',
38+
'otherQualifiedNameList',
3039
'variableName',
3140
'closeParen',
3241
'compoundStatement'

src/Parser.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ private function isExpressionStartFn() {
881881
case TokenKind::FunctionKeyword:
882882
return true;
883883
}
884-
return \in_array($token->kind, $this->reservedWordTokens);
884+
return \in_array($token->kind, $this->reservedWordTokens, true);
885885
};
886886
}
887887

@@ -1250,6 +1250,20 @@ private function isQualifiedNameStartFn() {
12501250
};
12511251
}
12521252

1253+
private function isQualifiedNameStartForCatchFn() {
1254+
return function ($token) {
1255+
switch ($token->kind) {
1256+
case TokenKind::BackslashToken:
1257+
case TokenKind::NamespaceKeyword:
1258+
case TokenKind::Name:
1259+
return true;
1260+
}
1261+
// Unfortunately, catch(int $x) is *syntactically valid* php which `php --syntax-check` would accept.
1262+
// (tolerant-php-parser is concerned with syntax, not semantics)
1263+
return in_array($token->kind, $this->reservedWordTokens, true);
1264+
};
1265+
}
1266+
12531267
private function parseQualifiedName($parentNode) {
12541268
return ($this->parseQualifiedNameFn())($parentNode);
12551269
}
@@ -2017,7 +2031,9 @@ private function parseCatchClause($parentNode) {
20172031
$catchClause->parent = $parentNode;
20182032
$catchClause->catch = $this->eat1(TokenKind::CatchKeyword);
20192033
$catchClause->openParen = $this->eat1(TokenKind::OpenParenToken);
2020-
$catchClause->qualifiedName = $this->parseQualifiedName($catchClause); // TODO generate missing token or error if null
2034+
$qualifiedNameList = $this->parseQualifiedNameCatchList($catchClause)->children ?? [];
2035+
$catchClause->qualifiedName = $qualifiedNameList[0] ?? null; // TODO generate missing token or error if null
2036+
$catchClause->otherQualifiedNameList = array_slice($qualifiedNameList, 1); // TODO: Generate error if the name list has missing tokens
20212037
$catchClause->variableName = $this->eat1(TokenKind::VariableName);
20222038
$catchClause->closeParen = $this->eat1(TokenKind::CloseParenToken);
20232039
$catchClause->compoundStatement = $this->parseCompoundStatement($catchClause);
@@ -2674,6 +2690,22 @@ private function parseQualifiedNameList($parentNode) {
26742690
$parentNode);
26752691
}
26762692

2693+
private function parseQualifiedNameCatchList($parentNode) {
2694+
$result = $this->parseDelimitedList(
2695+
DelimitedList\QualifiedNameList::class,
2696+
TokenKind::BarToken,
2697+
$this->isQualifiedNameStartForCatchFn(),
2698+
$this->parseQualifiedNameFn(),
2699+
$parentNode);
2700+
2701+
// Add a MissingToken so that this will Warn about `catch (T| $x) {}`
2702+
// TODO: Make this a reusable abstraction?
2703+
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
2704+
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
2705+
}
2706+
return $result;
2707+
}
2708+
26772709
private function parseInterfaceDeclaration($parentNode) {
26782710
$interfaceDeclaration = new InterfaceDeclaration(); // TODO verify not nested
26792711
$interfaceDeclaration->parent = $parentNode;

tests/ParserGrammarTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public function testSpecOutputTreeClassificationAndLength($testCaseFile, $expect
100100
$tokens = str_replace("\r\n", "\n", json_encode($sourceFile, JSON_PRETTY_PRINT));
101101
file_put_contents($expectedTreeFile, $tokens);
102102

103-
$this->assertCount(0, DiagnosticsProvider::getDiagnostics($sourceFile));
103+
$this->assertSame([], DiagnosticsProvider::getDiagnostics($sourceFile));
104104
}
105105

106106
public function outTreeProvider() {

tests/cases/parser/tryStatement1.php.tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
]
6363
}
6464
},
65+
"otherQualifiedNameList": [],
6566
"variableName": {
6667
"kind": "VariableName",
6768
"textLength": 2

tests/cases/parser/tryStatement10.php.tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"textLength": 1
4343
},
4444
"qualifiedName": null,
45+
"otherQualifiedNameList": [],
4546
"variableName": {
4647
"error": "MissingToken",
4748
"kind": "VariableName",

tests/cases/parser/tryStatement11.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
try {
4+
} catch (Hello| $e) {
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"kind": 0,
4+
"message": "'Name' expected.",
5+
"start": 28,
6+
"length": 0
7+
}
8+
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
"TryStatement": {
16+
"tryKeyword": {
17+
"kind": "TryKeyword",
18+
"textLength": 3
19+
},
20+
"compoundStatement": {
21+
"CompoundStatementNode": {
22+
"openBrace": {
23+
"kind": "OpenBraceToken",
24+
"textLength": 1
25+
},
26+
"statements": [],
27+
"closeBrace": {
28+
"kind": "CloseBraceToken",
29+
"textLength": 1
30+
}
31+
}
32+
},
33+
"catchClauses": [
34+
{
35+
"CatchClause": {
36+
"catch": {
37+
"kind": "CatchKeyword",
38+
"textLength": 5
39+
},
40+
"openParen": {
41+
"kind": "OpenParenToken",
42+
"textLength": 1
43+
},
44+
"qualifiedName": {
45+
"QualifiedName": {
46+
"globalSpecifier": null,
47+
"relativeSpecifier": null,
48+
"nameParts": [
49+
{
50+
"kind": "Name",
51+
"textLength": 5
52+
}
53+
]
54+
}
55+
},
56+
"otherQualifiedNameList": [
57+
{
58+
"kind": "BarToken",
59+
"textLength": 1
60+
},
61+
{
62+
"error": "MissingToken",
63+
"kind": "Name",
64+
"textLength": 0
65+
}
66+
],
67+
"variableName": {
68+
"kind": "VariableName",
69+
"textLength": 2
70+
},
71+
"closeParen": {
72+
"kind": "CloseParenToken",
73+
"textLength": 1
74+
},
75+
"compoundStatement": {
76+
"CompoundStatementNode": {
77+
"openBrace": {
78+
"kind": "OpenBraceToken",
79+
"textLength": 1
80+
},
81+
"statements": [],
82+
"closeBrace": {
83+
"kind": "CloseBraceToken",
84+
"textLength": 1
85+
}
86+
}
87+
}
88+
}
89+
}
90+
],
91+
"finallyClause": null
92+
}
93+
}
94+
],
95+
"endOfFileToken": {
96+
"kind": "EndOfFileToken",
97+
"textLength": 0
98+
}
99+
}
100+
}

tests/cases/parser/tryStatement12.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
try {
4+
} catch (Hello|NS\World $e) {
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
"TryStatement": {
16+
"tryKeyword": {
17+
"kind": "TryKeyword",
18+
"textLength": 3
19+
},
20+
"compoundStatement": {
21+
"CompoundStatementNode": {
22+
"openBrace": {
23+
"kind": "OpenBraceToken",
24+
"textLength": 1
25+
},
26+
"statements": [],
27+
"closeBrace": {
28+
"kind": "CloseBraceToken",
29+
"textLength": 1
30+
}
31+
}
32+
},
33+
"catchClauses": [
34+
{
35+
"CatchClause": {
36+
"catch": {
37+
"kind": "CatchKeyword",
38+
"textLength": 5
39+
},
40+
"openParen": {
41+
"kind": "OpenParenToken",
42+
"textLength": 1
43+
},
44+
"qualifiedName": {
45+
"QualifiedName": {
46+
"globalSpecifier": null,
47+
"relativeSpecifier": null,
48+
"nameParts": [
49+
{
50+
"kind": "Name",
51+
"textLength": 5
52+
}
53+
]
54+
}
55+
},
56+
"otherQualifiedNameList": [
57+
{
58+
"kind": "BarToken",
59+
"textLength": 1
60+
},
61+
{
62+
"QualifiedName": {
63+
"globalSpecifier": null,
64+
"relativeSpecifier": null,
65+
"nameParts": [
66+
{
67+
"kind": "Name",
68+
"textLength": 2
69+
},
70+
{
71+
"kind": "BackslashToken",
72+
"textLength": 1
73+
},
74+
{
75+
"kind": "Name",
76+
"textLength": 5
77+
}
78+
]
79+
}
80+
}
81+
],
82+
"variableName": {
83+
"kind": "VariableName",
84+
"textLength": 2
85+
},
86+
"closeParen": {
87+
"kind": "CloseParenToken",
88+
"textLength": 1
89+
},
90+
"compoundStatement": {
91+
"CompoundStatementNode": {
92+
"openBrace": {
93+
"kind": "OpenBraceToken",
94+
"textLength": 1
95+
},
96+
"statements": [],
97+
"closeBrace": {
98+
"kind": "CloseBraceToken",
99+
"textLength": 1
100+
}
101+
}
102+
}
103+
}
104+
}
105+
],
106+
"finallyClause": null
107+
}
108+
}
109+
],
110+
"endOfFileToken": {
111+
"kind": "EndOfFileToken",
112+
"textLength": 0
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)