66
77namespace Microsoft \PhpParser ;
88
9+ use Closure ;
910use Microsoft \PhpParser \Node \AnonymousFunctionUseClause ;
1011use Microsoft \PhpParser \Node \ArrayElement ;
1112use Microsoft \PhpParser \Node \Attribute ;
7071use Microsoft \PhpParser \Node \NamespaceAliasingClause ;
7172use Microsoft \PhpParser \Node \NamespaceUseGroupClause ;
7273use Microsoft \PhpParser \Node \NumericLiteral ;
74+ use Microsoft \PhpParser \Node \ParenthesizedIntersectionType ;
7375use Microsoft \PhpParser \Node \PropertyDeclaration ;
7476use Microsoft \PhpParser \Node \ReservedWord ;
7577use Microsoft \PhpParser \Node \StringLiteral ;
@@ -894,13 +896,31 @@ private function parseAndSetReturnTypeDeclarationList($parentNode) {
894896 * @return DelimitedList\QualifiedNameList|null
895897 */
896898 private function parseReturnTypeDeclarationList ($ parentNode ) {
899+ // TODO: Forbid mixing `|` and `&` in another PR, that's a parse error.
900+ // TODO: Forbid mixing (A&B)&C in another PR, that's a parse error.
897901 $ result = $ this ->parseDelimitedList (
898902 DelimitedList \QualifiedNameList::class,
899903 self ::TYPE_DELIMITER_TOKENS ,
900904 function ($ token ) {
901- return \in_array ($ token ->kind , $ this ->returnTypeDeclarationTokens , true ) || $ this ->isQualifiedNameStart ($ token );
905+ return \in_array ($ token ->kind , $ this ->returnTypeDeclarationTokens , true ) ||
906+ $ token ->kind === TokenKind::OpenParenToken ||
907+ $ this ->isQualifiedNameStart ($ token );
902908 },
903909 function ($ parentNode ) {
910+ $ openParen = $ this ->eatOptional (TokenKind::OpenParenToken);
911+ if ($ openParen ) {
912+ return $ this ->parseParenthesizedIntersectionType (
913+ $ parentNode ,
914+ $ openParen ,
915+ function ($ token ) {
916+ return \in_array ($ token ->kind , $ this ->returnTypeDeclarationTokens , true ) ||
917+ $ this ->isQualifiedNameStart ($ token );
918+ },
919+ function ($ parentNode ) {
920+ return $ this ->parseReturnTypeDeclaration ($ parentNode );
921+ }
922+ );
923+ }
904924 return $ this ->parseReturnTypeDeclaration ($ parentNode );
905925 },
906926 $ parentNode ,
@@ -925,18 +945,62 @@ private function tryParseParameterTypeDeclaration($parentNode) {
925945 return $ parameterTypeDeclaration ;
926946 }
927947
948+ private function parseParenthesizedIntersectionType ($ parentNode , Token $ openParen , Closure $ isTypeStart , Closure $ parseType ): ParenthesizedIntersectionType {
949+ $ node = new ParenthesizedIntersectionType ();
950+ $ node ->parent = $ parentNode ;
951+ $ node ->openParen = $ openParen ;
952+ $ node ->children = $ this ->parseDelimitedList (
953+ DelimitedList \QualifiedNameList::class,
954+ TokenKind::AmpersandToken,
955+ $ isTypeStart ,
956+ $ parseType ,
957+ $ node ,
958+ true );
959+ if ($ node ->children ) {
960+ // https://wiki.php.net/rfc/dnf_types
961+ if ((end ($ node ->children ->children )->kind ?? null ) === TokenKind::OpenParenToken) {
962+ // Add a MissingToken so that this will Warn about `function (A|(B&) $x) {}`
963+ $ node ->children ->children [] = new MissingToken (TokenKind::Name, $ this ->token ->fullStart );
964+ } elseif (count ($ node ->children ->children ) === 1 ) {
965+ // Must have at least 2 parts for A|(B&C)
966+ $ node ->children ->children [] = new MissingToken (TokenKind::AmpersandToken, $ this ->token ->fullStart );
967+ }
968+ } else {
969+ // Having less than 2 types (no types) in A|() is a parse error
970+ $ node ->children = new MissingToken (TokenKind::Name, $ this ->token ->fullStart );
971+ }
972+ $ node ->closeParen = $ this ->eat (TokenKind::CloseParenToken);
973+ return $ node ;
974+ }
975+
928976 /**
929- * @param Node $parentNode
977+ * @param Node|null $parentNode
930978 * @return DelimitedList\QualifiedNameList|null
931979 */
932980 private function tryParseParameterTypeDeclarationList ($ parentNode ) {
933981 $ result = $ this ->parseDelimitedList (
934982 DelimitedList \QualifiedNameList::class,
935983 self ::TYPE_DELIMITER_TOKENS ,
936984 function ($ token ) {
937- return \in_array ($ token ->kind , $ this ->parameterTypeDeclarationTokens , true ) || $ this ->isQualifiedNameStart ($ token );
985+ return \in_array ($ token ->kind , $ this ->parameterTypeDeclarationTokens , true ) ||
986+ $ token ->kind === TokenKind::OpenParenToken ||
987+ $ this ->isQualifiedNameStart ($ token );
938988 },
939989 function ($ parentNode ) {
990+ $ openParen = $ this ->eatOptional (TokenKind::OpenParenToken);
991+ if ($ openParen ) {
992+ return $ this ->parseParenthesizedIntersectionType (
993+ $ parentNode ,
994+ $ openParen ,
995+ function ($ token ) {
996+ return \in_array ($ token ->kind , $ this ->parameterTypeDeclarationTokens , true ) ||
997+ $ this ->isQualifiedNameStart ($ token );
998+ },
999+ function ($ parentNode ) {
1000+ return $ this ->tryParseParameterTypeDeclaration ($ parentNode );
1001+ }
1002+ );
1003+ }
9401004 return $ this ->tryParseParameterTypeDeclaration ($ parentNode );
9411005 },
9421006 $ parentNode ,
@@ -959,12 +1023,6 @@ private function parseCompoundStatement($parentNode) {
9591023 return $ compoundStatement ;
9601024 }
9611025
962- private function array_push_list (& $ array , $ list ) {
963- foreach ($ list as $ item ) {
964- $ array [] = $ item ;
965- }
966- }
967-
9681026 private function isClassMemberDeclarationStart (Token $ token ) {
9691027 switch ($ token ->kind ) {
9701028 // const-modifier
@@ -1544,6 +1602,9 @@ private function isParameterStartFn() {
15441602 case TokenKind::ProtectedKeyword:
15451603 case TokenKind::PrivateKeyword:
15461604 case TokenKind::AttributeToken:
1605+
1606+ // dnf types (A&B)|C
1607+ case TokenKind::OpenParenToken:
15471608 return true ;
15481609 }
15491610
@@ -3306,6 +3367,8 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke
33063367 }
33073368
33083369 /**
3370+ * Parse a comma separated qualified name list (e.g. interfaces implemented by a class)
3371+ *
33093372 * @param Node $parentNode
33103373 * @return DelimitedList\QualifiedNameList
33113374 */
@@ -3319,6 +3382,7 @@ private function parseQualifiedNameList($parentNode) {
33193382 }
33203383
33213384 private function parseQualifiedNameCatchList ($ parentNode ) {
3385+ // catch blocks don't support intersection types.
33223386 $ result = $ this ->parseDelimitedList (
33233387 DelimitedList \QualifiedNameList::class,
33243388 TokenKind::BarToken,
0 commit comments