1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-26 17:00:48 +02:00

Compare commits

..

51 Commits

Author SHA1 Message Date
Nikita Popov
447a020a1f Release PHP-Parser 5.4.0 2024-12-30 12:07:19 +01:00
Markus Staab
45f70ed80a Simplify NodeTraverser->traverseNode() 2024-12-30 11:10:07 +01:00
Markus Staab
74a361814d Simplify NodeTraverser->traverseArray() 2024-12-30 11:10:07 +01:00
Nikita Popov
6478c5ac53 Fix PropertyHook::getStmts() for set hook
Produce the correct desugaring if the propertyName attribute is
set, and set it in the parser. Otherwise throw an exception.

Fixes .
2024-12-27 16:56:00 +01:00
Nikita Popov
8bb415902e Fix cs 2024-12-27 16:33:38 +01:00
Nikita Popov
48fd76e542 Make PropertyHook::getStmts() less incorrect
Still missing the assignment to the correct property, but at
least we're not returning from a non-void function now...
2024-12-27 16:30:03 +01:00
Ondrej Mirtes
d20a197ca7 Emit error - Multiple properties cannot share the same hooks
Closes GH-1052.
2024-12-27 16:19:46 +01:00
Ondrej Mirtes
62dee28027 Rename ParserAbstract method checkPropertyHookList to checkEmptyPropertyHookList 2024-12-27 16:13:47 +01:00
Nikita Popov
b396e9e0d6 Add missing newline after usage message 2024-12-27 16:13:29 +01:00
Ondrej Mirtes
f212bb7afb Add PropertyHook::isFinal() helper method with tests 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
f43324a074 Missing flags subNode description in PropertyHook constructor PHPDoc 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
73b160f8c4 Add flags helper methods Property::isAbstract() and Property::isFinal() 2024-12-13 23:15:12 +01:00
Ondrej Mirtes
4f9dc8b0f5 Promoted properties with hooks do not need visibility modifier 2024-12-11 10:17:55 +01:00
Nathanael Esayeas
469377f4a8 Fix classmap authoritative autoloading ()
This patch resolves  by adding dummy class definitions
behind if (false) checks that are picked up the authoritative
autoloader.
2024-12-06 22:00:17 +01:00
Jorg Sowa
74d3f7fc24 Removed reudndant token generation 2024-11-24 09:54:02 +01:00
Jorg Sowa
25828ea952 Updated target.php to new PHP-Parser version 2024-11-24 09:54:02 +01:00
Jorg Sowa
05ed79595b Removed unrecheable statement 2024-11-19 21:17:27 +01:00
Markus Staab
954f7a411f More precise toLowerString() return type 2024-11-05 21:19:09 +01:00
Nikita Popov
8eea230464 Release PHP-Parser 5.3.1 2024-10-08 20:51:32 +02:00
Nikita Popov
9c7a3f8d8f Support declaring functions with name exit/die
For use in stubs.

Fixes .
2024-10-08 20:46:50 +02:00
Markus Staab
26573ea64f Makefile works on windows 2024-10-07 20:54:36 +02:00
Nikita Popov
3abf7425cd Release PHP-Parser 5.3.0 2024-09-29 15:56:26 +02:00
Nikita Popov
961f158f6d Avoid negative indendation in formatting-preserving printer
Fixes .
2024-09-29 15:34:59 +02:00
Nikita Popov
e50c67b7a9 Add basic support for tab indentation
Add a new "indent" option for the pretty printer, which can be
use to control the indentation width, or switch it to use tabs.

Tab width is currenlty hardcoded to 4, but also shouldn't matter
much.

Possibly the formatting-preserving printer should auto-detect
the indentation in the future.
2024-09-21 20:58:10 +02:00
Nikita Popov
26a0197186 Use PHP 8.4 for the PHP 7.4 integration test 2024-09-21 16:03:21 +02:00
Nikita Popov
aedfcc23cd Include trailing semicolon in GroupUse
Fixes https://github.com/nikic/PHP-Parser/issues/1026.
2024-09-21 15:55:21 +02:00
Nikita Popov
8d09ba87f4 Support PropertyHooks in NameResolver 2024-09-17 21:33:51 +02:00
Nikita Popov
23c79fbbfb Release PHP-Parser 5.2.0 2024-09-15 18:40:33 +02:00
Nikita Popov
b9c8374498 Run PHP 8.4 integration tests 2024-09-15 18:32:49 +02:00
Nikita Popov
d0826bd3e4 Add changelog entries 2024-09-15 18:28:36 +02:00
Nikita Popov
5ea6c2938b Exclude integration test 2024-09-05 21:41:03 +02:00
Nikita Popov
b493c51cce [8.4] Add support for asymmetric visibility modifiers
Represented using new PRIVATE_SET, PROTECTED_SET and PUBLIC_SET
bits in Modifiers.

RFC: https://wiki.php.net/rfc/asymmetric-visibility-v2
2024-09-05 21:38:16 +02:00
Nikita Popov
54139ca49b Disable fail-fast
PHP 8.4 currently fails because the version is installed version
is too old. Avoid cancelling other CI jobs because of that.
2024-09-01 12:48:37 +02:00
Nikita Popov
cde9bab3bb Add support for PHP 8.4 exit function
For backwards-compatibility, parse the simple single argument
case into an Exit_ node as previously. For more complex
expressions generate a function call.
2024-09-01 12:41:27 +02:00
Nikita Popov
018da15f3a Add token emulation support for asymmetric visibility modifiers 2024-08-31 20:41:07 +02:00
evenevent
ba14437165 chore: fix comment
Signed-off-by: evenevent <digping@icloud.com>
2024-08-15 09:22:56 +02:00
Nikita Popov
4a22c15169 Validate options in test_old
Regularly try to use this one with --version instead of
--php-version, which fails in a non-obvious way.
2024-08-11 17:08:58 +02:00
Nikita Popov
514f71024e Fix for new php-cs-fixer version 2024-08-11 17:03:56 +02:00
Ruud Kamphuis
6a97061265 Normalize enum value to ClassConstFetch
Fixes 

(cherry picked from commit 8a21ec3182)
2024-08-11 16:58:28 +02:00
Nikita Popov
e3f223f623 Fix expected json dump for old PHP versions 2024-07-28 19:05:16 +02:00
Nikita Popov
03caf4cc99 [8.4] Add support for property hooks
Add hooks subnode to Stmt\Property and Param, which contains an
array of PropertyHook.

The property hook support is considered experimental and subject
to change.

RFC: https://wiki.php.net/rfc/property-hooks
2024-07-28 19:02:18 +02:00
Nikita Popov
b11fc12cce Use ParserFactory in integration test
So we actually use the PHP 8 parser on new versions.
2024-07-28 18:11:46 +02:00
Nikita Popov
3beee6ec77 Add Modifiers::toString() helper
Convert an integer modifier into a string.
2024-07-28 17:09:14 +02:00
Nikita Popov
58de479119 Remove error for abstract/final properties
These are allowed since PHP 8.4, with the introduction of property
hooks. (Abstract properties require hooks, which is not validated
here.)
2024-07-21 18:59:18 +02:00
Nikita Popov
d3ae2ed679 Respect version mode line in pretty printer tests
By default parser uses newest supported and pretty printer uses
its default version. If version is specified, it's used for both
parser and printer. Additionally, parserVersion can be used to
specify a different version for parser and printer.
2024-07-21 18:23:53 +02:00
Nikita Popov
beba9c528f Remove support for alternative array syntax in PHP 8 parser
We cannot support both this syntax and property hooks. Drop
support for the alternative syntax in the PHP 8 parser. The
PHP 7 parser still supports it.
2024-07-19 22:29:35 +02:00
Nikita Popov
fadccead52 Avoid compatibilty token list in parser
Create the mapping using the entries in symbolToName, so we don't
need to repeat the compatibility tokens in another place.
2024-07-14 20:53:13 +02:00
Nikita Popov
d8235a2701 [8.4] Add support for __PROPERTY__ magic constant
Part of property hooks RFC.
2024-07-14 18:52:47 +02:00
Amirreza Nasiri
5973c30a46 Update JSON_representation.markdown
Fix the invalid JSON structure
2024-07-13 15:46:21 +02:00
Nikita Popov
b917ba7b9c Add PHP 8.4 to CI matrix
Not using it in the integration tests yet.
2024-07-13 11:11:15 +02:00
Nikita Popov
caf540443a Declare PHP 8.4 as the newest supported version
As the comment indicates, this includes partially supported
versions. PHP-Parser includes support for all parts of PHP 8.4
that have landed in php-src.
2024-07-13 11:10:30 +02:00
115 changed files with 6588 additions and 3438 deletions
.github/workflows
CHANGELOG.mdMakefileREADME.md
doc
grammar
lib/PhpParser
phpstan-baseline.neon
test
test_old
tools/fuzzing

@@ -38,6 +38,8 @@ jobs:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
fail-fast: false
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
@@ -54,7 +56,7 @@ jobs:
run: "php vendor/bin/phpunit"
test_old_73_80:
runs-on: "ubuntu-latest"
name: "PHP 7.4 Code on PHP 8.3 Integration Tests"
name: "PHP 7.4 Code on PHP 8.4 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
@@ -62,7 +64,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.3"
php-version: "8.4"
ini-file: "development"
tools: composer:v2
- name: "Install PHP 8 dependencies"
@@ -71,7 +73,7 @@ jobs:
run: "test_old/run-php-src.sh 7.4.33"
test_old_80_70:
runs-on: "ubuntu-latest"
name: "PHP 8.3 Code on PHP 7.4 Integration Tests"
name: "PHP 8.4 Code on PHP 7.4 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
@@ -85,7 +87,7 @@ jobs:
- name: "Install PHP 8 dependencies"
run: "COMPOSER_ROOT_VERSION=dev-master composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 8.3.0RC2"
run: "test_old/run-php-src.sh 8.4.0beta5"
phpstan:
runs-on: "ubuntu-latest"
name: "PHPStan"

@@ -1,3 +1,67 @@
Version 5.4.0 (2024-12-30)
--------------------------
### Added
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
* Added `PropertyHook::isFinal()` method.
* Emit an error if property hook is used on declaration with multiple properties.
### Fixed
* Make legacy class aliases compatible with classmap-authoritative autoloader.
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
hooks but no explicit visibility modifier.
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
on the hook to be set, which is now also set by the parser. If the attribute is not set,
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
desugaring.
Version 5.3.1 (2024-10-08)
--------------------------
### Added
* Added support for declaring functions with name `exit` or `die`, to allow their use in stubs.
Version 5.3.0 (2024-09-29)
--------------------------
### Added
* Added `indent` option to pretty printer, which can be used to specify the indentation to use
(defaulting to four spaces). This also allows using tab indentation.
### Fixed
* Resolve names in `PropertyHook`s in the `NameResolver`.
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
`Stmt\Use_` nodes.
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
in `ValueError`s.
Version 5.2.0 (2024-09-15)
--------------------------
### Added
* [8.4] Added support for `__PROPERTY__` magic constant, represented using a
`Node\Scalar\MagicConst\Property` node.
* [8.4] Added support for property hooks, which are represented using a new `hooks` subnode on
`Node\Stmt\Property` and `Node\Param`, which contains an array of `Node\PropertyHook`.
* [8.4] Added support for asymmetric visibility modifiers. Property `flags` can now hold the
additional bits `Modifiers::PUBLIC_SET`, `Modifiers::PROTECTED_SET` and `Modifiers::PRIVATE_SET`.
* [8.4] Added support for generalized exit function. For backwards compatibility, exit without
argument or a single plain argument continues to use a `Node\Expr\Exit_` node. Otherwise (e.g.
if a named argument is used) it will be represented as a plain `Node\Expr\FuncCall`.
* Added support for passing enum values to various builder methods, like `BuilderFactory::val()`.
### Removed
* Removed support for alternative array syntax `$array{0}` from the PHP 8 parser. It is still
supported by the PHP 7 parser. This is necessary in order to support property hooks.
Version 5.1.0 (2024-07-01)
--------------------------

@@ -4,7 +4,7 @@ tools/vendor:
composer install -d tools
phpstan: tools/vendor
tools/vendor/bin/phpstan
php tools/vendor/bin/phpstan
php-cs-fixer: tools/vendor
tools/vendor/bin/php-cs-fixer fix
php tools/vendor/bin/php-cs-fixer fix

@@ -6,7 +6,7 @@ PHP Parser
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x).
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).

@@ -43,7 +43,7 @@ following caveats:
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
This allows to parse PHP 8.3 source code running on PHP 7.4, for example. This emulation is not
This allows to parse PHP 8.4 source code running on PHP 7.4, for example. This emulation is not
perfect, but works well in practice.
Finally, it should be noted that the parser aims to accept all valid code, not reject all invalid

@@ -104,7 +104,7 @@ This will result in the following output (which includes attributes):
"nodeType": "Scalar_String",
"attributes": {
"startLine": 5,
"endLine": 5
"endLine": 5,
"kind": 2,
"rawValue": "\"\\n\""
},

@@ -37,6 +37,7 @@ integer should be printed as decimal, hexadecimal, etc). Additionally, it suppor
* `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
versions.
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
a legacy option, and `phpVersion` should be used to control this behavior instead.

@@ -81,7 +81,10 @@
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_PUBLIC_SET
%token T_PROTECTED_SET
%token T_PRIVATE_SET
%token T_VAR
%token T_UNSET
%token T_ISSET
@@ -103,6 +106,7 @@
%token T_TRAIT_C
%token T_METHOD_C
%token T_FUNC_C
%token T_PROPERTY_C
%token T_LINE
%token T_FILE
%token T_START_HEREDOC
@@ -252,7 +256,7 @@ top_statement:
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi
| group_use_declaration
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
;
@@ -262,9 +266,9 @@ use_type:
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
@@ -460,15 +464,16 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
fn_identifier:
identifier_not_reserved
| T_READONLY { $$ = Node\Identifier[$1]; }
| T_EXIT { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref fn_identifier '(' 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_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref fn_identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
@@ -671,18 +676,23 @@ property_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
| T_READONLY { $$ = Modifiers::READONLY; }
;
parameter:
optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
$this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
@@ -828,8 +838,14 @@ class_statement_list:
class_statement:
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
$this->checkProperty($$, #2); }
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1); }
#if PHP8
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
$this->checkPropertyHooksForMultiProperty($$, #5);
$this->checkEmptyPropertyHookList($6, #5);
$this->addPropertyNameToHooks($$); }
#endif
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
@@ -901,6 +917,9 @@ member_modifier:
T_PUBLIC { $$ = Modifiers::PUBLIC; }
| T_PROTECTED { $$ = Modifiers::PROTECTED; }
| T_PRIVATE { $$ = Modifiers::PRIVATE; }
| T_PUBLIC_SET { $$ = Modifiers::PUBLIC_SET; }
| T_PROTECTED_SET { $$ = Modifiers::PROTECTED_SET; }
| T_PRIVATE_SET { $$ = Modifiers::PRIVATE_SET; }
| T_STATIC { $$ = Modifiers::STATIC; }
| T_ABSTRACT { $$ = Modifiers::ABSTRACT; }
| T_FINAL { $$ = Modifiers::FINAL; }
@@ -926,6 +945,39 @@ property_declaration:
| property_decl_name '=' expr { $$ = Node\PropertyItem[$1, $3]; }
;
property_hook_list:
/* empty */ { $$ = []; }
| property_hook_list property_hook { push($1, $2); }
;
optional_property_hook_list:
/* empty */ { $$ = []; }
#if PHP8
| '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
#endif
;
property_hook:
optional_attributes property_hook_modifiers optional_ref identifier_not_reserved property_hook_body
{ $$ = Node\PropertyHook[$4, $5, ['flags' => $2, 'byRef' => $3, 'params' => [], 'attrGroups' => $1]];
$this->checkPropertyHook($$, null); }
| optional_attributes property_hook_modifiers optional_ref identifier_not_reserved '(' parameter_list ')' property_hook_body
{ $$ = Node\PropertyHook[$4, $8, ['flags' => $2, 'byRef' => $3, 'params' => $6, 'attrGroups' => $1]];
$this->checkPropertyHook($$, #5); }
;
property_hook_body:
';' { $$ = null; }
| '{' inner_statement_list '}' { $$ = $2; }
| T_DOUBLE_ARROW expr ';' { $$ = $2; }
;
property_hook_modifiers:
/* empty */ { $$ = 0; }
| property_hook_modifiers member_modifier
{ $this->checkPropertyHookModifiers($1, $2, #2); $$ = $1 | $2; }
;
expr_list_forbid_comma:
non_empty_expr_list no_comma
;
@@ -1030,10 +1082,8 @@ expr:
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
| T_EXIT exit_expr
{ $attrs = attributes();
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$$ = new Expr\Exit_($2, $attrs); }
| T_EXIT ctor_arguments
{ $$ = $this->createExitExpr($1, #1, $2, attributes()); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
@@ -1138,11 +1188,6 @@ class_name_or_var:
| fully_dereferenceable
;
exit_expr:
/* empty */ { $$ = null; }
| '(' optional_expr ')' { $$ = $2; }
;
backticks_expr:
/* empty */ { $$ = array(); }
| encaps_string_part
@@ -1165,6 +1210,7 @@ constant:
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| T_PROPERTY_C { $$ = Scalar\MagicConst\Property[]; }
;
class_constant:
@@ -1240,7 +1286,9 @@ callable_expr:
callable_variable:
simple_variable
| array_object_dereferenceable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#if PHP7
| array_object_dereferenceable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| function_call
| array_object_dereferenceable T_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
@@ -1282,7 +1330,9 @@ static_member:
new_variable:
simple_variable
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#if PHP7
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
#endif
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name

@@ -28,7 +28,7 @@ class ClassConst implements PhpParser\Builder {
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*/
public function __construct($name, $value) {
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
@@ -38,7 +38,7 @@ class ClassConst implements PhpParser\Builder {
* Add another constant to const group
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*
* @return $this The builder instance (for fluid interface)
*/

@@ -13,7 +13,6 @@ use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder {
/** @var Identifier|string */
protected $name;
/** @var ?Node\Expr */
protected ?Node\Expr $value = null;
/** @var array<string, mixed> */
protected array $attributes = [];

@@ -122,6 +122,28 @@ class Param implements PhpParser\Builder {
return $this;
}
/**
* Gives the promoted property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the promoted property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Adds an attribute group.
*

@@ -23,6 +23,8 @@ class Property implements PhpParser\Builder {
protected ?Node $type = null;
/** @var list<Node\AttributeGroup> */
protected array $attributeGroups = [];
/** @var list<Node\PropertyHook> */
protected array $hooks = [];
/**
* Creates a property builder.
@@ -88,6 +90,50 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Makes the property abstract. Requires at least one property hook to be specified as well.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
return $this;
}
/**
* Makes the property final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
return $this;
}
/**
* Gives the property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Sets default value for the property.
*
@@ -142,12 +188,27 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Adds a property hook.
*
* @return $this The builder instance (for fluid interface)
*/
public function addHook(Node\PropertyHook $hook) {
$this->hooks[] = $hook;
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Property The built property node
*/
public function getNode(): PhpParser\Node {
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
throw new PhpParser\Error('Only hooked properties may be declared abstract');
}
return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
[
@@ -155,7 +216,8 @@ class Property implements PhpParser\Builder {
],
$this->attributes,
$this->type,
$this->attributeGroups
$this->attributeGroups,
$this->hooks
);
}
}

@@ -210,7 +210,7 @@ class BuilderFactory {
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
*/
public function val($value): Expr {
return BuilderHelpers::normalizeValue($value);

@@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
@@ -214,7 +215,7 @@ final class BuilderHelpers {
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
*
* @return Expr The normalized value
*/
@@ -268,6 +269,10 @@ final class BuilderHelpers {
return new Expr\Array_($items);
}
if ($value instanceof \UnitEnum) {
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
}
throw new \LogicException('Invalid value');
}

@@ -20,9 +20,9 @@ class TokenStream {
*
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
*/
public function __construct(array $tokens) {
public function __construct(array $tokens, int $tabWidth) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
$this->indentMap = $this->calcIndentMap($tabWidth);
}
/**
@@ -248,7 +248,7 @@ class TokenStream {
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap(): array {
private function calcIndentMap(int $tabWidth): array {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $i => $token) {
@@ -258,11 +258,11 @@ class TokenStream {
$content = $token->text;
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
// Special case: Newline at the end of opening tag followed by whitespace.
$indent = \strlen($content);
$indent = $this->getIndent($content, $tabWidth);
}
}
}
@@ -272,4 +272,11 @@ class TokenStream {
return $indentMap;
}
private function getIndent(string $ws, int $tabWidth): int {
$spaces = \substr_count($ws, " ");
$tabs = \substr_count($ws, "\t");
assert(\strlen($ws) === $spaces + $tabs);
return $spaces + $tabs * $tabWidth;
}
}

@@ -5,15 +5,13 @@ namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
@@ -47,6 +45,8 @@ class Emulative extends Lexer {
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
new PropertyTokenEmulator(),
new AsymmetricVisibilityTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

@@ -0,0 +1,93 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\PhpVersion;
use PhpParser\Token;
final class AsymmetricVisibilityTokenEmulator extends TokenEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 4);
}
public function isEmulationNeeded(string $code): bool {
$code = strtolower($code);
return strpos($code, 'public(set)') !== false ||
strpos($code, 'protected(set)') !== false ||
strpos($code, 'private(set)') !== false;
}
public function emulate(string $code, array $tokens): array {
$map = [
\T_PUBLIC => \T_PUBLIC_SET,
\T_PROTECTED => \T_PROTECTED_SET,
\T_PRIVATE => \T_PRIVATE_SET,
];
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if (isset($map[$token->id]) && $i + 3 < $c && $tokens[$i + 1]->text === '(' &&
$tokens[$i + 2]->id === \T_STRING && \strtolower($tokens[$i + 2]->text) === 'set' &&
$tokens[$i + 3]->text === ')' &&
$this->isKeywordContext($tokens, $i)
) {
array_splice($tokens, $i, 4, [
new Token(
$map[$token->id], $token->text . '(' . $tokens[$i + 2]->text . ')',
$token->line, $token->pos),
]);
$c -= 3;
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array {
$reverseMap = [
\T_PUBLIC_SET => \T_PUBLIC,
\T_PROTECTED_SET => \T_PROTECTED,
\T_PRIVATE_SET => \T_PRIVATE,
];
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
if (isset($reverseMap[$token->id]) &&
\preg_match('/(public|protected|private)\((set)\)/i', $token->text, $matches)
) {
[, $modifier, $set] = $matches;
$modifierLen = \strlen($modifier);
array_splice($tokens, $i, 1, [
new Token($reverseMap[$token->id], $modifier, $token->line, $token->pos),
new Token(\ord('('), '(', $token->line, $token->pos + $modifierLen),
new Token(\T_STRING, $set, $token->line, $token->pos + $modifierLen + 1),
new Token(\ord(')'), ')', $token->line, $token->pos + $modifierLen + 4),
]);
$i += 3;
$c += 3;
}
}
return $tokens;
}
/** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool {
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
if ($prevToken === null) {
return false;
}
return $prevToken->id !== \T_OBJECT_OPERATOR
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
}
/** @param Token[] $tokens */
private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i]->id === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
}

@@ -14,8 +14,12 @@ abstract class KeywordEmulator extends TokenEmulator {
/** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
if ($prevToken === null) {
return false;
}
return $prevToken->id !== \T_OBJECT_OPERATOR
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array {

@@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\PhpVersion;
final class PropertyTokenEmulator extends KeywordEmulator {
public function getPhpVersion(): PhpVersion {
return PhpVersion::fromComponents(8, 4);
}
public function getKeywordString(): string {
return '__property__';
}
public function getKeywordToken(): int {
return \T_PROPERTY_C;
}
}

@@ -14,23 +14,47 @@ final class Modifiers {
public const ABSTRACT = 16;
public const FINAL = 32;
public const READONLY = 64;
public const PUBLIC_SET = 128;
public const PROTECTED_SET = 256;
public const PRIVATE_SET = 512;
public const VISIBILITY_MASK = 1 | 2 | 4;
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
private const TO_STRING_MAP = [
self::PUBLIC => 'public',
self::PROTECTED => 'protected',
self::PRIVATE => 'private',
self::STATIC => 'static',
self::ABSTRACT => 'abstract',
self::FINAL => 'final',
self::READONLY => 'readonly',
self::PUBLIC_SET => 'public(set)',
self::PROTECTED_SET => 'protected(set)',
self::PRIVATE_SET => 'private(set)',
];
public static function toString(int $modifier): string {
if (!isset(self::TO_STRING_MAP[$modifier])) {
throw new \InvalidArgumentException("Unknown modifier $modifier");
}
return self::TO_STRING_MAP[$modifier];
}
private static function isValidModifier(int $modifier): bool {
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
return $isPow2 && $modifier <= self::PRIVATE_SET;
}
/**
* @internal
*/
public static function verifyClassModifier(int $a, int $b): void {
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
assert(self::isValidModifier($b));
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
@@ -42,24 +66,16 @@ final class Modifiers {
* @internal
*/
public static function verifyModifier(int $a, int $b): void {
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
assert(self::isValidModifier($b));
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
) {
throw new Error('Multiple access type modifiers are not allowed');
}
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
throw new Error('Multiple static modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {

@@ -185,7 +185,7 @@ class NameContext {
// Check for relevant type-specific use statements
foreach ($this->origAliases[$type] as $alias => $orig) {
if ($type === Stmt\Use_::TYPE_CONSTANT) {
// Constants are are complicated-sensitive
// Constants are complicated-sensitive
$normalizedOrig = $this->normalizeConstName($orig->toString());
if ($normalizedOrig === $this->normalizeConstName($name)) {
$possibleNames[] = new Name($alias);

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ArrayItem.php';
if (false) {
// For classmap-authoritative support.
class ArrayItem extends \PhpParser\Node\ArrayItem {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ClosureUse.php';
if (false) {
// For classmap-authoritative support.
class ClosureUse extends \PhpParser\Node\ClosureUse {
}
}

@@ -53,7 +53,7 @@ class Identifier extends NodeAbstract {
/**
* Get lowercased identifier as string.
*
* @psalm-return non-empty-string
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased identifier as string
*/
public function toLowerString(): string {

@@ -129,7 +129,7 @@ class Name extends NodeAbstract {
* Returns lowercased string representation of the name, without taking the name type into
* account (e.g., no leading backslash for fully qualified names).
*
* @psalm-return non-empty-string
* @psalm-return non-empty-string&lowercase-string
* @return string Lowercased string representation
*/
public function toLowerString(): string {

@@ -21,6 +21,8 @@ class Param extends NodeAbstract {
public int $flags;
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var PropertyHook[] Property hooks for promoted properties */
public array $hooks;
/**
* Constructs a parameter node.
@@ -33,13 +35,15 @@ class Param extends NodeAbstract {
* @param array<string, mixed> $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param list<AttributeGroup> $attrGroups PHP attribute groups
* @param PropertyHook[] $hooks Property hooks for promoted properties
*/
public function __construct(
Expr $var, ?Expr $default = null, ?Node $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
array $attrGroups = [],
array $hooks = []
) {
$this->attributes = $attributes;
$this->type = $type;
@@ -49,10 +53,11 @@ class Param extends NodeAbstract {
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks'];
}
public function getType(): string {
@@ -63,11 +68,20 @@ class Param extends NodeAbstract {
* Whether this parameter uses constructor property promotion.
*/
public function isPromoted(): bool {
return $this->flags !== 0;
return $this->flags !== 0 || $this->hooks !== [];
}
public function isPublic(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC);
$public = (bool) ($this->flags & Modifiers::PUBLIC);
if ($public) {
return true;
}
if ($this->hooks === []) {
return false;
}
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
}
public function isProtected(): bool {
@@ -81,4 +95,25 @@ class Param extends NodeAbstract {
public function isReadonly(): bool {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the promoted property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the promoted property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the promoted property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
}

@@ -0,0 +1,105 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeAbstract;
class PropertyHook extends NodeAbstract implements FunctionLike {
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var int Modifiers */
public int $flags;
/** @var bool Whether hook returns by reference */
public bool $byRef;
/** @var Identifier Hook name */
public Identifier $name;
/** @var Param[] Parameters */
public array $params;
/** @var null|Expr|Stmt[] Hook body */
public $body;
/**
* Constructs a property hook node.
*
* @param string|Identifier $name Hook name
* @param null|Expr|Stmt[] $body Hook body
* @param array{
* flags?: int,
* byRef?: bool,
* params?: Param[],
* attrGroups?: AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
* 'flags => 0 : Flags
* 'byRef' => false : Whether hook returns by reference
* 'params' => array(): Parameters
* 'attrGroups' => array(): PHP attribute groups
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct($name, $body, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->body = $body;
$this->flags = $subNodes['flags'] ?? 0;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function returnsByRef(): bool {
return $this->byRef;
}
public function getParams(): array {
return $this->params;
}
public function getReturnType() {
return null;
}
/**
* Whether the property hook is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
public function getStmts(): ?array {
if ($this->body instanceof Expr) {
$name = $this->name->toLowerString();
if ($name === 'get') {
return [new Return_($this->body)];
}
if ($name === 'set') {
if (!$this->hasAttribute('propertyName')) {
throw new \LogicException(
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
}
$propName = $this->getAttribute('propertyName');
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
return [new Expression(new Assign($prop, $this->body))];
}
throw new \LogicException('Unknown property hook "' . $name . '"');
}
return $this->body;
}
public function getAttrGroups(): array {
return $this->attrGroups;
}
public function getType(): string {
return 'PropertyHook';
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'body'];
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Float_.php';
if (false) {
// For classmap-authoritative support.
class DNumber extends Float_ {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/InterpolatedString.php';
if (false) {
// For classmap-authoritative support.
class Encapsed extends InterpolatedString {
}
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\InterpolatedStringPart;
require __DIR__ . '/../InterpolatedStringPart.php';
if (false) {
// For classmap-authoritative support.
class EncapsedStringPart extends InterpolatedStringPart {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Int_.php';
if (false) {
// For classmap-authoritative support.
class LNumber extends Int_ {
}
}

@@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Property extends MagicConst {
public function getName(): string {
return '__PROPERTY__';
}
public function getType(): string {
return 'Scalar_MagicConst_Property';
}
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\DeclareItem;
require __DIR__ . '/../DeclareItem.php';
if (false) {
// For classmap-authoritative support.
class DeclareDeclare extends DeclareItem {
}
}

@@ -18,6 +18,8 @@ class Property extends Node\Stmt {
public ?Node $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var Node\PropertyHook[] Property hooks */
public array $hooks;
/**
* Constructs a class property list node.
@@ -27,17 +29,19 @@ class Property extends Node\Stmt {
* @param array<string, mixed> $attributes Additional attributes
* @param null|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param Node\PropertyHook[] $hooks Property hooks
*/
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) {
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = $type;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'props'];
return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
}
/**
@@ -76,6 +80,41 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the property is abstract.
*/
public function isAbstract(): bool {
return (bool) ($this->flags & Modifiers::ABSTRACT);
}
/**
* Whether the property is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
/**
* Whether the property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
public function getType(): string {
return 'Stmt_Property';
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\PropertyItem;
require __DIR__ . '/../PropertyItem.php';
if (false) {
// For classmap-authoritative support.
class PropertyProperty extends PropertyItem {
}
}

@@ -1,3 +1,11 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
require __DIR__ . '/../StaticVar.php';
if (false) {
// For classmap-authoritative support.
class StaticVar extends \PhpParser\Node\StaticVar {
}
}

@@ -1,3 +1,13 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\UseItem;
require __DIR__ . '/../UseItem.php';
if (false) {
// For classmap-authoritative support.
class UseUse extends UseItem {
}
}

@@ -185,6 +185,15 @@ class NodeDumper {
if ($flags & Modifiers::READONLY) {
$strs[] = 'READONLY';
}
if ($flags & Modifiers::PUBLIC_SET) {
$strs[] = 'PUBLIC_SET';
}
if ($flags & Modifiers::PROTECTED_SET) {
$strs[] = 'PROTECTED_SET';
}
if ($flags & Modifiers::PRIVATE_SET) {
$strs[] = 'PRIVATE_SET';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';

@@ -99,66 +99,72 @@ class NodeTraverser implements NodeTraverserInterface {
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
if (!$subNode instanceof Node) {
continue;
}
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if ($traverseChildren) {
$this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
}
@@ -176,77 +182,80 @@ class NodeTraverser implements NodeTraverserInterface {
$doNodes = [];
foreach ($nodes as $i => $node) {
if ($node instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
if (!$node instanceof Node) {
if (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
$traverseChildren = true;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if ($traverseChildren) {
$this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
}

@@ -110,6 +110,12 @@ class NameResolver extends NodeVisitorAbstract {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Node\PropertyHook) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@@ -7,6 +7,7 @@ namespace PhpParser;
* turn is based on work by Masato Bito.
*/
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Cast\Double;
@@ -14,6 +15,7 @@ use PhpParser\Node\Identifier;
use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
@@ -30,6 +32,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser {
@@ -409,8 +412,6 @@ abstract class ParserAbstract implements Parser {
$rule = $state - $this->numNonLeafStates;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error): void {
@@ -1137,32 +1138,12 @@ abstract class ParserAbstract implements Parser {
}
protected function checkClassConst(ClassConst $node, int $modifierPos): void {
if ($node->flags & Modifiers::STATIC) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error(
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::READONLY) {
$this->emitError(new Error(
"Cannot use 'readonly' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
protected function checkProperty(Property $node, int $modifierPos): void {
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error('Properties cannot be declared abstract',
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::FINAL) {
$this->emitError(new Error('Properties cannot be declared final',
$this->getAttributesAt($modifierPos)));
foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
if ($node->flags & $modifier) {
$this->emitError(new Error(
"Cannot use '" . Modifiers::toString($modifier) . "' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
}
@@ -1178,6 +1159,89 @@ abstract class ParserAbstract implements Parser {
}
}
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
if (count($property->props) > 1) {
$this->emitError(new Error(
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
}
}
/** @param PropertyHook[] $hooks */
protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
if (empty($hooks)) {
$this->emitError(new Error(
'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
}
}
protected function checkPropertyHook(PropertyHook $hook, ?int $paramListPos): void {
$name = $hook->name->toLowerString();
if ($name !== 'get' && $name !== 'set') {
$this->emitError(new Error(
'Unknown hook "' . $hook->name . '", expected "get" or "set"',
$hook->name->getAttributes()));
}
if ($name === 'get' && $paramListPos !== null) {
$this->emitError(new Error(
'get hook must not have a parameter list', $this->getAttributesAt($paramListPos)));
}
}
protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos): void {
try {
Modifiers::verifyModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
if ($b != Modifiers::FINAL) {
$this->emitError(new Error(
'Cannot use the ' . Modifiers::toString($b) . ' modifier on a property hook',
$this->getAttributesAt($modifierPos)));
}
}
/**
* @param Property|Param $node
*/
protected function addPropertyNameToHooks(Node $node): void {
if ($node instanceof Property) {
$name = $node->props[0]->name->toString();
} else {
$name = $node->var->name;
}
foreach ($node->hooks as $hook) {
$hook->setAttribute('propertyName', $name);
}
}
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
private function isSimpleExit(array $args): bool {
if (\count($args) === 0) {
return true;
}
if (\count($args) === 1) {
$arg = $args[0];
return $arg instanceof Arg && $arg->name === null &&
$arg->byRef === false && $arg->unpack === false;
}
return false;
}
/**
* @param array<Node\Arg|Node\VariadicPlaceholder> $args
* @param array<string, mixed> $attrs
*/
protected function createExitExpr(string $name, int $namePos, array $args, array $attrs): Expr {
if ($this->isSimpleExit($args)) {
// Create Exit node for backwards compatibility.
$attrs['kind'] = strtolower($name) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
return new Expr\Exit_(\count($args) === 1 ? $args[0]->value : null, $attrs);
}
return new Expr\FuncCall(new Name($name, $this->getAttributesAt($namePos)), $args, $attrs);
}
/**
* Creates the token map.
*
@@ -1190,42 +1254,23 @@ abstract class ParserAbstract implements Parser {
protected function createTokenMap(): array {
$tokenMap = [];
for ($i = 0; $i < 1000; ++$i) {
if ($i < 256) {
// Single-char tokens use an identity mapping.
$tokenMap[$i] = $i;
} elseif (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
$tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM;
} elseif (\T_OPEN_TAG_WITH_ECHO === $i) {
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = static::T_ECHO;
} elseif (\T_CLOSE_TAG === $i) {
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if (defined($name = static::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
// Single-char tokens use an identity mapping.
for ($i = 0; $i < 256; ++$i) {
$tokenMap[$i] = $i;
}
foreach ($this->symbolToName as $name) {
if ($name[0] === 'T') {
$tokenMap[\constant($name)] = constant(static::class . '::' . $name);
}
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = static::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = static::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = static::T_ENUM;
$tokenMap[\T_READONLY] = static::T_READONLY;
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[\T_CLOSE_TAG] = ord(';');
// We have create a map from PHP token IDs to external symbol IDs.
// We have created a map from PHP token IDs to external symbol IDs.
// Now map them to the internal symbol ID.
$fullTokenMap = [];
foreach ($tokenMap as $phpToken => $extSymbol) {

@@ -43,7 +43,7 @@ class PhpVersion {
* if it is still under development.
*/
public static function getNewestSupported(): self {
return self::fromComponents(8, 3);
return self::fromComponents(8, 4);
}
/**

@@ -23,7 +23,8 @@ class Standard extends PrettyPrinterAbstract {
. ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '')
. $this->p($node->var)
. ($node->default ? ' = ' . $this->p($node->default) : '');
. ($node->default ? ' = ' . $this->p($node->default) : '')
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : '');
}
protected function pArg(Node\Arg $node): string {
@@ -125,6 +126,10 @@ class Standard extends PrettyPrinterAbstract {
return '__TRAIT__';
}
protected function pScalar_MagicConst_Property(MagicConst\Property $node): string {
return '__PROPERTY__';
}
// Scalars
private function indentString(string $str): string {
@@ -827,7 +832,8 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';';
. $this->pCommaSeparated($node->props)
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';');
}
protected function pPropertyItem(Node\PropertyItem $node): string {
@@ -835,6 +841,15 @@ class Standard extends PrettyPrinterAbstract {
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pPropertyHook(Node\PropertyHook $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. ($node->byRef ? '&' : '') . $node->name
. ($node->params ? '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' : '')
. (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}'
: ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';');
}
protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)

@@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Cast;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\MatchArm;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
@@ -105,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @var int Current indentation level. */
protected int $indentLevel;
/** @var string String for single level of indentation */
private string $indent;
/** @var int Width in spaces to indent by. */
private int $indentWidth;
/** @var bool Whether to use tab indentation. */
private bool $useTabs;
/** @var int Width in spaces of one tab. */
private int $tabWidth = 4;
/** @var string Newline style. Does not include current indentation. */
protected string $newline;
/** @var string Newline including current indentation. */
@@ -169,12 +179,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
* PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case).
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
* * string $indent: The indentation to use. Should either be all spaces or a single
* tab. Defaults to four spaces (" ").
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax.
*
* @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
@@ -189,6 +201,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken =
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
$this->indent = $indent = $options['indent'] ?? ' ';
if ($indent === "\t") {
$this->useTabs = true;
$this->indentWidth = $this->tabWidth;
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
$this->useTabs = false;
$this->indentWidth = \strlen($indent);
} else {
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
}
}
/**
@@ -207,24 +230,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $level;
$this->nl = $this->newline . \str_repeat(' ', $level);
if ($this->useTabs) {
$tabs = \intdiv($level, $this->tabWidth);
$spaces = $level % $this->tabWidth;
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
} else {
$this->nl = $this->newline . \str_repeat(' ', $level);
}
}
/**
* Increase indentation level.
*/
protected function indent(): void {
$this->indentLevel += 4;
$this->nl .= ' ';
$this->indentLevel += $this->indentWidth;
$this->nl .= $this->indent;
}
/**
* Decrease indentation level.
*/
protected function outdent(): void {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
assert($this->indentLevel >= $this->indentWidth);
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
}
/**
@@ -536,7 +564,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->initializeModifierChangeMap();
$this->resetState();
$this->origTokens = new TokenStream($origTokens);
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
$this->preprocessNodes($stmts);
@@ -708,7 +736,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
@@ -811,7 +839,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
@@ -1195,6 +1223,9 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
. ($modifiers & Modifiers::PUBLIC ? 'public ' : '')
. ($modifiers & Modifiers::PROTECTED ? 'protected ' : '')
. ($modifiers & Modifiers::PRIVATE ? 'private ' : '')
. ($modifiers & Modifiers::PUBLIC_SET ? 'public(set) ' : '')
. ($modifiers & Modifiers::PROTECTED_SET ? 'protected(set) ' : '')
. ($modifiers & Modifiers::PRIVATE_SET ? 'private(set) ' : '')
. ($modifiers & Modifiers::STATIC ? 'static ' : '')
. ($modifiers & Modifiers::READONLY ? 'readonly ' : '');
}
@@ -1517,6 +1548,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\UseUse::class . '->uses' => ', ',
MatchArm::class . '->conds' => ', ',
AttributeGroup::class . '->attrs' => ', ',
PropertyHook::class . '->params' => ', ',
// statement lists
Expr\Closure::class . '->stmts' => "\n",
@@ -1554,10 +1586,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Expr\Closure::class . '->attrGroups' => ' ',
Expr\ArrowFunction::class . '->attrGroups' => ' ',
Param::class . '->attrGroups' => ' ',
PropertyHook::class . '->attrGroups' => ' ',
Stmt\Switch_::class . '->cases' => "\n",
Stmt\TraitUse::class . '->adaptations' => "\n",
Stmt\TryCatch::class . '->stmts' => "\n",
Stmt\While_::class . '->stmts' => "\n",
PropertyHook::class . '->body' => "\n",
Stmt\Property::class . '->hooks' => "\n",
Param::class . '->hooks' => "\n",
// dummy for top-level context
'File->stmts' => "\n",
@@ -1641,6 +1678,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
PropertyHook::class . '->flags' => ['pModifiers', \T_STRING],
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],
//Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO

@@ -17,6 +17,11 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
// PHP 8.4
'T_PROPERTY_C',
'T_PUBLIC_SET',
'T_PROTECTED_SET',
'T_PRIVATE_SET',
];
// PHP-Parser might be used together with another library that also emulates some or all

@@ -90,6 +90,76 @@ parameters:
count: 1
path: lib/PhpParser/Lexer/Emulative.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Expr/ArrayItem.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Expr/ClosureUse.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Scalar/DNumber.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Scalar/Encapsed.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Scalar/EncapsedStringPart.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Scalar/LNumber.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Stmt/DeclareDeclare.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Stmt/PropertyProperty.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Stmt/StaticVar.php
-
message: "#^If condition is always false\\.$#"
count: 1
path: lib/PhpParser/Node/Stmt/UseUse.php
-
message: "#^Constant T_PRIVATE_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROTECTED_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PUBLIC_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROPERTY_C not found\\.$#"
count: 1
path: lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
@@ -155,76 +225,11 @@ parameters:
count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ATTRIBUTE\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_COALESCE_EQUAL\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ENUM\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_FN\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_MATCH\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_FULLY_QUALIFIED\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_QUALIFIED\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NAME_RELATIVE\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_NULLSAFE_OBJECT_OPERATOR\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_PAAMAYIM_NEKUDOTAYIM\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_READONLY\\.$#"
count: 1
path: lib/PhpParser/ParserAbstract.php
-
message: "#^Unary operation \"\\+\" on string results in an error\\.$#"
count: 1

@@ -227,6 +227,16 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED),
$node
);
$node = $this->createParamBuilder('test')
->makeProtectedSet()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED_SET),
$node
);
}
public function testMakePrivate(): void {
@@ -239,6 +249,16 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE),
$node
);
$node = $this->createParamBuilder('test')
->makePrivateSet()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE_SET),
$node
);
}
public function testMakeReadonly(): void {

@@ -3,6 +3,7 @@
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Error;
use PhpParser\Modifiers;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
@@ -10,6 +11,8 @@ use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\PropertyItem;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt;
@@ -29,9 +32,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PRIVATE | Modifiers::STATIC,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -44,9 +45,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PROTECTED,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -59,9 +58,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::PUBLIC,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
@@ -74,12 +71,37 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals(
new Stmt\Property(
Modifiers::READONLY,
[
new \PhpParser\Node\PropertyItem('test')
]
[new PropertyItem('test')]
),
$node
);
$node = $this->createPropertyBuilder('test')
->makeFinal()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::FINAL, [new PropertyItem('test')]),
$node);
$node = $this->createPropertyBuilder('test')
->makePrivateSet()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::PRIVATE_SET, [new PropertyItem('test')]),
$node);
$node = $this->createPropertyBuilder('test')
->makeProtectedSet()
->getNode();
$this->assertEquals(
new Stmt\Property(Modifiers::PROTECTED_SET, [new PropertyItem('test')]),
$node);
}
public function testAbstractWithoutHook() {
$this->expectException(Error::class);
$this->expectExceptionMessage('Only hooked properties may be declared abstract');
$this->createPropertyBuilder('test')->makeAbstract()->getNode();
}
public function testDocComment(): void {
@@ -136,6 +158,23 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
);
}
public function testAddHook(): void {
$get = new PropertyHook('get', null);
$set = new PropertyHook('set', null);
$node = $this->createPropertyBuilder('test')
->addHook($get)
->addHook($set)
->makeAbstract()
->getNode();
$this->assertEquals(
new Stmt\Property(
Modifiers::ABSTRACT,
[new PropertyItem('test')],
[], null, [],
[$get, $set]),
$node);
}
public static function provideTestDefaultValues() {
return [
[

@@ -4,6 +4,7 @@ namespace PhpParser;
use PhpParser\Builder\Class_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
@@ -222,4 +223,14 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
$this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
BuilderHelpers::normalizeAttribute('test');
}
public function testNormalizeValueEnum() {
if (\PHP_VERSION_ID <= 80100) {
$this->markTestSkipped('Enums are supported since PHP 8.1');
}
include __DIR__ . '/../fixtures/Suit.php';
$this->assertEquals(new Expr\ClassConstFetch(new FullyQualified(\Suit::class), new Identifier('Hearts')), BuilderHelpers::normalizeValue(\Suit::Hearts));
}
}

@@ -91,6 +91,9 @@ class EmulativeTest extends LexerTest {
public static function provideTestReplaceKeywords() {
return [
// PHP 8.4
['__PROPERTY__', \T_PROPERTY_C],
// PHP 8.0
['match', \T_MATCH],
@@ -390,6 +393,37 @@ class EmulativeTest extends LexerTest {
[\T_READONLY, 'readonly'],
[ord('('), '('],
]],
// PHP 8.4: Asymmetric visibility modifiers
['private(set)', [
[\T_PRIVATE_SET, 'private(set)']
]],
['PROTECTED(SET)', [
[\T_PROTECTED_SET, 'PROTECTED(SET)']
]],
['Public(Set)', [
[\T_PUBLIC_SET, 'Public(Set)']
]],
['public (set)', [
[\T_PUBLIC, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
['->public(set)', [
[\T_OBJECT_OPERATOR, '->'],
[\T_STRING, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
['?-> public(set)', [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[\T_STRING, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')'],
]],
];
}
@@ -424,6 +458,19 @@ class EmulativeTest extends LexerTest {
[\T_ENCAPSED_AND_WHITESPACE, ' baz'],
[ord('"'), '"'],
]],
['8.4', '__PROPERTY__', [[\T_PROPERTY_C, '__PROPERTY__']]],
['8.3', '__PROPERTY__', [[\T_STRING, '__PROPERTY__']]],
['8.4', '__property__', [[\T_PROPERTY_C, '__property__']]],
['8.3', '__property__', [[\T_STRING, '__property__']]],
['8.4', 'public(set)', [
[\T_PUBLIC_SET, 'public(set)'],
]],
['8.3', 'public(set)', [
[\T_PUBLIC, 'public'],
[\ord('('), '('],
[\T_STRING, 'set'],
[\ord(')'), ')']
]],
];
}
}

@@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace PhpParser;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
class ModifiersTest extends TestCase {
public function testToString() {
$this->assertSame('public', Modifiers::toString(Modifiers::PUBLIC));
}
public function testToStringInvalid() {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown modifier 3');
Modifiers::toString(Modifiers::PUBLIC | Modifiers::PROTECTED);
}
}

@@ -14,6 +14,9 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate());
$this->assertFalse($node->isReadonly());
$this->assertFalse($node->isPublicSet());
$this->assertFalse($node->isProtectedSet());
$this->assertFalse($node->isPrivateSet());
}
/**
@@ -34,4 +37,28 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
['readonly'],
];
}
public function testSetVisibility() {
$node = new Param(new Variable('foo'));
$node->flags = Modifiers::PRIVATE_SET;
$this->assertTrue($node->isPrivateSet());
$node->flags = Modifiers::PROTECTED_SET;
$this->assertTrue($node->isProtectedSet());
$node->flags = Modifiers::PUBLIC_SET;
$this->assertTrue($node->isPublicSet());
}
public function testPromotedPropertyWithoutVisibilityModifier(): void {
$node = new Param(new Variable('foo'));
$get = new PropertyHook('get', null);
$node->hooks[] = $get;
$this->assertTrue($node->isPromoted());
$this->assertTrue($node->isPublic());
}
public function testNonPromotedPropertyIsNotPublic(): void {
$node = new Param(new Variable('foo'));
$this->assertFalse($node->isPublic());
}
}

@@ -0,0 +1,89 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
class PropertyHookTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideModifiers
*/
public function testModifiers($modifier): void {
$node = new PropertyHook(
'get',
null,
[
'flags' => constant(Modifiers::class . '::' . strtoupper($modifier)),
]
);
$this->assertTrue($node->{'is' . $modifier}());
}
public function testNoModifiers(): void {
$node = new PropertyHook('get', null);
$this->assertFalse($node->isFinal());
}
public static function provideModifiers() {
return [
['final'],
];
}
public function testGetStmts(): void {
$expr = new Variable('test');
$get = new PropertyHook('get', $expr);
$this->assertEquals([new Return_($expr)], $get->getStmts());
$set = new PropertyHook('set', $expr, [], ['propertyName' => 'abc']);
$this->assertEquals([
new Expression(new Assign(new PropertyFetch(new Variable('this'), 'abc'), $expr))
], $set->getStmts());
}
public function testGetStmtsSetHookFromParser(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$prettyPrinter = new Standard();
$stmts = $parser->parse(<<<'CODE'
<?php
class Test {
public $prop1 { set => 123; }
public function __construct(public $prop2 { set => 456; }) {}
}
CODE);
$hook1 = $stmts[0]->stmts[0]->hooks[0];
$this->assertEquals('$this->prop1 = 123;', $prettyPrinter->prettyPrint($hook1->getStmts()));
$hook2 = $stmts[0]->stmts[1]->params[0]->hooks[0];
$this->assertEquals('$this->prop2 = 456;', $prettyPrinter->prettyPrint($hook2->getStmts()));
}
public function testGetStmtsUnknownHook(): void {
$expr = new Variable('test');
$hook = new PropertyHook('foobar', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unknown property hook "foobar"');
$hook->getStmts();
}
public function testGetStmtsSetHookWithoutPropertyName(): void {
$expr = new Variable('test');
$set = new PropertyHook('set', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
$set->getStmts();
}
}

@@ -25,6 +25,9 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse($node->isPrivate());
$this->assertFalse($node->isStatic());
$this->assertFalse($node->isReadonly());
$this->assertFalse($node->isPublicSet());
$this->assertFalse($node->isProtectedSet());
$this->assertFalse($node->isPrivateSet());
}
public function testStaticImplicitlyPublic(): void {
@@ -45,4 +48,23 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
['readonly'],
];
}
public function testSetVisibility() {
$node = new Property(Modifiers::PRIVATE_SET, []);
$this->assertTrue($node->isPrivateSet());
$node = new Property(Modifiers::PROTECTED_SET, []);
$this->assertTrue($node->isProtectedSet());
$node = new Property(Modifiers::PUBLIC_SET, []);
$this->assertTrue($node->isPublicSet());
}
public function testIsFinal() {
$node = new Property(Modifiers::FINAL, []);
$this->assertTrue($node->isFinal());
}
public function testIsAbstract() {
$node = new Property(Modifiers::ABSTRACT, []);
$this->assertTrue($node->isAbstract());
}
}

@@ -261,6 +261,7 @@ PHP;
},
"flags": 0,
"attrGroups": [],
"hooks": [],
"attributes": {
"startLine": 4,
"startTokenPos": 9,
@@ -302,6 +303,7 @@ PHP;
},
"flags": 0,
"attrGroups": [],
"hooks": [],
"attributes": {
"startLine": 4,
"startTokenPos": 17,
@@ -464,7 +466,8 @@ JSON;
"value": 0
},
"flags": 0,
"attrGroups": []
"attrGroups": [],
"hooks": []
},
{
"nodeType": "Param",
@@ -505,7 +508,8 @@ JSON;
"value": 1
},
"flags": 0,
"attrGroups": []
"attrGroups": [],
"hooks": []
}
],
"returnType": null,

@@ -201,6 +201,18 @@ class A extends B implements C, D {
public const X A = X::Bar;
public const X\Foo B = X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
public Foo $foo {
#[X]
set(#[X] Bar $v) {}
}
public function __construct(
public Foo $bar {
#[X]
set(#[X] Bar $v) {}
}
) {}
}
#[X]
@@ -269,6 +281,18 @@ class A extends \NS\B implements \NS\C, \NS\D
public const \NS\X A = \NS\X::Bar;
public const \NS\X\Foo B = \NS\X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
public \NS\Foo $foo {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
}
public function __construct(public \NS\Foo $bar {
#[\NS\X]
set(#[\NS\X] \NS\Bar $v) {
}
})
{
}
}
#[\NS\X]
interface A extends \NS\C, \NS\D
@@ -543,7 +567,7 @@ EOC;
}
private function parseAndResolve(string $code): array {
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative());
$parser = new PhpParser\Parser\Php8(new PhpParser\Lexer\Emulative());
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new NameResolver());

@@ -10,18 +10,26 @@ use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Parser\Php7;
use PhpParser\PrettyPrinter\Standard;
class PrettyPrinterTest extends CodeTestAbstract {
/** @return array{0: Parser, 1: PrettyPrinter} */
private function createParserAndPrinter(array $options): array {
$parserVersion = $options['parserVersion'] ?? $options['version'] ?? null;
$printerVersion = $options['version'] ?? null;
$indent = isset($options['indent']) ? json_decode($options['indent']) : null;
$factory = new ParserFactory();
$parser = $factory->createForVersion($parserVersion !== null
? PhpVersion::fromString($parserVersion) : PhpVersion::getNewestSupported());
$prettyPrinter = new Standard([
'phpVersion' => $printerVersion !== null ? PhpVersion::fromString($printerVersion) : null,
'indent' => $indent,
]);
return [$parser, $prettyPrinter];
}
protected function doTestPrettyPrintMethod($method, $name, $code, $expected, $modeLine) {
$lexer = new Lexer\Emulative();
$parser = new Parser\Php7($lexer);
$options = $this->parseModeLine($modeLine);
$version = isset($options['version']) ? PhpVersion::fromString($options['version']) : null;
$prettyPrinter = new Standard(['phpVersion' => $version]);
[$parser, $prettyPrinter] = $this->createParserAndPrinter($this->parseModeLine($modeLine));
$output = canonicalize($prettyPrinter->$method($parser->parse($code)));
$this->assertSame($expected, $output, $name);
}
@@ -181,12 +189,9 @@ class PrettyPrinterTest extends CodeTestAbstract {
* @dataProvider provideTestFormatPreservingPrint
*/
public function testFormatPreservingPrint($name, $code, $modification, $expected, $modeLine): void {
$lexer = new Lexer\Emulative();
$parser = new Parser\Php7($lexer);
[$parser, $printer] = $this->createParserAndPrinter($this->parseModeLine($modeLine));
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
$printer = new PrettyPrinter\Standard();
$oldStmts = $parser->parse($code);
$oldTokens = $parser->getTokens();
@@ -222,14 +227,9 @@ CODE
* the pretty printer tests (i.e. returns the input if no changes occurred).
*/
$lexer = new Lexer\Emulative();
$parser = new Php7($lexer);
[$parser, $printer] = $this->createParserAndPrinter($this->parseModeLine($modeLine));
$traverser = new NodeTraverser(new NodeVisitor\CloningVisitor());
$printer = new PrettyPrinter\Standard();
try {
$oldStmts = $parser->parse($code);
} catch (Error $e) {
@@ -299,4 +299,10 @@ CODE
$this->expectExceptionMessage('Option "newline" must be one of "\n" or "\r\n"');
new PrettyPrinter\Standard(['newline' => 'foo']);
}
public function testInvalidIndent(): void {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Option "indent" must either be all spaces or a single tab');
new PrettyPrinter\Standard(['indent' => "\t "]);
}
}

@@ -0,0 +1,9 @@
Group use should include trailing semicolon
-----
<?php
use A\{B, C};
-----
$stmts[0] = new Stmt\Use_([new Node\UseItem(new Node\Name('A\B'))]);
-----
<?php
use A\B;

@@ -0,0 +1,70 @@
Indentation
-----
<?php
$x;
-----
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
-----
!!indent=" "
<?php
if ($a) {
$x;
}
-----
<?php
$x;
-----
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
-----
!!indent="\t"
<?php
if ($a) {
@@{"\t"}@@$x;
}
-----
<?php
if ($a) {
@@{"\t"}@@$x;
}
-----
$stmts[0]->stmts[] = new Stmt\Expression(new Expr\Variable('y'));
-----
!!indent="\t"
<?php
if ($a) {
@@{"\t"}@@$x;
@@{"\t"}@@$y;
}
-----
<?php
array_merge(
[
$x,
$y,
]
);
-----
$call = $stmts[0]->expr;
$stmts[0]->expr = new Expr\StaticCall(new Node\Name\FullyQualified('Compat'), $call->name, $call->args);
-----
<?php
\Compat::array_merge([
$x,
$y,
]);
-----
<?php
if ($a) {
if (
$b
) {}
}
-----
$stmts[0] = new Stmt\While_($stmts[0]->cond, $stmts[0]->stmts);
-----
<?php
while ($a) {
if (
$b
) {}
}

@@ -0,0 +1,128 @@
Property hooks
-----
<?php
class Test {
public $prop
{
get => 42;
}
}
-----
$stmts[0]->stmts[0]->hooks[] = new Node\PropertyHook('set', new Scalar\Int_(123));
-----
<?php
class Test {
public $prop
{
get => 42;
set => 123;
}
}
-----
<?php
class Test {
public function __construct(
public $prop
{ get => 42; }
) {}
}
-----
$stmts[0]->stmts[0]->params[0]->hooks[] = new Node\PropertyHook('set', new Scalar\Int_(123));
-----
<?php
class Test {
public function __construct(
public $prop
{ get => 42;
set => 123; }
) {}
}
-----
<?php
class Test {
public $prop {
set
{
$a;
}
}
}
-----
$stmts[0]->stmts[0]->hooks[0]->body[] = new Stmt\Expression(new Expr\Variable('b'));
-----
<?php
class Test {
public $prop {
set
{
$a;
$b;
}
}
}
-----
<?php
class Test {
public $prop {
get
=> 42;
}
}
-----
$stmts[0]->stmts[0]->hooks[0]->flags = Modifiers::FINAL;
-----
<?php
class Test {
public $prop {
final get
=> 42;
}
}
-----
<?php
// For now, just make sure this works.
class Test {
public $prop {
get
=> 42;
}
}
-----
$stmts[0]->stmts[0]->hooks[0]->body = [new Stmt\Return_(new Scalar\Int_(24))];
-----
<?php
// For now, just make sure this works.
class Test {
public $prop {
get {
return 24;
}
}
}
-----
<?php
// For now, just make sure this works.
class Test {
public $prop1 {
& get;
}
public $prop2 {
& get;
}
}
-----
$stmts[0]->stmts[0]->hooks[0]->body = new Scalar\Int_(24);
$stmts[0]->stmts[1]->hooks[0]->body = [new Stmt\Return_(new Scalar\Int_(24))];
-----
<?php
// For now, just make sure this works.
class Test {
public $prop1 {
&get => 24;
}
public $prop2 {
&get {
return 24;
}
}
}

@@ -31,6 +31,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_Nop(
comments: array(

@@ -714,6 +714,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -869,6 +871,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
@@ -883,6 +887,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -984,6 +990,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1016,6 +1024,8 @@ array(
name: foo
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -1029,6 +1039,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1058,6 +1070,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1087,6 +1101,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1118,6 +1134,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1156,6 +1174,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -1183,6 +1203,8 @@ array(
var: Expr_Error(
)
default: null
hooks: array(
)
)
)
uses: array(
@@ -1352,7 +1374,7 @@ class B {
const X = 1
}
-----
Syntax error, unexpected T_PUBLIC, expecting ';' from 6:5 to 6:10
Syntax error, unexpected T_PUBLIC, expecting ';' or '{' from 6:5 to 6:10
Syntax error, unexpected '}', expecting ';' from 12:1 to 12:1
array(
0: Stmt_Class(
@@ -1379,6 +1401,8 @@ array(
default: null
)
)
hooks: array(
)
comments: array(
0: /** @var ?string */
)
@@ -1405,6 +1429,8 @@ array(
name: s
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -0,0 +1,335 @@
Alternative array syntax
-----
<?php
$a{'b'};
$a{'b'}();
$a->b{'c'};
$a->b(){'c'};
A::$b{'c'};
A{0};
A::B{0};
new $array{'className'};
new $a->b{'c'}();
-----
!!version=7.4
array(
0: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: b
)
)
)
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: b
)
)
args: array(
)
)
)
2: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
)
3: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_MethodCall(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
args: array(
)
)
dim: Scalar_String(
value: c
)
)
)
4: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
name: A
)
name: VarLikeIdentifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
)
5: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
name: A
)
)
dim: Scalar_Int(
value: 0
)
)
)
6: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
name: A
)
name: Identifier(
name: B
)
)
dim: Scalar_Int(
value: 0
)
)
)
7: Stmt_Expression(
expr: Expr_New(
class: Expr_ArrayDimFetch(
var: Expr_Variable(
name: array
)
dim: Scalar_String(
value: className
)
)
args: array(
)
)
)
8: Stmt_Expression(
expr: Expr_New(
class: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
args: array(
)
)
)
)
-----
<?php
$a{'b'};
$a{'b'}();
$a->b{'c'};
$a->b(){'c'};
A::$b{'c'};
A{0};
A::B{0};
new $array{'className'};
new $a->b{'c'}();
-----
Syntax error, unexpected '{' from 3:3 to 3:3
Syntax error, unexpected '{' from 4:3 to 4:3
Syntax error, unexpected '{' from 5:6 to 5:6
Syntax error, unexpected '{' from 6:8 to 6:8
Syntax error, unexpected '{' from 7:6 to 7:6
Syntax error, unexpected '{' from 8:2 to 8:2
Syntax error, unexpected '{' from 9:5 to 9:5
Syntax error, unexpected '{' from 10:11 to 10:11
Syntax error, unexpected '{' from 11:10 to 11:10
array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: b
)
)
)
)
2: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
3: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: b
)
)
)
)
4: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
)
5: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: c
)
)
)
)
6: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
args: array(
)
)
)
7: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: c
)
)
)
)
8: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Name(
name: A
)
name: VarLikeIdentifier(
name: b
)
)
)
9: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: c
)
)
)
)
10: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: A
)
)
)
11: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_Int(
value: 0
)
)
)
)
12: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
name: A
)
name: Identifier(
name: B
)
)
)
13: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_Int(
value: 0
)
)
)
)
14: Stmt_Expression(
expr: Expr_New(
class: Expr_Variable(
name: array
)
args: array(
)
)
)
15: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: className
)
)
)
)
16: Stmt_Expression(
expr: Expr_New(
class: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
args: array(
)
)
)
17: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Scalar_String(
value: c
)
)
)
)
)

@@ -32,6 +32,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -60,6 +62,8 @@ array(
default: Scalar_Int(
value: 42
)
hooks: array(
)
)
)
returnType: null
@@ -86,6 +90,8 @@ array(
name: x
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -112,6 +118,8 @@ array(
name: x
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -138,6 +146,8 @@ array(
name: x
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -150,6 +160,8 @@ array(
name: rest
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -192,6 +204,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -204,6 +218,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -235,6 +251,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -247,6 +265,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -28,6 +28,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
uses: array(
@@ -60,6 +62,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
uses: array(
@@ -120,6 +124,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
uses: array(
@@ -162,6 +168,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
uses: array(

@@ -7,6 +7,13 @@ exit('Die!');
die;
die();
die('Exit!');
exit(status: 42);
exit(...$args);
exit($a, $b);
\exit($a);
exit(...);
DIE($a, $b);
-----
array(
0: Stmt_Expression(
@@ -43,4 +50,118 @@ array(
)
)
)
6: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: exit
)
args: array(
0: Arg(
name: Identifier(
name: status
)
value: Scalar_Int(
value: 42
)
byRef: false
unpack: false
)
)
)
)
7: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: exit
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: args
)
byRef: false
unpack: true
)
)
)
)
8: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: exit
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: a
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Variable(
name: b
)
byRef: false
unpack: false
)
)
)
)
9: Stmt_Expression(
expr: Expr_FuncCall(
name: Name_FullyQualified(
name: exit
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: a
)
byRef: false
unpack: false
)
)
)
)
10: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: exit
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
11: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: DIE
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: a
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Variable(
name: b
)
byRef: false
unpack: false
)
)
)
)
)

@@ -9,7 +9,6 @@ ${'a'}();
$$a();
$$$a();
$a['b']();
$a{'b'}();
$a->b['c']();
// array dereferencing
@@ -87,20 +86,6 @@ array(
)
)
6: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: b
)
)
args: array(
)
)
)
7: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
@@ -119,7 +104,7 @@ array(
)
)
)
8: Stmt_Expression(
7: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_FuncCall(
name: Name(

@@ -5,7 +5,6 @@ Object access
// property fetch variations
$a->b;
$a->b['c'];
$a->b{'c'};
// method call variations
$a->b();
@@ -15,7 +14,6 @@ $a->$b['c']();
// array dereferencing
$a->b()['c'];
$a->b(){'c'};
-----
array(
0: Stmt_Expression(
@@ -47,21 +45,6 @@ array(
)
)
2: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
)
3: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_Variable(
name: a
@@ -76,7 +59,7 @@ array(
0: // method call variations
)
)
4: Stmt_Expression(
3: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_Variable(
name: a
@@ -88,7 +71,7 @@ array(
)
)
)
5: Stmt_Expression(
4: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_Variable(
name: a
@@ -100,7 +83,7 @@ array(
)
)
)
6: Stmt_Expression(
5: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
@@ -119,7 +102,7 @@ array(
)
)
)
7: Stmt_Expression(
6: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_MethodCall(
var: Expr_Variable(
@@ -139,21 +122,4 @@ array(
0: // array dereferencing
)
)
8: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_MethodCall(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
args: array(
)
)
dim: Scalar_String(
value: c
)
)
)
)

@@ -5,7 +5,6 @@ Simple array access
$a['b'];
$a['b']['c'];
$a[] = $b;
$a{'b'};
${$a}['b'];
-----
array(
@@ -48,16 +47,6 @@ array(
)
)
3: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_Variable(
name: a
)
dim: Scalar_String(
value: b
)
)
)
4: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_Variable(
name: Expr_Variable(

@@ -9,7 +9,6 @@ A::${'b'};
// array access
A::$b['c'];
A::$b{'c'};
// class name variations can be found in staticCall.test
-----
@@ -65,22 +64,7 @@ array(
0: // array access
)
)
4: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
name: A
)
name: VarLikeIdentifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
)
5: Stmt_Nop(
4: Stmt_Nop(
comments: array(
0: // class name variations can be found in staticCall.test
)

@@ -13,7 +13,6 @@ new A::$b();
new $a->b();
new $a->b->c();
new $a->b['c']();
new $a->b{'c'}();
// test regression introduces by new dereferencing syntax
(new A);
@@ -141,25 +140,6 @@ array(
)
)
8: Stmt_Expression(
expr: Expr_New(
class: Expr_ArrayDimFetch(
var: Expr_PropertyFetch(
var: Expr_Variable(
name: a
)
name: Identifier(
name: b
)
)
dim: Scalar_String(
value: c
)
)
args: array(
)
)
)
9: Stmt_Expression(
expr: Expr_New(
class: Name(
name: A

@@ -8,7 +8,6 @@ new A()::FOO;
new A()::foo();
new A()::$foo;
new A()[0];
new A(){0};
new A()();
new class {}->foo;
@@ -17,7 +16,6 @@ new class {}::FOO;
new class {}::foo();
new class {}::$foo;
new class {}[0];
new class {}{0};
new class {}();
-----
array(
@@ -110,20 +108,6 @@ array(
)
)
6: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_New(
class: Name(
name: A
)
args: array(
)
)
dim: Scalar_Int(
value: 0
)
)
)
7: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_New(
class: Name(
@@ -136,7 +120,7 @@ array(
)
)
)
8: Stmt_Expression(
7: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Expr_New(
class: Stmt_Class(
@@ -158,7 +142,7 @@ array(
)
)
)
9: Stmt_Expression(
8: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_New(
class: Stmt_Class(
@@ -182,7 +166,7 @@ array(
)
)
)
10: Stmt_Expression(
9: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_New(
class: Stmt_Class(
@@ -204,7 +188,7 @@ array(
)
)
)
11: Stmt_Expression(
10: Stmt_Expression(
expr: Expr_StaticCall(
class: Expr_New(
class: Stmt_Class(
@@ -228,7 +212,7 @@ array(
)
)
)
12: Stmt_Expression(
11: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Expr_New(
class: Stmt_Class(
@@ -250,51 +234,29 @@ array(
)
)
)
12: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: null
implements: array(
)
stmts: array(
)
)
args: array(
)
)
dim: Scalar_Int(
value: 0
)
)
)
13: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: null
implements: array(
)
stmts: array(
)
)
args: array(
)
)
dim: Scalar_Int(
value: 0
)
)
)
14: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: null
implements: array(
)
stmts: array(
)
)
args: array(
)
)
dim: Scalar_Int(
value: 0
)
)
)
15: Stmt_Expression(
expr: Expr_FuncCall(
name: Expr_New(
class: Stmt_Class(

@@ -6,11 +6,9 @@ A->length;
A->length();
A[0];
A[0][1][2];
A{0};
A::B[0];
A::B[0][1][2];
A::B{0};
A::B->length;
A::B->length();
A::B::C;
@@ -83,18 +81,6 @@ array(
)
)
4: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
name: A
)
)
dim: Scalar_Int(
value: 0
)
)
)
5: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
@@ -109,7 +95,7 @@ array(
)
)
)
6: Stmt_Expression(
5: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
@@ -134,22 +120,7 @@ array(
)
)
)
7: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
name: A
)
name: Identifier(
name: B
)
)
dim: Scalar_Int(
value: 0
)
)
)
8: Stmt_Expression(
6: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Expr_ClassConstFetch(
class: Name(
@@ -164,7 +135,7 @@ array(
)
)
)
9: Stmt_Expression(
7: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_ClassConstFetch(
class: Name(
@@ -181,7 +152,7 @@ array(
)
)
)
10: Stmt_Expression(
8: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_ClassConstFetch(
class: Name(
@@ -196,7 +167,7 @@ array(
)
)
)
11: Stmt_Expression(
9: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Expr_ClassConstFetch(
class: Name(
@@ -211,7 +182,7 @@ array(
)
)
)
12: Stmt_Expression(
10: Stmt_Expression(
expr: Expr_StaticCall(
class: Expr_ClassConstFetch(
class: Name(
@@ -228,7 +199,7 @@ array(
)
)
)
13: Stmt_Expression(
11: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Scalar_MagicConst_Function(
)
@@ -237,7 +208,7 @@ array(
)
)
)
14: Stmt_Expression(
12: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Scalar_MagicConst_Function(
)
@@ -246,7 +217,7 @@ array(
)
)
)
15: Stmt_Expression(
13: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_ConstFetch(
name: Name(

@@ -211,6 +211,8 @@ array(
name: x
)
default: null
hooks: array(
)
)
)
uses: array(
@@ -289,6 +291,8 @@ array(
name: null
)
)
hooks: array(
)
)
)
uses: array(

@@ -3,7 +3,6 @@ UVS new expressions
<?php
new $className;
new $array['className'];
new $array{'className'};
new $obj->className;
new Test::$className;
new $test::$className;
@@ -34,20 +33,6 @@ array(
)
)
2: Stmt_Expression(
expr: Expr_New(
class: Expr_ArrayDimFetch(
var: Expr_Variable(
name: array
)
dim: Scalar_String(
value: className
)
)
args: array(
)
)
)
3: Stmt_Expression(
expr: Expr_New(
class: Expr_PropertyFetch(
var: Expr_Variable(
@@ -61,7 +46,7 @@ array(
)
)
)
4: Stmt_Expression(
3: Stmt_Expression(
expr: Expr_New(
class: Expr_StaticPropertyFetch(
class: Name(
@@ -75,7 +60,7 @@ array(
)
)
)
5: Stmt_Expression(
4: Stmt_Expression(
expr: Expr_New(
class: Expr_StaticPropertyFetch(
class: Expr_Variable(
@@ -89,7 +74,7 @@ array(
)
)
)
6: Stmt_Expression(
5: Stmt_Expression(
expr: Expr_New(
class: Expr_StaticPropertyFetch(
class: Expr_PropertyFetch(

@@ -10,6 +10,7 @@ __LINE__;
__METHOD__;
__NAMESPACE__;
__TRAIT__;
__PROPERTY__;
-----
array(
0: Stmt_Expression(
@@ -44,4 +45,8 @@ array(
expr: Scalar_MagicConst_Trait(
)
)
8: Stmt_Expression(
expr: Scalar_MagicConst_Property(
)
)
)

@@ -133,6 +133,8 @@ array(
default: null
)
)
hooks: array(
)
)
5: Stmt_Property(
attrGroups: array(
@@ -147,6 +149,8 @@ array(
default: null
)
)
hooks: array(
)
)
6: Stmt_ClassConst(
attrGroups: array(

@@ -159,6 +159,8 @@ array(
name: param
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -189,6 +191,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)

@@ -102,6 +102,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)

@@ -0,0 +1,191 @@
Asymmetric visibility modifiers
-----
<?php
class Test {
protected private(set) $a;
private public(set) $b;
protected(set) $c;
public function __construct(
protected private(set) $d,
private public(set) $e,
protected(set) $f,
) {}
}
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PROTECTED | PRIVATE_SET (514)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: a
)
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PRIVATE | PUBLIC_SET (132)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: b
)
default: null
)
)
hooks: array(
)
)
2: Stmt_Property(
attrGroups: array(
)
flags: PROTECTED_SET (256)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: c
)
default: null
)
)
hooks: array(
)
)
3: Stmt_ClassMethod(
attrGroups: array(
)
flags: PUBLIC (1)
byRef: false
name: Identifier(
name: __construct
)
params: array(
0: Param(
attrGroups: array(
)
flags: PROTECTED | PRIVATE_SET (514)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: d
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
)
flags: PRIVATE | PUBLIC_SET (132)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: e
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
)
flags: PROTECTED_SET (256)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: f
)
default: null
hooks: array(
)
)
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php
class Test {
private(set) private(set) $x;
private(set) public(set) $x;
}
-----
Multiple access type modifiers are not allowed from 3:18 to 3:29
Multiple access type modifiers are not allowed from 4:18 to 4:28
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PRIVATE_SET (512)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: x
)
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC_SET | PRIVATE_SET (640)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: x
)
default: null
)
)
hooks: array(
)
)
)
)
)

@@ -37,6 +37,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
@@ -51,6 +53,8 @@ array(
default: null
)
)
hooks: array(
)
)
2: Stmt_ClassMethod(
attrGroups: array(

@@ -28,6 +28,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -61,6 +63,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -94,6 +98,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -159,6 +165,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -247,69 +255,3 @@ array(
)
)
)
-----
<?php class B { abstract $b; }
-----
Properties cannot be declared abstract from 1:17 to 1:24
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: B
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: ABSTRACT (16)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: b
)
default: null
)
)
)
)
)
)
-----
<?php class A { final $a; }
-----
Properties cannot be declared final from 1:17 to 1:21
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: FINAL (32)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: a
)
default: null
)
)
)
)
)
)

@@ -33,6 +33,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_ClassMethod(
attrGroups: array(

@@ -36,6 +36,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
@@ -52,6 +54,8 @@ array(
default: null
)
)
hooks: array(
)
)
2: Stmt_Property(
attrGroups: array(
@@ -70,6 +74,8 @@ array(
default: null
)
)
hooks: array(
)
)
3: Stmt_Property(
attrGroups: array(
@@ -88,6 +94,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)

@@ -0,0 +1,638 @@
Property hooks
-----
<?php
class Test {
public $prop {
get { return 42; }
set { echo $value; }
}
private $prop2 {
get => 42;
set => $value;
}
abstract $prop3 {
&get;
set;
}
public $prop4 {
final get { return 42; }
set(string $value) { }
}
}
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: array(
0: Stmt_Return(
expr: Scalar_Int(
value: 42
)
)
)
)
1: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: set
)
params: array(
)
body: array(
0: Stmt_Echo(
exprs: array(
0: Expr_Variable(
name: value
)
)
)
)
)
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PRIVATE (4)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop2
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: Scalar_Int(
value: 42
)
)
1: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: set
)
params: array(
)
body: Expr_Variable(
name: value
)
)
)
)
2: Stmt_Property(
attrGroups: array(
)
flags: ABSTRACT (16)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop3
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: true
name: Identifier(
name: get
)
params: array(
)
body: null
)
1: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: set
)
params: array(
)
body: null
)
)
)
3: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop4
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: FINAL (32)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: array(
0: Stmt_Return(
expr: Scalar_Int(
value: 42
)
)
)
)
1: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: set
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: string
)
byRef: false
variadic: false
var: Expr_Variable(
name: value
)
default: null
hooks: array(
)
)
)
body: array(
)
)
)
)
)
)
)
-----
<?php
class Test {
public $prop {}
public function __construct(public $prop2 {}) {}
}
-----
Property hook list cannot be empty from 3:18 to 3:18
Property hook list cannot be empty from 4:47 to 4:47
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
)
)
1: Stmt_ClassMethod(
attrGroups: array(
)
flags: PUBLIC (1)
byRef: false
name: Identifier(
name: __construct
)
params: array(
0: Param(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: prop2
)
default: null
hooks: array(
)
)
)
returnType: null
stmts: array(
)
)
)
)
)
-----
<?php
class Test {
public $prop {
get() => 42;
}
}
-----
get hook must not have a parameter list from 4:12 to 4:12
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: Scalar_Int(
value: 42
)
)
)
)
)
)
)
-----
<?php
class Test {
public $prop { FOO => bar; }
}
-----
Unknown hook "FOO", expected "get" or "set" from 3:20 to 3:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: FOO
)
params: array(
)
body: Expr_ConstFetch(
name: Name(
name: bar
)
)
)
)
)
)
)
)
-----
<?php
class Test {
public $prop {
public public get;
protected get;
private get;
abstract static get;
readonly get;
}
}
-----
Cannot use the public modifier on a property hook from 4:9 to 4:14
Multiple access type modifiers are not allowed from 4:16 to 4:21
Cannot use the public modifier on a property hook from 4:16 to 4:21
Cannot use the protected modifier on a property hook from 5:9 to 5:17
Cannot use the private modifier on a property hook from 6:9 to 6:15
Cannot use the abstract modifier on a property hook from 7:9 to 7:16
Cannot use the static modifier on a property hook from 7:18 to 7:23
Cannot use the readonly modifier on a property hook from 8:9 to 8:16
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: PUBLIC (1)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: null
)
1: PropertyHook(
attrGroups: array(
)
flags: PROTECTED (2)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: null
)
2: PropertyHook(
attrGroups: array(
)
flags: PRIVATE (4)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: null
)
3: PropertyHook(
attrGroups: array(
)
flags: ABSTRACT | STATIC (24)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: null
)
4: PropertyHook(
attrGroups: array(
)
flags: READONLY (64)
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: null
)
)
)
)
)
)
-----
<?php
class Test
{
public $foo, $bar { get { return 42; } }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: array(
0: Stmt_Return(
expr: Scalar_Int(
value: 42
)
)
)
)
)
)
)
)
)
-----
<?php
class Test
{
public $foo, $bar { }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
Property hook list cannot be empty from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
)
)
)
)
)

@@ -0,0 +1,89 @@
Property modifiers
-----
<?php
class Test {
final public $prop;
abstract protected $prop;
readonly $prop;
private static $prop;
}
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC | FINAL (33)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PROTECTED | ABSTRACT (18)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
)
)
2: Stmt_Property(
attrGroups: array(
)
flags: READONLY (64)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
)
)
3: Stmt_Property(
attrGroups: array(
)
flags: PRIVATE | STATIC (12)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
hooks: array(
)
)
)
)
)

@@ -8,6 +8,8 @@ class Point {
protected array $y = [],
private string $z = 'hello',
public readonly int $a = 0,
public $h { set => $value; },
public $g = 1 { get => 2; },
) {}
}
-----
@@ -47,6 +49,8 @@ array(
default: Scalar_Float(
value: 0
)
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -64,6 +68,8 @@ array(
items: array(
)
)
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -80,6 +86,8 @@ array(
default: Scalar_String(
value: hello
)
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -96,6 +104,66 @@ array(
default: Scalar_Int(
value: 0
)
hooks: array(
)
)
4: Param(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: h
)
default: null
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: set
)
params: array(
)
body: Expr_Variable(
name: value
)
)
)
)
5: Param(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: g
)
default: Scalar_Int(
value: 1
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: Scalar_Int(
value: 2
)
)
)
)
)
returnType: null

@@ -83,6 +83,8 @@ array(
)
)
)
hooks: array(
)
)
2: Stmt_Property(
attrGroups: array(
@@ -97,6 +99,8 @@ array(
default: null
)
)
hooks: array(
)
)
3: Stmt_Property(
attrGroups: array(
@@ -111,6 +115,8 @@ array(
default: null
)
)
hooks: array(
)
)
4: Stmt_ClassMethod(
attrGroups: array(
@@ -146,6 +152,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -25,6 +25,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -39,6 +41,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -53,6 +57,8 @@ array(
name: c
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -67,6 +73,8 @@ array(
name: d
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -81,6 +89,8 @@ array(
name: e
)
default: null
hooks: array(
)
)
5: Param(
attrGroups: array(
@@ -95,6 +105,8 @@ array(
name: f
)
default: null
hooks: array(
)
)
6: Param(
attrGroups: array(
@@ -109,6 +121,8 @@ array(
name: g
)
default: null
hooks: array(
)
)
)
returnType: Identifier(

@@ -25,6 +25,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -50,6 +52,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -38,6 +38,8 @@ array(
name: null
)
)
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -52,6 +54,8 @@ array(
default: Scalar_String(
value: foo
)
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -71,6 +75,8 @@ array(
name: B
)
)
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -87,6 +93,8 @@ array(
value: 1
)
)
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -103,6 +111,8 @@ array(
value: 1
)
)
hooks: array(
)
)
5: Param(
attrGroups: array(
@@ -118,6 +128,8 @@ array(
items: array(
)
)
hooks: array(
)
)
6: Param(
attrGroups: array(
@@ -133,6 +145,8 @@ array(
items: array(
)
)
hooks: array(
)
)
7: Param(
attrGroups: array(
@@ -156,6 +170,8 @@ array(
)
)
)
hooks: array(
)
)
8: Param(
attrGroups: array(
@@ -189,6 +205,8 @@ array(
)
)
)
hooks: array(
)
)
)
returnType: null

@@ -57,6 +57,8 @@ array(
default: null
)
)
hooks: array(
)
)
1: Stmt_Property(
attrGroups: array(
@@ -87,6 +89,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -132,6 +136,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
returnType: UnionType(

@@ -0,0 +1,90 @@
Declaring exit and die function stubs
-----
<?php
function exit(string|int $status = 0): never {}
function die(string|int $status = 0): never {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: exit
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: Identifier(
name: string
)
1: Identifier(
name: int
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: status
)
default: Scalar_Int(
value: 0
)
hooks: array(
)
)
)
returnType: Identifier(
name: never
)
stmts: array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: die
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: Identifier(
name: string
)
1: Identifier(
name: int
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: status
)
default: Scalar_Int(
value: 0
)
hooks: array(
)
)
)
returnType: Identifier(
name: never
)
stmts: array(
)
)
)

@@ -42,6 +42,8 @@ array(
default: null
)
)
hooks: array(
)
)
)
)
@@ -73,6 +75,8 @@ array(
name: a
)
default: null
hooks: array(
)
)
)
returnType: IntersectionType(

@@ -29,6 +29,8 @@ array(
name: bar
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -45,6 +47,8 @@ array(
name: foo
)
default: null
hooks: array(
)
)
)
returnType: NullableType(

@@ -25,6 +25,8 @@ array(
name: bar
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -74,6 +76,8 @@ array(
name: name
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -86,6 +90,8 @@ array(
name: value
)
default: null
hooks: array(
)
)
)
returnType: null
@@ -118,6 +124,8 @@ array(
name: foo
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -24,6 +24,8 @@ array(
name: b
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -38,6 +40,8 @@ array(
name: c
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -52,6 +56,8 @@ array(
name: d
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -66,6 +72,8 @@ array(
name: f
)
default: null
hooks: array(
)
)
)
returnType: null

@@ -34,6 +34,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -48,6 +50,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -62,6 +66,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -76,6 +82,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -90,6 +98,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -107,6 +117,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -124,6 +136,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -141,6 +155,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -158,6 +174,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -228,6 +246,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -242,6 +262,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -256,6 +278,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -270,6 +294,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -284,6 +310,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -301,6 +329,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -318,6 +348,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -335,6 +367,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -352,6 +386,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -422,6 +458,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -436,6 +474,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -450,6 +490,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -464,6 +506,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -478,6 +522,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -495,6 +541,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -512,6 +560,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -529,6 +579,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -546,6 +598,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -616,6 +670,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -630,6 +686,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -644,6 +702,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -658,6 +718,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -672,6 +734,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -689,6 +753,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -706,6 +772,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -723,6 +791,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -740,6 +810,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -810,6 +882,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -824,6 +898,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -838,6 +914,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -852,6 +930,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -866,6 +946,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -883,6 +965,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -900,6 +984,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -917,6 +1003,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -934,6 +1022,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -1004,6 +1094,8 @@ array(
name: a1
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
@@ -1018,6 +1110,8 @@ array(
name: a2
)
default: null
hooks: array(
)
)
2: Param(
attrGroups: array(
@@ -1032,6 +1126,8 @@ array(
name: a3
)
default: null
hooks: array(
)
)
3: Param(
attrGroups: array(
@@ -1046,6 +1142,8 @@ array(
name: a4
)
default: null
hooks: array(
)
)
4: Param(
attrGroups: array(
@@ -1060,6 +1158,8 @@ array(
name: a5
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.0
)
@@ -1077,6 +1177,8 @@ array(
name: a6
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.1
)
@@ -1094,6 +1196,8 @@ array(
name: a7
)
default: null
hooks: array(
)
comments: array(
0: // PHP 7.2
)
@@ -1111,6 +1215,8 @@ array(
name: a8
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)
@@ -1128,6 +1234,8 @@ array(
name: a9
)
default: null
hooks: array(
)
comments: array(
0: // PHP 8.0
)

Some files were not shown because too many files have changed in this diff Show More