Skip to content

Start implementing support for mixing union and intersection types #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
arg_info = op_array->arg_info - 1;
}

if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
return ZEND_TYPE_LIST(arg_info->type)->num_types;
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ static inline bool can_elide_return_type_check(
return 0;
}

if (ZEND_TYPE_HAS_CLASS(info->type)) {
if (ZEND_TYPE_IS_COMPLEX(info->type)) {
if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) {
return 0;
}
Expand Down
4 changes: 2 additions & 2 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2208,7 +2208,7 @@ ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_i
}

tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type));
if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
tmp |= MAY_BE_OBJECT;
/* As we only have space to store one CE, we use a plain object type for class unions. */
if (ZEND_TYPE_HAS_NAME(arg_info->type)) {
Expand Down Expand Up @@ -2316,7 +2316,7 @@ static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_in
}
if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
if (ZEND_TYPE_HAS_CLASS(prop_info->type)) {
if (ZEND_TYPE_IS_COMPLEX(prop_info->type)) {
type |= MAY_BE_OBJECT;
if (pce) {
if (ZEND_TYPE_HAS_CE(prop_info->type)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
Test for a complex type with an intersection type and union of a simple type
--FILE--
<?php

interface X {}
interface Y {}

class A implements X, Y {}
class B {}
class C {}

class Test {
public X&Y|int $prop1;
public int|X&Y $prop2;
public X&Y|B $prop3;
public B|X&Y $prop4;

public function foo1(X&Y|int $v): X&Y|int {
var_dump($v);
return $v;
}
public function foo2(int|X&Y $v): int|X&Y {
var_dump($v);
return $v;
}
public function bar1(B|X&Y $v): B|X&Y {
var_dump($v);
return $v;
}
public function bar2(X&Y|B $v): X&Y|B {
var_dump($v);
return $v;
}
}

$test = new Test();
$a = new A();
$b = new B();
$i = 10;

$test->foo1($a);
$test->foo2($a);
$test->foo1($i);
$test->foo2($i);
$test->prop1 = $a;
$test->prop1 = $i;
$test->prop2 = $a;
$test->prop2 = $i;

$test->bar1($a);
$test->bar2($a);
$test->bar1($b);
$test->bar2($b);
$test->prop3 = $a;
$test->prop4 = $b;
$test->prop3 = $a;
$test->prop4 = $b;

$c = new C();
try {
$test->foo1($c);
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->foo2($c);
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->bar1($c);
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->bar2($c);
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->prop1 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->prop2 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->prop3 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->prop4 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}

?>
===DONE===
--EXPECTF--
object(A)#2 (0) {
}
object(A)#2 (0) {
}
int(10)
int(10)
object(A)#2 (0) {
}
object(A)#2 (0) {
}
object(B)#3 (0) {
}
object(B)#3 (0) {
}
Test::foo1(): Argument #1 ($v) must be of type X&Y|int, C given, called in %s on line %d
Test::foo2(): Argument #1 ($v) must be of type X&Y|int, C given, called in %s on line %d
Test::bar1(): Argument #1 ($v) must be of type B|X&Y, C given, called in %s on line %d
Test::bar2(): Argument #1 ($v) must be of type X&Y|B, C given, called in %s on line %d
Cannot assign C to property Test::$prop1 of type X&Y|int
Cannot assign C to property Test::$prop2 of type X&Y|int
Cannot assign C to property Test::$prop3 of type X&Y|B
Cannot assign C to property Test::$prop4 of type B|X&Y
===DONE===
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
--TEST--
Test for a complex type with an intersection type and union with another intersection
--FILE--
<?php

interface W {}
interface X {}
interface Y {}
interface Z {}

class A implements X, Y {}
class B implements W, Z {}
class C {}

function foo1(X&Y|W&Z $v): X&Y|W&Z {
return $v;
}
function foo2(W&Z|X&Y $v): W&Z|X&Y {
return $v;
}

function bar1(): X&Y|W&Z {
return new C();
}
function bar2(): W&Z|X&Y {
return new C();
}

$a = new A();
$b = new B();

$o = foo1($a);
var_dump($o);
$o = foo2($a);
var_dump($o);
$o = foo1($b);
var_dump($o);
$o = foo2($b);
var_dump($o);

try {
bar1();
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
bar2();
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}

?>
--EXPECTF--
object(A)#%d (0) {
}
object(A)#%d (0) {
}
object(B)#%d (0) {
}
object(B)#%d (0) {
}
bar1(): Return value must be of type X&Y|W&Z, C returned
bar2(): Return value must be of type W&Z|X&Y, C returned
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
--TEST--
Added element of intersection type
--FILE--
<?php

interface X {}
interface Y {}
interface Z {}

class A implements X, Y, Z {}

class Collection {
public X&Y $intersect;
}

function foo(): X&Y {
return new A();
}

function bar(X&Y $o): void {
var_dump($o);
}

try {
$o = foo();
var_dump($o);
} catch (\TypeError $e) {
echo $e->getMessage(), "\n";
}

$c = new Collection();
$a = new A();

try {
$c->intersect = $a;
echo 'OK', \PHP_EOL;
} catch (\TypeError $e) {
echo $e->getMessage(), "\n";
}

try {
bar($a);
} catch (\TypeError $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
object(A)#1 (0) {
}
OK
object(A)#3 (0) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
Assigning values to intersection types
--FILE--
<?php

interface X {}
interface Y {}
interface Z {}

class TestParent implements X, Y {}
class TestChild extends TestParent implements Z {}

class A {

public X&Y&Z $prop;

public function method1(X&Y $a): X&Y&Z {
return new TestChild();
}
public function method2(X $a): X&Y {
return new TestParent();
}
}

$tp = new TestParent();
$tc = new TestChild();

$o = new A();
try {
$o->prop = $tp;
} catch (TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}

$o->prop = $tc;

$r = $o->method1($tp);
var_dump($r);
$r = $o->method2($tp);
var_dump($r);
$r = $o->method1($tc);
var_dump($r);
$r = $o->method2($tc);
var_dump($r);


?>
--EXPECTF--
Cannot assign TestParent to property A::$prop of type X&Y&Z
object(TestChild)#%d (0) {
}
object(TestParent)#%d (0) {
}
object(TestChild)#%d (0) {
}
object(TestParent)#%d (0) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
array type cannot take part in an intersection type
--FILE--
<?php

function foo(): array&Iterator {}

?>
--EXPECTF--
Fatal error: Type array cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
bool type cannot take part in an intersection type
--FILE--
<?php

function foo(): bool&Iterator {}

?>
--EXPECTF--
Fatal error: Type bool cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
callable type cannot take part in an intersection type
--FILE--
<?php

function foo(): callable&Iterator {}

?>
--EXPECTF--
Fatal error: Type callable cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
false type cannot take part in an intersection type
--FILE--
<?php

function foo(): false&Iterator {}

?>
--EXPECTF--
Fatal error: Type false cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
float type cannot take part in an intersection type
--FILE--
<?php

function foo(): float&Iterator {}

?>
--EXPECTF--
Fatal error: Type float cannot be part of an intersection type in %s on line %d
Loading