Skip to content

Commit 6a96561

Browse files
authored
Merge pull request microsoft#357 from TysonAndre/readonly-properties
Support php 8.1 readonly property modifier RFC
2 parents 03f8cab + 78a7744 commit 6a96561

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+611
-123
lines changed

src/Node/Expression/ArgumentExpression.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ArgumentExpression extends Expression {
1919
/** @var Token|null */
2020
public $dotDotDotToken;
2121

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

2525
const CHILD_NAMES = [

src/Node/Parameter.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class Parameter extends Node {
1515
public $attributes;
1616
/** @var Token|null */
1717
public $visibilityToken;
18+
/** @var Token[]|null */
19+
public $modifiers;
1820
/** @var Token|null */
1921
public $questionToken;
2022
/** @var DelimitedList\QualifiedNameList|MissingToken|null */
@@ -33,6 +35,7 @@ class Parameter extends Node {
3335
const CHILD_NAMES = [
3436
'attributes',
3537
'visibilityToken',
38+
'modifiers',
3639
'questionToken',
3740
'typeDeclarationList',
3841
'byRefToken',

src/Parser.php

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -825,12 +825,17 @@ private function parseParameterFn() {
825825
if ($this->token->kind === TokenKind::AttributeToken) {
826826
$parameter->attributes = $this->parseAttributeGroups($parameter);
827827
}
828+
// Note that parameter modifiers are allowed to be repeated by the parser in php 8.1 (it is a compiler error)
829+
//
830+
// TODO: Remove the visibilityToken in a future backwards incompatible release
828831
$parameter->visibilityToken = $this->eatOptional([TokenKind::PublicKeyword, TokenKind::ProtectedKeyword, TokenKind::PrivateKeyword]);
832+
$parameter->modifiers = $this->parseParameterModifiers() ?: null;
833+
829834
$parameter->questionToken = $this->eatOptional1(TokenKind::QuestionToken);
830835
$parameter->typeDeclarationList = $this->tryParseParameterTypeDeclarationList($parameter);
831836
if ($parameter->typeDeclarationList) {
832837
$children = $parameter->typeDeclarationList->children;
833-
if (end($children) instanceof MissingToken && ($children[count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
838+
if (end($children) instanceof MissingToken && ($children[\count($children) - 2]->kind ?? null) === TokenKind::AmpersandToken) {
834839
array_pop($parameter->typeDeclarationList->children);
835840
$parameter->byRefToken = array_pop($parameter->typeDeclarationList->children);
836841
if (!$parameter->typeDeclarationList->children) {
@@ -964,6 +969,9 @@ private function isClassMemberDeclarationStart(Token $token) {
964969
// static-modifier
965970
case TokenKind::StaticKeyword:
966971

972+
// readonly-modifier
973+
case TokenKind::ReadonlyKeyword:
974+
967975
// class-modifier
968976
case TokenKind::AbstractKeyword:
969977
case TokenKind::FinalKeyword:
@@ -1443,7 +1451,7 @@ private function parseReservedWordExpression($parentNode) {
14431451
return $reservedWord;
14441452
}
14451453

1446-
private function isModifier($token) {
1454+
private function isModifier($token): bool {
14471455
switch ($token->kind) {
14481456
// class-modifier
14491457
case TokenKind::AbstractKeyword:
@@ -1457,14 +1465,45 @@ private function isModifier($token) {
14571465
// static-modifier
14581466
case TokenKind::StaticKeyword:
14591467

1468+
// readonly-modifier
1469+
case TokenKind::ReadonlyKeyword:
1470+
14601471
// var
14611472
case TokenKind::VarKeyword:
14621473
return true;
14631474
}
14641475
return false;
14651476
}
14661477

1467-
private function parseModifiers() {
1478+
private function isParameterModifier($token): bool {
1479+
switch ($token->kind) {
1480+
// visibility-modifier
1481+
case TokenKind::PublicKeyword:
1482+
case TokenKind::ProtectedKeyword:
1483+
case TokenKind::PrivateKeyword:
1484+
1485+
// readonly-modifier
1486+
case TokenKind::ReadonlyKeyword:
1487+
1488+
return true;
1489+
}
1490+
return false;
1491+
}
1492+
1493+
/** @return Token[] */
1494+
private function parseParameterModifiers(): array {
1495+
$modifiers = [];
1496+
$token = $this->getCurrentToken();
1497+
while ($this->isParameterModifier($token)) {
1498+
$modifiers[] = $token;
1499+
$this->advanceToken();
1500+
$token = $this->getCurrentToken();
1501+
}
1502+
return $modifiers;
1503+
}
1504+
1505+
/** @return Token[] */
1506+
private function parseModifiers(): array {
14681507
$modifiers = [];
14691508
$token = $this->getCurrentToken();
14701509
while ($this->isModifier($token)) {
@@ -3101,14 +3140,27 @@ private function parseObjectCreationExpression($parentNode) {
31013140
return $objectCreationExpression;
31023141
}
31033142

3143+
/**
3144+
* @return DelimitedList\ArgumentExpressionList|null
3145+
*/
31043146
private function parseArgumentExpressionList($parentNode) {
3105-
return $this->parseDelimitedList(
3147+
$list = $this->parseDelimitedList(
31063148
DelimitedList\ArgumentExpressionList::class,
31073149
TokenKind::CommaToken,
31083150
$this->isArgumentExpressionStartFn(),
31093151
$this->parseArgumentExpressionFn(),
31103152
$parentNode
31113153
);
3154+
$children = $list->children ?? null;
3155+
if (is_array($children) && \count($children) === 1) {
3156+
$arg = $children[0];
3157+
if ($arg instanceof ArgumentExpression) {
3158+
if ($arg->dotDotDotToken && $arg->expression instanceof MissingToken && !$arg->colonToken && !$arg->name) {
3159+
$arg->expression = null;
3160+
}
3161+
}
3162+
}
3163+
return $list;
31123164
}
31133165

31143166
/**
@@ -3283,6 +3335,9 @@ private function isInterfaceMemberDeclarationStart(Token $token) {
32833335
// static-modifier
32843336
case TokenKind::StaticKeyword:
32853337

3338+
// readonly-modifier
3339+
case TokenKind::ReadonlyKeyword:
3340+
32863341
// class-modifier
32873342
case TokenKind::AbstractKeyword:
32883343
case TokenKind::FinalKeyword:
@@ -3457,6 +3512,7 @@ private function isTraitMemberDeclarationStart($token) {
34573512
case TokenKind::StaticKeyword:
34583513
case TokenKind::AbstractKeyword:
34593514
case TokenKind::FinalKeyword:
3515+
case TokenKind::ReadonlyKeyword:
34603516

34613517
// method-declaration
34623518
case TokenKind::FunctionKeyword:
@@ -3537,7 +3593,6 @@ private function isEnumMemberDeclarationStart($token) {
35373593
case TokenKind::PublicKeyword:
35383594
case TokenKind::ProtectedKeyword:
35393595
case TokenKind::PrivateKeyword:
3540-
// case TokenKind::VarKeyword:
35413596
case TokenKind::StaticKeyword:
35423597
case TokenKind::AbstractKeyword:
35433598
case TokenKind::FinalKeyword:

src/PhpTokenizer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
define(__NAMESPACE__ . '\T_ENUM', defined('T_ENUM') ? constant('T_ENUM') : 'T_ENUM');
1919
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');
2020
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');
21+
define(__NAMESPACE__ . '\T_READONLY', defined('T_READONLY') ? constant('T_READONLY') : 'T_READONLY');
2122

2223
/**
2324
* Tokenizes content using PHP's built-in `token_get_all`, and converts to "lightweight" Token representation.
@@ -290,6 +291,7 @@ protected static function tokenGetAll(string $content, $parseContext): array
290291
T_PRIVATE => TokenKind::PrivateKeyword,
291292
T_PROTECTED => TokenKind::ProtectedKeyword,
292293
T_PUBLIC => TokenKind::PublicKeyword,
294+
T_READONLY => TokenKind::ReadonlyKeyword,
293295
T_REQUIRE => TokenKind::RequireKeyword,
294296
T_REQUIRE_ONCE => TokenKind::RequireOnceKeyword,
295297
T_RETURN => TokenKind::ReturnKeyword,

src/TokenKind.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class TokenKind {
9090
/** @deprecated use IterableReservedWord */
9191
const IterableKeyword = self::IterableReservedWord;
9292
const EnumKeyword = 171;
93+
const ReadonlyKeyword = 172;
9394

9495
const OpenBracketToken = 201;
9596
const CloseBracketToken = 202;

src/TokenStringMaps.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class TokenStringMaps {
6464
"private" => TokenKind::PrivateKeyword,
6565
"protected" => TokenKind::ProtectedKeyword,
6666
"public" => TokenKind::PublicKeyword,
67+
"readonly" => TokenKind::ReadonlyKeyword,
6768
"require" => TokenKind::RequireKeyword,
6869
"require_once" => TokenKind::RequireOnceKeyword,
6970
"return" => TokenKind::ReturnKeyword,

tests/cases/parser/classMethods3.php.tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"Parameter": {
6262
"attributes": null,
6363
"visibilityToken": null,
64+
"modifiers": null,
6465
"questionToken": null,
6566
"typeDeclarationList": null,
6667
"byRefToken": null,
@@ -81,6 +82,7 @@
8182
"Parameter": {
8283
"attributes": null,
8384
"visibilityToken": null,
85+
"modifiers": null,
8486
"questionToken": null,
8587
"typeDeclarationList": null,
8688
"byRefToken": null,

tests/cases/parser/classMethods4.php.tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"Parameter": {
6262
"attributes": null,
6363
"visibilityToken": null,
64+
"modifiers": null,
6465
"questionToken": null,
6566
"typeDeclarationList": null,
6667
"byRefToken": null,
@@ -81,6 +82,7 @@
8182
"Parameter": {
8283
"attributes": null,
8384
"visibilityToken": null,
85+
"modifiers": null,
8486
"questionToken": null,
8587
"typeDeclarationList": null,
8688
"byRefToken": null,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
$sl = strlen(...);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)