Add support for intersection types

This commit is contained in:
Nikita Popov 2021-09-02 18:25:16 +02:00
parent 0483391aca
commit ace6c67a8a
13 changed files with 1092 additions and 901 deletions

View File

@ -1,7 +1,9 @@
Version 4.12.1-dev Version 4.12.1-dev
------------------ ------------------
Nothing yet. ### Added
* [PHP 8.1] Added support for intersection types using a new `IntersectionType` node.
Version 4.12.0 (2021-07-21) Version 4.12.0 (2021-07-21)
--------------------------- ---------------------------

View File

@ -561,6 +561,7 @@ type_expr:
type { $$ = $1; } type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; } | '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; } | union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = Node\IntersectionType[$1]; }
; ;
type: type:
@ -584,10 +585,24 @@ union_type_without_static:
| union_type_without_static '|' type_without_static { push($1, $3); } | union_type_without_static '|' type_without_static { 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
{ 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); }
;
type_expr_without_static: type_expr_without_static:
type_without_static { $$ = $1; } type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; } | '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; } | union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = Node\IntersectionType[$1]; }
; ;
optional_type_without_static: optional_type_without_static:

View File

@ -5,6 +5,7 @@ namespace PhpParser;
use PhpParser\Node\ComplexType; use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
use PhpParser\Node\Identifier; use PhpParser\Node\Identifier;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\NullableType; use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;

View File

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

View File

@ -189,7 +189,7 @@ class NameResolver extends NodeVisitorAbstract
$node->type = $this->resolveType($node->type); $node->type = $this->resolveType($node->type);
return $node; return $node;
} }
if ($node instanceof Node\UnionType) { if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
foreach ($node->types as &$type) { foreach ($node->types as &$type) {
$type = $this->resolveType($type); $type = $this->resolveType($type);
} }

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,10 @@ class Standard extends PrettyPrinterAbstract
return $this->pImplode($node->types, '|'); return $this->pImplode($node->types, '|');
} }
protected function pIntersectionType(Node\IntersectionType $node) {
return $this->pImplode($node->types, '&');
}
protected function pIdentifier(Node\Identifier $node) { protected function pIdentifier(Node\Identifier $node) {
return $node->name; return $node->name;
} }

View File

@ -1343,6 +1343,7 @@ abstract class PrettyPrinterAbstract
//'Scalar_Encapsed->parts' => '', //'Scalar_Encapsed->parts' => '',
'Stmt_Catch->types' => '|', 'Stmt_Catch->types' => '|',
'UnionType->types' => '|', 'UnionType->types' => '|',
'IntersectionType->types' => '&',
'Stmt_If->elseifs' => ' ', 'Stmt_If->elseifs' => ' ',
'Stmt_TryCatch->catches' => ' ', 'Stmt_TryCatch->catches' => ' ',

View File

@ -140,6 +140,9 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase
$unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]); $unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]);
$this->assertSame($unionType, BuilderHelpers::normalizeType($unionType)); $this->assertSame($unionType, BuilderHelpers::normalizeType($unionType));
$intersectionType = new Node\IntersectionType([new Node\Name('A'), new Node\Name('B')]);
$this->assertSame($intersectionType, BuilderHelpers::normalizeType($intersectionType));
$expectedNullable = new Node\NullableType($intIdentifier); $expectedNullable = new Node\NullableType($intIdentifier);
$nullable = BuilderHelpers::normalizeType('?int'); $nullable = BuilderHelpers::normalizeType('?int');
$this->assertEquals($expectedNullable, $nullable); $this->assertEquals($expectedNullable, $nullable);

View File

@ -204,6 +204,7 @@ class A extends B implements C, D {
interface A extends C, D { interface A extends C, D {
public function a(A $a) : A; public function a(A $a) : A;
public function b(A|B|int $a): A|B|int; public function b(A|B|int $a): A|B|int;
public function c(A&B $a): A&B;
} }
#[X] #[X]
@ -268,6 +269,7 @@ interface A extends \NS\C, \NS\D
{ {
public function a(\NS\A $a) : \NS\A; public function a(\NS\A $a) : \NS\A;
public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int; public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int;
public function c(\NS\A&\NS\B $a) : \NS\A&\NS\B;
} }
#[\NS\X] #[\NS\X]
enum E : int enum E : int

View File

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

View File

@ -0,0 +1,104 @@
Union types
-----
<?php
class Test {
public A&B $prop;
}
function test(A&B $a): A&B {}
-----
!!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: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
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: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
)
returnType: IntersectionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
)
stmts: array(
)
)
)

View File

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