Compare commits

..

7 Commits

27 changed files with 1437 additions and 1136 deletions

View File

@ -1,8 +1,17 @@
Version 4.13.3-dev Version 4.14.1-dev
------------------ ------------------
Nothing yet. Nothing yet.
Version 4.14.0 (2022-05-31)
---------------------------
### Added
* Added support for readonly classes.
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
value of the literal (e.g. `"1_000"` rather than `1000`).
Version 4.13.2 (2021-11-30) Version 4.13.2 (2021-11-30)
--------------------------- ---------------------------

View File

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

View File

@ -689,9 +689,7 @@ array_expr:
scalar_dereference: scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */ /* alternative array syntax missing intentionally */
@ -793,10 +791,8 @@ ctor_arguments:
common_scalar: common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); } T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; } | T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; } | T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; } | T_DIR { $$ = Scalar\MagicConst\Dir[]; }

View File

@ -382,8 +382,18 @@ enum_case_expr:
class_entry_type: class_entry_type:
T_CLASS { $$ = 0; } T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } | class_modifiers T_CLASS { $$ = $1; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } ;
class_modifiers:
class_modifier { $$ = $1; }
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
;
class_modifier:
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
; ;
extends_from: extends_from:
@ -1014,9 +1024,7 @@ dereferencable_scalar:
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); } $$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; } | array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| '"' encaps_list '"' | '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
@ -1024,7 +1032,7 @@ dereferencable_scalar:
scalar: scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); } T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| dereferencable_scalar { $$ = $1; } | dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; } | constant { $$ = $1; }
| class_constant { $$ = $1; } | class_constant { $$ = $1; }

View File

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

View File

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

View File

@ -310,4 +310,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier); Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier; return $modifiers | $modifier;
} }
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
} }

View File

@ -11,7 +11,7 @@ class Const_ extends NodeAbstract
/** @var Expr Value */ /** @var Expr Value */
public $value; public $value;
/** @var Name Namespaced name (if using NameResolver) */ /** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value']; return ['value'];
} }
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/** /**
* @internal * @internal
* *

View File

@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute * @return LNumber The constructed LNumber, including kind attribute
*/ */
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$attributes['rawValue'] = $str;
$str = str_replace('_', '', $str); $str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) { if ('0' !== $str[0] || '0' === $str) {

View File

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

View File

@ -13,7 +13,7 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\AttributeGroup[] PHP attribute groups */ /** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups; public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */ /** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

@ -68,6 +68,10 @@ class Class_ extends ClassLike
return (bool) ($this->flags & self::MODIFIER_FINAL); return (bool) ($this->flags & self::MODIFIER_FINAL);
} }
public function isReadonly() : bool {
return (bool) ($this->flags & self::MODIFIER_READONLY);
}
/** /**
* Whether the class is anonymous. * Whether the class is anonymous.
* *
@ -77,6 +81,27 @@ class Class_ extends ClassLike
return null === $this->name; return null === $this->name;
} }
/**
* @internal
*/
public static function verifyClassModifier($a, $b) {
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class');
}
}
/** /**
* @internal * @internal
*/ */

View File

@ -20,7 +20,7 @@ class Function_ extends Node\Stmt implements FunctionLike
/** @var Node\AttributeGroup[] PHP attribute groups */ /** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups; public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */ /** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName; public $namespacedName;
/** /**

View File

@ -2147,8 +2147,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
}, },
392 => function ($stackPos) { 392 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(4-1)][0] === "'" || ($this->semStack[$stackPos-(4-1)][1] === "'" && ($this->semStack[$stackPos-(4-1)][0] === 'b' || $this->semStack[$stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); $this->semValue = new Expr\ArrayDimFetch(Scalar\String_::fromString($this->semStack[$stackPos-(4-1)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
$this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(4-1)]), $attrs), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
}, },
393 => function ($stackPos) { 393 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
@ -2275,11 +2274,10 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true); $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true);
}, },
434 => function ($stackPos) { 434 => function ($stackPos) {
$this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
}, },
435 => function ($stackPos) { 435 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, false);
$this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)], false), $attrs);
}, },
436 => function ($stackPos) { 436 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);

File diff suppressed because it is too large Load Diff

View File

@ -875,6 +875,15 @@ abstract class ParserAbstract implements Parser
return $attributes; return $attributes;
} }
protected function checkClassModifier($a, $b, $modifierPos) {
try {
Class_::verifyClassModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkModifier($a, $b, $modifierPos) { protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere // Jumping through some hoops here because verifyModifier() is also used elsewhere
try { try {

View File

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

View File

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

View File

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

View File

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

View File

@ -245,6 +245,7 @@ PHP;
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4, "endLine": 4,
"rawValue": "0",
"kind": 10 "kind": 10
} }
}, },
@ -273,7 +274,8 @@ PHP;
"value": 1, "value": 1,
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4,
"rawValue": "1.0"
} }
}, },
"flags": 0, "flags": 0,
@ -295,7 +297,8 @@ PHP;
"attributes": { "attributes": {
"startLine": 5, "startLine": 5,
"endLine": 5, "endLine": 5,
"kind": 1 "kind": 1,
"rawValue": "'Foo'"
} }
} }
], ],
@ -398,6 +401,7 @@ JSON;
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4, "endLine": 4,
"rawValue": "0",
"kind": 10 "kind": 10
}, },
"value": 0 "value": 0
@ -426,7 +430,8 @@ JSON;
"nodeType": "Scalar_DNumber", "nodeType": "Scalar_DNumber",
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4,
"rawValue": "1.0"
}, },
"value": 1 "value": 1
}, },
@ -448,7 +453,8 @@ JSON;
"attributes": { "attributes": {
"startLine": 5, "startLine": 5,
"endLine": 5, "endLine": 5,
"kind": 1 "kind": 1,
"rawValue": "'Foo'"
}, },
"value": "Foo" "value": "Foo"
} }

View File

@ -66,7 +66,7 @@ array(
) )
) )
----- -----
<?php class A { readonly readonly $a; } <?php class C { readonly readonly $a; }
----- -----
!!php7 !!php7
Multiple readonly modifiers are not allowed from 1:26 to 1:33 Multiple readonly modifiers are not allowed from 1:26 to 1:33
@ -76,7 +76,7 @@ array(
) )
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: C
) )
extends: null extends: null
implements: array( implements: array(
@ -231,8 +231,29 @@ array(
) )
----- -----
<?php abstract final class A { } <?php abstract final class A { }
-----
!!php7
Cannot use the final modifier on an abstract class from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32 // Type in the partial parse could conceivably be any of 0, 16 or 32
----- -----
!!php5
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20 Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array( array(
0: Stmt_Class( 0: Stmt_Class(
@ -258,6 +279,7 @@ array(
<?php readonly class A { } <?php readonly class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32 // Type in the partial parse could conceivably be any of 0, 16 or 32
----- -----
!!php5
Syntax error, unexpected T_READONLY from 1:7 to 1:14 Syntax error, unexpected T_READONLY from 1:7 to 1:14
array( array(
0: Stmt_Class( 0: Stmt_Class(
@ -280,7 +302,7 @@ array(
) )
) )
----- -----
<?php class A { abstract $a; } <?php class B { abstract $b; }
----- -----
Properties cannot be declared abstract from 1:17 to 1:24 Properties cannot be declared abstract from 1:17 to 1:24
array( array(
@ -289,7 +311,7 @@ array(
) )
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: B
) )
extends: null extends: null
implements: array( implements: array(
@ -303,7 +325,7 @@ array(
props: array( props: array(
0: Stmt_PropertyProperty( 0: Stmt_PropertyProperty(
name: VarLikeIdentifier( name: VarLikeIdentifier(
name: a name: b
) )
default: null default: null
) )

View File

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

View File

@ -0,0 +1,12 @@
Readonly class
-----
<?php
readonly class Foo
{
}
-----
!!php7
readonly class Foo
{
}