Skip to content

Commit 03f8cab

Browse files
authored
Merge pull request microsoft#356 from TysonAndre/intersection-type-support
Support parsing PHP 8.1 intersection types
2 parents 69f23ca + 9067f71 commit 03f8cab

File tree

5 files changed

+428
-6
lines changed

5 files changed

+428
-6
lines changed

src/Parser.php

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -828,11 +828,22 @@ private function parseParameterFn() {
828828
$parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]);
829829
$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
830830
$parameter->typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
831-
if ($parameter->questionToken && !$parameter->typeDeclarationList) {
831+
if ($parameter->typeDeclarationList) {
832+
$children = $parameter->typeDeclarationList->children;
833+
if (end($children) instanceof MissingToken && ($children[count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
834+
array_pop($parameter->typeDeclarationList->children);
835+
$parameter->byRefToken = array_pop($parameter->typeDeclarationList->children);
836+
if (!$parameter->typeDeclarationList->children) {
837+
unset($parameter->typeDeclarationList);
838+
}
839+
}
840+
} elseif ($parameter->questionToken) {
832841
// TODO ParameterType?
833842
$parameter->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
834843
}
835-
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
844+
if (!$parameter->byRefToken) {
845+
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
846+
}
836847
// TODO add post-parse rule that prevents assignment
837848
// TODO add post-parse rule that requires only last parameter be variadic
838849
$parameter->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
@@ -858,6 +869,11 @@ private function parseAndSetReturnTypeDeclarationList($parentNode) {
858869
$parentNode->returnTypeList = $returnTypeList;
859870
}
860871

872+
const TYPE_DELIMITER_TOKENS = [
873+
TokenKind::BarToken,
874+
TokenKind::AmpersandToken,
875+
];
876+
861877
/**
862878
* Attempt to parse the return type after the `:` and optional `?` token.
863879
*
@@ -866,7 +882,7 @@ private function parseAndSetReturnTypeDeclarationList($parentNode) {
866882
private function parseReturnTypeDeclarationList($parentNode) {
867883
$result = $this->parseDelimitedList(
868884
DelimitedList\QualifiedNameList::class,
869-
TokenKind::BarToken,
885+
self::TYPE_DELIMITER_TOKENS,
870886
function ($token) {
871887
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
872888
},
@@ -878,7 +894,7 @@ function ($parentNode) {
878894

879895
// Add a MissingToken so that this will warn about `function () : T| {}`
880896
// TODO: Make this a reusable abstraction?
881-
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
897+
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
882898
$result->children[] = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
883899
}
884900
return $result;
@@ -902,7 +918,7 @@ private function tryParseParameterTypeDeclaration($parentNode) {
902918
private function tryParseParameterTypeDeclarationList($parentNode) {
903919
$result = $this->parseDelimitedList(
904920
DelimitedList\QualifiedNameList::class,
905-
TokenKind::BarToken,
921+
self::TYPE_DELIMITER_TOKENS,
906922
function ($token) {
907923
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
908924
},
@@ -914,7 +930,7 @@ function ($parentNode) {
914930

915931
// Add a MissingToken so that this will Warn about `function (T| $x) {}`
916932
// TODO: Make this a reusable abstraction?
917-
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
933+
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
918934
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
919935
}
920936
return $result;

src/PhpTokenizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
define(__NAMESPACE__ . '\T_ATTRIBUTE', defined('T_ATTRIBUTE') ? constant('T_ATTRIBUTE') : 'T_ATTRIBUTE');
1717
// If this predates PHP 8.1, T_ENUM is unavailable. The replacement value is arbitrary - it just has to be different from other values of token constants.
1818
define(__NAMESPACE__ . '\T_ENUM', defined('T_ENUM') ? constant('T_ENUM') : 'T_ENUM');
19+
define(__NAMESPACE__ . '\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') ? constant('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') : 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG');
20+
define(__NAMESPACE__ . '\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') ? constant('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') : 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG');
1921

2022
/**
2123
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
@@ -338,6 +340,8 @@ protected static function tokenGetAll(string $content, $parseContext): array
338340
"^" => TokenKind::CaretToken,
339341
"|" => TokenKind::BarToken,
340342
"&" => TokenKind::AmpersandToken,
343+
T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
344+
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
341345
T_BOOLEAN_AND => TokenKind::AmpersandAmpersandToken,
342346
T_BOOLEAN_OR => TokenKind::BarBarToken,
343347
":" => TokenKind::ColonToken,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
function test(A&B&C $first, A&B &$second): A&B {
3+
}
4+
5+
class E {
6+
public ArrayAccess&Countable $arrayLike;
7+
function invalid(): A& {
8+
}
9+
}
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": "'ReturnType' expected.",
5+
"start": 139,
6+
"length": 0
7+
}
8+
]

0 commit comments

Comments
 (0)