Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -828,11 +828,22 @@ private function parseParameterFn() {
$parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]);
$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
$parameter->typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
if ($parameter->questionToken && !$parameter->typeDeclarationList) {
if ($parameter->typeDeclarationList) {
$children = $parameter->typeDeclarationList->children;
if (end($children) instanceof MissingToken && ($children[count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
array_pop($parameter->typeDeclarationList->children);
$parameter->byRefToken = array_pop($parameter->typeDeclarationList->children);
if (!$parameter->typeDeclarationList->children) {
unset($parameter->typeDeclarationList);
}
}
} elseif ($parameter->questionToken) {
// TODO ParameterType?
$parameter->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
}
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
if (!$parameter->byRefToken) {
$parameter->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
}
// TODO add post-parse rule that prevents assignment
// TODO add post-parse rule that requires only last parameter be variadic
$parameter->dotDotDotToken = $this->eatOptional1(TokenKind::DotDotDotToken);
Expand All @@ -858,6 +869,11 @@ private function parseAndSetReturnTypeDeclarationList($parentNode) {
$parentNode->returnTypeList = $returnTypeList;
}

const TYPE_DELIMITER_TOKENS = [
TokenKind::BarToken,
TokenKind::AmpersandToken,
];

/**
* Attempt to parse the return type after the `:` and optional `?` token.
*
Expand All @@ -866,7 +882,7 @@ private function parseAndSetReturnTypeDeclarationList($parentNode) {
private function parseReturnTypeDeclarationList($parentNode) {
$result = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
TokenKind::BarToken,
self::TYPE_DELIMITER_TOKENS,
function ($token) {
return \in_array($token->kind, $this->returnTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
},
Expand All @@ -878,7 +894,7 @@ function ($parentNode) {

// Add a MissingToken so that this will warn about `function () : T| {}`
// TODO: Make this a reusable abstraction?
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
$result->children[] = new MissingToken(TokenKind::ReturnType, $this->token->fullStart);
}
return $result;
Expand All @@ -902,7 +918,7 @@ private function tryParseParameterTypeDeclaration($parentNode) {
private function tryParseParameterTypeDeclarationList($parentNode) {
$result = $this->parseDelimitedList(
DelimitedList\QualifiedNameList::class,
TokenKind::BarToken,
self::TYPE_DELIMITER_TOKENS,
function ($token) {
return \in_array($token->kind, $this->parameterTypeDeclarationTokens, true) || $this->isQualifiedNameStart($token);
},
Expand All @@ -914,7 +930,7 @@ function ($parentNode) {

// Add a MissingToken so that this will Warn about `function (T| $x) {}`
// TODO: Make this a reusable abstraction?
if ($result && (end($result->children)->kind ?? null) === TokenKind::BarToken) {
if ($result && in_array(end($result->children)->kind ?? null, self::TYPE_DELIMITER_TOKENS)) {
$result->children[] = new MissingToken(TokenKind::Name, $this->token->fullStart);
}
return $result;
Expand Down
4 changes: 4 additions & 0 deletions src/PhpTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
define(__NAMESPACE__ . '\T_ATTRIBUTE', defined('T_ATTRIBUTE') ? constant('T_ATTRIBUTE') : 'T_ATTRIBUTE');
// 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.
define(__NAMESPACE__ . '\T_ENUM', defined('T_ENUM') ? constant('T_ENUM') : 'T_ENUM');
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');
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');

/**
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
Expand Down Expand Up @@ -338,6 +340,8 @@ protected static function tokenGetAll(string $content, $parseContext): array
"^" => TokenKind::CaretToken,
"|" => TokenKind::BarToken,
"&" => TokenKind::AmpersandToken,
T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG => TokenKind::AmpersandToken,
T_BOOLEAN_AND => TokenKind::AmpersandAmpersandToken,
T_BOOLEAN_OR => TokenKind::BarBarToken,
":" => TokenKind::ColonToken,
Expand Down
9 changes: 9 additions & 0 deletions tests/cases/parser81/intersection_type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
function test(A&B&C $first, A&B &$second): A&B {
}

class E {
public ArrayAccess&Countable $arrayLike;
function invalid(): A& {
}
}
8 changes: 8 additions & 0 deletions tests/cases/parser81/intersection_type.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"kind": 0,
"message": "'ReturnType' expected.",
"start": 139,
"length": 0
}
]
Loading