Compare commits

..

12 Commits

Author SHA1 Message Date
1c13d05035 Release PHP-Parser 4.10.0 2020-09-19 16:52:48 +02:00
c7dc3ce552 Add basic FPPP support for attributes 2020-09-19 16:43:45 +02:00
9f6ad686a7 Adjust skip list for moved test
The two remaining test_old failures are due to a bug on the PHP
side.
2020-09-19 15:55:07 +02:00
1899471f80 Update test for PHP 8 error behavior
Replace division by zero with a different error condition that
still warns, and adjust the expected message based on PHP version.
2020-09-19 15:52:05 +02:00
8505acd151 Correctly handle ?-> in encapsed strings
Followup upstream change.
2020-09-19 15:47:14 +02:00
c3e20d9970 Use 8.0.0beta4 to tests against 2020-09-19 15:27:27 +02:00
4c22c62783 [PHP 8.0] Add attributes support (#661)
Adds support for PHP 8 attributes, represented using `AttrGroup` nodes
containing `Attribute` nodes. The `attrGroup` subnode is added to all
nodes that can have attributes.

This is still missing FPPP support.

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
2020-09-13 21:01:17 +02:00
f66a32e2df Emulate PHP 8 attribute syntax
Perform emulation by replacing #[ with %[, then patching % back
to # and coalescing #[ into T_ATTRIBUTE if it is a freestanding
token.
2020-09-06 17:42:38 +02:00
75abbbd2d4 Handle flexible heredoc via TokenEmulator
Extend the interface to support preprocessing.
2020-09-06 17:05:23 +02:00
39b046007d Refactor token emulator registration
Only determine needed emulators based on PHP version once, and
add an adaptor that allows treating forward and reverse emulation
the same.

Previously the isEmulationNeeded() check was too conservative,
as it also considered emulators that are not relevant for the
version. Though possibly that check should just be dropped
altogether.
2020-09-06 15:50:52 +02:00
e3872b8906 Improve compatibility with other libs defining compat tokens 2020-09-06 15:00:28 +02:00
4a40a84cf6 Fixed PHPDoc typo 2020-09-02 12:33:46 +02:00
89 changed files with 3678 additions and 1898 deletions

View File

@ -35,7 +35,7 @@ matrix:
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
php: 7.0
script:
- test_old/run-php-src.sh 8.0.0beta1
- test_old/run-php-src.sh 8.0.0beta4
allow_failures:
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
fast_finish: true

View File

@ -1,8 +1,25 @@
Version 4.9.2-dev
-----------------
Version 4.10.1-dev
------------------
Nothing yet.
Version 4.10.0 (2020-09-19)
---------------------------
### Added
* [PHP 8.0] Added support for attributes. These are represented using a new `AttributeGroup` node
containing `Attribute` nodes. A new `attrGroups` subnode is available on all node types that
support attributes, i.e. `Stmt\Class_`, `Stmt\Trait_`, `Stmt\Interface_`, `Stmt\Function_`,
`Stmt\ClassMethod`, `Stmt\ClassConst`, `Stmt\Property`, `Expr\Closure`, `Expr\ArrowFunction` and
`Param`.
* [PHP 8.0] Added support for nullsafe properties inside interpolated strings, in line with an
upstream change.
### Fixed
* Improved compatibility with other libraries that use forward compatibility defines for PHP tokens.
Version 4.9.1 (2020-08-30)
--------------------------

View File

@ -82,6 +82,31 @@ no_comma:
optional_comma:
/* empty */
| ','
;
attribute_decl:
class_name { $$ = Node\Attribute[$1, []]; }
| class_name argument_list { $$ = Node\Attribute[$1, $2]; }
;
attribute_group:
attribute_decl { init($1); }
| attribute_group ',' attribute_decl { push($1, $3); }
;
attribute:
T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; }
;
attributes:
attribute { init($1); }
| attributes attribute { push($1, $2); }
;
optional_attributes:
/* empty */ { $$ = []; }
| attributes { $$ = $1; }
;
top_statement:
statement { $$ = $1; }
@ -317,18 +342,23 @@ block_or_error:
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8]]; }
{ $$ = 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
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
class_declaration_statement:
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
$this->checkClass($$, #2); }
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
| attributes class_entry_type identifier 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 '}'
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
$this->checkInterface($$, #3); }
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
;
class_entry_type:
@ -489,14 +519,14 @@ optional_visibility_modifier:
;
parameter:
optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($5, null, $2, $3, $4, attributes(), $1);
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($5, $7, $2, $3, $4, attributes(), $1);
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $2, $3, $4, attributes(), $1); }
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
;
type_expr:
@ -600,14 +630,15 @@ class_statement_list:
;
class_statement:
variable_modifiers optional_type_without_static property_declaration_list ';'
{ $attrs = attributes();
$$ = new Stmt\Property($1, $3, $attrs, $2); $this->checkProperty($$, #1); }
| method_modifiers T_CONST class_const_list ';'
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
optional_attributes variable_modifiers optional_type_without_static property_declaration_list ';'
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
$this->checkProperty($$, #2); }
| optional_attributes method_modifiers T_CONST class_const_list ';'
{ $$ = 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
{ $$ = 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]; }
| error { $$ = null; /* will be skipped */ }
;
@ -802,21 +833,27 @@ expr:
| T_THROW expr { $$ = Expr\Throw_[$2]; }
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8]]; }
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; }
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9]]; }
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
block_or_error
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9]]; }
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
| attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; }
;
anonymous_class:
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
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);
$this->checkClass($$[0], -1); }
;

View File

@ -117,28 +117,28 @@ function resolveMacros($code) {
$matches['args']
);
if ('attributes' == $name) {
if ('attributes' === $name) {
assertArgs(0, $args, $name);
return '$this->startAttributeStack[#1] + $this->endAttributes';
}
if ('stackAttributes' == $name) {
if ('stackAttributes' === $name) {
assertArgs(1, $args, $name);
return '$this->startAttributeStack[' . $args[0] . ']'
. ' + $this->endAttributeStack[' . $args[0] . ']';
}
if ('init' == $name) {
if ('init' === $name) {
return '$$ = array(' . implode(', ', $args) . ')';
}
if ('push' == $name) {
if ('push' === $name) {
assertArgs(2, $args, $name);
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
}
if ('pushNormalizing' == $name) {
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
@ -151,20 +151,20 @@ function resolveMacros($code) {
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
}
if ('parseVar' == $name) {
if ('parseVar' === $name) {
assertArgs(1, $args, $name);
return 'substr(' . $args[0] . ', 1)';
}
if ('parseEncapsed' == $name) {
if ('parseEncapsed' === $name) {
assertArgs(3, $args, $name);
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
}
if ('makeNop' == $name) {
if ('makeNop' === $name) {
assertArgs(3, $args, $name);
return '$startAttributes = ' . $args[1] . ';'
@ -182,7 +182,7 @@ function resolveMacros($code) {
. ' else { ' . $args[0] . ' = null; }';
}
if ('strKind' == $name) {
if ('strKind' === $name) {
assertArgs(1, $args, $name);
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
@ -190,7 +190,7 @@ function resolveMacros($code) {
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
if ('prependLeadingComments' == $name) {
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '

View File

@ -110,3 +110,4 @@
%token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED
%token T_NAME_RELATIVE
%token T_ATTRIBUTE

View File

@ -17,6 +17,8 @@ use PhpParser\Node\Expr;
*/
class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
@ -27,9 +29,11 @@ class PrintableNewAnonClassNode extends Expr
public $stmts;
public function __construct(
array $args, Node\Name $extends = null, array $implements, array $stmts, array $attributes
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
@ -42,7 +46,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(
$newNode->args, $class->extends, $class->implements,
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
@ -52,6 +56,6 @@ class PrintableNewAnonClassNode extends Expr
}
public function getSubNodeNames() : array {
return ['args', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
}
}

View File

@ -405,35 +405,60 @@ class Lexer
}
private function defineCompatibilityTokens() {
// PHP 7.4
if (!defined('T_BAD_CHARACTER')) {
\define('T_BAD_CHARACTER', -1);
}
if (!defined('T_FN')) {
\define('T_FN', -2);
}
if (!defined('T_COALESCE_EQUAL')) {
\define('T_COALESCE_EQUAL', -3);
static $compatTokensDefined = false;
if ($compatTokensDefined) {
return;
}
$compatTokens = [
// PHP 7.4
'T_BAD_CHARACTER',
'T_FN',
'T_COALESCE_EQUAL',
// PHP 8.0
if (!defined('T_NAME_QUALIFIED')) {
\define('T_NAME_QUALIFIED', -4);
'T_NAME_QUALIFIED',
'T_NAME_FULLY_QUALIFIED',
'T_NAME_RELATIVE',
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
];
// PHP-Parser might be used together with another library that also emulates some or all
// of these tokens. Perform a sanity-check that all already defined tokens have been
// assigned a unique ID.
$usedTokenIds = [];
foreach ($compatTokens as $token) {
if (\defined($token)) {
$tokenId = \constant($token);
$clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) {
throw new \Error(sprintf(
'Token %s has same ID as token %s, ' .
'you may be using a library with broken token emulation',
$token, $clashingToken
));
}
if (!defined('T_NAME_FULLY_QUALIFIED')) {
\define('T_NAME_FULLY_QUALIFIED', -5);
$usedTokenIds[$token] = $tokenId;
}
if (!defined('T_NAME_RELATIVE')) {
\define('T_NAME_RELATIVE', -6);
}
if (!defined('T_MATCH')) {
\define('T_MATCH', -7);
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
// downwards, but skip any IDs that may already be in use.
$newTokenId = -1;
foreach ($compatTokens as $token) {
if (!\defined($token)) {
while (isset($usedTokenIds[$newTokenId])) {
$newTokenId--;
}
if (!defined('T_NULLSAFE_OBJECT_OPERATOR')) {
\define('T_NULLSAFE_OBJECT_OPERATOR', -8);
\define($token, $newTokenId);
$newTokenId--;
}
}
$compatTokensDefined = true;
}
/**
* Creates the token map.
*
@ -486,6 +511,7 @@ class Lexer
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
return $tokenMap;
}

View File

@ -5,12 +5,15 @@ namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
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\TokenEmulatorInterface;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\Parser\Tokens;
class Emulative extends Lexer
@ -19,17 +22,11 @@ class Emulative extends Lexer
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
/** @var TokenEmulatorInterface[] */
private $tokenEmulators = [];
/** @var TokenEmulator[] */
private $emulators = [];
/** @var string */
private $targetPhpVersion;
@ -46,27 +43,47 @@ REGEX;
parent::__construct($options);
$this->tokenEmulators[] = new FnTokenEmulator();
$this->tokenEmulators[] = new MatchTokenEmulator();
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
$this->tokenEmulators[] = new NullsafeTokenEmulator();
$emulators = [
new FlexibleDocStringEmulator(),
new FnTokenEmulator(),
new MatchTokenEmulator(),
new CoaleseEqualTokenEmulator(),
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
// and the PHP version we're targeting for emulation.
foreach ($emulators as $emulator) {
$emulatorPhpVersion = $emulator->getPhpVersion();
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = $emulator;
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = new ReverseEmulator($emulator);
}
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
$this->patches = [];
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});
if ($this->isEmulationNeeded($code) === false) {
if (empty($emulators)) {
// Nothing to emulate, yay
parent::startLexing($code, $errorHandler);
return;
}
$collector = new ErrorHandler\Collecting();
$this->patches = [];
foreach ($emulators as $emulator) {
$code = $emulator->preprocessCode($code, $this->patches);
}
// 1. emulation of heredoc and nowdoc new syntax
$preparedCode = $this->processHeredocNowdoc($code);
parent::startLexing($preparedCode, $collector);
$collector = new ErrorHandler\Collecting();
parent::startLexing($code, $collector);
$this->sortPatches();
$this->fixupTokens();
$errors = $collector->getErrors();
@ -77,84 +94,28 @@ REGEX;
}
}
foreach ($this->tokenEmulators as $tokenEmulator) {
$emulatorPhpVersion = $tokenEmulator->getPhpVersion();
if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=')
&& $tokenEmulator->isEmulationNeeded($code)) {
$this->tokens = $tokenEmulator->emulate($code, $this->tokens);
} else if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<')
&& $tokenEmulator->isEmulationNeeded($code)) {
$this->tokens = $tokenEmulator->reverseEmulate($code, $this->tokens);
}
foreach ($emulators as $emulator) {
$this->tokens = $emulator->emulate($code, $this->tokens);
}
}
private function isHeredocNowdocEmulationNeeded(string $code): bool
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
}
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
}
private function sortPatches()
{
// skip version where this works without emulation
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
return false;
}
return strpos($code, '<<<') !== false;
}
private function processHeredocNowdoc(string $code): string
{
if ($this->isHeredocNowdocEmulationNeeded($code) === false) {
return $code;
}
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return $code;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$this->patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
return $code;
}
private function isEmulationNeeded(string $code): bool
{
foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
return true;
}
}
return $this->isHeredocNowdocEmulationNeeded($code);
// Patches may be contributed by different emulators.
// Make sure they are sorted by increasing patch position.
usort($this->patches, function($p1, $p2) {
return $p1[0] <=> $p2[0];
});
}
private function fixupTokens()
@ -173,7 +134,20 @@ REGEX;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
$token = $this->tokens[$i];
if (\is_string($token)) {
// We assume that patches don't apply to string tokens
if ($patchPos === $pos) {
// Only support replacement for string tokens.
assert($patchType === 'replace');
$this->tokens[$i] = $patchText;
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
}
$pos += \strlen($token);
continue;
}
@ -201,6 +175,11 @@ REGEX;
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
);
$posDelta += $patchTextLen;
} else if ($patchType === 'replace') {
// Replace inside the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
);
} else {
assert(false);
}
@ -247,7 +226,7 @@ REGEX;
if ($patchType === 'add') {
$posDelta += strlen($patchText);
$lineDelta += substr_count($patchText, "\n");
} else {
} else if ($patchType === 'remove') {
$posDelta -= strlen($patchText);
$lineDelta -= substr_count($patchText, "\n");
}

View File

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class AttributeEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '#[') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way.
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
array_splice($tokens, $i, 2, [
[\T_ATTRIBUTE, '#[', $line]
]);
$c--;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// TODO
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
$pos = 0;
while (false !== $pos = strpos($code, '#[', $pos)) {
// Replace #[ with %[
$code[$pos] = '%';
$patches[] = [$pos, 'replace', '#'];
$pos += 2;
}
return $code;
}
}

View File

@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
final class CoaleseEqualTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{

View File

@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class FlexibleDocStringEmulator extends TokenEmulator
{
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
public function getPhpVersion(): string
{
return Emulative::PHP_7_3;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '<<<') !== false;
}
public function emulate(string $code, array $tokens): array
{
// Handled by preprocessing + fixup.
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Not supported.
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return $code;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
return $code;
}
}

View File

@ -2,7 +2,7 @@
namespace PhpParser\Lexer\TokenEmulator;
abstract class KeywordEmulator implements TokenEmulatorInterface
abstract class KeywordEmulator extends TokenEmulator
{
abstract function getKeywordString(): string;
abstract function getKeywordToken(): int;

View File

@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NullsafeTokenEmulator implements TokenEmulatorInterface
final class NullsafeTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
@ -22,15 +22,35 @@ final class NullsafeTokenEmulator implements TokenEmulatorInterface
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]);
$c--;
continue;
}
// Handle ?-> inside encapsed string.
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
&& $tokens[$i - 1][0] === \T_VARIABLE
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
) {
$replacement = [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
[\T_STRING, $matches[1], $line],
];
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
$replacement[] = [
\T_ENCAPSED_AND_WHITESPACE,
\substr($tokens[$i][1], \strlen($matches[0])),
$line
];
}
array_splice($tokens, $i, 1, $replacement);
$c += \count($replacement) - 1;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}

View File

@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
final class NumericLiteralSeparatorEmulator extends TokenEmulator
{
const BIN = '(?:0b[01]+(?:_[01]+)*)';
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/**
* Reverses emulation direction of the inner emulator.
*/
final class ReverseEmulator extends TokenEmulator
{
/** @var TokenEmulator Inner emulator */
private $emulator;
public function __construct(TokenEmulator $emulator) {
$this->emulator = $emulator;
}
public function getPhpVersion(): string {
return $this->emulator->getPhpVersion();
}
public function isEmulationNeeded(string $code): bool {
return $this->emulator->isEmulationNeeded($code);
}
public function emulate(string $code, array $tokens): array {
return $this->emulator->reverseEmulate($code, $tokens);
}
public function reverseEmulate(string $code, array $tokens): array {
return $this->emulator->emulate($code, $tokens);
}
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
abstract class TokenEmulator
{
abstract public function getPhpVersion(): string;
abstract public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
abstract public function emulate(string $code, array $tokens): array;
/**
* @return array Modified Tokens
*/
abstract public function reverseEmulate(string $code, array $tokens): array;
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}

View File

@ -1,21 +0,0 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
interface TokenEmulatorInterface
{
public function getPhpVersion(): string;
public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
public function emulate(string $code, array $tokens): array;
/**
* @return array Modified Tokens
*/
public function reverseEmulate(string $code, array $tokens): array;
}

View File

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class Attribute extends NodeAbstract
{
/** @var Name Attribute name */
public $name;
/** @var Arg[] Attribute arguments */
public $args;
/**
* @param Node\Name $name Attribute name
* @param Arg[] $args Attribute arguments
* @param array $attributes Additional node attributes
*/
public function __construct(Name $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['name', 'args'];
}
public function getType() : string {
return 'Attribute';
}
}

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class AttributeGroup extends NodeAbstract
{
/** @var Attribute[] Attributes */
public $attrs;
/**
* @param Attribute[] $attrs PHP attributes
* @param array $attributes Additional node attributes
*/
public function __construct(array $attrs, array $attributes = []) {
$this->attributes = $attributes;
$this->attrs = $attrs;
}
public function getSubNodeNames() : array {
return ['attrs'];
}
public function getType() : string {
return 'AttributeGroup';
}
}

View File

@ -5,7 +5,7 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespaced name (for class constants, if using NameResolver)
* @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
*/
class Const_ extends NodeAbstract
{

View File

@ -22,6 +22,8 @@ class ArrowFunction extends Expr implements FunctionLike
/** @var Expr */
public $expr;
/** @var Node\AttributeGroup[] */
public $attrGroups;
/**
* @param array $subNodes Array of the following optional subnodes:
@ -30,6 +32,7 @@ class ArrowFunction extends Expr implements FunctionLike
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'expr' => Expr : Expression body
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
@ -40,10 +43,11 @@ class ArrowFunction extends Expr implements FunctionLike
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['static', 'byRef', 'params', 'returnType', 'expr'];
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
}
public function returnsByRef() : bool {
@ -58,6 +62,10 @@ class ArrowFunction extends Expr implements FunctionLike
return $this->returnType;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/**
* @return Node\Stmt\Return_[]
*/

View File

@ -20,6 +20,8 @@ class Closure extends Expr implements FunctionLike
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a lambda function node.
@ -31,6 +33,7 @@ class Closure extends Expr implements FunctionLike
* 'uses' => array(): use()s
* 'returnType' => null : Return type
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attributes groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
@ -42,10 +45,11 @@ class Closure extends Expr implements FunctionLike
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
@ -65,6 +69,10 @@ class Closure extends Expr implements FunctionLike
return $this->stmts;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
public function getType() : string {
return 'Expr_Closure';
}

View File

@ -16,21 +16,28 @@ interface FunctionLike extends Node
/**
* List of parameters
*
* @return Node\Param[]
* @return Param[]
*/
public function getParams() : array;
/**
* Get the declared return type or null
*
* @return null|Identifier|Node\Name|Node\NullableType|Node\UnionType
* @return null|Identifier|Name|NullableType|UnionType
*/
public function getReturnType();
/**
* The function body
*
* @return Node\Stmt[]|null
* @return Stmt[]|null
*/
public function getStmts();
/**
* Get PHP attribute groups.
*
* @return AttributeGroup[]
*/
public function getAttrGroups() : array;
}

View File

@ -18,6 +18,8 @@ class Param extends NodeAbstract
public $default;
/** @var int */
public $flags;
/** @var AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a parameter node.
@ -27,14 +29,16 @@ class Param extends NodeAbstract
* @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 $flags Optional visibility flags
* @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,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0
int $flags = 0,
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
@ -43,10 +47,11 @@ class Param extends NodeAbstract
$this->var = $var;
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['flags', 'type', 'byRef', 'variadic', 'var', 'default'];
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
}
public function getType() : string {

View File

@ -10,6 +10,8 @@ class ClassConst extends Node\Stmt
public $flags;
/** @var Node\Const_[] Constant declarations */
public $consts;
/** @var Node\AttributeGroup[] */
public $attrGroups;
/**
* Constructs a class const list node.
@ -17,15 +19,22 @@ class ClassConst extends Node\Stmt
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(array $consts, int $flags = 0, array $attributes = []) {
public function __construct(
array $consts,
int $flags = 0,
array $attributes = [],
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->consts = $consts;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['flags', 'consts'];
return ['attrGroups', 'flags', 'consts'];
}
/**

View File

@ -13,6 +13,8 @@ abstract class ClassLike extends Node\Stmt
public $name;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* @return TraitUse[]

View File

@ -19,6 +19,8 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $returnType;
/** @var Node\Stmt[]|null Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
private static $magicNames = [
'__construct' => true,
@ -48,6 +50,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'stmts' => array() : Statements
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
@ -59,10 +62,11 @@ class ClassMethod extends Node\Stmt implements FunctionLike
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['flags', 'byRef', 'name', 'params', 'returnType', 'stmts'];
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
@ -81,6 +85,10 @@ class ClassMethod extends Node\Stmt implements FunctionLike
return $this->stmts;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/**
* Whether the method is explicitly or implicitly public.
*

View File

@ -32,6 +32,7 @@ class Class_ extends ClassLike
* 'extends' => null : Name of extended class
* 'implements' => array(): Names of implemented interfaces
* 'stmts' => array(): Statements
* '$attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
@ -41,10 +42,11 @@ class Class_ extends ClassLike
$this->extends = $subNodes['extends'] ?? null;
$this->implements = $subNodes['implements'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['flags', 'name', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts'];
}
/**

View File

@ -20,6 +20,8 @@ class Function_ extends Node\Stmt implements FunctionLike
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a function node.
@ -30,6 +32,7 @@ class Function_ extends Node\Stmt implements FunctionLike
* 'params' => array(): Parameters
* 'returnType' => null : Return type
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
@ -40,10 +43,11 @@ class Function_ extends Node\Stmt implements FunctionLike
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['byRef', 'name', 'params', 'returnType', 'stmts'];
return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
@ -58,6 +62,10 @@ class Function_ extends Node\Stmt implements FunctionLike
return $this->returnType;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/** @return Node\Stmt[] */
public function getStmts() : array {
return $this->stmts;

View File

@ -16,6 +16,7 @@ class Interface_ extends ClassLike
* @param array $subNodes Array of the following optional subnodes:
* 'extends' => array(): Name of extended interfaces
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
@ -23,10 +24,11 @@ class Interface_ extends ClassLike
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->extends = $subNodes['extends'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['name', 'extends', 'stmts'];
return ['attrGroups', 'name', 'extends', 'stmts'];
}
public function getType() : string {

View File

@ -16,6 +16,8 @@ class Property extends Node\Stmt
public $props;
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
public $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a class property list node.
@ -24,16 +26,18 @@ class Property extends Node\Stmt
* @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
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null) {
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['flags', 'type', 'props'];
return ['attrGroups', 'flags', 'type', 'props'];
}
/**

View File

@ -12,16 +12,18 @@ class Trait_ extends ClassLike
* @param string|Node\Identifier $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['name', 'stmts'];
return ['attrGroups', 'name', 'stmts'];
}
public function getType() : string {

View File

@ -75,6 +75,7 @@ class NameResolver extends NodeVisitorAbstract
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
@ -83,25 +84,32 @@ class NameResolver extends NodeVisitorAbstract
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Trait_) {
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Function_) {
$this->addNamespacedName($node);
$this->resolveSignature($node);
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure
|| $node instanceof Expr\ArrowFunction
) {
$this->resolveSignature($node);
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Property) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
}
} else if ($node instanceof Stmt\ClassConst) {
$this->resolveAttrGroups($node);
} elseif ($node instanceof Expr\StaticCall
|| $node instanceof Expr\StaticPropertyFetch
|| $node instanceof Expr\ClassConstFetch
@ -157,6 +165,7 @@ class NameResolver extends NodeVisitorAbstract
private function resolveSignature($node) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$node->returnType = $this->resolveType($node->returnType);
}
@ -225,4 +234,13 @@ class NameResolver extends NodeVisitorAbstract
$node->namespacedName = Name::concat(
$this->nameContext->getNamespace(), (string) $node->name);
}
protected function resolveAttrGroups(Node $node)
{
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$attr->name = $this->resolveClassName($attr->name);
}
}
}
}

View File

@ -17,11 +17,11 @@ use PhpParser\Node\Stmt;
*/
class Php5 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 391;
protected $tokenToSymbolMapSize = 392;
protected $actionTableSize = 1069;
protected $gotoTableSize = 580;
protected $invalidSymbol = 164;
protected $invalidSymbol = 165;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
@ -193,36 +193,37 @@ class Php5 extends \PhpParser\ParserAbstract
"'`'",
"']'",
"'\"'",
"T_NULLSAFE_OBJECT_OPERATOR"
"T_NULLSAFE_OBJECT_OPERATOR",
"T_ATTRIBUTE"
);
protected $tokenToSymbol = array(
0, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 55, 162, 164, 159, 54, 37, 164,
157, 158, 52, 49, 8, 50, 51, 53, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 31, 154,
43, 16, 45, 30, 67, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 69, 164, 161, 36, 164, 160, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 155, 35, 156, 57, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 1, 2, 3, 4,
0, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 55, 162, 165, 159, 54, 37, 165,
157, 158, 52, 49, 8, 50, 51, 53, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 31, 154,
43, 16, 45, 30, 67, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 69, 165, 161, 36, 165, 160, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 155, 35, 156, 57, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
@ -236,7 +237,7 @@ class Php5 extends \PhpParser\ParserAbstract
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
153
153, 164
);
protected $action = array(

File diff suppressed because it is too large Load Diff

View File

@ -140,4 +140,5 @@ final class Tokens
const T_NAME_FULLY_QUALIFIED = 388;
const T_NAME_QUALIFIED = 389;
const T_NAME_RELATIVE = 390;
const T_ATTRIBUTE = 391;
}

View File

@ -18,7 +18,8 @@ class Standard extends PrettyPrinterAbstract
// Special nodes
protected function pParam(Node\Param $node) {
return ($this->pModifiers($node->flags))
return $this->pAttrGroups($node->attrGroups, true)
. $this->pModifiers($node->flags)
. ($node->type ? $this->p($node->type) . ' ' : '')
. ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '')
@ -52,6 +53,15 @@ class Standard extends PrettyPrinterAbstract
return '$' . $node->name;
}
protected function pAttribute(Node\Attribute $node) {
return $this->p($node->name)
. ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '');
}
protected function pAttributeGroup(Node\AttributeGroup $node) {
return '#[' . $this->pCommaSeparated($node->attrs) . ']';
}
// Names
protected function pName(Name $node) {
@ -600,7 +610,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_Closure(Expr\Closure $node) {
return ($node->static ? 'static ' : '')
return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'function ' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')'
. (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '')
@ -621,7 +632,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) {
return ($node->static ? 'static ' : '')
return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
@ -709,7 +721,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pStmt_Interface(Stmt\Interface_ $node) {
return 'interface ' . $node->name
return $this->pAttrGroups($node->attrGroups)
. 'interface ' . $node->name
. (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
@ -719,7 +732,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pStmt_Trait(Stmt\Trait_ $node) {
return 'trait ' . $node->name
return $this->pAttrGroups($node->attrGroups)
. 'trait ' . $node->name
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
@ -744,7 +758,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pStmt_Property(Stmt\Property $node) {
return (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';';
}
@ -755,7 +770,8 @@ class Standard extends PrettyPrinterAbstract
}
protected function pStmt_ClassMethod(Stmt\ClassMethod $node) {
return $this->pModifiers($node->flags)
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
@ -765,12 +781,14 @@ class Standard extends PrettyPrinterAbstract
}
protected function pStmt_ClassConst(Stmt\ClassConst $node) {
return $this->pModifiers($node->flags)
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Function(Stmt\Function_ $node) {
return 'function ' . ($node->byRef ? '&' : '') . $node->name
return $this->pAttrGroups($node->attrGroups)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
@ -925,7 +943,8 @@ class Standard extends PrettyPrinterAbstract
// Helpers
protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
return $this->pModifiers($node->flags)
return $this->pAttrGroups($node->attrGroups, $node->name === null)
. $this->pModifiers($node->flags)
. 'class' . $afterClassToken
. (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
@ -1030,11 +1049,21 @@ class Standard extends PrettyPrinterAbstract
return false;
}
private function pMaybeMultiline(array $nodes, $trailingComma = false) {
private function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
if (!$this->hasNodeWithComments($nodes)) {
return $this->pCommaSeparated($nodes);
} else {
return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl;
}
}
private function pAttrGroups(array $nodes, bool $inline = false): string {
$result = '';
$sep = $inline ? ' ' : $this->nl;
foreach ($nodes as $node) {
$result .= $this->p($node) . $sep;
}
return $result;
}
}

View File

@ -1375,6 +1375,7 @@ abstract class PrettyPrinterAbstract
'Stmt_Unset->vars' => ', ',
'Stmt_Use->uses' => ', ',
'MatchArm->conds' => ', ',
'AttributeGroup->attrs' => ', ',
// statement lists
'Expr_Closure->stmts' => "\n",
@ -1395,6 +1396,17 @@ abstract class PrettyPrinterAbstract
'Stmt_Function->stmts' => "\n",
'Stmt_If->stmts' => "\n",
'Stmt_Namespace->stmts' => "\n",
'Stmt_Class->attrGroups' => "\n",
'Stmt_Interface->attrGroups' => "\n",
'Stmt_Trait->attrGroups' => "\n",
'Stmt_Function->attrGroups' => "\n",
'Stmt_ClassMethod->attrGroups' => "\n",
'Stmt_ClassConst->attrGroups' => "\n",
'Stmt_Property->attrGroups' => "\n",
'Expr_PrintableNewAnonClass->attrGroups' => ' ',
'Expr_Closure->attrGroups' => ' ',
'Expr_ArrowFunction->attrGroups' => ' ',
'Param->attrGroups' => ' ',
'Stmt_Switch->cases' => "\n",
'Stmt_TraitUse->adaptations' => "\n",
'Stmt_TryCatch->stmts' => "\n",

View File

@ -121,9 +121,11 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase
'Modulo by zero'
],
[
new Expr\BinaryOp\Div(new Scalar\LNumber(42), new Scalar\LNumber(0)),
new Expr\BinaryOp\Plus(new Scalar\LNumber(42), new Scalar\String_("1foo")),
\ErrorException::class,
'Division by zero'
\PHP_VERSION_ID >= 80000
? 'A non-numeric value encountered'
: 'A non well formed numeric value encountered'
],
];
}

View File

@ -275,6 +275,33 @@ class EmulativeTest extends LexerTest
['?->', [
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
]],
['#[Attr]', [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
["#[\nAttr\n]", [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
// Test interaction of two patch-based emulators
["<<<LABEL\n LABEL, #[Attr]", [
[Tokens::T_START_HEREDOC, "<<<LABEL\n"],
[Tokens::T_END_HEREDOC, " LABEL"],
[ord(','), ','],
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
["#[Attr] <<<LABEL\n LABEL,", [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
[Tokens::T_START_HEREDOC, "<<<LABEL\n"],
[Tokens::T_END_HEREDOC, " LABEL"],
[ord(','), ','],
]],
];
}
@ -293,6 +320,22 @@ class EmulativeTest extends LexerTest
['7.4', 'match', [[Tokens::T_STRING, 'match']]],
['7.4', 'fn', [[Tokens::T_FN, 'fn']]],
['7.3', 'fn', [[Tokens::T_STRING, 'fn']]],
// Tested here to skip testLeaveStuffAloneInStrings.
['8.0', '"$foo?->bar"', [
[ord('"'), '"'],
[Tokens::T_VARIABLE, '$foo'],
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[Tokens::T_STRING, 'bar'],
[ord('"'), '"'],
]],
['8.0', '"$foo?->bar baz"', [
[ord('"'), '"'],
[Tokens::T_VARIABLE, '$foo'],
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[Tokens::T_STRING, 'bar'],
[Tokens::T_ENCAPSED_AND_WHITESPACE, ' baz'],
[ord('"'), '"'],
]],
];
}
}

View File

@ -248,6 +248,7 @@ PHP;
}
},
"flags": 0,
"attrGroups": [],
"attributes": {
"startLine": 4,
"endLine": 4
@ -275,6 +276,7 @@ PHP;
}
},
"flags": 0,
"attrGroups": [],
"attributes": {
"startLine": 4,
"endLine": 4
@ -302,6 +304,7 @@ PHP;
}
}
],
"attrGroups": [],
"attributes": {
"startLine": 4,
"comments": [

View File

@ -165,13 +165,8 @@ namespace Baz {
}
EOC;
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$stmts = $this->parseAndResolve($code);
$this->assertSame(
$this->canonicalize($expectedCode),
@ -187,33 +182,43 @@ EOC;
<?php
namespace NS;
#[X]
class A extends B implements C, D {
use E, F, G {
f as private g;
E::h as i;
E::j insteadof F, G;
}
#[X]
public float $php = 7.4;
public ?Foo $person;
protected static ?bool $probability;
public A|B|int $prop;
#[X]
const C = 1;
}
#[X]
interface A extends C, D {
public function a(A $a) : A;
public function b(A|B|int $a): A|B|int;
}
class ClassWithTypeProperties {
public float $php = 7.4;
public ?Foo $person;
protected static ?bool $probability;
public A|B|int $prop;
}
#[X]
trait A {}
function f(A $a) : A {}
#[X]
function f(#[X] A $a) : A {}
function f2(array $a) : array {}
function(A $a) : A {};
function fn3(?A $a) : ?A {}
function fn4(?array $a) : ?array {}
#[X]
function(A $a) : A {};
#[X]
fn(array $a): array => $a;
fn(A $a): A => $a;
fn(?A $a): ?A => $a;
@ -236,6 +241,7 @@ EOC;
$expectedCode = <<<'EOC'
namespace NS;
#[\NS\X]
class A extends \NS\B implements \NS\C, \NS\D
{
use \NS\E, \NS\F, \NS\G {
@ -243,34 +249,40 @@ class A extends \NS\B implements \NS\C, \NS\D
\NS\E::h as i;
\NS\E::j insteadof \NS\F, \NS\G;
}
#[\NS\X]
public float $php = 7.4;
public ?\NS\Foo $person;
protected static ?bool $probability;
public \NS\A|\NS\B|int $prop;
#[\NS\X]
const C = 1;
}
#[\NS\X]
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;
}
class ClassWithTypeProperties
#[\NS\X]
trait A
{
public float $php = 7.4;
public ?\NS\Foo $person;
protected static ?bool $probability;
public \NS\A|\NS\B|int $prop;
}
function f(\NS\A $a) : \NS\A
#[\NS\X]
function f(#[\NS\X] \NS\A $a) : \NS\A
{
}
function f2(array $a) : array
{
}
function (\NS\A $a) : \NS\A {
};
function fn3(?\NS\A $a) : ?\NS\A
{
}
function fn4(?array $a) : ?array
{
}
fn(array $a): array => $a;
#[\NS\X] function (\NS\A $a) : \NS\A {
};
#[\NS\X] fn(array $a): array => $a;
fn(\NS\A $a): \NS\A => $a;
fn(?\NS\A $a): ?\NS\A => $a;
\NS\A::b();
@ -287,13 +299,8 @@ try {
}
EOC;
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$stmts = $this->parseAndResolve($code);
$this->assertSame(
$this->canonicalize($expectedCode),
@ -512,4 +519,14 @@ EOC;
$this->assertEquals(
new Name\FullyQualified('Foo\bar'), $n2->getAttribute('namespacedName'));
}
private function parseAndResolve(string $code): array
{
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
return $traverser->traverse($stmts);
}
}

View File

@ -0,0 +1,179 @@
Attributes
-----
<?php
#[A]
class X {
#[A]
public function m(#[A] & $p) {}
#[A]
public
$prop;
#[A]
const
X = 42;
}
#[A]
trait X {}
#[A]
interface X {}
#[A]
function f() {}
new #[A] class {};
#[A] function() {};
#[A] fn()
=> 42;
-----
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('B'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->params[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
$stmts[2]->attrGroups[] = $attrGroup;
$stmts[3]->attrGroups[] = $attrGroup;
$stmts[4]->expr->class->attrGroups[] = $attrGroup;
$stmts[5]->expr->attrGroups[] = $attrGroup;
$stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
#[B]
class X {
#[A]
#[B]
public function m(#[A] #[B] & $p) {}
#[A]
#[B]
public
$prop;
#[A]
#[B]
const
X = 42;
}
#[A]
#[B]
trait X {}
#[A]
#[B]
interface X {}
#[A]
#[B]
function f() {}
new #[A] #[B] class {};
#[A] #[B] function() {};
#[A] #[B] fn()
=> 42;
-----
<?php
class X {
public function m() {}
public
$prop;
const
X = 42;
}
trait X {}
interface X {}
function f() {}
new class {};
function() {};
fn()
=> 42;
-----
// TODO: Currently we lose formatting for this case.
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('A'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
$stmts[2]->attrGroups[] = $attrGroup;
$stmts[3]->attrGroups[] = $attrGroup;
$stmts[4]->expr->class->attrGroups[] = $attrGroup;
$stmts[5]->expr->attrGroups[] = $attrGroup;
$stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
class X
{
#[A]
public function m()
{
}
#[A]
public $prop;
#[A]
const X = 42;
}
#[A]
trait X
{
}
#[A]
interface X
{
}
#[A]
function f()
{
}
new #[A] class
{
};
#[A] function () {
};
#[A] fn() => 42;
-----
<?php
#[ A, B]
class X {};
#[
A,
B,
]
class X {};
-----
$attr = new Node\Attribute(new Node\Name('C'), []);
$stmts[0]->attrGroups[0]->attrs[] = $attr;
$stmts[1]->attrGroups[0]->attrs[] = $attr;
-----
<?php
#[ A, B, C]
class X {};
#[
A,
B,
C,
]
class X {};

View File

@ -8,6 +8,8 @@ class MyClass {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: MyClass
@ -17,6 +19,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
type: null
props: array(

View File

@ -161,6 +161,8 @@ function test() {
Syntax error, unexpected '}' from 4:1 to 4:1
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
@ -306,6 +308,8 @@ function foo() {
Syntax error, unexpected '}', expecting T_STRING or T_VARIABLE or '{' or '$' from 4:1 to 4:1
array(
0: Stmt_Function[2:1 - 4:1](
attrGroups: array(
)
byRef: false
name: Identifier[2:10 - 2:12](
name: foo
@ -683,6 +687,8 @@ array(
)
)
4: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: X
@ -736,6 +742,8 @@ array(
)
)
2: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
@ -749,6 +757,8 @@ array(
)
)
3: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -763,6 +773,8 @@ array(
)
)
5: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: I
)
@ -895,6 +907,8 @@ class Foo {
Syntax error, unexpected T_STRING from 4:5 to 4:9
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Foo
@ -904,6 +918,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -998,12 +1014,16 @@ Syntax error, unexpected ')', expecting T_VARIABLE from 22:21 to 22:21
Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1027,12 +1047,16 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1047,6 +1071,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1070,12 +1096,16 @@ array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -1095,12 +1125,16 @@ array(
)
)
3: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: true
@ -1120,12 +1154,16 @@ array(
)
)
4: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1144,6 +1182,8 @@ array(
)
)
5: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Bar
@ -1153,6 +1193,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
@ -1160,6 +1202,8 @@ array(
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1181,10 +1225,14 @@ array(
)
6: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -1219,9 +1267,9 @@ $array = [
];
-----
!!php7
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 3:18 to 3:34
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 6:12 to 6:28
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 9:21 to 9:37
Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 3:18 to 3:34
Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 6:12 to 6:28
Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 9:21 to 9:37
array(
0: Stmt_Expression(
expr: Expr_Assign(
@ -1330,6 +1378,8 @@ function foo() :
Syntax error, unexpected '{' from 3:1 to 3:1
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
@ -1351,4 +1401,4 @@ array(
$a = ["a "thing"];
-----
!!php7
Syntax error, unexpected T_STRING, expecting ',' or ')' or ']' from 2:11 to 2:15
Syntax error, unexpected T_STRING, expecting ',' or ']' or ')' from 2:11 to 2:15

View File

@ -59,6 +59,8 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: getArr
@ -92,6 +94,8 @@ array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: arrGen

View File

@ -12,10 +12,14 @@ fn(): int => $x;
array(
0: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: bool
@ -36,10 +40,14 @@ array(
)
1: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -60,10 +68,14 @@ array(
)
2: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: true
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: true
@ -82,10 +94,14 @@ array(
)
3: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: true
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -104,10 +120,14 @@ array(
)
4: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -118,6 +138,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -136,6 +158,8 @@ array(
)
5: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(

View File

@ -12,10 +12,14 @@ function() use($a) : \Foo\Bar {};
array(
0: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -40,10 +44,14 @@ array(
)
1: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -69,6 +77,8 @@ array(
)
2: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
@ -94,10 +104,14 @@ array(
)
3: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: true
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -117,6 +131,8 @@ array(
)
4: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: true
byRef: false
params: array(
@ -130,10 +146,14 @@ array(
)
5: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -155,6 +175,8 @@ array(
)
6: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(

View File

@ -7,6 +7,8 @@ function() use($a,) {};
array(
0: Stmt_Expression(
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(

View File

@ -71,11 +71,13 @@ array(
4: Stmt_Expression(
expr: Scalar_Encapsed(
parts: array(
0: Expr_Variable(
0: Expr_NullsafePropertyFetch(
var: Expr_Variable(
name: a
)
1: Scalar_EncapsedStringPart(
value: ?->b
name: Identifier(
name: b
)
)
)
)

View File

@ -204,10 +204,14 @@ array(
name: Expr_FuncCall(
name: Expr_FuncCall(
name: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -274,10 +278,14 @@ array(
name: f
)
expr: Expr_Closure(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false

View File

@ -54,6 +54,8 @@ class Foo {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
@ -63,6 +65,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
@ -75,6 +79,8 @@ array(
)
)
1: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
@ -87,6 +93,8 @@ array(
)
)
2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -99,6 +107,8 @@ array(
)
)
3: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -111,6 +121,8 @@ array(
)
)
4: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -123,6 +135,8 @@ array(
)
)
5: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -135,6 +149,8 @@ array(
)
)
6: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
@ -156,6 +172,8 @@ array(
)
)
7: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
@ -344,6 +362,8 @@ array(
)
)
10: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Foo

View File

@ -0,0 +1,388 @@
Attributes
-----
<?php
#[
A1,
A2(),
A3(0),
A4(x: 1),
]
function a() {
}
#[A5]
class C {
#[A6]
public function m(
#[A7] $param,
) {}
#[A14]
public $prop;
}
#[A8]
interface I {}
#[A9]
trait T {}
$x = #[A10] function() {};
$y = #[A11] fn() => 0;
$a = #[A12] static function() {};
$b = #[A13] static fn() => 0;
-----
!!php7
array(
0: Stmt_Function(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A1
)
)
args: array(
)
)
1: Attribute(
name: Name(
parts: array(
0: A2
)
)
args: array(
)
)
2: Attribute(
name: Name(
parts: array(
0: A3
)
)
args: array(
0: Arg(
name: null
value: Scalar_LNumber(
value: 0
)
byRef: false
unpack: false
)
)
)
3: Attribute(
name: Name(
parts: array(
0: A4
)
)
args: array(
0: Arg(
name: Identifier(
name: x
)
value: Scalar_LNumber(
value: 1
)
byRef: false
unpack: false
)
)
)
)
)
)
byRef: false
name: Identifier(
name: a
)
params: array(
)
returnType: null
stmts: array(
)
)
1: Stmt_Class(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A5
)
)
args: array(
)
)
)
)
)
flags: 0
name: Identifier(
name: C
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A6
)
)
args: array(
)
)
)
)
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
name: m
)
params: array(
0: Param(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A7
)
)
args: array(
)
)
)
)
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: param
)
default: null
)
)
returnType: null
stmts: array(
)
)
1: Stmt_Property(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A14
)
)
args: array(
)
)
)
)
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
)
)
)
2: Stmt_Interface(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A8
)
)
args: array(
)
)
)
)
)
name: Identifier(
name: I
)
extends: array(
)
stmts: array(
)
)
3: Stmt_Trait(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A9
)
)
args: array(
)
)
)
)
)
name: Identifier(
name: T
)
stmts: array(
)
)
4: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: x
)
expr: Expr_Closure(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A10
)
)
args: array(
)
)
)
)
)
static: false
byRef: false
params: array(
)
uses: array(
)
returnType: null
stmts: array(
)
)
)
)
5: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: y
)
expr: Expr_ArrowFunction(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A11
)
)
args: array(
)
)
)
)
)
static: false
byRef: false
params: array(
)
returnType: null
expr: Scalar_LNumber(
value: 0
)
)
)
)
6: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Expr_Closure(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A12
)
)
args: array(
)
)
)
)
)
static: true
byRef: false
params: array(
)
uses: array(
)
returnType: null
stmts: array(
)
)
)
)
7: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: b
)
expr: Expr_ArrowFunction(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A13
)
)
args: array(
)
)
)
)
)
static: true
byRef: false
params: array(
)
returnType: null
expr: Scalar_LNumber(
value: 0
)
)
)
)
)

View File

@ -9,6 +9,8 @@ abstract class A {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
name: Identifier(
name: A
@ -18,6 +20,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -30,6 +34,8 @@ array(
)
)
1: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_ABSTRACT (17)
byRef: false
name: Identifier(

View File

@ -25,6 +25,8 @@ array(
0: Stmt_Expression(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: null
@ -32,6 +34,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -52,6 +56,8 @@ array(
1: Stmt_Expression(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: Name(
@ -81,6 +87,8 @@ array(
2: Stmt_Expression(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: null
@ -88,6 +96,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -108,6 +118,8 @@ array(
3: Stmt_Expression(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: Name(
@ -152,6 +164,8 @@ array(
)
)
4: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -161,6 +175,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -173,6 +189,8 @@ array(
0: Stmt_Return(
expr: Expr_New(
class: Stmt_Class(
attrGroups: array(
)
flags: 0
name: null
extends: Name(
@ -184,6 +202,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(

View File

@ -0,0 +1,66 @@
Class position
-----
<?php
class A {
}
try {
} catch (Exception $e) {
}
class B {
}
?>
-----
!!positions
array(
0: Stmt_Class[3:1 - 4:1](
attrGroups: array(
)
flags: 0
name: Identifier[3:7 - 3:7](
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
1: Stmt_TryCatch[6:1 - 8:1](
stmts: array(
)
catches: array(
0: Stmt_Catch[7:3 - 8:1](
types: array(
0: Name[7:10 - 7:18](
parts: array(
0: Exception
)
)
)
var: Expr_Variable[7:20 - 7:21](
name: e
)
stmts: array(
)
)
)
finally: null
)
2: Stmt_Class[10:1 - 11:1](
attrGroups: array(
)
flags: 0
name: Identifier[10:7 - 10:7](
name: B
)
extends: null
implements: array(
)
stmts: array(
)
)
)

View File

@ -17,6 +17,8 @@ array(
)
stmts: array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A

View File

@ -9,6 +9,8 @@ class A {
Cannot use 'static' as constant modifier from 3:5 to 3:10
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -18,6 +20,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
consts: array(
0: Const(
@ -43,6 +47,8 @@ class A {
Cannot use 'abstract' as constant modifier from 3:5 to 3:12
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -52,6 +58,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
consts: array(
0: Const(
@ -77,6 +85,8 @@ class A {
Cannot use 'final' as constant modifier from 3:5 to 3:9
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -86,6 +96,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
consts: array(
0: Const(
@ -111,6 +123,8 @@ class A {
Multiple access type modifiers are not allowed from 3:12 to 3:17
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -120,6 +134,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
consts: array(
0: Const(

View File

@ -12,6 +12,8 @@ class Foo {
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Foo
@ -21,6 +23,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
@ -34,6 +38,8 @@ array(
)
)
1: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
consts: array(
0: Const(
@ -47,6 +53,8 @@ array(
)
)
2: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
consts: array(
0: Const(
@ -60,6 +68,8 @@ array(
)
)
3: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
consts: array(
0: Const(

View File

@ -6,6 +6,8 @@ final class A {}
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
name: Identifier(
name: A

View File

@ -14,6 +14,8 @@ abstract class A {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
name: Identifier(
name: A
@ -23,6 +25,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: 0
type: null
props: array(
@ -35,6 +39,8 @@ array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
type: null
props: array(
@ -47,6 +53,8 @@ array(
)
)
2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
byRef: false
name: Identifier(
@ -58,6 +66,8 @@ array(
stmts: null
)
3: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
byRef: false
name: Identifier(
@ -70,6 +80,8 @@ array(
)
)
4: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -82,6 +94,8 @@ array(
)
)
5: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC | MODIFIER_FINAL (40)
byRef: false
name: Identifier(
@ -94,6 +108,8 @@ array(
)
)
6: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(

View File

@ -8,6 +8,8 @@ interface A extends C, D {
-----
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: A
)
@ -25,6 +27,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(

View File

@ -5,6 +5,8 @@ Invalid modifier combination
Multiple access type modifiers are not allowed from 1:24 to 1:29
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -14,6 +16,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -34,6 +38,8 @@ array(
Multiple access type modifiers are not allowed from 1:24 to 1:32
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -43,6 +49,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_PROTECTED (3)
type: null
props: array(
@ -63,6 +71,8 @@ array(
Multiple abstract modifiers are not allowed from 1:26 to 1:33
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -72,6 +82,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
byRef: false
name: Identifier(
@ -91,6 +103,8 @@ array(
Multiple static modifiers are not allowed from 1:24 to 1:29
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -100,6 +114,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
type: null
props: array(
@ -120,6 +136,8 @@ array(
Multiple final modifiers are not allowed from 1:23 to 1:27
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -129,6 +147,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
byRef: false
name: Identifier(
@ -149,6 +169,8 @@ array(
Cannot use the final modifier on an abstract class member from 1:26 to 1:30
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -158,6 +180,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
byRef: false
name: Identifier(
@ -178,6 +202,8 @@ array(
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
name: Identifier(
name: A
@ -200,6 +226,8 @@ array(
Properties cannot be declared abstract from 1:17 to 1:24
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -209,6 +237,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16)
type: null
props: array(
@ -229,6 +259,8 @@ array(
Properties cannot be declared final from 1:17 to 1:21
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -238,6 +270,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_FINAL (32)
type: null
props: array(

View File

@ -5,6 +5,8 @@ Invalid class name
Cannot use 'self' as class name as it is reserved from 1:13 to 1:16
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: self
@ -22,6 +24,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:13 to 1:18
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: PARENT
@ -45,6 +49,8 @@ array(
Cannot use 'self' as class name as it is reserved from 1:23 to 1:26
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -66,6 +72,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -87,6 +95,8 @@ array(
Cannot use 'static' as class name as it is reserved from 1:23 to 1:28
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -108,6 +118,8 @@ array(
Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -130,6 +142,8 @@ array(
Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -152,6 +166,8 @@ array(
Cannot use 'static' as interface name as it is reserved from 1:26 to 1:31
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -174,6 +190,8 @@ array(
Cannot use 'self' as class name as it is reserved from 1:17 to 1:20
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: self
)
@ -189,6 +207,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:17 to 1:22
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: PARENT
)
@ -210,6 +230,8 @@ array(
Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: A
)
@ -230,6 +252,8 @@ array(
Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: A
)
@ -250,6 +274,8 @@ array(
Cannot use 'static' as interface name as it is reserved from 1:27 to 1:32
array(
0: Stmt_Interface(
attrGroups: array(
)
name: Identifier(
name: A
)

View File

@ -10,6 +10,8 @@ class A {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -19,6 +21,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: 0
type: null
props: array(
@ -31,6 +35,8 @@ array(
)
)
1: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
@ -43,6 +49,8 @@ array(
)
)
2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_STATIC (24)
byRef: false
name: Identifier(

View File

@ -11,6 +11,8 @@ class A {
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -20,6 +22,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: Identifier(
name: string
@ -34,6 +38,8 @@ array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED | MODIFIER_STATIC (10)
type: Name(
parts: array(
@ -50,6 +56,8 @@ array(
)
)
2: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: NullableType(
type: Identifier(

View File

@ -13,6 +13,8 @@ class Point {
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Point
@ -22,6 +24,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -29,6 +33,8 @@ array(
)
params: array(
0: Param(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: Identifier(
name: float
@ -43,6 +49,8 @@ array(
)
)
1: Param(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
type: Identifier(
name: array
@ -58,6 +66,8 @@ array(
)
)
2: Param(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: Identifier(
name: string

View File

@ -18,6 +18,8 @@ class A extends B implements C, D {
-----
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -41,6 +43,8 @@ array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
@ -62,6 +66,8 @@ array(
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
@ -84,6 +90,8 @@ array(
)
)
2: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
type: null
props: array(
@ -96,6 +104,8 @@ array(
)
)
3: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: null
props: array(
@ -108,6 +118,8 @@ array(
)
)
4: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -120,6 +132,8 @@ array(
)
)
5: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9)
byRef: false
name: Identifier(
@ -127,6 +141,8 @@ array(
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -142,6 +158,8 @@ array(
)
)
6: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_FINAL (33)
byRef: false
name: Identifier(
@ -158,6 +176,8 @@ array(
)
)
7: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2)
byRef: false
name: Identifier(
@ -170,6 +190,8 @@ array(
)
)
8: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
byRef: false
name: Identifier(

View File

@ -5,6 +5,8 @@ Some special methods cannot be static
Constructor __construct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -14,6 +16,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -34,6 +38,8 @@ array(
Destructor __destruct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -43,6 +49,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -63,6 +71,8 @@ array(
Clone method __clone() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -72,6 +82,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -92,6 +104,8 @@ array(
Constructor __CONSTRUCT() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -101,6 +115,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -121,6 +137,8 @@ array(
Destructor __Destruct() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -130,6 +148,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(
@ -150,6 +170,8 @@ array(
Clone method __cLoNe() cannot be static from 1:17 to 1:22
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
@ -159,6 +181,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8)
byRef: false
name: Identifier(

View File

@ -8,6 +8,8 @@ class Test {
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
@ -17,6 +19,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9)
byRef: false
name: Identifier(

View File

@ -23,11 +23,15 @@ class B {
-----
array(
0: Stmt_Trait(
attrGroups: array(
)
name: Identifier(
name: A
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
@ -42,6 +46,8 @@ array(
)
)
1: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: B

View File

@ -6,12 +6,16 @@ function test(bool $a, Int $b, FLOAT $c, StRiNg $d, iterable $e, object $f, mixe
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: bool
@ -24,6 +28,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: int
@ -36,6 +42,8 @@ array(
default: null
)
2: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: float
@ -48,6 +56,8 @@ array(
default: null
)
3: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: string
@ -60,6 +70,8 @@ array(
default: null
)
4: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: iterable
@ -72,6 +84,8 @@ array(
default: null
)
5: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: object
@ -84,6 +98,8 @@ array(
default: null
)
6: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: mixed

View File

@ -7,12 +7,16 @@ function &a($b) {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: a
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: true
@ -28,12 +32,16 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: true
name: Identifier(
name: a
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false

View File

@ -17,6 +17,8 @@ array(
)
stmts: array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: A

View File

@ -16,12 +16,16 @@ function a(
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: a
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -38,6 +42,8 @@ array(
)
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -50,6 +56,8 @@ array(
)
)
2: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -69,6 +77,8 @@ array(
)
)
3: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -83,6 +93,8 @@ array(
)
)
4: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -97,6 +109,8 @@ array(
)
)
5: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -110,6 +124,8 @@ array(
)
)
6: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -123,6 +139,8 @@ array(
)
)
7: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -144,6 +162,8 @@ array(
)
)
8: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false

View File

@ -8,12 +8,16 @@ function test(?Foo $bar, ?string $foo) : ?Baz {
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: NullableType(
type: Name(
@ -30,6 +34,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: NullableType(
type: Identifier(

View File

@ -8,12 +8,16 @@ function foo($bar, ) {
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -42,6 +46,8 @@ class Foo
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Foo
@ -51,6 +57,8 @@ array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
@ -58,6 +66,8 @@ array(
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -68,6 +78,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -93,10 +105,14 @@ fn($foo, ) => $bar;
array(
0: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false

View File

@ -9,6 +9,8 @@ function test4() : Foo\Bar {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test1
@ -20,6 +22,8 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test2
@ -33,6 +37,8 @@ array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test3
@ -46,6 +52,8 @@ array(
)
)
3: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test4

View File

@ -9,6 +9,8 @@ function a() {
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: a

View File

@ -6,12 +6,16 @@ function a($b, array $c, callable $d, E $f) {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: a
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -22,6 +26,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: array
@ -34,6 +40,8 @@ array(
default: null
)
2: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: callable
@ -46,6 +54,8 @@ array(
default: null
)
3: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(

View File

@ -11,6 +11,8 @@ function test(A|B $a): int|false {}
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
@ -20,6 +22,8 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: UnionType(
types: array(
@ -48,12 +52,16 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(

View File

@ -8,12 +8,16 @@ function test($a, Type &... $b) {}
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -24,6 +28,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -39,12 +45,16 @@ array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -55,6 +65,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: true
@ -70,12 +82,16 @@ array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -86,6 +102,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(
@ -105,12 +123,16 @@ array(
)
)
3: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
@ -121,6 +143,8 @@ array(
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: Name(
parts: array(

View File

@ -6,12 +6,16 @@ function foo(...$foo = []) {}
Variadic parameter cannot have a default value from 2:24 to 2:25
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: foo
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false

View File

@ -33,6 +33,8 @@ function gen() {
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: gen

View File

@ -17,6 +17,8 @@ function gen() {
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: gen

View File

@ -10,6 +10,8 @@ function gen() {
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: gen

View File

@ -19,4 +19,4 @@ $a?->b($c)?->d;
$a?->b($c)();
new $a?->b();
"{$a?->b}";
"{$a}?->b";
"{$a?->b}";

View File

@ -0,0 +1,61 @@
Attributes
-----
<?php
#[
A1,
A2(),
A3(0),
A4(x: 1),
]
function a() {
}
#[A5]
class C {
#[A6]
public function m(
#[A7] $param,
) {}
#[A12]
public $prop;
}
#[A8]
interface I {}
#[A9]
trait T {}
$x = #[A10] function() {};
$y = #[A11] fn() => 0;
new #[A13] class {};
-----
!!php7
#[A1, A2, A3(0), A4(x: 1)]
function a()
{
}
#[A5]
class C
{
#[A6]
public function m(#[A7] $param)
{
}
#[A12]
public $prop;
}
#[A8]
interface I
{
}
#[A9]
trait T
{
}
$x = #[A10] function () {
};
$y = #[A11] fn() => 0;
new #[A13] class
{
};

View File

@ -101,6 +101,7 @@ switch ($testType) {
| Zend.tests.bug74947
# pretty print differences due to negative LNumbers
| Zend.tests.neg_num_string
| Zend.tests.numeric_strings.neg_num_string
| Zend.tests.bug72918
# pretty print difference due to nop statements
| ext.mbstring.tests.htmlent