Skip to content

Commit a9c2be3

Browse files
committed
Disjunctive normal form types
Support for variance and Reflection
1 parent 13479ee commit a9c2be3

30 files changed

+1070
-66
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
Union of two intersection type
3+
--FILE--
4+
<?php
5+
6+
interface W {}
7+
interface X {}
8+
interface Y {}
9+
interface Z {}
10+
11+
class A implements X, Y {}
12+
class B implements W, Z {}
13+
class C {}
14+
15+
function foo1((X&Y)|(W&Z) $v): (X&Y)|(W&Z) {
16+
return $v;
17+
}
18+
function foo2((W&Z)|(X&Y) $v): (W&Z)|(X&Y) {
19+
return $v;
20+
}
21+
22+
function bar1(): (X&Y)|(W&Z) {
23+
return new C();
24+
}
25+
function bar2(): (W&Z)|(X&Y) {
26+
return new C();
27+
}
28+
29+
$a = new A();
30+
$b = new B();
31+
32+
$o = foo1($a);
33+
var_dump($o);
34+
$o = foo2($a);
35+
var_dump($o);
36+
$o = foo1($b);
37+
var_dump($o);
38+
$o = foo2($b);
39+
var_dump($o);
40+
41+
try {
42+
bar1();
43+
} catch (\TypeError $e) {
44+
echo $e->getMessage(), \PHP_EOL;
45+
}
46+
try {
47+
bar2();
48+
} catch (\TypeError $e) {
49+
echo $e->getMessage(), \PHP_EOL;
50+
}
51+
52+
?>
53+
--EXPECTF--
54+
object(A)#%d (0) {
55+
}
56+
object(A)#%d (0) {
57+
}
58+
object(B)#%d (0) {
59+
}
60+
object(B)#%d (0) {
61+
}
62+
bar1(): Return value must be of type (X&Y)|(W&Z), C returned
63+
bar2(): Return value must be of type (W&Z)|(X&Y), C returned
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
--TEST--
2+
Union of null and intersection type
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
class A implements X, Y {}
10+
class C {}
11+
12+
class Test {
13+
public (X&Y)|null $prop1;
14+
public null|(X&Y) $prop2;
15+
16+
public function foo1((X&Y)|null $v): (X&Y)|null {
17+
var_dump($v);
18+
return $v;
19+
}
20+
public function foo2(null|(X&Y) $v): null|(X&Y) {
21+
var_dump($v);
22+
return $v;
23+
}
24+
}
25+
26+
$test = new Test();
27+
$a = new A();
28+
$n = null;
29+
30+
$test->foo1($a);
31+
$test->foo2($a);
32+
$test->foo1($n);
33+
$test->foo2($n);
34+
$test->prop1 = $a;
35+
$test->prop1 = $n;
36+
$test->prop2 = $a;
37+
$test->prop2 = $n;
38+
39+
$c = new C();
40+
try {
41+
$test->foo1($c);
42+
} catch (\TypeError $e) {
43+
echo $e->getMessage(), \PHP_EOL;
44+
}
45+
try {
46+
$test->foo2($c);
47+
} catch (\TypeError $e) {
48+
echo $e->getMessage(), \PHP_EOL;
49+
}
50+
try {
51+
$test->prop1 = $c;
52+
} catch (\TypeError $e) {
53+
echo $e->getMessage(), \PHP_EOL;
54+
}
55+
try {
56+
$test->prop2 = $c;
57+
} catch (\TypeError $e) {
58+
echo $e->getMessage(), \PHP_EOL;
59+
}
60+
61+
?>
62+
===DONE===
63+
--EXPECTF--
64+
object(A)#2 (0) {
65+
}
66+
object(A)#2 (0) {
67+
}
68+
NULL
69+
NULL
70+
Test::foo1(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d
71+
Test::foo2(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d
72+
Cannot assign C to property Test::$prop1 of type (X&Y)|null
73+
Cannot assign C to property Test::$prop2 of type (X&Y)|null
74+
===DONE===
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
--TEST--
2+
Union of a simple and intersection type
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
class A implements X, Y {}
10+
class B {}
11+
class C {}
12+
13+
class Test {
14+
public (X&Y)|int $prop1;
15+
public int|(X&Y) $prop2;
16+
public (X&Y)|B $prop3;
17+
public B|(X&Y) $prop4;
18+
19+
public function foo1((X&Y)|int $v): (X&Y)|int {
20+
var_dump($v);
21+
return $v;
22+
}
23+
public function foo2(int|(X&Y) $v): int|(X&Y) {
24+
var_dump($v);
25+
return $v;
26+
}
27+
public function bar1(B|(X&Y) $v): B|(X&Y) {
28+
var_dump($v);
29+
return $v;
30+
}
31+
public function bar2((X&Y)|B $v): (X&Y)|B {
32+
var_dump($v);
33+
return $v;
34+
}
35+
}
36+
37+
$test = new Test();
38+
$a = new A();
39+
$b = new B();
40+
$i = 10;
41+
42+
$test->foo1($a);
43+
$test->foo2($a);
44+
$test->foo1($i);
45+
$test->foo2($i);
46+
$test->prop1 = $a;
47+
$test->prop1 = $i;
48+
$test->prop2 = $a;
49+
$test->prop2 = $i;
50+
51+
$test->bar1($a);
52+
$test->bar2($a);
53+
$test->bar1($b);
54+
$test->bar2($b);
55+
$test->prop3 = $a;
56+
$test->prop4 = $b;
57+
$test->prop3 = $a;
58+
$test->prop4 = $b;
59+
60+
$c = new C();
61+
try {
62+
$test->foo1($c);
63+
} catch (\TypeError $e) {
64+
echo $e->getMessage(), \PHP_EOL;
65+
}
66+
try {
67+
$test->foo2($c);
68+
} catch (\TypeError $e) {
69+
echo $e->getMessage(), \PHP_EOL;
70+
}
71+
try {
72+
$test->bar1($c);
73+
} catch (\TypeError $e) {
74+
echo $e->getMessage(), \PHP_EOL;
75+
}
76+
try {
77+
$test->bar2($c);
78+
} catch (\TypeError $e) {
79+
echo $e->getMessage(), \PHP_EOL;
80+
}
81+
try {
82+
$test->prop1 = $c;
83+
} catch (\TypeError $e) {
84+
echo $e->getMessage(), \PHP_EOL;
85+
}
86+
try {
87+
$test->prop2 = $c;
88+
} catch (\TypeError $e) {
89+
echo $e->getMessage(), \PHP_EOL;
90+
}
91+
try {
92+
$test->prop3 = $c;
93+
} catch (\TypeError $e) {
94+
echo $e->getMessage(), \PHP_EOL;
95+
}
96+
try {
97+
$test->prop4 = $c;
98+
} catch (\TypeError $e) {
99+
echo $e->getMessage(), \PHP_EOL;
100+
}
101+
102+
?>
103+
===DONE===
104+
--EXPECTF--
105+
object(A)#2 (0) {
106+
}
107+
object(A)#2 (0) {
108+
}
109+
int(10)
110+
int(10)
111+
object(A)#2 (0) {
112+
}
113+
object(A)#2 (0) {
114+
}
115+
object(B)#3 (0) {
116+
}
117+
object(B)#3 (0) {
118+
}
119+
Test::foo1(): Argument #1 ($v) must be of type (X&Y)|int, C given, called in %s on line %d
120+
Test::foo2(): Argument #1 ($v) must be of type (X&Y)|int, C given, called in %s on line %d
121+
Test::bar1(): Argument #1 ($v) must be of type B|(X&Y), C given, called in %s on line %d
122+
Test::bar2(): Argument #1 ($v) must be of type (X&Y)|B, C given, called in %s on line %d
123+
Cannot assign C to property Test::$prop1 of type (X&Y)|int
124+
Cannot assign C to property Test::$prop2 of type (X&Y)|int
125+
Cannot assign C to property Test::$prop3 of type (X&Y)|B
126+
Cannot assign C to property Test::$prop4 of type B|(X&Y)
127+
===DONE===
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Co-variance check failure for intersection type where child replace one of intersection type members with a supertype
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B extends A {}
8+
interface C {}
9+
interface X {}
10+
11+
class Test implements B, C {}
12+
13+
class Foo {
14+
public function foo(): (B&C)|X {
15+
return new Test();
16+
}
17+
}
18+
19+
/* This fails because A is a parent type for B */
20+
class FooChild extends Foo {
21+
public function foo(): (A&C)|X {
22+
return new Test();
23+
}
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
Fatal error: Declaration of FooChild::foo(): (A&C)|X must be compatible with Foo::foo(): (B&C)|X in %s on line %d
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Co-variance check failure for intersection type where child removes one of intersection type members
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
interface X {}
9+
10+
class Test implements A, B {}
11+
12+
class Foo {
13+
public function foo(): (A&B)|X {
14+
return new Test();
15+
}
16+
}
17+
18+
/* This fails because just A larger than A&B */
19+
class FooChild extends Foo {
20+
public function foo(): A|X {
21+
return new Test();
22+
}
23+
}
24+
25+
?>
26+
--EXPECTF--
27+
Fatal error: Declaration of FooChild::foo(): A|X must be compatible with Foo::foo(): (A&B)|X in %s on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Co-variance check failure for intersection type where child removes one of intersection type members
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
interface C {}
9+
interface X {}
10+
11+
class Test implements A, B, C {}
12+
13+
class Foo {
14+
public function foo(): (A&B&C)|X {
15+
return new Test();
16+
}
17+
}
18+
19+
/* This fails because just (A&B) larger than ((A&B)&C) */
20+
class FooChild extends Foo {
21+
public function foo(): (A&B)|X {
22+
return new Test();
23+
}
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
Fatal error: Declaration of FooChild::foo(): (A&B)|X must be compatible with Foo::foo(): (A&B&C)|X in %s on line %d
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 1
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
interface L {}
10+
11+
class TestOne implements X, Y {}
12+
class TestTwo implements X {}
13+
14+
interface A
15+
{
16+
public function foo(): (X&Y)|L;
17+
}
18+
19+
interface B extends A
20+
{
21+
public function foo(): TestOne|TestTwo;
22+
}
23+
24+
?>
25+
--EXPECTF--
26+
Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): (X&Y)|L in %s on line %d

0 commit comments

Comments
 (0)