Compare commits

..

69 Commits

Author SHA1 Message Date
6bb5176bc4 Release PHP-Parser 4.15.4 2023-03-05 20:49:14 +01:00
cad49f8ed3 Fix formatting preservation for alternative elseif/else syntax
Test taken from PR #797.

(cherry picked from commit 9b46dffb12)
2023-03-05 20:44:50 +01:00
570e980a20 Release PHP-Parser 4.15.3 2023-01-16 23:05:37 +01:00
a50b4310f7 Test PHP 8.2 in CI 2023-01-16 22:27:18 +01:00
8863f92b58 Support readonly before DNF type
This makes us match the PHP 8.2 handling of readonly. Handling of
"readonly" functions is moved to the parser to allow distinguishing
them from readonly properties with DNF types. We have to uglify the
grammar to avoid some shift/reduce conflicts. Thank you WordPress.

(cherry-picked from 0dd85ebd34)
2023-01-16 22:19:17 +01:00
3182d12b55 Fix attrGroups/attributes confusion in EnumCase builder
Found by staabm in #907.

(cherry picked from commit 21a3e8cac5)
2022-12-14 21:51:15 +01:00
1df465cd90 added type int to argument of addAlias function 2022-11-13 16:33:17 +01:00
f59bbe44bf Release PHP-Parser 4.15.2 2022-11-12 16:38:23 +01:00
2e11deec46 Bail out on PHP tags in removed code
If dropping a node would drop PHP tags, bail out of formatting
preservation. This will lose formatting, but at least produce
legal code.

Closes GH-884.

(cherry picked from commit b0edd4c411)
2022-11-12 16:33:34 +01:00
a4fe65bf60 Add more tests for formatting preservation with InlineHTML
It's all broken...

(cherry picked from commit bad10e1618)
2022-11-12 16:33:21 +01:00
e072fd2c30 Adjust tests to work on 32-bit
Fixes #662.

(cherry picked from commit 950bf8f1d1)
2022-11-12 16:24:01 +01:00
7027899d7f Fix parsing of large hex floats containing "e"
These ended up taking the code path for normal floats and being
cast to zero.

(cherry picked from commit 4ce9781260)
2022-11-12 16:24:01 +01:00
2f1fd784fe Fixed type in UnionType 2022-09-10 22:41:13 +02:00
0ef6c55a3f Release PHP-Parser 4.15.1 2022-09-04 09:30:47 +02:00
8216e878be Fix empty list insertion of multiple attributes
(cherry picked from commit 44c6a97705)
2022-09-04 09:29:05 +02:00
617d0220b9 Release PHP-Parser 4.15.0 2022-09-03 21:54:32 +02:00
a951e9e24d Add support for DNF types (#862)
(cherry picked from commit 9b2a01aa0c)
2022-09-03 21:50:20 +02:00
b30e7e73d5 Support empty list insertion for attributes
(cherry picked from commit a2608f0b74)
2022-09-03 21:48:21 +02:00
ff24d1d61a Fix length bounds check in Name::slice()
The length check did not take into account that there may be a
non-zero offset at this point.

Fixes #875.

(cherry picked from commit 53b907d405)
2022-09-03 21:47:28 +02:00
e55f8c6b30 Add __serialize/__unserialize to ClassMethod::$magicNames
(cherry picked from commit a3f2bb634d)
2022-09-03 21:46:15 +02:00
3ee592b6aa Handle true/false/null types in builder APIs
(cherry picked from commit 71ed641cd4)
2022-09-03 21:45:46 +02:00
3fe2422e34 Add support for true type
The null/false types were alread accepted previously, even though
they are only legal as standalone types since PHP 8.2.

Non-trivial backport from 1eba33d4124a248dd5c970fa7d6064d2e31b8635.
2022-09-03 21:36:40 +02:00
2d589921f2 Fix incorrect doc type 2022-06-04 12:44:36 +02:00
cdb731fa8b Update integration test target to 8.1 2022-05-31 23:18:52 +02:00
e727475d08 Support readonly as function name
This special case was added after the PHP 8.1 release.
2022-05-31 23:18:52 +02:00
34bea19b6e Release PHP-Parser 4.14.0 2022-05-31 22:59:12 +02:00
678ccbe072 [PHP 8.2] Add readonly class support (#834)
RFC: https://wiki.php.net/rfc/readonly_classes

PHP implementation: php/php-src#7305
2022-05-15 23:19:31 +02:00
5d83adcc0e [String_] Add rawValue attribute (#831) 2022-05-15 23:04:59 +02:00
3bf0082455 [DNumber] Add rawValue attribute to hold the original value (#833) 2022-05-15 18:12:28 +02:00
d3eb10aca1 [LNumber] Add rawValue attribute to LNumber to allow numeric separator etc. (#832) 2022-05-15 18:09:05 +02:00
a6e34665fd Reflect support for PHP 8.1 in the README 2022-01-03 21:20:19 +01:00
f4b835f7d8 Fix PHPDoc type of namespacedName properties 2021-12-06 21:33:02 +01:00
210577fe3c Release PHP-Parser 4.13.2 2021-11-30 20:35:32 +01:00
f09f22760e Declare namespacedName property
For historical reasons, this property is used by the NameResolver
(in default mode). Declare it explicitly, rather than using a doc
comment.
2021-11-27 21:02:58 +01:00
d4cb98ae38 Fix typo in property name
This test was working on a dynamic "subNode" property, rather
than an actual subnode (either subNode1 or subNode2).

This test is generally not very valuable, I think it dates back
to a time where __get()/__set() were used.
2021-11-27 20:57:41 +01:00
68d2a52b42 Avoid creation of dynamic property in test
This test requires a property that is not a subnode -- but it does
not need to be dynamic, a declared property works just as well.
2021-11-27 20:53:55 +01:00
63f8699143 Add CallLike test for NullsafeMethodCall 2021-11-14 17:44:47 +01:00
4122ff3a91 Make NullsafeMethodCall extend from CallLike 2021-11-14 17:43:29 +01:00
6f1f206862 Fix minor typo 2021-11-13 22:39:16 +01:00
99a24b6a55 Added builders for enum and enum case 2021-11-05 22:26:04 +01:00
63a79e8daa Release PHP-Parser 4.13.1 2021-11-03 21:52:16 +01:00
6a21234e58 Code highlighting 2021-10-31 09:11:00 +01:00
7064539974 Rename identifier/identifier_ex productions
The names were easy to get wrong, because the corresponding names
in zend_language_parser.y are T_STRING/identifier, so just copying
identifier from upstream gives the wrong behavior.
2021-10-17 20:26:06 +02:00
4bfc4595ed Support reserved keywords as enum cases
Fixes #807.
2021-10-17 20:20:35 +02:00
54f19a0a66 Fixed array value evaluation with unpacked array 2021-10-11 21:35:21 +02:00
8da6d7ac62 Fixed ArrowFunction::$expr 2021-10-09 11:26:27 +02:00
f6e1fbf3a2 Update .gitattributes 2021-10-08 21:23:55 +02:00
50953a2691 Release PHP-Parser 4.13.0 2021-09-20 14:20:58 +02:00
0a20979a62 Unified builder methods for setting types 2021-09-14 18:09:07 +02:00
a45fb2a621 Add CallLike parent class
This provides a helper to determine whether a call is a first-class
callable, and a way to strip the args type to Arg[] if it isn't.
2021-09-12 22:29:08 +02:00
08501991d4 Don't make VariadicPlaceholder an expression
And don't store it in an Arg.
2021-09-12 21:59:26 +02:00
b5234eacd0 Tweak coding style 2021-09-12 21:54:03 +02:00
632ead3a82 Print comma before comments for new array item (#805)
Print comma before rather than after comments. Also switch to multiline mode if inserting an item with comments.

Fixes #804.
2021-09-12 21:51:25 +02:00
13549aa794 Add support for first-class callables
I'm somewhat unsure about the AST structure here.
VariadicPlaceholder is not a general expression. Maybe Arg->expr
should be Expr|VariadicPlaceholder? Or possibly the call arguments
should be an array of Arg|VariadicPlaceholder?
2021-09-03 17:18:40 +02:00
d2c645f163 Adjust token count in octal emulator 2021-09-03 16:35:10 +02:00
def24f2224 Add support for explicit octal literals 2021-09-03 16:29:30 +02:00
cfeb195205 Add test for new in initializer
This already works because we don't validate initializer contents,
everything is accepted from a parser perspective.
2021-09-03 15:10:29 +02:00
ace6c67a8a Add support for intersection types 2021-09-03 15:06:33 +02:00
0483391aca Introduce ComplexType base class
With the upcoming addition of intersection types, a type can
be Identifier|Name|NullableType|UnionType|IntersectionType, which
is quite the mouthful. Give NullableType and UnionType a common
base class ComplexType, which does not have any behavior, but
allows to write these types (and check them in instanceof) more
easily.
2021-09-02 18:35:05 +02:00
9aebf377fc Allow multiple modifiers for property promotion
Fixes issue #800.
2021-08-08 19:12:44 +02:00
5a43015499 Simplify BuilderHelpers::normalizeName() implementation
In order to get rid of the flag in `BuilderHelpers::normalizeNameCommon()` I have moved all the logic related to the normalization of the name to the `BuilderHelpers::normalizeName()` method and expr-related stuff to the `BuilderHelpers::normalizeNameOrExpr()` method which later calls the basic `normalizeName()` as well
2021-07-21 12:51:18 +02:00
6608f01670 Release PHP-Parser 4.12.0 2021-07-21 12:44:31 +02:00
55c4269232 Add support for new PHP 8.1 modifiers (#796)
Implement support for readonly properties (https://wiki.php.net/rfc/readonly_properties_v2) and
final class contstants (https://wiki.php.net/rfc/final_class_const).
2021-07-21 12:43:29 +02:00
c4304c76bd Try to pass --ignore-platform-req=php on 8.1 only 2021-07-21 12:11:59 +02:00
b099e8fc76 Pass --ignore-platform-req=php 2021-07-21 12:08:11 +02:00
acf16edc8e Add PHP 8.1 to GH actions 2021-07-21 11:58:38 +02:00
a8b5ed4306 Fix JSON encoding test on PHP 8.1
Duplicate the test expectation for a different order.
2021-07-21 11:49:28 +02:00
c758510a37 Add support for PHP 8.1
With the introduction of intersection types, PHP now lexes the
token '&' either as T_AMPERSAND_(NOT_)FOLLOWED_BY_VAR_OR_VARARG.
This completely breaks parsing of any code containing '&'.

Fix this by canonicalizing to the new token format (unconditionally,
independent of emulation) and adjusting the parser to use the two
new tokens.

This doesn't add actual support for intersection types yet.
2021-07-09 16:52:58 +02:00
feed91cf0f Avoid ctype_alnum() on integer
This is deprecated in PHP 8.1
2021-07-09 15:46:50 +02:00
114 changed files with 6333 additions and 3405 deletions

1
.gitattributes vendored
View File

@ -2,6 +2,7 @@
/doc export-ignore
/test export-ignore
/test_old export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
CHANGELOG.md export-ignore

View File

@ -39,6 +39,8 @@ jobs:
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
@ -49,7 +51,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
tools: composer:v2
- name: "Install dependencies"
run: "composer update --no-progress --prefer-dist"
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
- name: "PHPUnit"
run: "php vendor/bin/phpunit"
test_old_73_80:
@ -70,7 +72,7 @@ jobs:
run: "test_old/run-php-src.sh 7.3.21"
test_old_80_70:
runs-on: "ubuntu-latest"
name: "PHP 8.0 Code on PHP 7.0 Integration Tests"
name: "PHP 8.1 Code on PHP 7.0 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
@ -83,4 +85,4 @@ jobs:
- name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 8.0.0"
run: "test_old/run-php-src.sh 8.1.6"

View File

@ -1,7 +1,116 @@
Version 4.11.1-dev
------------------
Version 4.15.4 (2023-03-05)
---------------------------
Nothing yet.
### Fixed
* Fixed formatting-preservation for alternative if syntax with trailing comments.
Version 4.15.3 (2023-01-16)
---------------------------
### Fixed
* Support readonly property with PHP 8.2 DNF type.
* Fixed PHP attribute group and PHP-Parser attribute mixup in EnumCase builder.
Version 4.15.2 (2022-11-12)
---------------------------
### Fixed
* Fixed parsing of large hex float literals that contain an "e" character.
* Fixed tests to pass on 32-bit.
* Fixed generation of invalid code when using formatting-preserving pretty printer with code that
uses inline HTML.
Version 4.15.1 (2022-09-04)
---------------------------
### Fixed
* Fixed formatting preservation when adding *multiple* attributes to a class/method/etc that
previously had none. This fixes a regression in the 4.15.0 release.
Version 4.15.0 (2022-09-03)
---------------------------
### Added
* PHP 8.2: Added support for `true` type.
* PHP 8.2: Added support for DNF types.
### Fixed
* Support `readonly` as a function name.
* Added `__serialize` and `__unserialize` to magic method list.
* Fixed bounds check in `Name::slice()`.
* Fixed formatting preservation when adding attributes to a class/method/etc that previously had none.
Version 4.14.0 (2022-05-31)
---------------------------
### Added
* Added support for readonly classes.
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
value of the literal (e.g. `"1_000"` rather than `1000`).
Version 4.13.2 (2021-11-30)
---------------------------
### Added
* Added builders for enums and enum cases.
### Fixed
* NullsafeMethodCall now extends from CallLike.
* The `namespacedName` property populated by the `NameResolver` is now declared on relevant nodes,
to avoid a dynamic property deprecation warning with PHP 8.2.
Version 4.13.1 (2021-11-03)
---------------------------
### Fixed
* Support reserved keywords as enum cases.
* Support array unpacking in constant expression evaluator.
Version 4.13.0 (2021-09-20)
---------------------------
### Added
* [PHP 8.1] Added support for intersection types using a new `IntersectionType` node. Additionally
a `ComplexType` parent class for `NullableType`, `UnionType` and `IntersectionType` has been
added.
* [PHP 8.1] Added support for explicit octal literals.
* [PHP 8.1] Added support for first-class callables. These are represented using a call whose first
argument is a `VariadicPlaceholder`. The representation is intended to be forward-compatible with
partial function application, just like the PHP feature itself. Call nodes now extend from
`Expr\CallLike`, which provides an `isFirstClassCallable()` method to determine whether a
placeholder id present. `getArgs()` can be used to assert that the call is not a first-class
callable and returns `Arg[]` rather than `array<Arg|VariadicPlaceholder>`.
### Fixed
* Multiple modifiers for promoted properties are now accepted. In particular this allows something
like `public readonly` for promoted properties.
* Formatting-preserving pretty printing for comments in array literals has been fixed.
Version 4.12.0 (2021-07-21)
---------------------------
### Added
* [PHP 8.1] Added support for readonly properties (through a new `MODIFIER_READONLY`).
* [PHP 8.1] Added support for final class constants.
### Fixed
* Fixed compatibility with PHP 8.1. `&` tokens are now canonicalized to the
`T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` and `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` tokens
used in PHP 8.1. This happens unconditionally, regardless of whether the emulative lexer is used.
Version 4.11.0 (2021-07-03)
---------------------------

View File

@ -3,10 +3,10 @@ PHP Parser
[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and
This is a PHP 5.2 to PHP 8.2 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0).
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.2).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
@ -222,4 +222,4 @@ Component documentation:
* Parent and sibling references
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc

View File

@ -193,7 +193,7 @@ anonymous classes), you know that once you've seen a class declaration, there is
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
instruct the traverser to not recurse into the class node:
```
```php
private $classes = [];
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_) {
@ -211,7 +211,7 @@ after finding it. For example, if you are looking for the node of a class with a
discounting exotic cases like conditionally defining a class two times), you can stop traversal
once you found it:
```
```php
private $class = null;
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_ &&
@ -251,7 +251,7 @@ Stmt_Return(
the following method calls will be performed:
```
```php
$visitorA->enterNode(Stmt_Return)
$visitorB->enterNode(Stmt_Return)
$visitorA->enterNode(Expr_Variable)

View File

@ -20,6 +20,11 @@ top_statement_list:
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
ampersand:
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
@ -246,7 +251,12 @@ variables_list:
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
| ampersand { $$ = true; }
;
optional_arg_ref:
/* empty */ { $$ = false; }
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
;
optional_ellipsis:
@ -254,8 +264,13 @@ optional_ellipsis:
| T_ELLIPSIS { $$ = true; }
;
identifier_maybe_readonly:
identifier { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
@ -378,7 +393,7 @@ new_else_single:
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| ampersand variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
;
@ -393,9 +408,9 @@ non_empty_parameter_list:
;
parameter:
optional_param_type optional_ref optional_ellipsis plain_variable
optional_param_type optional_arg_ref optional_ellipsis plain_variable
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
| optional_param_type optional_arg_ref optional_ellipsis plain_variable '=' static_scalar
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
;
@ -428,7 +443,7 @@ non_empty_argument_list:
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
;
@ -562,8 +577,8 @@ expr:
variable { $$ = $1; }
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand new_expr { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
@ -589,7 +604,8 @@ expr:
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -678,9 +694,7 @@ array_expr:
scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
@ -712,8 +726,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
@ -782,10 +801,8 @@ ctor_arguments:
common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
@ -816,7 +833,10 @@ static_operation:
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG static_scalar
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG static_scalar
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -986,8 +1006,8 @@ non_empty_array_pair_list:
array_pair:
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
;

View File

@ -20,6 +20,11 @@ top_statement_list:
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
ampersand:
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
@ -33,15 +38,15 @@ reserved_non_modifiers:
semi_reserved:
reserved_non_modifiers
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY
;
identifier_ex:
identifier_maybe_reserved:
T_STRING { $$ = Node\Identifier[$1]; }
| semi_reserved { $$ = Node\Identifier[$1]; }
;
identifier:
identifier_not_reserved:
T_STRING { $$ = Node\Identifier[$1]; }
;
@ -176,14 +181,14 @@ non_empty_inline_use_declarations:
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS identifier
| namespace_name T_AS identifier_not_reserved
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
legacy_namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| legacy_namespace_name T_AS identifier
| legacy_namespace_name T_AS identifier_not_reserved
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
@ -203,7 +208,7 @@ non_empty_constant_declaration_list:
;
constant_declaration:
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
@ -216,7 +221,7 @@ non_empty_class_const_list:
;
class_const:
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list_ex:
@ -284,8 +289,8 @@ non_empty_statement:
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
;
@ -327,7 +332,12 @@ non_empty_variables_list:
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
| ampersand { $$ = true; }
;
optional_arg_ref:
/* empty */ { $$ = false; }
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
;
optional_ellipsis:
@ -340,23 +350,31 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
identifier_not_reserved { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
class_declaration_statement:
optional_attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
$this->checkClass($$, #2); }
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #3); }
| optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
$this->checkInterface($$, #3); }
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
| optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
| optional_attributes T_ENUM identifier enum_scalar_type implements_list '{' class_statement_list '}'
| optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}'
{ $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkEnum($$, #3); }
;
@ -372,8 +390,18 @@ enum_case_expr:
class_entry_type:
T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
| class_modifiers T_CLASS { $$ = $1; }
;
class_modifiers:
class_modifier { $$ = $1; }
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
;
class_modifier:
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
extends_from:
@ -426,7 +454,7 @@ non_empty_declare_list:
;
declare_list_element:
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
@ -490,7 +518,8 @@ new_elseif_list:
;
new_elseif:
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
T_ELSEIF '(' expr ')' ':' inner_statement_list
{ $$ = Stmt\ElseIf_[$3, $6]; $this->fixupAlternativeElse($$); }
;
else_single:
@ -500,12 +529,13 @@ else_single:
new_else_single:
/* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
| T_ELSE ':' inner_statement_list
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
;
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| ampersand variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
| array_short_syntax { $$ = array($1, false); }
;
@ -520,21 +550,30 @@ non_empty_parameter_list:
| non_empty_parameter_list ',' parameter { push($1, $3); }
;
optional_visibility_modifier:
optional_property_modifiers:
/* empty */ { $$ = 0; }
| T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| optional_property_modifiers property_modifier
{ $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
property_modifier:
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
parameter:
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
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_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
| 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_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
| 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); }
;
@ -542,6 +581,7 @@ type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = $1; }
;
type:
@ -555,20 +595,52 @@ type_without_static:
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type_element:
type { $$ = $1; }
| '(' intersection_type ')' { $$ = $2; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
union_type_element '|' union_type_element { init($1, $3); }
| union_type '|' union_type_element { push($1, $3); }
;
union_type_without_static_element:
type_without_static { $$ = $1; }
| '(' intersection_type_without_static ')' { $$ = $2; }
;
union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); }
| union_type_without_static '|' type_without_static { push($1, $3); }
union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
| union_type_without_static '|' union_type_without_static_element { push($1, $3); }
;
intersection_type_list:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ push($1, $3); }
;
intersection_type:
intersection_type_list { $$ = Node\IntersectionType[$1]; }
;
intersection_type_without_static_list:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
;
intersection_type_without_static:
intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
;
type_expr_without_static:
type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = $1; }
;
optional_type_without_static:
@ -585,6 +657,11 @@ optional_return_type:
argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
| '(' variadic_placeholder ')' { init($2); }
;
variadic_placeholder:
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
;
non_empty_argument_list:
@ -594,9 +671,9 @@ non_empty_argument_list:
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
| identifier_ex ':' expr
| identifier_maybe_reserved ':' expr
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
;
@ -645,11 +722,12 @@ class_statement:
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
optional_return_type method_body
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
$this->checkClassMethod($$, #2); }
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
| optional_attributes T_CASE identifier enum_case_expr semi
| optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
| error { $$ = null; /* will be skipped */ }
;
@ -667,22 +745,22 @@ trait_adaptation_list:
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier_ex ';'
| trait_method_reference T_AS member_modifier identifier_maybe_reserved ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS identifier ';'
| trait_method_reference T_AS identifier_not_reserved ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier_ex { $$ = array(null, $1); }
| identifier_maybe_reserved { $$ = array(null, $1); }
;
method_body:
@ -712,6 +790,7 @@ member_modifier:
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
property_declaration_list:
@ -756,7 +835,7 @@ expr:
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| match { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
@ -783,7 +862,8 @@ expr:
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -892,8 +972,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
@ -953,7 +1038,7 @@ constant:
;
class_constant:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
{ $$ = Expr\ClassConstFetch[$1, $3]; }
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
@ -972,9 +1057,7 @@ dereferencable_scalar:
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
@ -982,7 +1065,7 @@ dereferencable_scalar:
scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| class_constant { $$ = $1; }
@ -1072,13 +1155,13 @@ new_variable:
;
member_name:
identifier_ex { $$ = $1; }
identifier_maybe_reserved { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = $1; }
;
property_name:
identifier { $$ = $1; }
identifier_not_reserved { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
@ -1106,10 +1189,10 @@ inner_array_pair_list:
array_pair:
expr { $$ = Expr\ArrayItem[$1, null, false]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| /* empty */ { $$ = null; }
@ -1133,8 +1216,10 @@ encaps_str_varname:
encaps_var:
plain_variable { $$ = $1; }
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier_not_reserved
{ $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'

View File

@ -128,14 +128,6 @@ function resolveMacros($code) {
. ' else { ' . $args[0] . ' = null; }';
}
if ('strKind' === $name) {
assertArgs(1, $args, $name);
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);

View File

@ -18,7 +18,7 @@
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left '&'
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
@ -74,7 +74,7 @@
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_VAR
%token T_UNSET
%token T_ISSET

View File

@ -77,6 +77,17 @@ class ClassConst implements PhpParser\Builder
return $this;
}
/**
* Makes the constant final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Sets doc comment for the constant.
*

View File

@ -67,7 +67,7 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
@ -78,7 +78,13 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder
{
protected $name;
protected $value = null;
protected $attributes = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*/
public function __construct($name) {
$this->name = $name;
}
/**
* Sets the value.
*
* @param Node\Expr|string|int $value
*
* @return $this
*/
public function setValue($value) {
$this->value = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built enum case node.
*
* @return Stmt\EnumCase The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\EnumCase(
$this->name,
$this->value,
$this->attributeGroups,
$this->attributes
);
}
}

View File

@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Enum_ extends Declaration
{
protected $name;
protected $scalarType = null;
protected $implements = [];
protected $uses = [];
protected $enumCases = [];
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets the scalar type.
*
* @param string|Identifier $type
*
* @return $this
*/
public function setScalarType($scalarType) {
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\EnumCase::class => &$this->enumCases,
Stmt\ClassConst::class => &$this->constants,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Enum_ The built enum node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Enum_($this->name, [
'scalarType' => $this->scalarType,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
* bool, iterable, or a class/interface name.
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
*
* @return $this The builder instance (for fluid interface)
*/

View File

@ -47,7 +47,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
@ -63,7 +63,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*

View File

@ -7,8 +7,8 @@ use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
use PhpParser\Node\ComplexType;
class Property implements PhpParser\Builder
{
@ -77,6 +77,17 @@ class Property implements PhpParser\Builder
return $this;
}
/**
* Makes the property readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Sets default value for the property.
*
@ -108,7 +119,7 @@ class Property implements PhpParser\Builder
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|NullableType|Identifier $type
* @param string|Name|Identifier|ComplexType $type
*
* @return $this
*/

View File

@ -71,6 +71,17 @@ class BuilderFactory
return new Builder\Trait_($name);
}
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*
* @return Builder\Enum_ The created enum builder
*/
public function enum(string $name) : Builder\Enum_ {
return new Builder\Enum_($name);
}
/**
* Creates a trait use builder.
*
@ -188,6 +199,17 @@ class BuilderFactory
return new Builder\ClassConst($name, $value);
}
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*
* @return Builder\EnumCase The created use const builder
*/
public function enumCase($name) : Builder\EnumCase {
return new Builder\EnumCase($name);
}
/**
* Creates node a for a literal value.
*
@ -311,7 +333,7 @@ class BuilderFactory
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*

View File

@ -2,13 +2,13 @@
namespace PhpParser;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
/**
* This class defines helpers used in the implementation of builders. Don't use it directly.
@ -104,29 +104,6 @@ final class BuilderHelpers
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
return self::normalizeNameCommon($name, false);
}
/**
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
*
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
return self::normalizeNameCommon($name, true);
}
/**
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
* @param bool $allowExpr Whether to also allow expressions
*
* @return Name|Expr The normalized name, or expression (if allowed)
*/
private static function normalizeNameCommon($name, bool $allowExpr) {
if ($name instanceof Name) {
return $name;
}
@ -147,16 +124,28 @@ final class BuilderHelpers
return new Name($name);
}
if ($allowExpr) {
if ($name instanceof Expr) {
return $name;
}
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
/**
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
*
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
if ($name instanceof Expr) {
return $name;
}
if (!is_string($name) && !($name instanceof Name)) {
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
}
throw new \LogicException('Name must be a string or an instance of Node\Name');
return self::normalizeName($name);
}
/**
@ -165,18 +154,18 @@ final class BuilderHelpers
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
* @param string|Name|Identifier|ComplexType $type The type to normalize
*
* @return Name|Identifier|NullableType|UnionType The normalized type
* @return Name|Identifier|ComplexType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof NullableType && !$type instanceof UnionType
!$type instanceof ComplexType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
'Type must be a string, or an instance of Name, Identifier or ComplexType'
);
}
return $type;
@ -189,7 +178,20 @@ final class BuilderHelpers
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never',
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);
@ -321,4 +323,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
}

View File

@ -2,6 +2,7 @@
namespace PhpParser;
use function array_merge;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
@ -150,6 +151,8 @@ class ConstExprEvaluator
foreach ($expr->items as $item) {
if (null !== $item->key) {
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
} elseif ($item->unpack) {
$array = array_merge($array, $this->evaluate($item->value));
} else {
$array[] = $this->evaluate($item->value);
}

View File

@ -206,6 +206,11 @@ class TokenStream
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
}
/**
* Get indentation before token position.
*

View File

@ -134,10 +134,11 @@ class Lexer
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
//
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
// the trailing whitespace anymore.
//
// We also canonicalize to the PHP 8 T_NAME_* tokens.
// Additionally, we perform a number of canonicalizations here:
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
// * Use PHP 8.0 T_NAME_* tokens.
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
$filePos = 0;
$line = 1;
@ -208,6 +209,22 @@ class Lexer
}
}
if ($token === '&') {
$next = $i + 1;
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
$next++;
}
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
$this->tokens[$i] = $token = [
$followedByVarOrVarArg
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
'&',
$line,
];
}
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
@ -424,6 +441,9 @@ class Lexer
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
];
// PHP-Parser might be used together with another library that also emulates some or all
@ -514,7 +534,10 @@ class Lexer
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
return $tokenMap;
}
@ -523,7 +546,7 @@ class Lexer
// Based on semi_reserved production.
return array_fill_keys([
\T_STRING,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,

View File

@ -8,11 +8,14 @@ use PhpParser\Lexer;
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\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
@ -22,6 +25,7 @@ class Emulative extends Lexer
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
const PHP_8_2 = '8.2dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
@ -35,11 +39,11 @@ class Emulative extends Lexer
/**
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulated. Defaults to newest supported.
* version to emulate. Defaults to newest supported.
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
unset($options['phpVersion']);
parent::__construct($options);
@ -53,6 +57,9 @@ class Emulative extends Lexer
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

View File

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
class ExplicitOctalEmulator extends TokenEmulator {
public function getPhpVersion(): string {
return Emulative::PHP_8_1;
}
public function isEmulationNeeded(string $code): bool {
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
) {
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
array_splice($tokens, $i, 2, [
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
]);
$c--;
}
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = substr($str, 1);
$str = str_replace('_', '', $str);
$num = octdec($str);
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array {
// Explicit octals were not legal code previously, don't bother.
return $tokens;
}
}

View File

@ -33,7 +33,7 @@ abstract class KeywordEmulator extends TokenEmulator
/**
* @param mixed[] $tokens
* @return mixed[]|null
* @return array|string|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
/*
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
* parser. This emulator only exists to handle this special case, which is skipped by the
* PHP 8.1 ReadonlyTokenEmulator.
*/
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
public function getKeywordString(): string {
return 'readonly';
}
public function getKeywordToken(): int {
return \T_READONLY;
}
public function getPhpVersion(): string {
return Emulative::PHP_8_2;
}
public function reverseEmulate(string $code, array $tokens): array {
// Don't bother
return $tokens;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class ReadonlyTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_1;
}
public function getKeywordString(): string
{
return 'readonly';
}
public function getKeywordToken(): int
{
return \T_READONLY;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
if (!parent::isKeywordContext($tokens, $pos)) {
return false;
}
// Support "function readonly("
return !(isset($tokens[$pos + 1]) &&
($tokens[$pos + 1][0] === '(' ||
($tokens[$pos + 1][0] === \T_WHITESPACE &&
isset($tokens[$pos + 2]) &&
$tokens[$pos + 2][0] === '(')));
}
}

View File

@ -2,6 +2,7 @@
namespace PhpParser\Node;
use PhpParser\Node\VariadicPlaceholder;
use PhpParser\NodeAbstract;
class Arg extends NodeAbstract

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* This is a base class for complex types, including nullable types and union types.
*
* It does not provide any shared behavior and exists only for type-checking purposes.
*/
abstract class ComplexType extends NodeAbstract
{
}

View File

@ -4,9 +4,6 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
*/
class Const_ extends NodeAbstract
{
/** @var Identifier Name */
@ -14,6 +11,9 @@ class Const_ extends NodeAbstract
/** @var Expr Value */
public $value;
/** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* Constructs a const node for use in class const and const statements.
*
@ -30,7 +30,7 @@ class Const_ extends NodeAbstract
public function getSubNodeNames() : array {
return ['name', 'value'];
}
public function getType() : string {
return 'Const';
}

View File

@ -17,7 +17,7 @@ class ArrowFunction extends Expr implements FunctionLike
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
public $returnType;
/** @var Expr */
@ -42,7 +42,7 @@ class ArrowFunction extends Expr implements FunctionLike
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
$this->expr = $subNodes['expr'];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
abstract class CallLike extends Expr {
/**
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
* callables.
*
* @return array<Arg|VariadicPlaceholder>
*/
abstract public function getRawArgs(): array;
/**
* Returns whether this call expression is actually a first class callable.
*/
public function isFirstClassCallable(): bool {
foreach ($this->getRawArgs() as $arg) {
if ($arg instanceof VariadicPlaceholder) {
return true;
}
}
return false;
}
/**
* Assert that this is not a first-class callable and return only ordinary Args.
*
* @return Arg[]
*/
public function getArgs(): array {
assert(!$this->isFirstClassCallable());
return $this->getRawArgs();
}
}

View File

@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;

View File

@ -5,19 +5,19 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class FuncCall extends Expr
class FuncCall extends CallLike
{
/** @var Node\Name|Expr Function name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $name Function name
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -32,4 +32,8 @@ class FuncCall extends Expr
public function getType() : string {
return 'Expr_FuncCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class MethodCall extends Expr
class MethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +38,8 @@ class MethodCall extends Expr
public function getType() : string {
return 'Expr_MethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,20 +3,22 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
class New_ extends Expr
class New_ extends CallLike
{
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = [], array $attributes = []) {
@ -32,4 +34,8 @@ class New_ extends Expr
public function getType() : string {
return 'Expr_New';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class NullsafeMethodCall extends Expr
class NullsafeMethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a nullsafe method call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +38,8 @@ class NullsafeMethodCall extends Expr
public function getType() : string {
return 'Expr_NullsafeMethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,25 +3,27 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class StaticCall extends Expr
class StaticCall extends CallLike
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a static method call node.
*
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +39,8 @@ class StaticCall extends Expr
public function getType() : string {
return 'Expr_StaticCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -23,7 +23,7 @@ interface FunctionLike extends Node
/**
* Get the declared return type or null
*
* @return null|Identifier|Name|NullableType|UnionType
* @return null|Identifier|Name|ComplexType
*/
public function getReturnType();

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class IntersectionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
public $types;
/**
* Constructs an intersection type.
*
* @param (Identifier|Name)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {
$this->attributes = $attributes;
$this->types = $types;
}
public function getSubNodeNames() : array {
return ['types'];
}
public function getType() : string {
return 'IntersectionType';
}
}

View File

@ -162,7 +162,7 @@ class Name extends NodeAbstract
$realLength = $numParts - $realOffset;
} else {
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
if ($realLength < 0 || $realLength > $numParts) {
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
}
}

View File

@ -2,9 +2,7 @@
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class NullableType extends NodeAbstract
class NullableType extends ComplexType
{
/** @var Identifier|Name Type */
public $type;

View File

@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
/** @var null|Identifier|Name|ComplexType Type declaration */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
@ -24,14 +24,14 @@ class Param extends NodeAbstract
/**
* Constructs a parameter node.
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
$var, Expr $default = null, $type = null,

View File

@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value'];
}
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/**
* @internal
*
@ -36,13 +47,7 @@ class DNumber extends Scalar
public static function parse(string $str) : float {
$str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) {
return (float) $str;
}
// otherwise it's an integer notation that overflowed into a float
// if it starts with 0 it's one of the special integer notations
// Check whether this is one of the special integer notations.
if ('0' === $str[0]) {
// hex
if ('x' === $str[1] || 'X' === $str[1]) {
@ -54,16 +59,18 @@ class DNumber extends Scalar
return bindec($str);
}
// oct
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
// so that only the digits before that are used
return octdec(substr($str, 0, strcspn($str, '89')));
// oct, but only if the string does not contain any of '.eE'.
if (false === strpbrk($str, '.eE')) {
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
// (8 or 9) so that only the digits before that are used.
return octdec(substr($str, 0, strcspn($str, '89')));
}
}
// dec
return (float) $str;
}
public function getType() : string {
return 'Scalar_DNumber';
}

View File

@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute
*/
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$attributes['rawValue'] = $str;
$str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) {
@ -62,11 +64,16 @@ class LNumber extends Scalar
throw new Error('Invalid numeric literal', $attributes);
}
// Strip optional explicit octal prefix.
if ('o' === $str[1] || 'O' === $str[1]) {
$str = substr($str, 2);
}
// use intval instead of octdec to get proper cutting behavior with malformed numbers
$attributes['kind'] = LNumber::KIND_OCT;
return new LNumber(intval($str, 8), $attributes);
}
public function getType() : string {
return 'Scalar_LNumber';
}

View File

@ -42,6 +42,22 @@ class String_ extends Scalar
return ['value'];
}
/**
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*/
public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
{
$attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
? Scalar\String_::KIND_SINGLE_QUOTED
: Scalar\String_::KIND_DOUBLE_QUOTED;
$attributes['rawValue'] = $str;
$string = self::parse($str, $parseUnicodeEscape);
return new self($string, $attributes);
}
/**
* @internal
*

View File

@ -65,6 +65,15 @@ class ClassConst extends Node\Stmt
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}
/**
* Whether constant is final.
*
* @return bool
*/
public function isFinal() : bool {
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
}
public function getType() : string {
return 'Stmt_ClassConst';
}

View File

@ -4,9 +4,6 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
abstract class ClassLike extends Node\Stmt
{
/** @var Node\Identifier|null Name */
@ -16,6 +13,9 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* @return TraitUse[]
*/

View File

@ -15,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[]|null Statements */
public $stmts;
@ -23,21 +23,23 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $attrGroups;
private static $magicNames = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__serialize' => true,
'__unserialize' => true,
];
/**

View File

@ -13,6 +13,7 @@ class Class_ extends ClassLike
const MODIFIER_STATIC = 8;
const MODIFIER_ABSTRACT = 16;
const MODIFIER_FINAL = 32;
const MODIFIER_READONLY = 64;
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
@ -67,6 +68,10 @@ class Class_ extends ClassLike
return (bool) ($this->flags & self::MODIFIER_FINAL);
}
public function isReadonly() : bool {
return (bool) ($this->flags & self::MODIFIER_READONLY);
}
/**
* Whether the class is anonymous.
*
@ -76,6 +81,27 @@ class Class_ extends ClassLike
return null === $this->name;
}
/**
* @internal
*/
public static function verifyClassModifier($a, $b) {
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class');
}
}
/**
* @internal
*/
@ -96,6 +122,10 @@ class Class_ extends ClassLike
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class member');
}

View File

@ -5,9 +5,6 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
class Function_ extends Node\Stmt implements FunctionLike
{
/** @var bool Whether function returns by reference */
@ -16,13 +13,16 @@ class Function_ extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* Constructs a function node.
*

View File

@ -3,10 +3,9 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
class Property extends Node\Stmt
{
@ -14,7 +13,7 @@ class Property extends Node\Stmt
public $flags;
/** @var PropertyProperty[] Properties */
public $props;
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
/** @var null|Identifier|Name|ComplexType Type declaration */
public $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
@ -22,11 +21,11 @@ class Property extends Node\Stmt
/**
* Constructs a class property list node.
*
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
$this->attributes = $attributes;
@ -77,6 +76,15 @@ class Property extends Node\Stmt
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
}
/**
* Whether the property is readonly.
*
* @return bool
*/
public function isReadonly() : bool {
return (bool) ($this->flags & Class_::MODIFIER_READONLY);
}
public function getType() : string {
return 'Stmt_Property';
}

View File

@ -2,17 +2,15 @@
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class UnionType extends NodeAbstract
class UnionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
/** @var (Identifier|Name|IntersectionType)[] Types */
public $types;
/**
* Constructs a union type.
*
* @param (Identifier|Name)[] $types Types
* @param (Identifier|Name|IntersectionType)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Represents the "..." in "foo(...)" of the first-class callable syntax.
*/
class VariadicPlaceholder extends NodeAbstract {
/**
* Create a variadic argument placeholder (first-class callable syntax).
*
* @param array $attributes Additional attributes
*/
public function __construct(array $attributes = []) {
$this->attributes = $attributes;
}
public function getType(): string {
return 'VariadicPlaceholder';
}
public function getSubNodeNames(): array {
return [];
}
}

View File

@ -128,6 +128,9 @@ class NodeDumper
if ($flags & Class_::MODIFIER_FINAL) {
$strs[] = 'MODIFIER_FINAL';
}
if ($flags & Class_::MODIFIER_READONLY) {
$strs[] = 'MODIFIER_READONLY';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';

View File

@ -161,7 +161,7 @@ class NameResolver extends NodeVisitorAbstract
return null;
}
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
private function addAlias(Stmt\UseUse $use, int $type, Name $prefix = null) {
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
@ -189,7 +189,7 @@ class NameResolver extends NodeVisitorAbstract
$node->type = $this->resolveType($node->type);
return $node;
}
if ($node instanceof Node\UnionType) {
if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
foreach ($node->types as &$type) {
$type = $this->resolveType($type);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,111 +35,114 @@ final class Tokens
const T_COALESCE = 283;
const T_BOOLEAN_OR = 284;
const T_BOOLEAN_AND = 285;
const T_IS_EQUAL = 286;
const T_IS_NOT_EQUAL = 287;
const T_IS_IDENTICAL = 288;
const T_IS_NOT_IDENTICAL = 289;
const T_SPACESHIP = 290;
const T_IS_SMALLER_OR_EQUAL = 291;
const T_IS_GREATER_OR_EQUAL = 292;
const T_SL = 293;
const T_SR = 294;
const T_INSTANCEOF = 295;
const T_INC = 296;
const T_DEC = 297;
const T_INT_CAST = 298;
const T_DOUBLE_CAST = 299;
const T_STRING_CAST = 300;
const T_ARRAY_CAST = 301;
const T_OBJECT_CAST = 302;
const T_BOOL_CAST = 303;
const T_UNSET_CAST = 304;
const T_POW = 305;
const T_NEW = 306;
const T_CLONE = 307;
const T_EXIT = 308;
const T_IF = 309;
const T_ELSEIF = 310;
const T_ELSE = 311;
const T_ENDIF = 312;
const T_LNUMBER = 313;
const T_DNUMBER = 314;
const T_STRING = 315;
const T_STRING_VARNAME = 316;
const T_VARIABLE = 317;
const T_NUM_STRING = 318;
const T_INLINE_HTML = 319;
const T_ENCAPSED_AND_WHITESPACE = 320;
const T_CONSTANT_ENCAPSED_STRING = 321;
const T_ECHO = 322;
const T_DO = 323;
const T_WHILE = 324;
const T_ENDWHILE = 325;
const T_FOR = 326;
const T_ENDFOR = 327;
const T_FOREACH = 328;
const T_ENDFOREACH = 329;
const T_DECLARE = 330;
const T_ENDDECLARE = 331;
const T_AS = 332;
const T_SWITCH = 333;
const T_MATCH = 334;
const T_ENDSWITCH = 335;
const T_CASE = 336;
const T_DEFAULT = 337;
const T_BREAK = 338;
const T_CONTINUE = 339;
const T_GOTO = 340;
const T_FUNCTION = 341;
const T_FN = 342;
const T_CONST = 343;
const T_RETURN = 344;
const T_TRY = 345;
const T_CATCH = 346;
const T_FINALLY = 347;
const T_USE = 348;
const T_INSTEADOF = 349;
const T_GLOBAL = 350;
const T_STATIC = 351;
const T_ABSTRACT = 352;
const T_FINAL = 353;
const T_PRIVATE = 354;
const T_PROTECTED = 355;
const T_PUBLIC = 356;
const T_VAR = 357;
const T_UNSET = 358;
const T_ISSET = 359;
const T_EMPTY = 360;
const T_HALT_COMPILER = 361;
const T_CLASS = 362;
const T_TRAIT = 363;
const T_INTERFACE = 364;
const T_ENUM = 365;
const T_EXTENDS = 366;
const T_IMPLEMENTS = 367;
const T_OBJECT_OPERATOR = 368;
const T_NULLSAFE_OBJECT_OPERATOR = 369;
const T_LIST = 370;
const T_ARRAY = 371;
const T_CALLABLE = 372;
const T_CLASS_C = 373;
const T_TRAIT_C = 374;
const T_METHOD_C = 375;
const T_FUNC_C = 376;
const T_LINE = 377;
const T_FILE = 378;
const T_START_HEREDOC = 379;
const T_END_HEREDOC = 380;
const T_DOLLAR_OPEN_CURLY_BRACES = 381;
const T_CURLY_OPEN = 382;
const T_PAAMAYIM_NEKUDOTAYIM = 383;
const T_NAMESPACE = 384;
const T_NS_C = 385;
const T_DIR = 386;
const T_NS_SEPARATOR = 387;
const T_ELLIPSIS = 388;
const T_NAME_FULLY_QUALIFIED = 389;
const T_NAME_QUALIFIED = 390;
const T_NAME_RELATIVE = 391;
const T_ATTRIBUTE = 392;
const T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG = 286;
const T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG = 287;
const T_IS_EQUAL = 288;
const T_IS_NOT_EQUAL = 289;
const T_IS_IDENTICAL = 290;
const T_IS_NOT_IDENTICAL = 291;
const T_SPACESHIP = 292;
const T_IS_SMALLER_OR_EQUAL = 293;
const T_IS_GREATER_OR_EQUAL = 294;
const T_SL = 295;
const T_SR = 296;
const T_INSTANCEOF = 297;
const T_INC = 298;
const T_DEC = 299;
const T_INT_CAST = 300;
const T_DOUBLE_CAST = 301;
const T_STRING_CAST = 302;
const T_ARRAY_CAST = 303;
const T_OBJECT_CAST = 304;
const T_BOOL_CAST = 305;
const T_UNSET_CAST = 306;
const T_POW = 307;
const T_NEW = 308;
const T_CLONE = 309;
const T_EXIT = 310;
const T_IF = 311;
const T_ELSEIF = 312;
const T_ELSE = 313;
const T_ENDIF = 314;
const T_LNUMBER = 315;
const T_DNUMBER = 316;
const T_STRING = 317;
const T_STRING_VARNAME = 318;
const T_VARIABLE = 319;
const T_NUM_STRING = 320;
const T_INLINE_HTML = 321;
const T_ENCAPSED_AND_WHITESPACE = 322;
const T_CONSTANT_ENCAPSED_STRING = 323;
const T_ECHO = 324;
const T_DO = 325;
const T_WHILE = 326;
const T_ENDWHILE = 327;
const T_FOR = 328;
const T_ENDFOR = 329;
const T_FOREACH = 330;
const T_ENDFOREACH = 331;
const T_DECLARE = 332;
const T_ENDDECLARE = 333;
const T_AS = 334;
const T_SWITCH = 335;
const T_MATCH = 336;
const T_ENDSWITCH = 337;
const T_CASE = 338;
const T_DEFAULT = 339;
const T_BREAK = 340;
const T_CONTINUE = 341;
const T_GOTO = 342;
const T_FUNCTION = 343;
const T_FN = 344;
const T_CONST = 345;
const T_RETURN = 346;
const T_TRY = 347;
const T_CATCH = 348;
const T_FINALLY = 349;
const T_USE = 350;
const T_INSTEADOF = 351;
const T_GLOBAL = 352;
const T_STATIC = 353;
const T_ABSTRACT = 354;
const T_FINAL = 355;
const T_PRIVATE = 356;
const T_PROTECTED = 357;
const T_PUBLIC = 358;
const T_READONLY = 359;
const T_VAR = 360;
const T_UNSET = 361;
const T_ISSET = 362;
const T_EMPTY = 363;
const T_HALT_COMPILER = 364;
const T_CLASS = 365;
const T_TRAIT = 366;
const T_INTERFACE = 367;
const T_ENUM = 368;
const T_EXTENDS = 369;
const T_IMPLEMENTS = 370;
const T_OBJECT_OPERATOR = 371;
const T_NULLSAFE_OBJECT_OPERATOR = 372;
const T_LIST = 373;
const T_ARRAY = 374;
const T_CALLABLE = 375;
const T_CLASS_C = 376;
const T_TRAIT_C = 377;
const T_METHOD_C = 378;
const T_FUNC_C = 379;
const T_LINE = 380;
const T_FILE = 381;
const T_START_HEREDOC = 382;
const T_END_HEREDOC = 383;
const T_DOLLAR_OPEN_CURLY_BRACES = 384;
const T_CURLY_OPEN = 385;
const T_PAAMAYIM_NEKUDOTAYIM = 386;
const T_NAMESPACE = 387;
const T_NS_C = 388;
const T_DIR = 389;
const T_NS_SEPARATOR = 390;
const T_ELLIPSIS = 391;
const T_NAME_FULLY_QUALIFIED = 392;
const T_NAME_QUALIFIED = 393;
const T_NAME_RELATIVE = 394;
const T_ATTRIBUTE = 395;
}

View File

@ -16,9 +16,12 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse;
@ -664,6 +667,7 @@ abstract class ParserAbstract implements Parser
'false' => true,
'mixed' => true,
'never' => true,
'true' => true,
];
if (!$name->isUnqualified()) {
@ -875,6 +879,33 @@ abstract class ParserAbstract implements Parser
return $attributes;
}
/** @param ElseIf_|Else_ $node */
protected function fixupAlternativeElse($node) {
// Make sure a trailing nop statement carrying comments is part of the node.
$numStmts = \count($node->stmts);
if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) {
$nopAttrs = $node->stmts[$numStmts - 1]->getAttributes();
if (isset($nopAttrs['endLine'])) {
$node->setAttribute('endLine', $nopAttrs['endLine']);
}
if (isset($nopAttrs['endFilePos'])) {
$node->setAttribute('endFilePos', $nopAttrs['endFilePos']);
}
if (isset($nopAttrs['endTokenPos'])) {
$node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']);
}
}
}
protected function checkClassModifier($a, $b, $modifierPos) {
try {
Class_::verifyClassModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {
@ -977,6 +1008,12 @@ abstract class ParserAbstract implements Parser
break;
}
}
if ($node->flags & Class_::MODIFIER_READONLY) {
$this->emitError(new Error(
sprintf('Method %s() cannot be readonly', $node->name),
$this->getAttributesAt($modifierPos)));
}
}
protected function checkClassConst(ClassConst $node, $modifierPos) {
@ -990,9 +1027,9 @@ abstract class ParserAbstract implements Parser
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
if ($node->flags & Class_::MODIFIER_READONLY) {
$this->emitError(new Error(
"Cannot use 'final' as constant modifier",
"Cannot use 'readonly' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}

View File

@ -33,6 +33,10 @@ class Standard extends PrettyPrinterAbstract
. $this->p($node->value);
}
protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) {
return '...';
}
protected function pConst(Node\Const_ $node) {
return $node->name . ' = ' . $this->p($node->value);
}
@ -42,7 +46,19 @@ class Standard extends PrettyPrinterAbstract
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
$types = [];
foreach ($node->types as $typeNode) {
if ($typeNode instanceof Node\IntersectionType) {
$types[] = '('. $this->p($typeNode) . ')';
continue;
}
$types[] = $this->p($typeNode);
}
return implode('|', $types);
}
protected function pIntersectionType(Node\IntersectionType $node) {
return $this->pImplode($node->types, '&');
}
protected function pIdentifier(Node\Identifier $node) {

View File

@ -774,7 +774,8 @@ abstract class PrettyPrinterAbstract
}
if ($skipRemovedNode) {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
@ -824,7 +825,11 @@ abstract class PrettyPrinterAbstract
return null;
}
if ($insertStr === ', ' && $this->isMultiline($origNodes)) {
// We go multiline if the original code was multiline,
// or if it's an array item with a comment above it.
if ($insertStr === ', ' &&
($this->isMultiline($origNodes) || $arrItem->getComments())
) {
$insertStr = ',';
$insertNewline = true;
}
@ -842,11 +847,11 @@ abstract class PrettyPrinterAbstract
$this->setIndentLevel($lastElemIndentLevel);
if ($insertNewline) {
$result .= $insertStr . $this->nl;
$comments = $arrItem->getComments();
if ($comments) {
$result .= $this->nl . $this->pComments($comments);
$result .= $this->pComments($comments) . $this->nl;
}
$result .= $insertStr . $this->nl;
} else {
$result .= $insertStr;
}
@ -873,7 +878,8 @@ abstract class PrettyPrinterAbstract
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
return null;
@ -919,11 +925,14 @@ abstract class PrettyPrinterAbstract
foreach ($delayedAdd as $delayedAddNode) {
if (!$first) {
$result .= $insertStr;
if ($insertNewline) {
$result .= $this->nl;
}
}
$result .= $this->p($delayedAddNode, true);
$first = false;
}
$result .= $extraRight;
$result .= $extraRight === "\n" ? $this->nl : $extraRight;
}
return $result;
@ -1074,7 +1083,8 @@ abstract class PrettyPrinterAbstract
. ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '');
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : '');
}
/**
@ -1123,7 +1133,8 @@ abstract class PrettyPrinterAbstract
for ($i = 0; $i < 256; $i++) {
// Since PHP 7.1 The lower range is 0x80. However, we also want to support code for
// older versions.
$this->labelCharMap[chr($i)] = $i >= 0x7f || ctype_alnum($i);
$chr = chr($i);
$this->labelCharMap[$chr] = $i >= 0x7f || ctype_alnum($chr);
}
}
@ -1341,6 +1352,7 @@ abstract class PrettyPrinterAbstract
//'Scalar_Encapsed->parts' => '',
'Stmt_Catch->types' => '|',
'UnionType->types' => '|',
'IntersectionType->types' => '&',
'Stmt_If->elseifs' => ' ',
'Stmt_TryCatch->catches' => ' ',
@ -1447,6 +1459,16 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],
'Stmt_Interface->attrGroups' => [null, '', "\n"],
'Stmt_Class->attrGroups' => [null, '', "\n"],
'Stmt_ClassConst->attrGroups' => [null, '', "\n"],
'Stmt_ClassMethod->attrGroups' => [null, '', "\n"],
'Stmt_Function->attrGroups' => [null, '', "\n"],
'Stmt_Property->attrGroups' => [null, '', "\n"],
'Stmt_Trait->attrGroups' => [null, '', "\n"],
'Expr_ArrowFunction->attrGroups' => [null, '', ' '],
'Expr_Closure->attrGroups' => [null, '', ' '],
'Expr_PrintableNewAnonClass->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with:
* Expr_Isset->vars

View File

@ -65,6 +65,21 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
),
$node
);
$node = $this->createClassConstBuilder("TEST", 1)
->makeFinal()
->getNode()
;
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("TEST", new LNumber(1) )
],
Stmt\Class_::MODIFIER_FINAL
),
$node
);
}
public function testDocComment() {

View File

@ -68,6 +68,20 @@ class ClassTest extends \PHPUnit\Framework\TestCase
);
}
public function testReadonly() {
$node = $this->createClassBuilder('Test')
->makeReadonly()
->getNode()
;
$this->assertEquals(
new Stmt\Class_('Test', [
'flags' => Stmt\Class_::MODIFIER_READONLY
]),
$node
);
}
public function testStatementOrder() {
$method = new Stmt\ClassMethod('testMethod');
$property = new Stmt\Property(

View File

@ -0,0 +1,84 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt;
class EnumCaseTest extends \PHPUnit\Framework\TestCase
{
public function createEnumCaseBuilder($name) {
return new EnumCase($name);
}
public function testDocComment() {
$node = $this->createEnumCaseBuilder('TEST')
->setDocComment('/** Test */')
->getNode();
$this->assertEquals(
new Stmt\EnumCase(
"TEST",
null,
[],
[
'comments' => [new Comment\Doc('/** Test */')]
]
),
$node
);
}
public function testAddAttribute() {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
);
$attributeGroup = new AttributeGroup([$attribute]);
$node = $this->createEnumCaseBuilder('ATTR_GROUP')
->addAttribute($attributeGroup)
->getNode();
$this->assertEquals(
new Stmt\EnumCase(
"ATTR_GROUP",
null,
[$attributeGroup]
),
$node
);
}
/**
* @dataProvider provideTestDefaultValues
*/
public function testValues($value, $expectedValueNode) {
$node = $this->createEnumCaseBuilder('TEST')
->setValue($value)
->getNode()
;
$this->assertEquals($expectedValueNode, $node->expr);
}
public function provideTestDefaultValues() {
return [
[
31415,
new Scalar\LNumber(31415)
],
[
'Hallo World',
new Scalar\String_('Hallo World')
],
];
}
}

View File

@ -0,0 +1,159 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt;
class EnumTest extends \PHPUnit\Framework\TestCase
{
protected function createEnumBuilder($class) {
return new Enum_($class);
}
public function testImplements() {
$node = $this->createEnumBuilder('SomeEnum')
->implement('Namespaced\SomeInterface', new Name('OtherInterface'))
->getNode()
;
$this->assertEquals(
new Stmt\Enum_('SomeEnum', [
'implements' => [
new Name('Namespaced\SomeInterface'),
new Name('OtherInterface'),
],
]),
$node
);
}
public function testSetScalarType() {
$node = $this->createEnumBuilder('Test')
->setScalarType('int')
->getNode()
;
$this->assertEquals(
new Stmt\Enum_('Test', [
'scalarType' => new Identifier('int'),
]),
$node
);
}
public function testStatementOrder() {
$method = new Stmt\ClassMethod('testMethod');
$enumCase = new Stmt\EnumCase(
'TEST_ENUM_CASE'
);
$const = new Stmt\ClassConst([
new Node\Const_('TEST_CONST', new Node\Scalar\String_('ABC'))
]);
$use = new Stmt\TraitUse([new Name('SomeTrait')]);
$node = $this->createEnumBuilder('Test')
->addStmt($method)
->addStmt($enumCase)
->addStmts([$const, $use])
->getNode()
;
$this->assertEquals(
new Stmt\Enum_('Test', [
'stmts' => [$use, $enumCase, $const, $method]
]),
$node
);
}
public function testDocComment() {
$docComment = <<<'DOC'
/**
* Test
*/
DOC;
$enum = $this->createEnumBuilder('Test')
->setDocComment($docComment)
->getNode();
$this->assertEquals(
new Stmt\Enum_('Test', [], [
'comments' => [
new Comment\Doc($docComment)
]
]),
$enum
);
$enum = $this->createEnumBuilder('Test')
->setDocComment(new Comment\Doc($docComment))
->getNode();
$this->assertEquals(
new Stmt\Enum_('Test', [], [
'comments' => [
new Comment\Doc($docComment)
]
]),
$enum
);
}
public function testAddAttribute() {
$attribute = new Attribute(
new Name('Attr'),
[new Arg(new LNumber(1), false, false, [], new Identifier('name'))]
);
$attributeGroup = new AttributeGroup([$attribute]);
$enum = $this->createEnumBuilder('ATTR_GROUP')
->addAttribute($attributeGroup)
->getNode();
$this->assertEquals(
new Stmt\Enum_('ATTR_GROUP', [
'attrGroups' => [
$attributeGroup,
]
], []),
$enum
);
}
public function testInvalidStmtError() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"');
$this->createEnumBuilder('Test')
->addStmt(new Stmt\PropertyProperty('property'))
;
}
public function testInvalidDocComment() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
$this->createEnumBuilder('Test')
->setDocComment(new Comment('Test'));
}
public function testEmptyName() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name cannot be empty');
$this->createEnumBuilder('Test')
->implement('');
}
public function testInvalidName() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
$this->createEnumBuilder('Test')
->implement(['Foo']);
}
}

View File

@ -177,7 +177,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase
public function testInvalidTypeError() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier, NullableType or UnionType');
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
$this->createParamBuilder('test')->setType(new \stdClass);
}

View File

@ -66,6 +66,21 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
),
$node
);
$node = $this->createPropertyBuilder('test')
->makeReadonly()
->getNode()
;
$this->assertEquals(
new Stmt\Property(
Stmt\Class_::MODIFIER_READONLY,
[
new Stmt\PropertyProperty('test')
]
),
$node
);
}
public function testDocComment() {

View File

@ -27,6 +27,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
['class', Builder\Class_::class],
['interface', Builder\Interface_::class],
['trait', Builder\Trait_::class],
['enum', Builder\Enum_::class],
['method', Builder\Method::class],
['function', Builder\Function_::class],
['property', Builder\Property::class],
@ -34,6 +35,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
['use', Builder\Use_::class],
['useFunction', Builder\Use_::class],
['useConst', Builder\Use_::class],
['enumCase', Builder\EnumCase::class],
];
}

View File

@ -125,8 +125,11 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
$this->assertEquals(new Node\Identifier('iterable'), BuilderHelpers::normalizeType('iterable'));
$this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
$this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object'));
$this->assertEquals(new Node\Identifier('null'), BuilderHelpers::normalizeType('null'));
$this->assertEquals(new Node\Identifier('false'), BuilderHelpers::normalizeType('false'));
$this->assertEquals(new Node\Identifier('mixed'), BuilderHelpers::normalizeType('mixed'));
$this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
$this->assertEquals(new Node\Identifier('true'), BuilderHelpers::normalizeType('true'));
$intIdentifier = new Node\Identifier('int');
$this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));
@ -140,13 +143,16 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
$unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]);
$this->assertSame($unionType, BuilderHelpers::normalizeType($unionType));
$intersectionType = new Node\IntersectionType([new Node\Name('A'), new Node\Name('B')]);
$this->assertSame($intersectionType, BuilderHelpers::normalizeType($intersectionType));
$expectedNullable = new Node\NullableType($intIdentifier);
$nullable = BuilderHelpers::normalizeType('?int');
$this->assertEquals($expectedNullable, $nullable);
$this->assertEquals($intIdentifier, $nullable->type);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier, NullableType or UnionType');
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
BuilderHelpers::normalizeType(1);
}

View File

@ -107,8 +107,11 @@ class CodeParsingTest extends CodeTestAbstract
$endFilePos < $startFilePos ||
$endTokenPos < $startTokenPos
) {
// Nops and error can have inverted order, if they are empty
if (!$node instanceof Stmt\Nop && !$node instanceof Expr\Error) {
// Nop and Error can have inverted order, if they are empty.
// This can also happen for a Param containing an Error.
if (!$node instanceof Stmt\Nop && !$node instanceof Expr\Error &&
!$node instanceof Node\Param
) {
throw new \Exception('End < start on ' . $node->getType());
}
}

View File

@ -22,6 +22,9 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase
['"foo"', "foo"],
['[0, 1]', [0, 1]],
['["foo" => "bar"]', ["foo" => "bar"]],
['[...["bar"]]', ["bar"]],
['[...["foo" => "bar"]]', ["foo" => "bar"]],
['["a", "b" => "b", ...["b" => "bb", "c"]]', ["a", "b" => "bb", "c"]],
['NULL', null],
['False', false],
['true', true],

View File

@ -241,8 +241,8 @@ class EmulativeTest extends LexerTest
['1_000', [
[Tokens::T_LNUMBER, '1_000'],
]],
['0xCAFE_F00D', [
[Tokens::T_LNUMBER, '0xCAFE_F00D'],
['0x7AFE_F00D', [
[Tokens::T_LNUMBER, '0x7AFE_F00D'],
]],
['0b0101_1111', [
[Tokens::T_LNUMBER, '0b0101_1111'],
@ -342,6 +342,32 @@ class EmulativeTest extends LexerTest
[ord('{'), '{'],
[ord('}'), '}'],
]],
['0o123', [
[Tokens::T_LNUMBER, '0o123'],
]],
['0O123', [
[Tokens::T_LNUMBER, '0O123'],
]],
['0o1_2_3', [
[Tokens::T_LNUMBER, '0o1_2_3'],
]],
['0o1000000000000000000000', [
[Tokens::T_DNUMBER, '0o1000000000000000000000'],
]],
['readonly class', [
[Tokens::T_READONLY, 'readonly'],
[Tokens::T_CLASS, 'class'],
]],
['function readonly(', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
['function readonly (', [
[Tokens::T_FUNCTION, 'function'],
[Tokens::T_READONLY, 'readonly'],
[ord('('), '('],
]],
];
}

View File

@ -0,0 +1,38 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\VariadicPlaceholder;
class CallableLikeTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideTestIsFirstClassCallable
*/
public function testIsFirstClassCallable(CallLike $node, bool $isFirstClassCallable) {
$this->assertSame($isFirstClassCallable, $node->isFirstClassCallable());
if (!$isFirstClassCallable) {
$this->assertSame($node->getRawArgs(), $node->getArgs());
}
}
public function provideTestIsFirstClassCallable() {
$normalArgs = [new Arg(new LNumber(1))];
$callableArgs = [new VariadicPlaceholder()];
return [
[new FuncCall(new Name('test'), $normalArgs), false],
[new FuncCall(new Name('test'), $callableArgs), true],
[new MethodCall(new Variable('this'), 'test', $normalArgs), false],
[new MethodCall(new Variable('this'), 'test', $callableArgs), true],
[new StaticCall(new Name('Test'), 'test', $normalArgs), false],
[new StaticCall(new Name('Test'), 'test', $callableArgs), true],
[new New_(new Name('Test'), $normalArgs), false],
[new NullsafeMethodCall(new Variable('this'), 'test', $normalArgs), false],
// This is not legal code, but accepted by the parser.
[new New_(new Name('Test'), $callableArgs), true],
[new NullsafeMethodCall(new Variable('this'), 'test', $callableArgs), true],
];
}
}

View File

@ -73,6 +73,12 @@ class NameTest extends \PHPUnit\Framework\TestCase
(new Name('foo\bar\baz'))->slice(0, -4);
}
public function testSliceLengthTooLargeWithOffset() {
$this->expectException(\OutOfBoundsException::class);
$this->expectExceptionMessage('Length 3 is out of bounds');
(new Name('foo\bar\baz'))->slice(1, 3);
}
public function testConcat() {
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
$this->assertEquals(

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class DNumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234.56;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(DNumber::class, $lLumber);
/** @var DNumber $dnumber */
$this->assertSame(1234.56, $lLumber->value);
$this->assertSame('1_234.56', $lLumber->getAttribute('rawValue'));
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class NumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(LNumber::class, $lLumber);
/** @var LNumber $lnumber */
$this->assertSame(1234, $lLumber->value);
$this->assertSame('1_234', $lLumber->getAttribute('rawValue'));
}
}

View File

@ -2,8 +2,28 @@
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class StringTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo "sequence \x41";');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$string = $echo->exprs[0];
$this->assertInstanceOf(String_::class, $string);
/** @var String_ $string */
$this->assertSame('sequence A', $string->value);
$this->assertSame('"sequence \\x41"', $string->getAttribute('rawValue'));
}
/**
* @dataProvider provideTestParseEscapeSequences
*/

View File

@ -22,6 +22,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($node->isPublic());
$this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate());
$this->assertFalse($node->isFinal());
}
public function provideModifiers() {
@ -29,6 +30,7 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
['public'],
['protected'],
['private'],
['final'],
];
}
}

View File

@ -23,6 +23,7 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
$this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate());
$this->assertFalse($node->isStatic());
$this->assertFalse($node->isReadonly());
}
public function testStaticImplicitlyPublic() {
@ -31,6 +32,12 @@ class PropertyTest extends \PHPUnit\Framework\TestCase
$this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate());
$this->assertTrue($node->isStatic());
$this->assertFalse($node->isReadonly());
}
public function testReadonly() {
$node = new Property(Class_::MODIFIER_READONLY, []);
$this->assertTrue($node->isReadonly());
}
public function provideModifiers() {

View File

@ -6,11 +6,13 @@ class DummyNode extends NodeAbstract
{
public $subNode1;
public $subNode2;
public $notSubNode;
public function __construct($subNode1, $subNode2, $attributes) {
public function __construct($subNode1, $subNode2, $notSubNode, $attributes) {
parent::__construct($attributes);
$this->subNode1 = $subNode1;
$this->subNode2 = $subNode2;
$this->notSubNode = $notSubNode;
}
public function getSubNodeNames() : array {
@ -40,8 +42,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
],
];
$node = new DummyNode('value1', 'value2', $attributes);
$node->notSubNode = 'value3';
$node = new DummyNode('value1', 'value2', 'value3', $attributes);
return [
[$attributes, $node],
@ -90,7 +91,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
}
public function testSetDocComment() {
$node = new DummyNode(null, null, []);
$node = new DummyNode(null, null, null, []);
// Add doc comment to node without comments
$docComment = new Comment\Doc('/** doc */');
@ -120,19 +121,19 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
/**
* @dataProvider provideNodes
*/
public function testChange(array $attributes, Node $node) {
public function testChange(array $attributes, DummyNode $node) {
// direct modification
$node->subNode = 'newValue';
$this->assertSame('newValue', $node->subNode);
$node->subNode1 = 'newValue';
$this->assertSame('newValue', $node->subNode1);
// indirect modification
$subNode =& $node->subNode;
$subNode =& $node->subNode1;
$subNode = 'newNewValue';
$this->assertSame('newNewValue', $node->subNode);
$this->assertSame('newNewValue', $node->subNode1);
// removal
unset($node->subNode);
$this->assertObjectNotHasAttribute('subNode', $node);
unset($node->subNode1);
$this->assertFalse(isset($node->subNode1));
}
/**
@ -244,6 +245,7 @@ PHP;
"attributes": {
"startLine": 4,
"endLine": 4,
"rawValue": "0",
"kind": 10
}
},
@ -272,7 +274,8 @@ PHP;
"value": 1,
"attributes": {
"startLine": 4,
"endLine": 4
"endLine": 4,
"rawValue": "1.0"
}
},
"flags": 0,
@ -294,7 +297,8 @@ PHP;
"attributes": {
"startLine": 5,
"endLine": 5,
"kind": 1
"kind": 1,
"rawValue": "'Foo'"
}
}
],
@ -305,6 +309,7 @@ PHP;
}
],
"attrGroups": [],
"namespacedName": null,
"attributes": {
"startLine": 4,
"comments": [
@ -334,6 +339,137 @@ PHP;
}
]
JSON;
$expected81 = <<<'JSON'
[
{
"nodeType": "Stmt_Function",
"attributes": {
"startLine": 4,
"comments": [
{
"nodeType": "Comment",
"text": "\/\/ comment",
"line": 2,
"filePos": 6,
"tokenPos": 1,
"endLine": 2,
"endFilePos": 15,
"endTokenPos": 1
},
{
"nodeType": "Comment_Doc",
"text": "\/** doc comment *\/",
"line": 3,
"filePos": 17,
"tokenPos": 3,
"endLine": 3,
"endFilePos": 34,
"endTokenPos": 3
}
],
"endLine": 6
},
"byRef": false,
"name": {
"nodeType": "Identifier",
"attributes": {
"startLine": 4,
"endLine": 4
},
"name": "functionName"
},
"params": [
{
"nodeType": "Param",
"attributes": {
"startLine": 4,
"endLine": 4
},
"type": null,
"byRef": true,
"variadic": false,
"var": {
"nodeType": "Expr_Variable",
"attributes": {
"startLine": 4,
"endLine": 4
},
"name": "a"
},
"default": {
"nodeType": "Scalar_LNumber",
"attributes": {
"startLine": 4,
"endLine": 4,
"rawValue": "0",
"kind": 10
},
"value": 0
},
"flags": 0,
"attrGroups": []
},
{
"nodeType": "Param",
"attributes": {
"startLine": 4,
"endLine": 4
},
"type": null,
"byRef": false,
"variadic": false,
"var": {
"nodeType": "Expr_Variable",
"attributes": {
"startLine": 4,
"endLine": 4
},
"name": "b"
},
"default": {
"nodeType": "Scalar_DNumber",
"attributes": {
"startLine": 4,
"endLine": 4,
"rawValue": "1.0"
},
"value": 1
},
"flags": 0,
"attrGroups": []
}
],
"returnType": null,
"stmts": [
{
"nodeType": "Stmt_Echo",
"attributes": {
"startLine": 5,
"endLine": 5
},
"exprs": [
{
"nodeType": "Scalar_String",
"attributes": {
"startLine": 5,
"endLine": 5,
"kind": 1,
"rawValue": "'Foo'"
},
"value": "Foo"
}
]
}
],
"attrGroups": [],
"namespacedName": null
}
]
JSON;
if (version_compare(PHP_VERSION, '8.1', '>=')) {
$expected = $expected81;
}
$parser = new Parser\Php7(new Lexer());
$stmts = $parser->parse(canonicalize($code));

View File

@ -204,6 +204,7 @@ class A extends B implements C, D {
interface A extends C, D {
public function a(A $a) : A;
public function b(A|B|int $a): A|B|int;
public function c(A&B $a): A&B;
}
#[X]
@ -268,6 +269,7 @@ interface A extends \NS\C, \NS\D
{
public function a(\NS\A $a) : \NS\A;
public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int;
public function c(\NS\A&\NS\B $a) : \NS\A&\NS\B;
}
#[\NS\X]
enum E : int
@ -351,7 +353,7 @@ EOC;
$this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
$this->assertNull($stmts[0]->stmts[5]->class->namespacedName);
$this->assertSame('NS\\F', (string) $stmts[0]->stmts[6]->namespacedName);
$stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
@ -360,7 +362,7 @@ EOC;
$this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
$this->assertNull($stmts[0]->stmts[5]->class->namespacedName);
$this->assertSame('F', (string) $stmts[0]->stmts[6]->namespacedName);
}

View File

@ -135,6 +135,8 @@ EOC;
['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
['0o7', ['kind' => Scalar\LNumber::KIND_OCT]],
['0O7', ['kind' => Scalar\LNumber::KIND_OCT]],
['[]', ['kind' => Expr\Array_::KIND_SHORT]],
['array()', ['kind' => Expr\Array_::KIND_LONG]],
["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],

View File

@ -0,0 +1,106 @@
Inserting array item with comment
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
];
-----
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
$node->setAttribute('comments', [new Comment\Doc(<<<COMMENT
/**
* A doc comment
*/
COMMENT
)]);
$array = $stmts[0]->expr->expr;
$array->items[] = $node;
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
/**
* A doc comment
*/
'c' => 'baz',
];
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
];
-----
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
$node->setAttribute('comments', [new Comment("/* Block comment */")]);
$array = $stmts[0]->expr->expr;
$array->items[] = $node;
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
/* Block comment */
'c' => 'baz',
];
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
];
-----
$node = new Expr\ArrayItem(new Scalar\String_('baz'), new Scalar\String_('c'));
$node->setAttribute('comments', [new Comment("// Line comment")]);
$array = $stmts[0]->expr->expr;
$array->items[] = $node;
-----
<?php
$items = [
'a' => 'foo',
'b' => 'bar',
// Line comment
'c' => 'baz',
];
-----
<?php
$items = [
'a' => 'foo',
];
-----
$node = new Expr\ArrayItem(new Scalar\String_('bar'), new Scalar\String_('b'));
$node->setAttribute('comments', [new Comment("// Line comment")]);
$array = $stmts[0]->expr->expr;
$array->items[] = $node;
-----
<?php
$items = [
'a' => 'foo',
// Line comment
'b' => 'bar',
];
-----
<?php
$items = [];
-----
$node = new Expr\ArrayItem(new Scalar\String_('foo'), new Scalar\String_('a'));
$node->setAttribute('comments', [new Comment("// Line comment")]);
$array = $stmts[0]->expr->expr;
$array->items[] = $node;
-----
<?php
$items = [
// Line comment
'a' => 'foo',
];

View File

@ -102,12 +102,16 @@ function() {};
fn()
=> 42;
-----
// TODO: Currently we lose formatting for this case.
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('A'), []),
]);
$attrGroup2 = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('B'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->attrGroups[] = $attrGroup2;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup2;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
@ -119,39 +123,34 @@ $stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
class X
{
#[B]
class X {
#[A]
public function m()
{
}
#[B]
public function m() {}
#[A]
public $prop;
public
$prop;
#[A]
const X = 42;
const
X = 42;
}
#[A]
trait X
{
}
trait X {}
#[A]
interface X
{
}
interface X {}
#[A]
function f()
{
}
function f() {}
new #[A] class
{
};
#[A] function () {
};
#[A] fn() => 42;
new #[A] class {};
#[A] function() {};
#[A] fn()
=> 42;
-----
<?php
@ -176,4 +175,4 @@ class X {};
B,
C,
]
class X {};
class X {};

View File

@ -49,4 +49,4 @@ class Test {
public function test() {
// some code
}
}
}

View File

@ -107,4 +107,4 @@ Foo
new class
($a, $b)
extends Foo {};
extends Foo {};

View File

@ -42,13 +42,71 @@ function test() {
baz();
}
-----
// TODO Fix broken result
// TODO Preserve formatting
$stmts[0]->stmts[1] = $stmts[0]->stmts[2];
-----
<?php
function test()
{
foo();
baz();
baz();
}
-----
<?php
function test() {
foo();<?php
foo();
?>Bar<?php
baz();
}
-----
// TODO Preserve formatting
unset($stmts[0]->stmts[2]);
-----
<?php
function test()
{
foo();
?>Bar<?php
}
-----
<?php
function test() {
foo();
?>Bar<?php
baz();
}
}
-----
// TODO Preserve formatting
array_splice($stmts[0]->stmts, 0, 1, []);
-----
<?php
function test()
{
?>Bar<?php
baz();
}
-----
<?php
function test() {
foo();
?>Bar<?php
baz();
}
-----
// TODO Preserve formatting
array_splice($stmts[0]->stmts, 1, 1, []);
-----
<?php
function test()
{
foo();
baz();
}

View File

@ -319,6 +319,16 @@ function test(): A
|B|C {}
-----
<?php
function test(): A
&B {}
-----
$stmts[0]->returnType->types[] = new Node\Name('C');
-----
<?php
function test(): A
&B&C {}
-----
<?php
function test() {
if ($x) {
$a;

View File

@ -1008,7 +1008,7 @@ function(Foo);
Syntax error, unexpected ')', expecting T_VARIABLE from 3:18 to 3:18
Syntax error, unexpected ')', expecting T_VARIABLE from 7:31 to 7:31
Syntax error, unexpected ')', expecting T_VARIABLE from 11:17 to 11:17
Syntax error, unexpected ')', expecting T_VARIABLE from 15:15 to 15:15
Syntax error, unexpected T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, expecting T_VARIABLE from 15:14 to 15:14
Syntax error, unexpected ')', expecting T_VARIABLE from 19:17 to 19:17
Syntax error, unexpected ')', expecting T_VARIABLE from 22:21 to 22:21
Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13
@ -1137,7 +1137,7 @@ array(
)
flags: 0
type: null
byRef: true
byRef: false
variadic: false
var: Expr_Error(
)

View File

@ -0,0 +1,121 @@
First-class callables
-----
<?php
foo(...);
$this->foo(...);
A::foo(...);
// These are invalid, but accepted on the parser level.
new Foo(...);
$this?->foo(...);
#[Foo(...)]
function foo() {}
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
1: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_Variable(
name: this
)
name: Identifier(
name: foo
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
2: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: foo
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
3: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: Foo
)
)
args: array(
0: VariadicPlaceholder(
)
)
comments: array(
0: // These are invalid, but accepted on the parser level.
)
)
comments: array(
0: // These are invalid, but accepted on the parser level.
)
)
4: Stmt_Expression(
expr: Expr_NullsafeMethodCall(
var: Expr_Variable(
name: this
)
name: Identifier(
name: foo
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
5: Stmt_Function(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: Foo
)
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
)
)
byRef: false
name: Identifier(
name: foo
)
params: array(
)
returnType: null
stmts: array(
)
)
)

View File

@ -0,0 +1,30 @@
Explicit octal syntax
-----
<?php
0o123;
0O123;
0o1_2_3;
0o1000000000000000000000;
-----
array(
0: Stmt_Expression(
expr: Scalar_LNumber(
value: 83
)
)
1: Stmt_Expression(
expr: Scalar_LNumber(
value: 83
)
)
2: Stmt_Expression(
expr: Scalar_LNumber(
value: 83
)
)
3: Stmt_Expression(
expr: Scalar_DNumber(
value: 9.2233720368548E+18
)
)
)

View File

@ -17,6 +17,7 @@ Different float syntaxes
// (all are actually the same number, just in different representations)
18446744073709551615;
0xFFFFFFFFFFFFFFFF;
0xEEEEEEEEEEEEEEEE;
01777777777777777777777;
0177777777777777777777787;
0b1111111111111111111111111111111111111111111111111111111111111111;
@ -92,7 +93,7 @@ array(
)
12: Stmt_Expression(
expr: Scalar_DNumber(
value: 1.844674407371E+19
value: 1.7216961135462E+19
)
)
13: Stmt_Expression(
@ -105,4 +106,9 @@ array(
value: 1.844674407371E+19
)
)
)
15: Stmt_Expression(
expr: Scalar_DNumber(
value: 1.844674407371E+19
)
)
)

View File

@ -4,7 +4,7 @@ Different integer syntaxes
6.674_083e-11;
299_792_458;
0xCAFE_F00D;
0x7AFE_F00D;
0b0101_1111;
0137_041;
@ -42,7 +42,7 @@ array(
)
2: Stmt_Expression(
expr: Scalar_LNumber(
value: 3405705229
value: 2063527949
)
)
3: Stmt_Expression(
@ -196,4 +196,4 @@ array(
)
)
)
)
)

View File

@ -78,11 +78,11 @@ array(
-----
<?php
class A {
final const X = 1;
readonly const X = 1;
}
-----
!!php7
Cannot use 'final' as constant modifier from 3:5 to 3:9
Cannot use 'readonly' as constant modifier from 3:5 to 3:12
array(
0: Stmt_Class(
attrGroups: array(
@ -98,7 +98,7 @@ array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
flags: MODIFIER_READONLY (64)
consts: array(
0: Const(
name: Identifier(

View File

@ -7,6 +7,7 @@ class Foo {
public const B = 2;
protected const C = 3;
private const D = 4;
final const E = 5;
}
-----
!!php7
@ -82,6 +83,21 @@ array(
)
)
)
4: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
consts: array(
0: Const(
name: Identifier(
name: E
)
value: Scalar_LNumber(
value: 5
)
)
)
)
)
)
)

View File

@ -2,10 +2,14 @@ Enum
-----
<?php
enum A {}
enum A {
case class;
}
enum B implements Bar, Baz {
}
enum C: int implements Bar {}
enum C: int implements Bar {
case Foo = 1;
}
-----
!!php7
array(
@ -19,6 +23,14 @@ array(
implements: array(
)
stmts: array(
0: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: class
)
expr: null
)
)
)
1: Stmt_Enum(
@ -60,6 +72,16 @@ array(
)
)
stmts: array(
0: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Foo
)
expr: Scalar_LNumber(
value: 1
)
)
)
)
)

View File

@ -66,6 +66,40 @@ array(
)
)
-----
<?php class C { readonly readonly $a; }
-----
!!php7
Multiple readonly modifiers are not allowed from 1:26 to 1:33
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: C
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
type: null
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: a
)
default: null
)
)
)
)
)
)
-----
<?php class A { abstract abstract function a(); }
-----
Multiple abstract modifiers are not allowed from 1:26 to 1:33
@ -197,8 +231,29 @@ array(
)
-----
<?php abstract final class A { }
-----
!!php7
Cannot use the final modifier on an abstract class from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
!!php5
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array(
0: Stmt_Class(
@ -221,7 +276,33 @@ array(
)
)
-----
<?php class A { abstract $a; }
<?php readonly class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
!!php5
Syntax error, unexpected T_CLASS, expecting '(' from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
1: Stmt_Nop(
comments: array(
0: // Type in the partial parse could conceivably be any of 0, 16 or 32
)
)
)
-----
<?php class B { abstract $b; }
-----
Properties cannot be declared abstract from 1:17 to 1:24
array(
@ -230,7 +311,7 @@ array(
)
flags: 0
name: Identifier(
name: A
name: B
)
extends: null
implements: array(
@ -244,7 +325,7 @@ array(
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: a
name: b
)
default: null
)
@ -285,4 +366,4 @@ array(
)
)
)
)
)

View File

@ -6,6 +6,7 @@ class A {
public string $a;
protected static D $b;
private ?float $c;
readonly static public ?int $d;
}
-----
!!php7
@ -73,6 +74,24 @@ array(
)
)
)
3: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_STATIC | MODIFIER_READONLY (73)
type: NullableType(
type: Identifier(
name: int
)
)
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: d
)
default: null
)
)
)
)
)
)

View File

@ -6,7 +6,8 @@ class Point {
public function __construct(
public float $x = 0.0,
protected array $y = [],
private string $z = 'hello'
private string $z = 'hello',
public readonly int $a = 0,
) {}
}
-----
@ -81,6 +82,22 @@ array(
value: hello
)
)
3: Param(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_READONLY (65)
type: Identifier(
name: int
)
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: Scalar_LNumber(
value: 0
)
)
)
returnType: null
stmts: array(

View File

@ -0,0 +1,68 @@
Readonly class
-----
<?php
readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
readonly class A {
}
-----
!!php5
Syntax error, unexpected T_CLASS, expecting '(' from 3:10 to 3:14
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
final readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL | MODIFIER_READONLY (96)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)

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