Skip to content

Disjunctive normal form types #10

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
7 changes: 7 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ PHP 8.2 UPGRADE NOTES
13. Other Changes
========================================

- Core:
. The iterable type is now a built-in compile time alias for array|Traversable.
Error messages relating to iterable will therefore now use array|Traversable.
Type Reflection is preserved for single iterable (and ?iterable) to produce
a ReflectionNamedType with name iterable, however usage of iterable in
union types will be converted to array|Traversable

========================================
14. Performance Improvements
========================================
3 changes: 0 additions & 3 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2114,9 +2114,6 @@ static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
if (type_mask & MAY_BE_CALLABLE) {
result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
}
if (type_mask & MAY_BE_ITERABLE) {
result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
}
if (type_mask & MAY_BE_STATIC) {
result_mask |= MAY_BE_OBJECT;
}
Expand Down
7 changes: 7 additions & 0 deletions Zend/tests/return_types/generators001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ function test6() : object|callable {
yield 6;
}

function test7() : iterable {
yield 7;
}

var_dump(
test1(),
test2(),
test3(),
test4(),
test5(),
test6(),
test7(),
);
?>
--EXPECTF--
Expand All @@ -48,3 +53,5 @@ object(Generator)#%d (%d) {
}
object(Generator)#%d (%d) {
}
object(Generator)#%d (%d) {
}
63 changes: 63 additions & 0 deletions Zend/tests/type_declarations/dnf_types/dnf_2_intersection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
--TEST--
Union of two intersection type
--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,74 @@
--TEST--
Union of null and intersection type
--FILE--
<?php

interface X {}
interface Y {}

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

class Test {
public (X&Y)|null $prop1;
public null|(X&Y) $prop2;

public function foo1((X&Y)|null $v): (X&Y)|null {
var_dump($v);
return $v;
}
public function foo2(null|(X&Y) $v): null|(X&Y) {
var_dump($v);
return $v;
}
}

$test = new Test();
$a = new A();
$n = null;

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

$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->prop1 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}
try {
$test->prop2 = $c;
} catch (\TypeError $e) {
echo $e->getMessage(), \PHP_EOL;
}

?>
===DONE===
--EXPECTF--
object(A)#2 (0) {
}
object(A)#2 (0) {
}
NULL
NULL
Test::foo1(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d
Test::foo2(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d
Cannot assign C to property Test::$prop1 of type (X&Y)|null
Cannot assign C to property Test::$prop2 of type (X&Y)|null
===DONE===
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
Union of a simple and intersection 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,13 @@
--TEST--
Duplicate class alias type
--FILE--
<?php

interface X {}

use A as B;
function foo(): (X&A)|(X&B) {}

?>
--EXPECTF--
Fatal error: Type X&A is redundant with type X&A in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Duplicate class alias type at runtime
--FILE--
<?php

class A {}
interface X {}

class_alias('A', 'B');
function foo(): (X&A)|(X&B) {}

?>
===DONE===
--EXPECT--
===DONE===
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Intersection with child class
--FILE--
<?php

interface X {}
class A {}
class B extends A {}

function test(): (A&X)|(B&X) {}

?>
===DONE===
--EXPECT--
===DONE===
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
A less restrictive type constrain is part of the DNF type 001
--FILE--
<?php

interface A {}
interface B {}

function test(): (A&B)|A {}

?>
===DONE===
--EXPECTF--
Fatal error: Type A is less restrictive than type A&B in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
A less restrictive type constrain is part of the DNF type 002
--FILE--
<?php

interface A {}
interface B {}

function test(): A|(A&B) {}

?>
===DONE===
--EXPECTF--
Fatal error: Type A is less restrictive than type A&B in %s on line %d
Loading