Add support for union types

We definitely need to introduce a general "Type" abstraction in
the next major version.
This commit is contained in:
Nikita Popov 2019-10-25 21:35:43 +02:00
parent 5b1cd2e4f2
commit 664c10121e
19 changed files with 1022 additions and 821 deletions

View File

@ -461,6 +461,7 @@ parameter:
type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
;
type:
@ -469,6 +470,11 @@ type:
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
;
optional_type:
/* empty */ { $$ = null; }
| type_expr { $$ = $1; }

View File

@ -17,7 +17,7 @@ class ArrowFunction extends Expr implements FunctionLike
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType */
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
public $returnType;
/** @var Expr */

View File

@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|Node\Identifier|Node\Name|Node\NullableType Return type */
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;

View File

@ -23,7 +23,7 @@ interface FunctionLike extends Node
/**
* Get the declared return type or null
*
* @return null|Identifier|Node\Name|Node\NullableType
* @return null|Identifier|Node\Name|Node\NullableType|Node\UnionType
*/
public function getReturnType();

View File

@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|Identifier|Name|NullableType Type declaration */
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
@ -20,12 +20,12 @@ class Param extends NodeAbstract
/**
* Constructs a parameter node.
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|NullableType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @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 $attributes Additional attributes
*/
public function __construct(
$var, Expr $default = null, $type = null,

View File

@ -15,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType Return type */
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[]|null Statements */
public $stmts;

View File

@ -16,7 +16,7 @@ class Function_ extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType Return type */
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;

View File

@ -6,6 +6,7 @@ use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
class Property extends Node\Stmt
{
@ -13,16 +14,16 @@ class Property extends Node\Stmt
public $flags;
/** @var PropertyProperty[] Properties */
public $props;
/** @var null|Identifier|Name|NullableType Type declaration */
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
public $type;
/**
* Constructs a class property list node.
*
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|NullableType $type Type declaration
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null) {
$this->attributes = $attributes;

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class UnionType extends NodeAbstract
{
/** @var (Identifier|Name)[] Types */
public $types;
/**
* Constructs a union 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 'UnionType';
}
}

View File

@ -162,12 +162,18 @@ class NameResolver extends NodeVisitorAbstract
}
private function resolveType($node) {
if ($node instanceof Name) {
return $this->resolveClassName($node);
}
if ($node instanceof Node\NullableType) {
$node->type = $this->resolveType($node->type);
return $node;
}
if ($node instanceof Name) {
return $this->resolveClassName($node);
if ($node instanceof Node\UnionType) {
foreach ($node->types as &$type) {
$type = $this->resolveType($type);
}
return $node;
}
return $node;
}

File diff suppressed because it is too large Load Diff

View File

@ -656,6 +656,8 @@ abstract class ParserAbstract implements Parser
'iterable' => true,
'void' => true,
'object' => true,
'null' => true,
'false' => true,
];
if (!$name->isUnqualified()) {

View File

@ -37,6 +37,10 @@ class Standard extends PrettyPrinterAbstract
return '?' . $this->p($node->type);
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
}
protected function pIdentifier(Node\Identifier $node) {
return $node->name;
}

View File

@ -1295,6 +1295,7 @@ abstract class PrettyPrinterAbstract
//'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully
//'Scalar_Encapsed->parts' => '',
'Stmt_Catch->types' => '|',
'UnionType->types' => '|',
'Stmt_If->elseifs' => ' ',
'Stmt_TryCatch->catches' => ' ',
@ -1396,6 +1397,7 @@ abstract class PrettyPrinterAbstract
* Stmt_TraitUseAdaptation_Precedence->insteadof
* Stmt_Unset->vars
* Stmt_Use->uses
* UnionType->types
*/
/* TODO

View File

@ -94,13 +94,6 @@ namespace Baz {
C;
E;
K;
class ClassWithTypeProperties
{
public float $php = 7.4;
public ?Foo $person;
protected static ?bool $probability;
}
}
EOC;
$expectedCode = <<<'EOC'
@ -169,12 +162,6 @@ namespace Baz {
\Y\T\B\C;
\Y\T\D\E;
\Z\T\K;
class ClassWithTypeProperties
{
public float $php = 7.4;
public ?\Baz\Foo $person;
protected static ?bool $probability;
}
}
EOC;
@ -210,6 +197,14 @@ class A extends B implements C, D {
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;
}
function f(A $a) : A {}
@ -252,6 +247,14 @@ class A extends \NS\B implements \NS\C, \NS\D
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
{
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
{

View File

@ -306,4 +306,14 @@ $stmts[0]->expr->expr->items[] = new Expr\ArrayItem(new Scalar\LNumber(24));
$array = [
1, 2,
3, 24,
];
];
-----
<?php
function test(): A
|B {}
-----
$stmts[0]->returnType->types[] = new Node\Name('C');
-----
<?php
function test(): A
|B|C {}

View File

@ -38,4 +38,15 @@ function foo(
$b,
$x,
$y
) {}
) {}
-----
<?php
function test(): A
|B
|C {}
-----
array_pop($stmts[0]->returnType->types);
-----
<?php
function test(): A
|B {}

View File

@ -0,0 +1,92 @@
Union types
-----
<?php
class Test {
public A|iterable|null $prop;
}
function test(A|B $a): int|false {}
-----
!!php7
array(
0: Stmt_Class(
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
flags: MODIFIER_PUBLIC (1)
type: UnionType(
types: array(
0: Name(
parts: array(
0: A
)
)
1: Identifier(
name: iterable
)
2: Identifier(
name: null
)
)
)
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
)
)
)
1: Stmt_Function(
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
type: UnionType(
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: UnionType(
types: array(
0: Identifier(
name: int
)
1: Identifier(
name: false
)
)
)
stmts: array(
)
)
)

View File

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