Skip to content

Commit

Permalink
Support readonly before DNF type
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nikic committed Sep 18, 2022
1 parent 132690f commit 0dd85eb
Show file tree
Hide file tree
Showing 8 changed files with 2,122 additions and 1,993 deletions.
19 changes: 16 additions & 3 deletions grammar/php.y
Original file line number Diff line number Diff line change
Expand Up @@ -468,15 +468,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 '}'
Expand Down Expand Up @@ -1090,8 +1098,13 @@ lexical_var:
optional_ref plain_variable { $$ = Node\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]; }
Expand Down
2 changes: 2 additions & 0 deletions lib/PhpParser/Lexer/Emulative.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function __construct(array $options = []) {
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
];

// Collect emulators that are relevant for the PHP version we're running
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);

namespace PhpParser\Lexer\TokenEmulator;

use PhpParser\PhpVersion;

/*
* 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(): PhpVersion {
return PhpVersion::fromComponents(8, 2);
}

public function reverseEmulate(string $code, array $tokens): array {
// Don't bother
return $tokens;
}
}
1,982 changes: 1,000 additions & 982 deletions lib/PhpParser/Parser/Php7.php

Large diffs are not rendered by default.

2,008 changes: 1,002 additions & 1,006 deletions lib/PhpParser/Parser/Php8.php

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions test/PhpParser/Lexer/EmulativeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,12 @@ public function provideTestLexNewFeatures() {
]],
['function readonly(', [
[\T_FUNCTION, 'function'],
[\T_STRING, 'readonly'],
[\T_READONLY, 'readonly'],
[ord('('), '('],
]],
['function readonly (', [
[\T_FUNCTION, 'function'],
[\T_STRING, 'readonly'],
[\T_READONLY, 'readonly'],
[ord('('), '('],
]],
];
Expand Down
37 changes: 37 additions & 0 deletions test/code/parser/stmt/function/disjointNormalFormTypes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down Expand Up @@ -65,6 +66,42 @@ array(
)
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC | 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: PropertyItem(
name: VarLikeIdentifier(
name: prop2
)
default: null
)
)
)
)
)
1: Stmt_Function(
Expand Down
32 changes: 32 additions & 0 deletions test/code/parser/stmt/function/readonlyFunction.test
Original file line number Diff line number Diff line change
@@ -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(
)
)
)
)

0 comments on commit 0dd85eb

Please sign in to comment.