mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-04-24 16:02:05 +02:00
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 0dd85ebd34d7463d53ec92035105b04799a687e1)
This commit is contained in:
parent
3182d12b55
commit
8863f92b58
@ -264,8 +264,13 @@ optional_ellipsis:
|
||||
| T_ELLIPSIS { $$ = true; }
|
||||
;
|
||||
|
||||
identifier_maybe_readonly:
|
||||
identifier { $$ = $1; }
|
||||
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
||||
;
|
||||
|
||||
@ -721,8 +726,13 @@ lexical_var:
|
||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||
;
|
||||
|
||||
name_readonly:
|
||||
T_READONLY { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
|
||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
|
||||
|
@ -350,15 +350,23 @@ block_or_error:
|
||||
| error { $$ = []; }
|
||||
;
|
||||
|
||||
identifier_maybe_readonly:
|
||||
identifier_not_reserved { $$ = $1; }
|
||||
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
|
||||
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||
| attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error
|
||||
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
|
||||
$this->checkClass($$, #2); }
|
||||
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||
$this->checkClass($$, #3); }
|
||||
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
|
||||
@ -962,8 +970,13 @@ lexical_var:
|
||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||
;
|
||||
|
||||
name_readonly:
|
||||
T_READONLY { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
|
||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||
|
@ -14,6 +14,7 @@ use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
@ -24,6 +25,7 @@ class Emulative extends Lexer
|
||||
const PHP_7_4 = '7.4dev';
|
||||
const PHP_8_0 = '8.0dev';
|
||||
const PHP_8_1 = '8.1dev';
|
||||
const PHP_8_2 = '8.2dev';
|
||||
|
||||
/** @var mixed[] Patches used to reverse changes introduced in the code */
|
||||
private $patches = [];
|
||||
@ -41,7 +43,7 @@ class Emulative extends Lexer
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
|
||||
unset($options['phpVersion']);
|
||||
|
||||
parent::__construct($options);
|
||||
@ -57,6 +59,7 @@ class Emulative extends Lexer
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
new ReadonlyFunctionTokenEmulator(),
|
||||
];
|
||||
|
||||
// Collect emulators that are relevant for the PHP version we're running
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
/*
|
||||
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
|
||||
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
|
||||
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
|
||||
* parser. This emulator only exists to handle this special case, which is skipped by the
|
||||
* PHP 8.1 ReadonlyTokenEmulator.
|
||||
*/
|
||||
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
|
||||
public function getKeywordString(): string {
|
||||
return 'readonly';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int {
|
||||
return \T_READONLY;
|
||||
}
|
||||
|
||||
public function getPhpVersion(): string {
|
||||
return Emulative::PHP_8_2;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
// Don't bother
|
||||
return $tokens;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -360,12 +360,12 @@ class EmulativeTest extends LexerTest
|
||||
]],
|
||||
['function readonly(', [
|
||||
[Tokens::T_FUNCTION, 'function'],
|
||||
[Tokens::T_STRING, 'readonly'],
|
||||
[Tokens::T_READONLY, 'readonly'],
|
||||
[ord('('), '('],
|
||||
]],
|
||||
['function readonly (', [
|
||||
[Tokens::T_FUNCTION, 'function'],
|
||||
[Tokens::T_STRING, 'readonly'],
|
||||
[Tokens::T_READONLY, 'readonly'],
|
||||
[ord('('), '('],
|
||||
]],
|
||||
];
|
||||
|
@ -280,7 +280,7 @@ array(
|
||||
// Type in the partial parse could conceivably be any of 0, 16 or 32
|
||||
-----
|
||||
!!php5
|
||||
Syntax error, unexpected T_READONLY from 1:7 to 1:14
|
||||
Syntax error, unexpected T_CLASS, expecting '(' from 1:16 to 1:20
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
@ -366,4 +366,4 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ readonly class A {
|
||||
}
|
||||
-----
|
||||
!!php5
|
||||
Syntax error, unexpected T_READONLY from 3:1 to 3:8
|
||||
Syntax error, unexpected T_CLASS, expecting '(' from 3:10 to 3:14
|
||||
array(
|
||||
0: Stmt_Class(
|
||||
attrGroups: array(
|
||||
@ -65,4 +65,4 @@ array(
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ DNF types
|
||||
|
||||
class Test {
|
||||
public (A&B)|(X&Y) $prop;
|
||||
public readonly (A&B)|C $prop2;
|
||||
}
|
||||
|
||||
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
|
||||
@ -66,6 +67,42 @@ array(
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Stmt_Property(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: MODIFIER_PUBLIC | MODIFIER_READONLY (65)
|
||||
type: UnionType(
|
||||
types: array(
|
||||
0: IntersectionType(
|
||||
types: array(
|
||||
0: Name(
|
||||
parts: array(
|
||||
0: A
|
||||
)
|
||||
)
|
||||
1: Name(
|
||||
parts: array(
|
||||
0: B
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Name(
|
||||
parts: array(
|
||||
0: C
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
props: array(
|
||||
0: Stmt_PropertyProperty(
|
||||
name: VarLikeIdentifier(
|
||||
name: prop2
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Stmt_Function(
|
||||
|
32
test/code/parser/stmt/function/readonlyFunction.test
Normal file
32
test/code/parser/stmt/function/readonlyFunction.test
Normal file
@ -0,0 +1,32 @@
|
||||
readonly function
|
||||
-----
|
||||
<?php
|
||||
function readonly() {}
|
||||
readonly();
|
||||
-----
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: readonly
|
||||
)
|
||||
params: array(
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
1: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: readonly
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user