Compare commits

...

7 Commits

Author SHA1 Message Date
617d0220b9 Release PHP-Parser 4.15.0 2022-09-03 21:54:32 +02:00
a951e9e24d Add support for DNF types (#862)
(cherry picked from commit 9b2a01aa0c)
2022-09-03 21:50:20 +02:00
b30e7e73d5 Support empty list insertion for attributes
(cherry picked from commit a2608f0b74)
2022-09-03 21:48:21 +02:00
ff24d1d61a Fix length bounds check in Name::slice()
The length check did not take into account that there may be a
non-zero offset at this point.

Fixes #875.

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

Non-trivial backport from 1eba33d4124a248dd5c970fa7d6064d2e31b8635.
2022-09-03 21:36:40 +02:00
18 changed files with 1347 additions and 1025 deletions

View File

@ -1,7 +1,17 @@
Version 4.14.1-dev
------------------
Version 4.15.0 (2022-09-03)
---------------------------
Nothing yet.
### Added
* PHP 8.2: Added support for `true` type.
* PHP 8.2: Added support for DNF types.
### Fixed
* Support `readonly` as a function name.
* Added `__serialize` and `__unserialize` to magic method list.
* Fixed bounds check in `Name::slice()`.
* Fixed formatting preservation when adding attributes to a class/method/etc that previously had none.
Version 4.14.0 (2022-05-31)
---------------------------

View File

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

View File

@ -571,7 +571,7 @@ type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = Node\IntersectionType[$1]; }
| intersection_type { $$ = $1; }
;
type:
@ -585,34 +585,52 @@ type_without_static:
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type_element:
type { $$ = $1; }
| '(' intersection_type ')' { $$ = $2; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
union_type_element '|' union_type_element { init($1, $3); }
| union_type '|' union_type_element { push($1, $3); }
;
union_type_without_static_element:
type_without_static { $$ = $1; }
| '(' intersection_type_without_static ')' { $$ = $2; }
;
union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); }
| union_type_without_static '|' type_without_static { push($1, $3); }
union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
| union_type_without_static '|' union_type_without_static_element { push($1, $3); }
;
intersection_type_list:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ push($1, $3); }
;
intersection_type:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
intersection_type_list { $$ = Node\IntersectionType[$1]; }
;
intersection_type_without_static_list:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
;
intersection_type_without_static:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
;
type_expr_without_static:
type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = Node\IntersectionType[$1]; }
| intersection_type_without_static { $$ = $1; }
;
optional_type_without_static:

View File

@ -178,7 +178,20 @@ final class BuilderHelpers
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed', 'never',
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);

View File

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

View File

@ -23,21 +23,23 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $attrGroups;
private static $magicNames = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__serialize' => true,
'__unserialize' => true,
];
/**

View File

@ -10,7 +10,7 @@ class UnionType extends ComplexType
/**
* Constructs a union type.
*
* @param (Identifier|Name)[] $types Types
* @param (Identifier|Name|IntersectionType)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {

File diff suppressed because it is too large Load Diff

View File

@ -664,6 +664,7 @@ abstract class ParserAbstract implements Parser
'false' => true,
'mixed' => true,
'never' => true,
'true' => true,
];
if (!$name->isUnqualified()) {

View File

@ -46,7 +46,15 @@ class Standard extends PrettyPrinterAbstract
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
$types = [];
foreach ($node->types as $typeNode) {
if ($typeNode instanceof Node\IntersectionType) {
$types[] = '('. $this->p($typeNode) . ')';
continue;
}
$types[] = $this->p($typeNode);
}
return implode('|', $types);
}
protected function pIntersectionType(Node\IntersectionType $node) {

View File

@ -927,7 +927,7 @@ abstract class PrettyPrinterAbstract
$result .= $this->p($delayedAddNode, true);
$first = false;
}
$result .= $extraRight;
$result .= $extraRight === "\n" ? $this->nl : $extraRight;
}
return $result;
@ -1454,6 +1454,16 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],
'Stmt_Interface->attrGroups' => [null, '', "\n"],
'Stmt_Class->attrGroups' => [null, '', "\n"],
'Stmt_ClassConst->attrGroups' => [null, '', "\n"],
'Stmt_ClassMethod->attrGroups' => [null, '', "\n"],
'Stmt_Function->attrGroups' => [null, '', "\n"],
'Stmt_Property->attrGroups' => [null, '', "\n"],
'Stmt_Trait->attrGroups' => [null, '', "\n"],
'Expr_ArrowFunction->attrGroups' => [null, '', ' '],
'Expr_Closure->attrGroups' => [null, '', ' '],
'Expr_PrintableNewAnonClass->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with:
* Expr_Isset->vars

View File

@ -125,8 +125,11 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
$this->assertEquals(new Node\Identifier('iterable'), BuilderHelpers::normalizeType('iterable'));
$this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
$this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object'));
$this->assertEquals(new Node\Identifier('null'), BuilderHelpers::normalizeType('null'));
$this->assertEquals(new Node\Identifier('false'), BuilderHelpers::normalizeType('false'));
$this->assertEquals(new Node\Identifier('mixed'), BuilderHelpers::normalizeType('mixed'));
$this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
$this->assertEquals(new Node\Identifier('true'), BuilderHelpers::normalizeType('true'));
$intIdentifier = new Node\Identifier('int');
$this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));

View File

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

View File

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

View File

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

View File

@ -0,0 +1,158 @@
DNF types
-----
<?php
class Test {
public (A&B)|(X&Y) $prop;
}
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
type: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
)
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
)
returnType: UnionType(
types: array(
0: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
1: IntersectionType(
types: array(
0: Name(
parts: array(
0: X
)
)
1: Name(
parts: array(
0: Y
)
)
)
)
)
)
stmts: array(
)
)
)

View File

@ -0,0 +1,56 @@
standalone null, false and true types
-----
<?php
function test(): null {}
function test(): false {}
function test(): true {}
-----
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: null
)
stmts: array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: false
)
stmts: array(
)
)
2: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: true
)
stmts: array(
)
)
)

View File

@ -0,0 +1,18 @@
Union types
-----
<?php
class Test {
public (A&B)|(X&Y) $prop;
}
function test((A&B)|(X&Y) $a): (A&B)|(X&Y) {}
-----
!!php7
class Test
{
public (A&B)|(X&Y) $prop;
}
function test((A&B)|(X&Y) $a) : (A&B)|(X&Y)
{
}