Skip to content

Support php 8.1 readonly property modifier RFC #357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 16, 2021
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
2 changes: 1 addition & 1 deletion src/Node/Expression/ArgumentExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ArgumentExpression extends Expression {
/** @var Token|null */
public $dotDotDotToken;

/** @var Expression */
/** @var Expression|null null for first-class callable syntax */
public $expression;

const CHILD_NAMES = [
Expand Down
3 changes: 3 additions & 0 deletions src/Node/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class Parameter extends Node {
public $attributes;
/** @var Token|null */
public $visibilityToken;
/** @var Token[]|null */
public $modifiers;
/** @var Token|null */
public $questionToken;
/** @var DelimitedList\QualifiedNameList|MissingToken|null */
Expand All @@ -33,6 +35,7 @@ class Parameter extends Node {
const CHILD_NAMES = [
'attributes',
'visibilityToken',
'modifiers',
'questionToken',
'typeDeclarationList',
'byRefToken',
Expand Down
65 changes: 60 additions & 5 deletions src/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -825,12 +825,17 @@ private function parseParameterFn() {
if ($this->token->kind === TokenKind::AttributeToken) {
$parameter->attributes = $this->parseAttributeGroups($parameter);
}
// Note that parameter modifiers are allowed to be repeated by the parser in php 8.1 (it is a compiler error)
//
// TODO: Remove the visibilityToken in a future backwards incompatible release
$parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]);
$parameter->modifiers = $this->parseParameterModifiers() ?: null;

$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
$parameter->typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
if ($parameter->typeDeclarationList) {
$children = $parameter->typeDeclarationList->children;
if (end($children) instanceof MissingToken && ($children[count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
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) {
Expand Down Expand Up @@ -964,6 +969,9 @@ private function isClassMemberDeclarationStart(Token $token) {
// static-modifier
case TokenKind::StaticKeyword:

// readonly-modifier
case TokenKind::ReadonlyKeyword:

// class-modifier
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
Expand Down Expand Up @@ -1443,7 +1451,7 @@ private function parseReservedWordExpression($parentNode) {
return $reservedWord;
}

private function isModifier($token) {
private function isModifier($token): bool {
switch ($token->kind) {
// class-modifier
case TokenKind::AbstractKeyword:
Expand All @@ -1457,14 +1465,45 @@ private function isModifier($token) {
// static-modifier
case TokenKind::StaticKeyword:

// readonly-modifier
case TokenKind::ReadonlyKeyword:

// var
case TokenKind::VarKeyword:
return true;
}
return false;
}

private function parseModifiers() {
private function isParameterModifier($token): bool {
switch ($token->kind) {
// visibility-modifier
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:

// readonly-modifier
case TokenKind::ReadonlyKeyword:

return true;
}
return false;
}

/** @return Token[] */
private function parseParameterModifiers(): array {
$modifiers = [];
$token = $this->getCurrentToken();
while ($this->isParameterModifier($token)) {
$modifiers[] = $token;
$this->advanceToken();
$token = $this->getCurrentToken();
}
return $modifiers;
}

/** @return Token[] */
private function parseModifiers(): array {
$modifiers = [];
$token = $this->getCurrentToken();
while ($this->isModifier($token)) {
Expand Down Expand Up @@ -3101,14 +3140,27 @@ private function parseObjectCreationExpression($parentNode) {
return $objectCreationExpression;
}

/**
* @return DelimitedList\ArgumentExpressionList|null
*/
private function parseArgumentExpressionList($parentNode) {
return $this->parseDelimitedList(
$list = $this->parseDelimitedList(
DelimitedList\ArgumentExpressionList::class,
TokenKind::CommaToken,
$this->isArgumentExpressionStartFn(),
$this->parseArgumentExpressionFn(),
$parentNode
);
$children = $list->children ?? null;
if (is_array($children) && \count($children) === 1) {
$arg = $children[0];
if ($arg instanceof ArgumentExpression) {
if ($arg->dotDotDotToken && $arg->expression instanceof MissingToken && !$arg->colonToken && !$arg->name) {
$arg->expression = null;
}
}
}
return $list;
}

/**
Expand Down Expand Up @@ -3283,6 +3335,9 @@ private function isInterfaceMemberDeclarationStart(Token $token) {
// static-modifier
case TokenKind::StaticKeyword:

// readonly-modifier
case TokenKind::ReadonlyKeyword:

// class-modifier
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
Expand Down Expand Up @@ -3457,6 +3512,7 @@ private function isTraitMemberDeclarationStart($token) {
case TokenKind::StaticKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
case TokenKind::ReadonlyKeyword:

// method-declaration
case TokenKind::FunctionKeyword:
Expand Down Expand Up @@ -3537,7 +3593,6 @@ private function isEnumMemberDeclarationStart($token) {
case TokenKind::PublicKeyword:
case TokenKind::ProtectedKeyword:
case TokenKind::PrivateKeyword:
// case TokenKind::VarKeyword:
case TokenKind::StaticKeyword:
case TokenKind::AbstractKeyword:
case TokenKind::FinalKeyword:
Expand Down
2 changes: 2 additions & 0 deletions src/PhpTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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');
define(__NAMESPACE__ . '\T_READONLY', defined('T_READONLY') ? constant('T_READONLY') : 'T_READONLY');

/**
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
Expand Down Expand Up @@ -290,6 +291,7 @@ protected static function tokenGetAll(string $content, $parseContext): array
T_PRIVATE => TokenKind::PrivateKeyword,
T_PROTECTED => TokenKind::ProtectedKeyword,
T_PUBLIC => TokenKind::PublicKeyword,
T_READONLY => TokenKind::ReadonlyKeyword,
T_REQUIRE => TokenKind::RequireKeyword,
T_REQUIRE_ONCE => TokenKind::RequireOnceKeyword,
T_RETURN => TokenKind::ReturnKeyword,
Expand Down
1 change: 1 addition & 0 deletions src/TokenKind.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class TokenKind {
/** @deprecated use IterableReservedWord */
const IterableKeyword = self::IterableReservedWord;
const EnumKeyword = 171;
const ReadonlyKeyword = 172;

const OpenBracketToken = 201;
const CloseBracketToken = 202;
Expand Down
1 change: 1 addition & 0 deletions src/TokenStringMaps.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class TokenStringMaps {
"private" => TokenKind::PrivateKeyword,
"protected" => TokenKind::ProtectedKeyword,
"public" => TokenKind::PublicKeyword,
"readonly" => TokenKind::ReadonlyKeyword,
"require" => TokenKind::RequireKeyword,
"require_once" => TokenKind::RequireOnceKeyword,
"return" => TokenKind::ReturnKeyword,
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/parser/classMethods3.php.tree
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"Parameter": {
"attributes": null,
"visibilityToken": null,
"modifiers": null,
"questionToken": null,
"typeDeclarationList": null,
"byRefToken": null,
Expand All @@ -81,6 +82,7 @@
"Parameter": {
"attributes": null,
"visibilityToken": null,
"modifiers": null,
"questionToken": null,
"typeDeclarationList": null,
"byRefToken": null,
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/parser/classMethods4.php.tree
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"Parameter": {
"attributes": null,
"visibilityToken": null,
"modifiers": null,
"questionToken": null,
"typeDeclarationList": null,
"byRefToken": null,
Expand All @@ -81,6 +82,7 @@
"Parameter": {
"attributes": null,
"visibilityToken": null,
"modifiers": null,
"questionToken": null,
"typeDeclarationList": null,
"byRefToken": null,
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/parser/first_class_callable_syntax.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
$sl = strlen(...);
1 change: 1 addition & 0 deletions tests/cases/parser/first_class_callable_syntax.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
87 changes: 87 additions & 0 deletions tests/cases/parser/first_class_callable_syntax.php.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ExpressionStatement": {
"expression": {
"AssignmentExpression": {
"leftOperand": {
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 3
}
}
},
"operator": {
"kind": "EqualsToken",
"textLength": 1
},
"byRef": null,
"rightOperand": {
"CallExpression": {
"callableExpression": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 6
}
]
}
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"argumentExpressionList": {
"ArgumentExpressionList": {
"children": [
{
"ArgumentExpression": {
"name": null,
"colonToken": null,
"dotDotDotToken": {
"kind": "DotDotDotToken",
"textLength": 3
},
"expression": null
}
}
]
}
},
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
}
}
}
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}
2 changes: 2 additions & 0 deletions tests/cases/parser/first_class_callable_syntax_error.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
$a = intdiv(..., 3);
8 changes: 8 additions & 0 deletions tests/cases/parser/first_class_callable_syntax_error.php.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"kind": 0,
"message": "'Expression' expected.",
"start": 21,
"length": 0
}
]
Loading