Compare commits

..

20 Commits

Author SHA1 Message Date
32cdab9a03 Release PHP-Parser 4.19.0 2024-03-16 15:49:31 +01:00
c2403aa729 Update minimum PHP version in docs 2024-03-02 18:21:59 +01:00
051ad218f8 Do not use implicitly nullable parameters (#984)
Implicitly nullable parameters will be deprecated in PHP 8.4
(see https://wiki.php.net/rfc/deprecate-implicitly-nullable-types).
To avoid deprecation warnings, replace all implicitly nullable
parameters with explicit ones. Unfortunately, this also means
that we have to drop support for PHP 7.0.
2024-03-02 18:19:50 +01:00
1bcbb2179f Release PHP-Parser 4.18.0 2023-12-10 22:03:43 +01:00
05c01865ea Add PHP 8.3 to CI 2023-12-10 21:57:59 +01:00
2a5e81f7ca Fix NameResolver for class constant native type 2023-11-28 20:10:38 +01:00
e453389866 Add forward-compatibility ParserFactory methods
Add ParserFactory::createForNewestSupportedVersion() and
ParserFactory::createForHostVersion() for forward-compatibility
with PHP-Parser 5. These methods do not accept an externally
constructed lexer and always enable all attributes.
2023-11-12 16:49:53 +01:00
402b6cf345 build: Exclude grammar from export git artifact 2023-11-01 21:31:02 +01:00
54103d8387 Don't drop class statements before error (#952)
When encountering a null statement (indicating that an error occurred),
retain the preceding statements. These were accidentally dropped
previously.
2023-10-03 23:00:18 +02:00
a6303e50c9 Release PHP-Parser 4.17.1 2023-08-13 21:53:39 +02:00
44fc92194b Fix ClassConst::$type phpdoc
The property should be nullable, and on 4.x we should convert
string to Identifier.

Fixes #939.
2023-08-13 21:49:54 +02:00
844c228bf2 Release PHP-Parser 4.17.0 2023-08-13 16:53:08 +02:00
6d2584bdf1 Support fixup for dynamic class const name
This is new in PHP 8.3.

(cherry picked from commit e9416a0eae)
2023-08-13 16:46:31 +02:00
21a61ece15 Properly handle static deref LHS
The rules for static and array/object deref are slightly different:
The former does not allow constants.

(cherry picked from commit 7b4a8c1ebd)
2023-08-13 16:46:25 +02:00
8f8e47b6c1 Support new variables in fixup
(cherry picked from commit 6a88bdb05a)
2023-08-13 16:41:25 +02:00
0aad06bce3 Fix logic for new operand parentheses requirement
We need to perform this check recursively.

(cherry picked from commit cc34c2450c)
2023-08-13 16:36:00 +02:00
80a680bf59 Properly handle new/instanceof operand restrictions
Fixes #912.

(cherry picked from commit 1eb6b5653e)
2023-08-13 16:35:51 +02:00
cfc54e30a4 [PHP 8.3] Support dynamic class const fetch
RFC: https://wiki.php.net/rfc/dynamic_class_constant_fetch
2023-07-30 23:38:32 +02:00
05e84f7201 Support readonly anonymous classes 2023-07-30 23:38:32 +02:00
73ccbabbe7 Add support for typed constants
RFC: https://wiki.php.net/rfc/typed_class_constants
2023-07-30 23:38:32 +02:00
67 changed files with 1938 additions and 1387 deletions

1
.gitattributes vendored
View File

@ -1,5 +1,6 @@
/.github export-ignore
/doc export-ignore
/grammar export-ignore
/test export-ignore
/test_old export-ignore
.editorconfig export-ignore

View File

@ -5,9 +5,9 @@ on:
pull_request:
jobs:
tests_70:
tests_71:
runs-on: "ubuntu-latest"
name: "PHP 7.0 Unit Tests"
name: "PHP 7.1 Unit Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
@ -15,7 +15,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "xdebug"
php-version: "7.0"
php-version: "7.1"
tools: composer:v2
- name: "Install dependencies"
run: |
@ -34,13 +34,13 @@ jobs:
strategy:
matrix:
php-version:
- "7.1"
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
@ -70,9 +70,9 @@ jobs:
run: "composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 7.3.21"
test_old_80_70:
test_old_80_71:
runs-on: "ubuntu-latest"
name: "PHP 8.1 Code on PHP 7.0 Integration Tests"
name: "PHP 8.1 Code on PHP 7.1 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
@ -80,7 +80,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "7.0"
php-version: "7.1"
tools: composer:v2
- name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist"

View File

@ -1,3 +1,50 @@
Version 4.19.0 (2024-03-16)
---------------------------
### Changed
* Do not use implicitly nullable parameters, which are deprecated in PHP 8.4.
* Remove support for running on PHP 7.0, which does not support explicitly nullable parameters.
Version 4.18.0 (2023-12-10)
---------------------------
### Added
* Added methods `ParserFactory::createForNewestSupportedVersion()` and
`ParserFactory::createForHostVersion()` for forward-compatibility with PHP-Parser 5.0.
### Fixed
* Fixed missing name resolution of class constant types.
* Fixed class members being dropped if an error is encountered while parsing a later class member
(when error recovery is enabeld).
### Changed
* The `grammar/` directory has been excluded from exported git archives.
Version 4.17.1 (2023-08-13)
---------------------------
### Fixed
* Fixed phpdoc mismatches for `ClassConst::$type` introduced in previous release.
Version 4.17.0 (2023-08-13)
---------------------------
### Added
* [PHP 8.3] Added support for typed class constants.
* [PHP 8.3] Added supprot for dynamic class constant fetch.
* [PHP 8.3] Added support for readonly anonymous classes.
### Fixed
* Fixed missing required parentheses when pretty printing new with an expression class name.
* Fixed missing required parentheses when pretty printing `(CONST)::$x` and similar.
Version 4.16.0 (2023-06-25)
---------------------------

View File

@ -6,7 +6,7 @@ PHP Parser
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_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.2).
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.1; 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).

View File

@ -13,7 +13,7 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.1",
"ext-tokenizer": "*"
},
"require-dev": {

View File

@ -34,7 +34,7 @@ The parser supports parsing PHP 5.2-8.0, with the following exceptions:
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
This allows to parse PHP 7.4 source code running on PHP 7.0, for example. This emulation is somewhat
This allows to parse PHP 7.4 source code running on PHP 7.1, for example. This emulation is somewhat
hacky and not perfect, but it should work well on any sane code.
What output does it produce?

View File

@ -469,7 +469,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;

View File

@ -221,7 +221,10 @@ non_empty_class_const_list:
;
class_const:
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
T_STRING '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
| semi_reserved '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
;
inner_statement_list_ex:
@ -705,7 +708,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;
@ -722,6 +725,9 @@ 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_CONST type_expr class_const_list semi
{ $$ = new Stmt\ClassConst($5, $2, attributes(), $1, $4);
$this->checkClassConst($$, #2); }
| 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]];
@ -943,8 +949,8 @@ expr:
;
anonymous_class:
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
optional_attributes class_entry_type ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
$this->checkClass($$[0], -1); }
;
@ -1040,6 +1046,8 @@ constant:
class_constant:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
{ $$ = Expr\ClassConstFetch[$1, $3]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}'
{ $$ = Expr\ClassConstFetch[$1, $4]; }
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error

View File

@ -19,6 +19,8 @@ class ClassConst implements PhpParser\Builder
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/** @var Identifier|Node\Name|Node\ComplexType */
protected $type;
/**
* Creates a class constant builder
@ -116,6 +118,19 @@ class ClassConst implements PhpParser\Builder
return $this;
}
/**
* Sets the constant type.
*
* @param string|Node\Name|Identifier|Node\ComplexType $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Returns the built class node.
*
@ -126,7 +141,8 @@ class ClassConst implements PhpParser\Builder
$this->constants,
$this->flags,
$this->attributes,
$this->attributeGroups
$this->attributeGroups,
$this->type
);
}
}

View File

@ -349,15 +349,15 @@ class BuilderFactory
/**
* Creates a class constant fetch node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier $name Constant name
* @param string|Name|Expr $class Class name
* @param string|Identifier|Expr $name Constant name
*
* @return Expr\ClassConstFetch
*/
public function classConstFetch($class, $name): Expr\ClassConstFetch {
return new Expr\ClassConstFetch(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifier($name)
BuilderHelpers::normalizeIdentifierOrExpr($name)
);
}

View File

@ -37,7 +37,7 @@ class ConstExprEvaluator
*
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
*/
public function __construct(callable $fallbackEvaluator = null) {
public function __construct(?callable $fallbackEvaluator = null) {
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
throw new ConstExprEvaluationException(
"Expression of type {$expr->getType()} cannot be evaluated"

View File

@ -19,6 +19,8 @@ class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var int Modifiers */
public $flags;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
@ -29,11 +31,12 @@ class PrintableNewAnonClassNode extends Expr
public $stmts;
public function __construct(
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $attrGroups, int $flags, array $args, ?Node\Name $extends = null, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->flags = $flags;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
@ -46,7 +49,7 @@ class PrintableNewAnonClassNode extends Expr
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
@ -56,6 +59,6 @@ class PrintableNewAnonClassNode extends Expr
}
public function getSubNodeNames() : array {
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts'];
}
}

View File

@ -69,7 +69,7 @@ class Lexer
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing
*/
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing();
}

View File

@ -74,7 +74,7 @@ class Emulative extends Lexer
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});

View File

@ -36,7 +36,7 @@ class NameContext
*
* @param Name|null $namespace Null is the global namespace
*/
public function startNamespace(Name $namespace = null) {
public function startNamespace(?Name $namespace = null) {
$this->namespace = $namespace;
$this->origAliases = $this->aliases = [
Stmt\Use_::TYPE_NORMAL => [],

View File

@ -27,7 +27,7 @@ class Arg extends NodeAbstract
*/
public function __construct(
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
Identifier $name = null
?Identifier $name = null
) {
$this->attributes = $attributes;
$this->name = $name;

View File

@ -18,7 +18,7 @@ class ArrayDimFetch extends Expr
* @param null|Expr $dim Array index / dim
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->dim = $dim;

View File

@ -23,7 +23,7 @@ class ArrayItem extends Expr
* @param bool $byRef Whether to assign by reference
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;

View File

@ -10,15 +10,15 @@ class ClassConstFetch extends Expr
{
/** @var Name|Expr Class name */
public $class;
/** @var Identifier|Error Constant name */
/** @var Identifier|Expr|Error Constant name */
public $name;
/**
* Constructs a class const fetch node.
*
* @param Name|Expr $class Class name
* @param string|Identifier|Error $name Constant name
* @param array $attributes Additional attributes
* @param Name|Expr $class Class name
* @param string|Identifier|Expr|Error $name Constant name
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
$this->attributes = $attributes;
@ -29,7 +29,7 @@ class ClassConstFetch extends Expr
public function getSubNodeNames() : array {
return ['class', 'name'];
}
public function getType() : string {
return 'Expr_ClassConstFetch';
}

View File

@ -19,7 +19,7 @@ class Exit_ extends Expr
* @param null|Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr = null, array $attributes = []) {
public function __construct(?Expr $expr = null, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@ -18,7 +18,7 @@ class Yield_ extends Expr
* @param null|Expr $key Key expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
public function __construct(?Expr $value = null, ?Expr $key = null, array $attributes = []) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;

View File

@ -162,7 +162,7 @@ class Name extends NodeAbstract
*
* @return static|null Sliced name
*/
public function slice(int $offset, int $length = null) {
public function slice(int $offset, ?int $length = null) {
$numParts = count($this->parts);
$realOffset = $offset < 0 ? $offset + $numParts : $offset;

View File

@ -34,7 +34,7 @@ class Param extends NodeAbstract
* @param AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
$var, Expr $default = null, $type = null,
$var, ?Expr $default = null, $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,

View File

@ -15,7 +15,7 @@ class Break_ extends Node\Stmt
* @param null|Node\Expr $num Number of loops to break
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
public function __construct(?Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}

View File

@ -23,7 +23,7 @@ class Catch_ extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct(
array $types, Expr\Variable $var = null, array $stmts = [], array $attributes = []
array $types, ?Expr\Variable $var = null, array $stmts = [], array $attributes = []
) {
$this->attributes = $attributes;
$this->types = $types;

View File

@ -10,31 +10,36 @@ class ClassConst extends Node\Stmt
public $flags;
/** @var Node\Const_[] Constant declarations */
public $consts;
/** @var Node\AttributeGroup[] */
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Identifier|Node\Name|Node\ComplexType|null Type declaration */
public $type;
/**
* Constructs a class const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param null|string|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration
*/
public function __construct(
array $consts,
int $flags = 0,
array $attributes = [],
array $attrGroups = []
array $attrGroups = [],
$type = null
) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->consts = $consts;
$this->attrGroups = $attrGroups;
$this->type = \is_string($type) ? new Node\Identifier($type) : $type;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'consts'];
return ['attrGroups', 'flags', 'type', 'consts'];
}
/**

View File

@ -15,7 +15,7 @@ class Continue_ extends Node\Stmt
* @param null|Node\Expr $num Number of loops to continue
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
public function __construct(?Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}

View File

@ -18,7 +18,7 @@ class Declare_ extends Node\Stmt
* @param Node\Stmt[]|null $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $declares, array $stmts = null, array $attributes = []) {
public function __construct(array $declares, ?array $stmts = null, array $attributes = []) {
$this->attributes = $attributes;
$this->declares = $declares;
$this->stmts = $stmts;

View File

@ -20,7 +20,7 @@ class EnumCase extends Node\Stmt
* @param AttributeGroup[] $attrGroups PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) {
public function __construct($name, ?Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) {
parent::__construct($attributes);
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->expr = $expr;

View File

@ -22,7 +22,7 @@ class Namespace_ extends Node\Stmt
* @param null|Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = [], array $attributes = []) {
public function __construct(?Node\Name $name = null, $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->stmts = $stmts;

View File

@ -18,7 +18,7 @@ class PropertyProperty extends Node\Stmt
* @param null|Node\Expr $default Default value
* @param array $attributes Additional attributes
*/
public function __construct($name, Node\Expr $default = null, array $attributes = []) {
public function __construct($name, ?Node\Expr $default = null, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name;
$this->default = $default;

View File

@ -15,7 +15,7 @@ class Return_ extends Node\Stmt
* @param null|Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr = null, array $attributes = []) {
public function __construct(?Node\Expr $expr = null, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@ -20,7 +20,7 @@ class StaticVar extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct(
Expr\Variable $var, Node\Expr $default = null, array $attributes = []
Expr\Variable $var, ?Node\Expr $default = null, array $attributes = []
) {
$this->attributes = $attributes;
$this->var = $var;

View File

@ -21,7 +21,7 @@ class TryCatch extends Node\Stmt
* @param null|Finally_ $finally Optional finally node
* @param array $attributes Additional attributes
*/
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = []) {
public function __construct(array $stmts, array $catches, ?Finally_ $finally = null, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
$this->catches = $catches;

View File

@ -39,7 +39,7 @@ class NodeDumper
*
* @return string Dumped value
*/
public function dump($node, string $code = null) : string {
public function dump($node, ?string $code = null) : string {
$this->code = $code;
return $this->dumpRecursive($node);
}

View File

@ -35,7 +35,7 @@ class NameResolver extends NodeVisitorAbstract
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
public function __construct(?ErrorHandler $errorHandler = null, array $options = []) {
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
$this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
$this->replaceNodes = $options['replaceNodes'] ?? true;
@ -118,6 +118,9 @@ class NameResolver extends NodeVisitorAbstract
$this->addNamespacedName($const);
}
} else if ($node instanceof Stmt\ClassConst) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} else if ($node instanceof Stmt\EnumCase) {
$this->resolveAttrGroups($node);
@ -161,7 +164,7 @@ class NameResolver extends NodeVisitorAbstract
return null;
}
private function addAlias(Stmt\UseUse $use, int $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

View File

@ -14,5 +14,5 @@ interface Parser
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null);
public function parse(string $code, ?ErrorHandler $errorHandler = null);
}

View File

@ -24,7 +24,7 @@ class Multiple implements Parser
$this->parsers = $parsers;
}
public function parse(string $code, ErrorHandler $errorHandler = null) {
public function parse(string $code, ?ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing;
}

View File

@ -1738,7 +1738,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
259 => function ($stackPos) {
if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }
if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } else { $this->semValue = $this->semStack[$stackPos-(2-1)]; }
},
260 => function ($stackPos) {
$this->semValue = array();

File diff suppressed because it is too large Load Diff

View File

@ -155,7 +155,7 @@ abstract class ParserAbstract implements Parser
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null) {
public function parse(string $code, ?ErrorHandler $errorHandler = null) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
$this->lexer->startLexing($code, $this->errorHandler);

View File

@ -2,6 +2,9 @@
namespace PhpParser;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Php7;
class ParserFactory
{
const PREFER_PHP7 = 1;
@ -18,7 +21,7 @@ class ParserFactory
*
* @return Parser The parser instance
*/
public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser {
public function create(int $kind, ?Lexer $lexer = null, array $parserOptions = []) : Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
@ -41,4 +44,33 @@ class ParserFactory
);
}
}
/**
* Create a parser targeting the newest version supported by this library. Code for older
* versions will be accepted if there have been no relevant backwards-compatibility breaks in
* PHP.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForNewestSupportedVersion(): Parser {
return new Php7(new Emulative($this->getLexerOptions()));
}
/**
* Create a parser targeting the host PHP version, that is the PHP version we're currently
* running on. This parser will not use any token emulation.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForHostVersion(): Parser {
return new Php7(new Lexer($this->getLexerOptions()));
}
private function getLexerOptions(): array {
return ['usedAttributes' => [
'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos',
]];
}
}

View File

@ -529,7 +529,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_StaticCall(Expr\StaticCall $node) {
return $this->pDereferenceLhs($node->class) . '::'
return $this->pStaticDereferenceLhs($node->class) . '::'
. ($node->name instanceof Expr
? ($node->name instanceof Expr\Variable
? $this->p($node->name)
@ -606,7 +606,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name);
return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name);
}
protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
@ -618,7 +618,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
}
protected function pExpr_ShellExec(Expr\ShellExec $node) {
@ -814,7 +814,9 @@ class Standard extends PrettyPrinterAbstract
protected function pStmt_ClassConst(Stmt\ClassConst $node) {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';';
. 'const '
. (null !== $node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Function(Stmt\Function_ $node) {
@ -1067,6 +1069,14 @@ class Standard extends PrettyPrinterAbstract
}
}
protected function pStaticDereferenceLhs(Node $node) {
if (!$this->staticDereferenceLhsRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
protected function pCallLhs(Node $node) {
if (!$this->callLhsRequiresParens($node)) {
return $this->p($node);
@ -1075,9 +1085,12 @@ class Standard extends PrettyPrinterAbstract
}
}
protected function pNewVariable(Node $node) {
// TODO: This is not fully accurate.
return $this->pDereferenceLhs($node);
protected function pNewVariable(Node $node): string {
if (!$this->newOperandRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
/**

View File

@ -21,6 +21,8 @@ abstract class PrettyPrinterAbstract
const FIXUP_BRACED_NAME = 4; // Name operand that may require bracing
const FIXUP_VAR_BRACED_NAME = 5; // Name operand that may require ${} bracing
const FIXUP_ENCAPSED = 6; // Encapsed string part
const FIXUP_NEW = 7; // New/instanceof operand
const FIXUP_STATIC_DEREF_LHS = 8; // LHS of static dereferencing operation
protected $precedenceMap = [
// [precedence, associativity]
@ -977,6 +979,19 @@ abstract class PrettyPrinterAbstract
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_STATIC_DEREF_LHS:
if ($this->staticDereferenceLhsRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)
) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_NEW:
if ($this->newOperandRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_BRACED_NAME:
case self::FIXUP_VAR_BRACED_NAME:
if ($subNode instanceof Expr
@ -1047,13 +1062,26 @@ abstract class PrettyPrinterAbstract
}
/**
* Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis.
* Determines whether the LHS of an array/object operation must be wrapped in parentheses.
*
* @param Node $node LHS of dereferencing operation
*
* @return bool Whether parentheses are required
*/
protected function dereferenceLhsRequiresParens(Node $node) : bool {
// A constant can occur on the LHS of an array/object deref, but not a static deref.
return $this->staticDereferenceLhsRequiresParens($node)
&& !$node instanceof Expr\ConstFetch;
}
/**
* Determines whether the LHS of a static operation must be wrapped in parentheses.
*
* @param Node $node LHS of dereferencing operation
*
* @return bool Whether parentheses are required
*/
protected function staticDereferenceLhsRequiresParens(Node $node): bool {
return !($node instanceof Expr\Variable
|| $node instanceof Node\Name
|| $node instanceof Expr\ArrayDimFetch
@ -1066,10 +1094,31 @@ abstract class PrettyPrinterAbstract
|| $node instanceof Expr\StaticCall
|| $node instanceof Expr\Array_
|| $node instanceof Scalar\String_
|| $node instanceof Expr\ConstFetch
|| $node instanceof Expr\ClassConstFetch);
}
/**
* Determines whether an expression used in "new" or "instanceof" requires parentheses.
*
* @param Node $node New or instanceof operand
*
* @return bool Whether parentheses are required
*/
protected function newOperandRequiresParens(Node $node): bool {
if ($node instanceof Node\Name || $node instanceof Expr\Variable) {
return false;
}
if ($node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch ||
$node instanceof Expr\NullsafePropertyFetch
) {
return $this->newOperandRequiresParens($node->var);
}
if ($node instanceof Expr\StaticPropertyFetch) {
return $this->newOperandRequiresParens($node->class);
}
return true;
}
/**
* Print modifiers, including trailing whitespace.
*
@ -1171,7 +1220,7 @@ abstract class PrettyPrinterAbstract
Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT],
Expr\Instanceof_::class => [
'expr' => self::FIXUP_PREC_LEFT,
'class' => self::FIXUP_PREC_RIGHT, // TODO: FIXUP_NEW_VARIABLE
'class' => self::FIXUP_NEW,
],
Expr\Ternary::class => [
'cond' => self::FIXUP_PREC_LEFT,
@ -1179,10 +1228,13 @@ abstract class PrettyPrinterAbstract
],
Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS],
Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS],
Expr\StaticCall::class => ['class' => self::FIXUP_STATIC_DEREF_LHS],
Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], // TODO: FIXUP_NEW_VARIABLE
Expr\ClassConstFetch::class => [
'class' => self::FIXUP_STATIC_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Expr\New_::class => ['class' => self::FIXUP_NEW],
Expr\MethodCall::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
@ -1192,7 +1244,7 @@ abstract class PrettyPrinterAbstract
'name' => self::FIXUP_BRACED_NAME,
],
Expr\StaticPropertyFetch::class => [
'class' => self::FIXUP_DEREF_LHS,
'class' => self::FIXUP_STATIC_DEREF_LHS,
'name' => self::FIXUP_VAR_BRACED_NAME,
],
Expr\PropertyFetch::class => [
@ -1278,6 +1330,7 @@ abstract class PrettyPrinterAbstract
'Param->default' => $stripEquals,
'Stmt_Break->num' => $stripBoth,
'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassConst->type' => $stripRight,
'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Stmt_Enum->scalarType' => $stripColon,
@ -1319,6 +1372,7 @@ abstract class PrettyPrinterAbstract
'Stmt_Break->num' => [\T_BREAK, false, ' ', null],
'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
'Stmt_ClassConst->type' => [\T_CONST, false, ' ', null],
'Stmt_Class->extends' => [null, false, ' extends ', null],
'Stmt_Enum->scalarType' => [null, false, ' : ', null],
'Stmt_EnumCase->expr' => [null, false, ' = ', null],
@ -1508,6 +1562,7 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassMethod->flags' => \T_FUNCTION,
'Stmt_Class->flags' => \T_CLASS,
'Stmt_Property->flags' => \T_VARIABLE,
'Expr_PrintableNewAnonClass->flags' => \T_CLASS,
'Param->flags' => \T_VARIABLE,
//'Stmt_TraitUseAdaptation_Alias->newModifier' => 0, // TODO
];

View File

@ -142,6 +142,18 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase
);
}
public function testType() {
$node = $this->createClassConstBuilder('TYPE', 1)
->setType('int')
->getNode();
$this->assertEquals(
new Stmt\ClassConst(
[new Const_('TYPE', new LNumber(1))],
0, [], [], new Identifier('int')),
$node
);
}
/**
* @dataProvider provideTestDefaultValues
*/

View File

@ -210,6 +210,10 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
new Expr\ClassConstFetch(new Expr\Variable('foo'), new Identifier('BAR')),
$factory->classConstFetch(new Expr\Variable('foo'), 'BAR')
);
$this->assertEquals(
new Expr\ClassConstFetch(new Name('Foo'), new Expr\Variable('foo')),
$factory->classConstFetch('Foo', $factory->var('foo'))
);
}
public function testVar() {
@ -243,7 +247,7 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
public function testInvalidIdentifier() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Expected string or instance of Node\Identifier');
(new BuilderFactory())->classConstFetch('Foo', new Expr\Variable('foo'));
(new BuilderFactory())->classConstFetch('Foo', new Name('foo'));
}
public function testInvalidIdentifierOrExpr() {

View File

@ -189,7 +189,7 @@ class A extends B implements C, D {
E::h as i;
E::j insteadof F, G;
}
#[X]
public float $php = 7.4;
public ?Foo $person;
@ -198,6 +198,10 @@ class A extends B implements C, D {
#[X]
const C = 1;
public const X A = X::Bar;
public const X\Foo B = X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
}
#[X]
@ -263,6 +267,9 @@ class A extends \NS\B implements \NS\C, \NS\D
public \NS\A|\NS\B|int $prop;
#[\NS\X]
const C = 1;
public const \NS\X A = \NS\X::Bar;
public const \NS\X\Foo B = \NS\X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
}
#[\NS\X]
interface A extends \NS\C, \NS\D

View File

@ -5,6 +5,8 @@ namespace PhpParser;
/* This test is very weak, because PHPUnit's assertEquals assertion is way too slow dealing with the
* large objects involved here. So we just do some basic instanceof tests instead. */
use PhpParser\Node\Stmt\Echo_;
class ParserFactoryTest extends \PHPUnit\Framework\TestCase
{
/** @dataProvider provideTestCreate */
@ -33,4 +35,26 @@ class ParserFactoryTest extends \PHPUnit\Framework\TestCase
]
];
}
/** @dataProvider provideTestLexerAttributes */
public function testLexerAttributes(Parser $parser) {
$stmts = $parser->parse("<?php /* Bar */ echo 'Foo';");
$stmt = $stmts[0];
$this->assertInstanceOf(Echo_::class, $stmt);
$this->assertCount(1, $stmt->getComments());
$this->assertSame(1, $stmt->getStartLine());
$this->assertSame(1, $stmt->getEndLine());
$this->assertSame(3, $stmt->getStartTokenPos());
$this->assertSame(6, $stmt->getEndTokenPos());
$this->assertSame(16, $stmt->getStartFilePos());
$this->assertSame(26, $stmt->getEndFilePos());
}
public function provideTestLexerAttributes() {
$factory = new ParserFactory();
return [
[$factory->createForHostVersion()],
[$factory->createForNewestSupportedVersion()],
];
}
}

View File

@ -42,6 +42,12 @@ $foo -> bar;
$foo -> bar;
self :: $foo;
self :: $foo;
new Foo();
$x instanceof Foo;
Foo :: bar;
Foo :: $bar;
Foo :: bar();
Foo :: bar;
-----
$stmts[0]->expr->name = new Expr\Variable('a');
$stmts[1]->expr->name = new Expr\BinaryOp\Concat(new Expr\Variable('a'), new Expr\Variable('b'));
@ -54,6 +60,12 @@ $stmts[5]->expr->name = new Expr\Variable('bar');
$stmts[6]->expr->name = new Expr\BinaryOp\Concat(new Expr\Variable('a'), new Expr\Variable('b'));
$stmts[7]->expr->name = new Node\VarLikeIdentifier('bar');
$stmts[8]->expr->name = new Expr\BinaryOp\Concat(new Expr\Variable('a'), new Expr\Variable('b'));
$stmts[9]->expr->class = new Scalar\String_('Foo');
$stmts[10]->expr->class = new Scalar\String_('Foo');
$stmts[11]->expr->class = new Expr\ConstFetch(new Node\Name('FOO'));
$stmts[12]->expr->class = new Expr\ConstFetch(new Node\Name('FOO'));
$stmts[13]->expr->class = new Expr\ConstFetch(new Node\Name('FOO'));
$stmts[14]->expr->name = new Expr\Variable('bar');
-----
<?php
$a ();
@ -64,4 +76,10 @@ $foo -> foo;
$foo -> {$bar};
$foo -> {$a . $b};
self :: $bar;
self :: ${$a . $b};
self :: ${$a . $b};
new ('Foo')();
$x instanceof ('Foo');
(FOO) :: bar;
(FOO) :: $bar;
(FOO) :: bar();
Foo :: {$bar};

View File

@ -49,6 +49,10 @@ X
private
$x
;
const
X
= 1;
}
foreach (
@ -86,6 +90,7 @@ $stmts[9]->expr = new Expr\Variable('x');
$stmts[10]->extends = new Node\Name\FullyQualified('Bar');
$stmts[10]->stmts[0]->returnType = new Node\Name('Y');
$stmts[10]->stmts[1]->props[0]->default = new Scalar\DNumber(42.0);
$stmts[10]->stmts[2]->type = new Node\Identifier('int');
$stmts[11]->keyVar = new Expr\Variable('z');
$stmts[12]->vars[0]->default = new Scalar\String_('abc');
$stmts[13]->finally = new Stmt\Finally_([]);
@ -140,6 +145,10 @@ X extends \Bar
private
$x = 42.0
;
const int
X
= 1;
}
foreach (

View File

@ -54,4 +54,15 @@ function test(
= 'z',
public T3 $z
= 'x',
) {}
) {}
-----
<?php
new class {};
new readonly class {};
-----
$stmts[0]->expr->class->flags = Stmt\Class_::MODIFIER_READONLY;
$stmts[1]->expr->class->flags = 0;
-----
<?php
readonly class {};
class {};

View File

@ -35,6 +35,11 @@ Bar
y
;
}
const
int
X
= 1;
}
$foo [ $bar ];
@ -97,6 +102,7 @@ $stmts[2]->extends = null;
$stmts[2]->stmts[0]->returnType = null;
$stmts[2]->stmts[1]->props[0]->default = null;
$stmts[2]->stmts[2]->adaptations[0]->newName = null;
$stmts[2]->stmts[3]->type = null;
$stmts[3]->expr->dim = null;
$stmts[4]->expr->expr = null;
$stmts[5]->expr->if = null;
@ -141,6 +147,10 @@ Foo
public
;
}
const
X
= 1;
}
$foo [];

View File

@ -745,6 +745,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(
@ -899,12 +900,13 @@ array(
<?php
class Foo {
public $bar1;
publi $foo;
public $bar;
}
-----
!!php7
Syntax error, unexpected T_STRING from 4:5 to 4:9
Syntax error, unexpected T_STRING from 5:5 to 5:9
array(
0: Stmt_Class(
attrGroups: array(
@ -918,6 +920,20 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: bar1
)
default: null
)
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
@ -1508,6 +1524,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(
@ -1521,4 +1538,4 @@ array(
)
)
)
)
)

View File

@ -0,0 +1,43 @@
Dynamic class constant fetch
-----
<?php
Foo::{bar()};
$foo::{bar()};
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
)
name: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
)
args: array(
)
)
)
)
1: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_Variable(
name: foo
)
name: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
)
args: array(
)
)
)
)
)

View File

@ -152,6 +152,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(
@ -175,6 +176,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(

View File

@ -205,6 +205,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(

View File

@ -23,6 +23,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
type: null
consts: array(
0: Const(
name: Identifier(
@ -61,6 +62,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
type: null
consts: array(
0: Const(
name: Identifier(
@ -99,6 +101,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
type: null
consts: array(
0: Const(
name: Identifier(
@ -137,6 +140,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
consts: array(
0: Const(
name: Identifier(
@ -150,4 +154,4 @@ array(
)
)
)
)
)

View File

@ -27,6 +27,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(
@ -42,6 +43,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
consts: array(
0: Const(
name: Identifier(
@ -57,6 +59,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
type: null
consts: array(
0: Const(
name: Identifier(
@ -72,6 +75,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: null
consts: array(
0: Const(
name: Identifier(
@ -87,6 +91,7 @@ array(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
type: null
consts: array(
0: Const(
name: Identifier(
@ -100,4 +105,4 @@ array(
)
)
)
)
)

View File

@ -0,0 +1,26 @@
Readonly anonymous class
-----
<?php
new readonly class {};
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
name: null
extends: null
implements: array(
)
stmts: array(
)
)
args: array(
)
)
)
)

View File

@ -46,6 +46,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(

View File

@ -0,0 +1,125 @@
Typed constants
-----
<?php
class Test {
const int X = 1;
private const string Y = "a", Z = "b";
const array ARRAY = [];
const Foo|Bar|null FOO = null;
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
type: Identifier(
name: int
)
consts: array(
0: Const(
name: Identifier(
name: X
)
value: Scalar_LNumber(
value: 1
)
)
)
)
1: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: Identifier(
name: string
)
consts: array(
0: Const(
name: Identifier(
name: Y
)
value: Scalar_String(
value: a
)
)
1: Const(
name: Identifier(
name: Z
)
value: Scalar_String(
value: b
)
)
)
)
2: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
type: Identifier(
name: array
)
consts: array(
0: Const(
name: Identifier(
name: ARRAY
)
value: Expr_Array(
items: array(
)
)
)
)
)
3: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: Name(
parts: array(
0: Foo
)
)
1: Name(
parts: array(
0: Bar
)
)
2: Identifier(
name: null
)
)
)
consts: array(
0: Const(
name: Identifier(
name: FOO
)
value: Expr_ConstFetch(
name: Name(
parts: array(
0: null
)
)
)
)
)
)
)
)
)

View File

@ -127,6 +127,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(

View File

@ -10,7 +10,9 @@ new class($a) extends A {
$this->a = $a;
}
};
new readonly class {};
-----
!!php7
new class
{
};
@ -25,3 +27,6 @@ new class($a) extends A
$this->a = $a;
}
};
new readonly class
{
};

View File

@ -0,0 +1,9 @@
Dynamic class constant fetch
-----
<?php
Foo::{bar()};
$foo::{bar()};
-----
!!php7
Foo::{bar()};
$foo::{bar()};

View File

@ -2,10 +2,22 @@ Parentheses for complex new/instanceof expressions
-----
<?php
new ('a' . 'b');
new (x);
new (foo());
new ('foo');
new (x[0]);
new (x->y);
new ((x)::$y);
$x instanceof ('a' . 'b');
$x instanceof ($y++);
-----
!!php7
new ('a' . 'b')();
new (x)();
new (foo())();
new ('foo')();
new (x[0])();
new (x->y)();
new ((x)::$y)();
$x instanceof ('a' . 'b');
$x instanceof ($y++);
$x instanceof ($y++);

View File

@ -11,6 +11,9 @@ A::$$b[$c]();
($a->b)();
(A::$b)();
('a' . 'b')::X;
(A)::X;
(A)::$x;
(A)::x();
-----
!!php7
(function () {
@ -22,4 +25,7 @@ $A::{$b[$c]}();
A::${$b}[$c]();
($a->b)();
(A::$b)();
('a' . 'b')::X;
('a' . 'b')::X;
(A)::X;
(A)::$x;
(A)::x();

View File

@ -7,7 +7,7 @@ class Foo
const A = 1, B = 2;
public const C = 3, D = 4;
protected const E = 5, F = 6;
private const G = 7, H = 8;
private const int G = 7, H = 8;
}
-----
!!php7
@ -16,5 +16,5 @@ class Foo
const A = 1, B = 2;
public const C = 3, D = 4;
protected const E = 5, F = 6;
private const G = 7, H = 8;
}
private const int G = 7, H = 8;
}