Skip to content

Commit 0dd85eb

Browse files
committed
Support readonly before DNF type
This makes us match the PHP 8.2 handling of readonly. Handling of "readonly" functions is moved to the parser to allow distinguishing them from readonly properties with DNF types. We have to uglify the grammar to avoid some shift/reduce conflicts. Thank you WordPress.
1 parent 132690f commit 0dd85eb

File tree

8 files changed

+2122
-1993
lines changed

8 files changed

+2122
-1993
lines changed

grammar/php.y

+16-3
Original file line numberDiff line numberDiff line change
@@ -468,15 +468,23 @@ block_or_error:
468468
| error { $$ = []; }
469469
;
470470

471+
identifier_maybe_readonly:
472+
identifier_not_reserved { $$ = $1; }
473+
| T_READONLY { $$ = Node\Identifier[$1]; }
474+
;
475+
471476
function_declaration_statement:
472-
T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
477+
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
473478
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
474-
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
479+
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
475480
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
476481
;
477482

478483
class_declaration_statement:
479-
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
484+
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
485+
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
486+
$this->checkClass($$, #2); }
487+
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
480488
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
481489
$this->checkClass($$, #3); }
482490
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
@@ -1090,8 +1098,13 @@ lexical_var:
10901098
optional_ref plain_variable { $$ = Node\ClosureUse[$2, $1]; }
10911099
;
10921100

1101+
name_readonly:
1102+
T_READONLY { $$ = Name[$1]; }
1103+
;
1104+
10931105
function_call:
10941106
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
1107+
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
10951108
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
10961109
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
10971110
{ $$ = Expr\StaticCall[$1, $3, $4]; }

lib/PhpParser/Lexer/Emulative.php

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
1515
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
1616
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
17+
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
1718
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
1819
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
1920
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
@@ -58,6 +59,7 @@ public function __construct(array $options = []) {
5859
new EnumTokenEmulator(),
5960
new ReadonlyTokenEmulator(),
6061
new ExplicitOctalEmulator(),
62+
new ReadonlyFunctionTokenEmulator(),
6163
];
6264

6365
// Collect emulators that are relevant for the PHP version we're running
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PhpParser\Lexer\TokenEmulator;
4+
5+
use PhpParser\PhpVersion;
6+
7+
/*
8+
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
9+
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
10+
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
11+
* parser. This emulator only exists to handle this special case, which is skipped by the
12+
* PHP 8.1 ReadonlyTokenEmulator.
13+
*/
14+
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
15+
public function getKeywordString(): string {
16+
return 'readonly';
17+
}
18+
19+
public function getKeywordToken(): int {
20+
return \T_READONLY;
21+
}
22+
23+
public function getPhpVersion(): PhpVersion {
24+
return PhpVersion::fromComponents(8, 2);
25+
}
26+
27+
public function reverseEmulate(string $code, array $tokens): array {
28+
// Don't bother
29+
return $tokens;
30+
}
31+
}

lib/PhpParser/Parser/Php7.php

+1,000-982
Large diffs are not rendered by default.

lib/PhpParser/Parser/Php8.php

+1,002-1,006
Large diffs are not rendered by default.

test/PhpParser/Lexer/EmulativeTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,12 @@ public function provideTestLexNewFeatures() {
360360
]],
361361
['function readonly(', [
362362
[\T_FUNCTION, 'function'],
363-
[\T_STRING, 'readonly'],
363+
[\T_READONLY, 'readonly'],
364364
[ord('('), '('],
365365
]],
366366
['function readonly (', [
367367
[\T_FUNCTION, 'function'],
368-
[\T_STRING, 'readonly'],
368+
[\T_READONLY, 'readonly'],
369369
[ord('('), '('],
370370
]],
371371
];

test/code/parser/stmt/function/disjointNormalFormTypes.test

+37
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ DNF types
44

55
class Test {
66
public (A&B)|(X&Y) $prop;
7+
public readonly (A&B)|C $prop2;
78
}
89

910
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
@@ -65,6 +66,42 @@ array(
6566
)
6667
)
6768
)
69+
1: Stmt_Property(
70+
attrGroups: array(
71+
)
72+
flags: PUBLIC | READONLY (65)
73+
type: UnionType(
74+
types: array(
75+
0: IntersectionType(
76+
types: array(
77+
0: Name(
78+
parts: array(
79+
0: A
80+
)
81+
)
82+
1: Name(
83+
parts: array(
84+
0: B
85+
)
86+
)
87+
)
88+
)
89+
1: Name(
90+
parts: array(
91+
0: C
92+
)
93+
)
94+
)
95+
)
96+
props: array(
97+
0: PropertyItem(
98+
name: VarLikeIdentifier(
99+
name: prop2
100+
)
101+
default: null
102+
)
103+
)
104+
)
68105
)
69106
)
70107
1: Stmt_Function(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
readonly function
2+
-----
3+
<?php
4+
function readonly() {}
5+
readonly();
6+
-----
7+
array(
8+
0: Stmt_Function(
9+
attrGroups: array(
10+
)
11+
byRef: false
12+
name: Identifier(
13+
name: readonly
14+
)
15+
params: array(
16+
)
17+
returnType: null
18+
stmts: array(
19+
)
20+
)
21+
1: Stmt_Expression(
22+
expr: Expr_FuncCall(
23+
name: Name(
24+
parts: array(
25+
0: readonly
26+
)
27+
)
28+
args: array(
29+
)
30+
)
31+
)
32+
)

0 commit comments

Comments
 (0)