Skip to content

Commit 8863f92

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. (cherry-picked from 0dd85eb)
1 parent 3182d12 commit 8863f92

File tree

11 files changed

+2099
-1937
lines changed

11 files changed

+2099
-1937
lines changed

grammar/php5.y

+11-1
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,13 @@ optional_ellipsis:
264264
| T_ELLIPSIS { $$ = true; }
265265
;
266266

267+
identifier_maybe_readonly:
268+
identifier { $$ = $1; }
269+
| T_READONLY { $$ = Node\Identifier[$1]; }
270+
;
271+
267272
function_declaration_statement:
268-
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
273+
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
269274
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
270275
;
271276

@@ -721,8 +726,13 @@ lexical_var:
721726
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
722727
;
723728

729+
name_readonly:
730+
T_READONLY { $$ = Name[$1]; }
731+
;
732+
724733
function_call:
725734
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
735+
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
726736
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
727737
{ $$ = Expr\StaticCall[$1, $3, $4]; }
728738
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list

grammar/php7.y

+16-3
Original file line numberDiff line numberDiff line change
@@ -350,15 +350,23 @@ block_or_error:
350350
| error { $$ = []; }
351351
;
352352

353+
identifier_maybe_readonly:
354+
identifier_not_reserved { $$ = $1; }
355+
| T_READONLY { $$ = Node\Identifier[$1]; }
356+
;
357+
353358
function_declaration_statement:
354-
T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
359+
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
355360
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
356-
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
361+
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
357362
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
358363
;
359364

360365
class_declaration_statement:
361-
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
366+
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
367+
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
368+
$this->checkClass($$, #2); }
369+
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
362370
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
363371
$this->checkClass($$, #3); }
364372
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
@@ -962,8 +970,13 @@ lexical_var:
962970
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
963971
;
964972

973+
name_readonly:
974+
T_READONLY { $$ = Name[$1]; }
975+
;
976+
965977
function_call:
966978
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
979+
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
967980
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
968981
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
969982
{ $$ = Expr\StaticCall[$1, $3, $4]; }

lib/PhpParser/Lexer/Emulative.php

+4-1
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;
@@ -24,6 +25,7 @@ class Emulative extends Lexer
2425
const PHP_7_4 = '7.4dev';
2526
const PHP_8_0 = '8.0dev';
2627
const PHP_8_1 = '8.1dev';
28+
const PHP_8_2 = '8.2dev';
2729

2830
/** @var mixed[] Patches used to reverse changes introduced in the code */
2931
private $patches = [];
@@ -41,7 +43,7 @@ class Emulative extends Lexer
4143
*/
4244
public function __construct(array $options = [])
4345
{
44-
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
46+
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
4547
unset($options['phpVersion']);
4648

4749
parent::__construct($options);
@@ -57,6 +59,7 @@ public function __construct(array $options = [])
5759
new EnumTokenEmulator(),
5860
new ReadonlyTokenEmulator(),
5961
new ExplicitOctalEmulator(),
62+
new ReadonlyFunctionTokenEmulator(),
6063
];
6164

6265
// 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\Lexer\Emulative;
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(): string {
24+
return Emulative::PHP_8_2;
25+
}
26+
27+
public function reverseEmulate(string $code, array $tokens): array {
28+
// Don't bother
29+
return $tokens;
30+
}
31+
}

0 commit comments

Comments
 (0)