@@ -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);
0 commit comments