Skip to content

Commit 6c701e3

Browse files
authored
Merge pull request #303 from TysonAndre/parse-union-type
Support php 8.0 union types for param/property/return
2 parents ff5dda4 + c4a9d19 commit 6c701e3

File tree

135 files changed

+2342
-46
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+2342
-46
lines changed

src/Node/Expression/AnonymousFunctionCreationExpression.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class AnonymousFunctionCreationExpression extends Expression implements Function
3838
'colonToken',
3939
'questionToken',
4040
'returnType',
41+
'otherReturnTypes',
4142

4243
// FunctionBody
4344
'compoundStatementOrSemicolon'

src/Node/Expression/ArrowFunctionCreationExpression.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ArrowFunctionCreationExpression extends Expression implements FunctionLike
4040
'colonToken',
4141
'questionToken',
4242
'returnType',
43+
'otherReturnTypes',
4344

4445
// body
4546
'arrowToken',

src/Node/FunctionReturnType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ trait FunctionReturnType {
1515
public $questionToken;
1616
/** @var Token|QualifiedName */
1717
public $returnType;
18+
/** @var DelimitedList\QualifiedNameList|null TODO: Merge with returnType in a future backwards incompatible release */
19+
public $otherReturnTypes;
1820
}

src/Node/MethodDeclaration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class MethodDeclaration extends Node implements FunctionLike {
3535
'colonToken',
3636
'questionToken',
3737
'returnType',
38+
'otherReturnTypes',
3839

3940
// FunctionBody
4041
'compoundStatementOrSemicolon'

src/Node/MissingMemberDeclaration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ class MissingMemberDeclaration extends Node {
2020
/** @var QualifiedName|Token|null */
2121
public $typeDeclaration;
2222

23+
/** @var DelimitedList\QualifiedNameList|null */
24+
public $otherTypeDeclarations;
25+
2326
const CHILD_NAMES = [
2427
'modifiers',
2528
'questionToken',
2629
'typeDeclaration',
30+
'otherTypeDeclarations',
2731
];
2832
}

src/Node/Parameter.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ class Parameter extends Node {
1414
public $questionToken;
1515
/** @var QualifiedName|Token|null */
1616
public $typeDeclaration;
17+
/**
18+
* @var DelimitedList\QualifiedNameList a list of other types, to support php 8 union types while remaining backwards compatible.
19+
* TODO: Merge with typeDeclaration in a future backwards incompatible release.
20+
*/
21+
public $otherTypeDeclarations;
1722
/** @var Token|null */
1823
public $byRefToken;
1924
/** @var Token|null */
@@ -28,6 +33,7 @@ class Parameter extends Node {
2833
const CHILD_NAMES = [
2934
'questionToken',
3035
'typeDeclaration',
36+
'otherTypeDeclarations',
3137
'byRefToken',
3238
'dotDotDotToken',
3339
'variableName',

src/Node/PropertyDeclaration.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class PropertyDeclaration extends Node {
2121
/** @var QualifiedName|Token|null */
2222
public $typeDeclaration;
2323

24+
/**
25+
* @var DelimitedList\QualifiedNameList|null
26+
* TODO: Unify with typeDeclaration in a future backwards incompatible release
27+
*/
28+
public $otherTypeDeclarations;
29+
2430
/** @var DelimitedList\ExpressionList */
2531
public $propertyElements;
2632

@@ -31,6 +37,7 @@ class PropertyDeclaration extends Node {
3137
'modifiers',
3238
'questionToken',
3339
'typeDeclaration',
40+
'otherTypeDeclarations',
3441
'propertyElements',
3542
'semicolon'
3643
];

src/Node/Statement/FunctionDeclaration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class FunctionDeclaration extends StatementNode implements NamespacedNameInterfa
3131
'colonToken',
3232
'questionToken',
3333
'returnType',
34+
'otherReturnTypes',
3435

3536
// FunctionBody
3637
'compoundStatementOrSemicolon'

src/Parser.php

Lines changed: 126 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ public function __construct() {
131131
$this->parameterTypeDeclarationTokens =
132132
[TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
133133
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
134-
TokenKind::ObjectReservedWord]; // TODO update spec
135-
$this->returnTypeDeclarationTokens = \array_merge([TokenKind::VoidReservedWord], $this->parameterTypeDeclarationTokens);
134+
TokenKind::ObjectReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord]; // TODO update spec
135+
$this->returnTypeDeclarationTokens = \array_merge([TokenKind::VoidReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord], $this->parameterTypeDeclarationTokens);
136136
}
137137

138138
/**
@@ -656,7 +656,14 @@ private function parseParameterFn() {
656656
$parameter = new Parameter();
657657
$parameter->parent = $parentNode;
658658
$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
659-
$parameter->typeDeclaration = $this->tryParseParameterTypeDeclaration($parameter);
659+
$typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
660+
if ($typeDeclarationList) {
661+
$parameter->typeDeclaration = array_shift($typeDeclarationList->children);
662+
$parameter->typeDeclaration->parent = $parameter;
663+
if ($typeDeclarationList->children) {
664+
$parameter->otherTypeDeclarations = $typeDeclarationList;
665+
}
666+
}
660667
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
661668
// TODO add post-parse rule that prevents assignment
662669
// TODO add post-parse rule that requires only last parameter be variadic
@@ -671,13 +678,52 @@ private function parseParameterFn() {
671678
};
672679
}
673680

674-
private function parseReturnTypeDeclaration($parentNode) {
675-
$returnTypeDeclaration =
676-
$this->eatOptional($this->returnTypeDeclarationTokens)
677-
?? $this->parseQualifiedName($parentNode)
678-
?? new MissingToken(TokenKind::ReturnType, $this->getCurrentToken()->fullStart);
681+
/**
682+
* @param ArrowFunctionCreationExpression|AnonymousFunctionCreationExpression|FunctionDeclaration|MethodDeclaration $parentNode a node with FunctionReturnType trait
683+
*/
684+
private function parseAndSetReturnTypeDeclarationList($parentNode) {
685+
$returnTypeList = $this->parseReturnTypeDeclarationList($parentNode);
686+
if (!$returnTypeList) {
687+
$parentNode->returnType = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
688+
return;
689+
}
690+
$returnType = array_shift($returnTypeList->children);
691+
$parentNode->returnType = $returnType;
692+
$returnType->parent = $parentNode;
693+
if ($returnTypeList->children) {
694+
$parentNode->otherReturnTypes = $returnTypeList;
695+
}
696+
}
679697

680-
return $returnTypeDeclaration;
698+
/**
699+
* Attempt to parse the return type after the `:` and optional `?` token.
700+
*
701+
* @return DelimitedList\QualifiedNameList|null
702+
*/
703+
private function parseReturnTypeDeclarationList($parentNode) {
704+
$result = $this->parseDelimitedList(
705+
DelimitedList\QualifiedNameList::class,
706+
TokenKind::BarToken,
707+
function ($token) {
708+
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
709+
},
710+
function ($parentNode) {
711+
return $this->parseReturnTypeDeclaration($parentNode);
712+
},
713+
$parentNode,
714+
false);
715+
716+
// Add a MissingToken so that this will warn about `function () : T| {}`
717+
// TODO: Make this a reusable abstraction?
718+
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
719+
$result->children[] = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
720+
}
721+
return $result;
722+
}
723+
724+
private function parseReturnTypeDeclaration($parentNode) {
725+
return $this->eatOptional($this->returnTypeDeclarationTokens)
726+
?? $this->parseQualifiedName($parentNode);
681727
}
682728

683729
private function tryParseParameterTypeDeclaration($parentNode) {
@@ -686,6 +732,31 @@ private function tryParseParameterTypeDeclaration($parentNode) {
686732
return $parameterTypeDeclaration;
687733
}
688734

735+
/**
736+
* @param Node $parentNode
737+
* @return DelimitedList\QualifiedNameList|null
738+
*/
739+
private function tryParseParameterTypeDeclarationList($parentNode) {
740+
$result = $this->parseDelimitedList(
741+
DelimitedList\QualifiedNameList::class,
742+
TokenKind::BarToken,
743+
function ($token) {
744+
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
745+
},
746+
function ($parentNode) {
747+
return $this->tryParseParameterTypeDeclaration($parentNode);
748+
},
749+
$parentNode,
750+
true);
751+
752+
// Add a MissingToken so that this will Warn about `function (T| $x) {}`
753+
// TODO: Make this a reusable abstraction?
754+
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
755+
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
756+
}
757+
return $result;
758+
}
759+
689760
private function parseCompoundStatement($parentNode) {
690761
$compoundStatement = new CompoundStatementNode();
691762
$compoundStatement->openBrace = $this->eat1(TokenKind::OpenBraceToken);
@@ -1231,7 +1302,7 @@ private function isParameterStartFn() {
12311302
}
12321303

12331304
// scalar-type
1234-
return \in_array($token->kind, $this->parameterTypeDeclarationTokens);
1305+
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true);
12351306
};
12361307
}
12371308

@@ -1271,33 +1342,31 @@ private function parseDelimitedList($className, $delimiter, $isElementStartFn, $
12711342
return $node;
12721343
}
12731344

1345+
/**
1346+
* @internal
1347+
*/
1348+
const QUALIFIED_NAME_START_TOKENS = [
1349+
TokenKind::BackslashToken,
1350+
TokenKind::NamespaceKeyword,
1351+
TokenKind::Name,
1352+
];
1353+
12741354
private function isQualifiedNameStart($token) {
1275-
return ($this->isQualifiedNameStartFn())($token);
1355+
return \in_array($token->kind, self::QUALIFIED_NAME_START_TOKENS, true);
12761356
}
12771357

12781358
private function isQualifiedNameStartFn() {
12791359
return function ($token) {
1280-
switch ($token->kind) {
1281-
case TokenKind::BackslashToken:
1282-
case TokenKind::NamespaceKeyword:
1283-
case TokenKind::Name:
1284-
return true;
1285-
}
1286-
return false;
1360+
return \in_array($token->kind, self::QUALIFIED_NAME_START_TOKENS, true);
12871361
};
12881362
}
12891363

12901364
private function isQualifiedNameStartForCatchFn() {
12911365
return function ($token) {
1292-
switch ($token->kind) {
1293-
case TokenKind::BackslashToken:
1294-
case TokenKind::NamespaceKeyword:
1295-
case TokenKind::Name:
1296-
return true;
1297-
}
12981366
// Unfortunately, catch(int $x) is *syntactically valid* php which `php --syntax-check` would accept.
12991367
// (tolerant-php-parser is concerned with syntax, not semantics)
1300-
return in_array($token->kind, $this->reservedWordTokens, true);
1368+
return \in_array($token->kind, self::QUALIFIED_NAME_START_TOKENS, true) ||
1369+
\in_array($token->kind, $this->reservedWordTokens, true);
13011370
};
13021371
}
13031372

@@ -1395,7 +1464,7 @@ private function parseFunctionType(Node $functionDeclaration, $canBeAbstract = f
13951464
if ($this->checkToken(TokenKind::ColonToken)) {
13961465
$functionDeclaration->colonToken = $this->eat1(TokenKind::ColonToken);
13971466
$functionDeclaration->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
1398-
$functionDeclaration->returnType = $this->parseReturnTypeDeclaration($functionDeclaration);
1467+
$this->parseAndSetReturnTypeDeclarationList($functionDeclaration);
13991468
}
14001469

14011470
if ($canBeAbstract) {
@@ -2842,31 +2911,38 @@ private function parseClassConstDeclaration($parentNode, $modifiers) {
28422911
*/
28432912
private function parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers, $questionToken = null)
28442913
{
2845-
$typeDeclaration = $this->tryParseParameterTypeDeclaration(null);
2846-
if ($questionToken !== null && $typeDeclaration === null) {
2847-
$typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->getCurrentToken()->fullStart);
2848-
}
2914+
$typeDeclarationList = $this->tryParseParameterTypeDeclarationList(null);
28492915
if ($this->getCurrentToken()->kind !== TokenKind::VariableName) {
2850-
return $this->makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken, $typeDeclaration);
2916+
return $this->makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken, $typeDeclarationList);
28512917
}
2852-
return $this->parsePropertyDeclaration($parentNode, $modifiers, $questionToken, $typeDeclaration);
2918+
return $this->parsePropertyDeclaration($parentNode, $modifiers, $questionToken, $typeDeclarationList);
28532919
}
28542920

28552921
/**
28562922
* @param Node $parentNode
28572923
* @param Token[] $modifiers
28582924
* @param Token|null $questionToken
2859-
* @param QualifiedName|Token|null $typeDeclaration
2925+
* @param DelimitedList\QualifiedNameList|null $typeDeclarationList
28602926
*/
2861-
private function parsePropertyDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclaration = null) {
2927+
private function parsePropertyDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclarationList = null) {
28622928
$propertyDeclaration = new PropertyDeclaration();
28632929
$propertyDeclaration->parent = $parentNode;
28642930

28652931
$propertyDeclaration->modifiers = $modifiers;
2866-
$propertyDeclaration->questionToken = $questionToken; //
2867-
$propertyDeclaration->typeDeclaration = $typeDeclaration;
2868-
if ($typeDeclaration instanceof Node) {
2869-
$typeDeclaration->parent = $propertyDeclaration;
2932+
$propertyDeclaration->questionToken = $questionToken;
2933+
if ($typeDeclarationList) {
2934+
/** $typeDeclarationList is a Node or a Token (e.g. IntKeyword) */
2935+
$typeDeclaration = \array_shift($typeDeclarationList->children);
2936+
$propertyDeclaration->typeDeclaration = $typeDeclaration;
2937+
if ($typeDeclaration instanceof Node) {
2938+
$typeDeclaration->parent = $propertyDeclaration;
2939+
}
2940+
if ($typeDeclarationList->children) {
2941+
$propertyDeclaration->otherTypeDeclarations = $typeDeclarationList;
2942+
$typeDeclarationList->parent = $propertyDeclaration;
2943+
}
2944+
} elseif ($questionToken) {
2945+
$propertyDeclaration->typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
28702946
}
28712947
$propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration);
28722948
$propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
@@ -3143,16 +3219,22 @@ private function parseTraitElementFn() {
31433219
* @param Node $parentNode
31443220
* @param Token[] $modifiers
31453221
* @param Token $questionToken
3146-
* @param QualifiedName|Token|null $typeDeclaration
3222+
* @param DelimitedList\QualifiedNameList|null $typeDeclarationList
31473223
*/
3148-
private function makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclaration = null) {
3224+
private function makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclarationList = null) {
31493225
$missingTraitMemberDeclaration = new MissingMemberDeclaration();
31503226
$missingTraitMemberDeclaration->parent = $parentNode;
31513227
$missingTraitMemberDeclaration->modifiers = $modifiers;
31523228
$missingTraitMemberDeclaration->questionToken = $questionToken;
3153-
$missingTraitMemberDeclaration->typeDeclaration = $typeDeclaration;
3154-
if ($typeDeclaration instanceof Node) {
3155-
$typeDeclaration->parent = $missingTraitMemberDeclaration;
3229+
if ($typeDeclarationList) {
3230+
$missingTraitMemberDeclaration->typeDeclaration = \array_shift($typeDeclarationList->children);
3231+
$missingTraitMemberDeclaration->typeDeclaration->parent = $missingTraitMemberDeclaration;
3232+
if ($typeDeclarationList->children) {
3233+
$missingTraitMemberDeclaration->otherTypeDeclarations = $typeDeclarationList;
3234+
$typeDeclarationList->parent = $missingTraitMemberDeclaration;
3235+
}
3236+
} elseif ($questionToken) {
3237+
$missingTraitMemberDeclaration->typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
31563238
}
31573239
return $missingTraitMemberDeclaration;
31583240
}
@@ -3397,7 +3479,7 @@ private function parseArrowFunctionCreationExpression($parentNode, $staticModifi
33973479
if ($this->checkToken(TokenKind::ColonToken)) {
33983480
$arrowFunction->colonToken = $this->eat1(TokenKind::ColonToken);
33993481
$arrowFunction->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
3400-
$arrowFunction->returnType = $this->parseReturnTypeDeclaration($arrowFunction);
3482+
$this->parseAndSetReturnTypeDeclarationList($arrowFunction);
34013483
}
34023484

34033485
$arrowFunction->arrowToken = $this->eat1(TokenKind::DoubleArrowToken);

tests/cases/parser/abstractMethodDeclaration1.php.tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"colonToken": null,
6868
"questionToken": null,
6969
"returnType": null,
70+
"otherReturnTypes": null,
7071
"compoundStatementOrSemicolon": {
7172
"kind": "SemicolonToken",
7273
"textLength": 1

0 commit comments

Comments
 (0)