]> granicus.if.org Git - php/commitdiff
Implement union types
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 25 Sep 2019 11:21:13 +0000 (13:21 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 8 Nov 2019 14:15:48 +0000 (15:15 +0100)
According to RFC: https://wiki.php.net/rfc/union_types_v2

The type representation now makes use of both the pointer payload
and the type mask at the same time. Additionall, zend_type_list is
introduced as a new kind of pointer payload, which is used to store
multiple class types. Each of the class types is a tagged pointer,
which may be either a class name or class entry. The latter is only
used for typed properties, while arguments/returns will instead use
cache slots. A type list can contain a mix of both names and CEs at
the same time, as not all classes may be resolvable.

One thing this is missing is support for union types in arginfo
and stubs, which I want to handle separately.

I've also dropped the special object code from the JIT implementation
for now -- I plan to add this back in a different form at a later time.
For now I did not want to include non-trivial JIT changes together
with large functional changes.

Another possible piece of follow-up work is to implement "iterable"
as an internal alias for "array|Traversable". I believe this will
eliminate quite a few special-cases that had to be implemented.

Closes GH-4838.

66 files changed:
UPGRADING
Zend/tests/assert/expect_015.phpt
Zend/tests/return_types/generators002.phpt
Zend/tests/type_declarations/nullable_void.phpt
Zend/tests/type_declarations/typed_properties_043.phpt
Zend/tests/type_declarations/typed_properties_095.phpt
Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/incdec_prop.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/inheritance.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/inheritance_internal.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/legal_default_values.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/multiple_classes.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/prop_ref_assign.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/standalone_false.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/standalone_null.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/type_checking_strict.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/type_checking_weak.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/variance/invalid_001.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/variance/invalid_002.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/variance/invalid_003.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/variance/valid.phpt [new file with mode: 0644]
Zend/tests/type_declarations/union_types/void_with_class.phpt [new file with mode: 0644]
Zend/zend.c
Zend/zend_API.c
Zend/zend_ast.c
Zend/zend_ast.h
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_execute.c
Zend/zend_inheritance.c
Zend/zend_language_parser.y
Zend/zend_opcode.c
Zend/zend_string.h
Zend/zend_type_info.h
Zend/zend_types.h
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
ext/opcache/Optimizer/compact_literals.c
ext/opcache/Optimizer/dfa_pass.c
ext/opcache/Optimizer/zend_inference.c
ext/opcache/ZendAccelerator.c
ext/opcache/jit/zend_jit_disasm_x86.c
ext/opcache/jit/zend_jit_helpers.c
ext/opcache/jit/zend_jit_x86.dasc
ext/opcache/zend_accelerator_util_funcs.c
ext/opcache/zend_file_cache.c
ext/opcache/zend_persist.c
ext/opcache/zend_persist_calc.c
ext/reflection/php_reflection.c
ext/reflection/reflection.stub.php
ext/reflection/reflection_arginfo.h
ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
ext/reflection/tests/union_types.phpt [new file with mode: 0644]
ext/zend_test/test.c

index 1412162b6feffad6f4ac20fd69c2db5523887afb..1bb6d93574fde829f8a6d0dd0787cfe87ea6aa63 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -318,6 +318,8 @@ PHP 8.0 UPGRADE NOTES
 ========================================
 
 - Core:
+  . Added support for union types.
+    RFC: https://wiki.php.net/rfc/union_types_v2
   . Added ValueError class.
 
 ========================================
index 72f13ff83f15f4383423a4b24427786b62cd761e..769eed8270aa3a75d43ff33557f6f38955f34410 100644 (file)
@@ -62,7 +62,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use ($c,&$d) : ?X {
        }
 }));
 
-assert(0 && ($a = function &(array &$a, X $b = null) use ($c,&$d) : X {
+assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use ($c,&$d) : X {
        final class A {
                final protected function f2() {
                        if (!$x) {
@@ -204,7 +204,7 @@ Warning: assert(): assert(0 && ($a = function &(array &$a, ?X $b = null) use($c,
 
 })) failed in %sexpect_015.php on line %d
 
-Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null) use($c, &$d): X {
+Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use($c, &$d): X {
     final class A {
         protected final function f2() {
             if (!$x) {
index 90bada714b5c89541fd522aa7b767563e720ace9..2e42f4b052880a9ab4d4707b7b0abd9b9312b0f7 100644 (file)
@@ -6,4 +6,4 @@ function test1() : StdClass {
     yield 1;
 }
 --EXPECTF--
-Fatal error: Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d
+Fatal error: Generators may only declare a return type containing Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d
index 4ff0edb0d8130e5f6b1d4cb08b5c79e389498014..725c11bb594c2ecb9929dea6c58c33d09fee5ee7 100644 (file)
@@ -8,4 +8,4 @@ function test() : ?void {
 
 ?>
 --EXPECTF--
-Fatal error: Void type cannot be nullable in %s on line %d
+Fatal error: Void can only be used as a standalone type in %s on line %d
index eefe35879626cee26a3ee7262a8e8a5901e8ec63..79f01545e151d553df8949584f705afe27b41bc9 100644 (file)
@@ -41,9 +41,9 @@ var_dump(Bar::$selfProp, Bar::$selfNullProp, Bar::$parentProp);
 
 ?>
 --EXPECT--
-Cannot write a value to a 'self' typed static property of a trait
-Cannot write a non-null value to a 'self' typed static property of a trait
-Cannot access parent:: when current class scope has no parent
+Cannot assign stdClass to property Test::$selfProp of type self
+Cannot assign stdClass to property Test::$selfNullProp of type ?self
+Cannot assign stdClass to property Test::$parentProp of type parent
 NULL
 object(Bar)#3 (0) {
 }
index 3f1027f08f8b3afb2929e4987765fdb08aa19336..8470d4f437b8e4596ce8359ccff7c7943cd1f1ef 100644 (file)
@@ -62,22 +62,26 @@ var_dump(_ZendTestClass::$staticIntProp);
 int(123)
 Cannot assign string to property _ZendTestClass::$intProp of type int
 Cannot assign _ZendTestClass to property _ZendTestClass::$classProp of type ?stdClass
-object(_ZendTestClass)#1 (2) {
+object(_ZendTestClass)#1 (3) {
   ["intProp"]=>
   int(456)
   ["classProp"]=>
   object(stdClass)#2 (0) {
   }
+  ["classUnionProp"]=>
+  NULL
 }
 int(123)
 Cannot assign string to property _ZendTestClass::$intProp of type int
 Cannot assign Test to property _ZendTestClass::$classProp of type ?stdClass
-object(Test)#4 (2) {
+object(Test)#4 (3) {
   ["intProp"]=>
   int(456)
   ["classProp"]=>
   object(stdClass)#1 (0) {
   }
+  ["classUnionProp"]=>
+  NULL
 }
 int(123)
 Cannot assign string to property _ZendTestClass::$staticIntProp of type int
diff --git a/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt
new file mode 100644 (file)
index 0000000..7c36909
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Generator return value has to have Traversable-ish, but may also have extra types
+--FILE--
+<?php
+
+interface I {
+    public function test(): iterable|false;
+}
+
+class C implements I {
+    public function test(): iterable|false {
+        yield;
+    }
+}
+
+var_dump((new C)->test());
+
+?>
+--EXPECT--
+object(Generator)#2 (0) {
+}
diff --git a/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt
new file mode 100644 (file)
index 0000000..8526c65
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Generator return type with multiple classes
+--FILE--
+<?php
+
+interface I {
+    public function test(): Generator|ArrayAccess|array;
+}
+class C implements I {
+    function test(): Generator|ArrayAccess|array {
+        yield;
+    }
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt
new file mode 100644 (file)
index 0000000..6c40adc
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Argument default value not legal for any type in the union
+--FILE--
+<?php
+
+function test(int|float $arg = "0") {
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot use string as default value for parameter $arg of type int|float in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt
new file mode 100644 (file)
index 0000000..f594175
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Default value not legal for any type in the union
+--FILE--
+<?php
+
+class Test {
+    public int|float $prop = "0";
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/incdec_prop.phpt b/Zend/tests/type_declarations/union_types/incdec_prop.phpt
new file mode 100644 (file)
index 0000000..dde6f59
--- /dev/null
@@ -0,0 +1,132 @@
+--TEST--
+Increment/decrement a typed property with int|float type
+--FILE--
+<?php
+
+class Test {
+    public int|float $prop;
+    public int|bool $prop2;
+}
+
+/* Incrementing a int|float property past int min/max is legal */
+
+$test = new Test;
+$test->prop = PHP_INT_MAX;
+$x = $test->prop++;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MAX;
+$x = ++$test->prop;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MIN;
+$x = $test->prop--;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MIN;
+$x = --$test->prop;
+var_dump(is_double($test->prop));
+
+$test = new Test;
+$test->prop = PHP_INT_MAX;
+$r =& $test->prop;
+$x = $test->prop++;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MAX;
+$x = ++$test->prop;
+$r =& $test->prop;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MIN;
+$x = $test->prop--;
+$r =& $test->prop;
+var_dump(is_double($test->prop));
+
+$test->prop = PHP_INT_MIN;
+$x = --$test->prop;
+$r =& $test->prop;
+var_dump(is_double($test->prop));
+
+/* Incrementing a non-int|float property past int min/max is an error,
+ * even if the result of the overflow (a float) would technically be allowed
+ * under a type coercion. */
+
+try {
+    $test->prop2 = PHP_INT_MAX;
+    $x = $test->prop2++;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MAX;
+    $x = ++$test->prop2;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MIN;
+    $x = $test->prop2--;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MIN;
+    $x = --$test->prop2;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MAX;
+    $r =& $test->prop2;
+    $x = $test->prop2++;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MAX;
+    $r =& $test->prop2;
+    $x = ++$test->prop2;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MIN;
+    $r =& $test->prop2;
+    $x = $test->prop2--;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->prop2 = PHP_INT_MIN;
+    $r =& $test->prop2;
+    $x = --$test->prop2;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+Cannot increment property Test::$prop2 of type int|bool past its maximal value
+Cannot increment property Test::$prop2 of type int|bool past its maximal value
+Cannot decrement property Test::$prop2 of type int|bool past its minimal value
+Cannot decrement property Test::$prop2 of type int|bool past its minimal value
+Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value
+Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value
+Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value
+Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value
diff --git a/Zend/tests/type_declarations/union_types/inheritance.phpt b/Zend/tests/type_declarations/union_types/inheritance.phpt
new file mode 100644 (file)
index 0000000..a0a1b65
--- /dev/null
@@ -0,0 +1,46 @@
+--TEST--
+Various inheritance scenarios for properties/methods with union types
+--FILE--
+<?php
+
+class X {
+    public A|B|int $prop;
+    public function method(A|B|int $arg): A|B|int { }
+
+    private A|B|int $prop2;
+    private function method2(A|B|int $arg): A|B|int { }
+}
+
+class Y extends X {
+}
+
+trait T {
+    public A|B|int $prop;
+    public function method(A|B|int $arg): A|B|int { }
+
+    private A|B|int $prop2;
+    private function method2(A|B|int $arg): A|B|int { }
+}
+
+class Z {
+    use T;
+}
+
+class U extends X {
+    use T;
+}
+
+class V extends X {
+    use T;
+
+    public A|B|int $prop;
+    public function method(A|B|int $arg): A|B|int { }
+
+    private A|B|int $prop2;
+    private function method2(A|B|int $arg): A|B|int { }
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/tests/type_declarations/union_types/inheritance_internal.phpt b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt
new file mode 100644 (file)
index 0000000..bb53411
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+Inheritance of union type from internal class
+--SKIPIF--
+<?php
+if (!extension_loaded('zend-test')) die('skip requires zend-test extension');
+?>
+--FILE--
+<?php
+
+class C extends _ZendTestClass {}
+
+$obj = new _ZendTestChildClass;
+$obj->classUnionProp = new stdClass;
+$obj->classUnionProp = new ArrayIterator;
+try {
+    $obj->classUnionProp = new DateTime;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+$obj = new C;
+$obj->classUnionProp = new stdClass;
+$obj->classUnionProp = new ArrayIterator;
+try {
+    $obj->classUnionProp = new DateTime;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null
+Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null
diff --git a/Zend/tests/type_declarations/union_types/legal_default_values.phpt b/Zend/tests/type_declarations/union_types/legal_default_values.phpt
new file mode 100644 (file)
index 0000000..2807397
--- /dev/null
@@ -0,0 +1,45 @@
+--TEST--
+The default value must be legal for one of the types in the union
+--FILE--
+<?php
+
+class Test {
+    public int|float $a = 1;
+    public int|float $b = 2.0;
+    public float|string $c = 3; // Strict typing exception
+    public float|string $d = 4.0;
+    public float|string $e = "5";
+}
+
+function test(
+    int|float $a = 1,
+    int|float $b = 2.0,
+    float|string $c = 3, // Strict typing exception
+    float|string $d = 4.0,
+    float|string $e = "5"
+) {
+    var_dump($a, $b, $c, $d, $e);
+}
+
+var_dump(new Test);
+test();
+
+?>
+--EXPECT--
+object(Test)#1 (5) {
+  ["a"]=>
+  int(1)
+  ["b"]=>
+  float(2)
+  ["c"]=>
+  float(3)
+  ["d"]=>
+  float(4)
+  ["e"]=>
+  string(1) "5"
+}
+int(1)
+float(2)
+float(3)
+float(4)
+string(1) "5"
diff --git a/Zend/tests/type_declarations/union_types/multiple_classes.phpt b/Zend/tests/type_declarations/union_types/multiple_classes.phpt
new file mode 100644 (file)
index 0000000..aac56c6
--- /dev/null
@@ -0,0 +1,83 @@
+--TEST--
+Union types with multiple classes
+--FILE--
+<?php
+
+class Test {
+    public X|Y|Z|int $prop;
+    public function method(X|Y|Z|int $arg): X|Y|Z|int {
+        return $arg;
+    }
+}
+
+// Check that nothing here triggers autoloading.
+spl_autoload_register(function($class) {
+    echo "Loading $class\n";
+});
+
+$test = new Test;
+
+$test->prop = 42; 
+var_dump($test->prop);
+var_dump($test->method(42));
+
+$test->prop = "42";
+var_dump($test->prop);
+var_dump($test->method("42"));
+
+try {
+    $test->prop = new stdClass;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $test->method(new stdClass);
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+if (true) {
+    class X {}
+}
+
+$test->prop = new X;
+var_dump($test->prop);
+var_dump($test->method(new X));
+
+if (true) {
+    class Z {}
+}
+
+$test->prop = new Z;
+var_dump($test->prop);
+var_dump($test->method(new Z));
+
+if (true) {
+    class Y {}
+}
+
+$test->prop = new Y;
+var_dump($test->prop);
+var_dump($test->method(new Y));
+
+?>
+--EXPECTF--
+int(42)
+int(42)
+int(42)
+int(42)
+Cannot assign stdClass to property Test::$prop of type X|Y|Z|int
+Argument 1 passed to Test::method() must be of type X|Y|Z|int, instance of stdClass given, called in %s on line %d
+object(X)#4 (0) {
+}
+object(X)#6 (0) {
+}
+object(Z)#6 (0) {
+}
+object(Z)#4 (0) {
+}
+object(Y)#4 (0) {
+}
+object(Y)#6 (0) {
+}
diff --git a/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt
new file mode 100644 (file)
index 0000000..a8db8fc
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+Assignments to references that are held by properties with union types
+--FILE--
+<?php
+
+class Test {
+    public int|string $x;
+    public float|string $y;
+}
+
+$test = new Test;
+$r = "foobar";
+$test->x =& $r;
+$test->y =& $r;
+
+$v = 42;
+try {
+    $r = $v;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+var_dump($r, $v);
+
+$v = 42.0;
+try {
+    $r = $v;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+var_dump($r, $v);
+
+unset($r, $test->x, $test->y);
+
+$test->x = 42;
+try {
+    $test->y =& $test->x;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+unset($test->x, $test->y);
+
+$test->y = 42.0;
+try {
+    $test->x =& $test->y;
+} catch (TypeError $e) {
+    echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Cannot assign int to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion
+string(6) "foobar"
+int(42)
+Cannot assign float to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion
+string(6) "foobar"
+float(42)
+Reference with value of type int held by property Test::$x of type string|int is not compatible with property Test::$y of type string|float
+Reference with value of type float held by property Test::$y of type string|float is not compatible with property Test::$x of type string|int
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt
new file mode 100644 (file)
index 0000000..91d2d94
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using both bool and false in a union
+--FILE--
+<?php
+
+function test(): bool|false {
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate type false is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt
new file mode 100644 (file)
index 0000000..5739f69
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Duplicate class type
+--FILE--
+<?php
+
+function test(): Foo|int|FOO {
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate type FOO is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt
new file mode 100644 (file)
index 0000000..f9cd3e0
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using a type twice in a union
+--FILE--
+<?php
+
+function test(): int|INT {
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate type int is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt
new file mode 100644 (file)
index 0000000..5b65a33
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using both iterable and Traversable
+--FILE--
+<?php
+
+function test(): iterable|Traversable {
+}
+
+?>
+--EXPECTF--
+Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt
new file mode 100644 (file)
index 0000000..e3f7c58
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using both iterable and Traversable, with extra classes
+--FILE--
+<?php
+
+function test(): iterable|Traversable|ArrayAccess {
+}
+
+?>
+--EXPECTF--
+Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt
new file mode 100644 (file)
index 0000000..c6b0949
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using both iterable and array
+--FILE--
+<?php
+
+function test(): iterable|array {
+}
+
+?>
+--EXPECTF--
+Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt
new file mode 100644 (file)
index 0000000..5597794
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Combining nullability with null
+--FILE--
+<?php
+
+function test(): ?null {
+}
+
+?>
+--EXPECTF--
+Fatal error: Null can not be used as a standalone type in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt
new file mode 100644 (file)
index 0000000..e9f785e
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Using both object and a class type
+--FILE--
+<?php
+
+function test(): object|Test {
+}
+
+?>
+--EXPECTF--
+Fatal error: Type Test|object contains both object and a class type, which is redundant in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/standalone_false.phpt b/Zend/tests/type_declarations/union_types/standalone_false.phpt
new file mode 100644 (file)
index 0000000..3932f92
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+False cannot be used as a standalone type
+--FILE--
+<?php
+
+function test(): false {}
+
+?>
+--EXPECTF--
+Fatal error: False can not be used as a standalone type in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/standalone_null.phpt b/Zend/tests/type_declarations/union_types/standalone_null.phpt
new file mode 100644 (file)
index 0000000..7a25f9c
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Null cannot be used as a standalone type
+--FILE--
+<?php
+
+function test(): null {}
+
+?>
+--EXPECTF--
+Fatal error: Null can not be used as a standalone type in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt
new file mode 100644 (file)
index 0000000..1e68024
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Nullable false cannot be used as a standalone type
+--FILE--
+<?php
+
+function test(): ?false {}
+
+?>
+--EXPECTF--
+Fatal error: False can not be used as a standalone type in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/type_checking_strict.phpt b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt
new file mode 100644 (file)
index 0000000..f098b63
--- /dev/null
@@ -0,0 +1,211 @@
+--TEST--
+Behavior of union type checks (strict)
+--SKIPIF--
+<?php
+if (!extension_loaded('json')) die('skip requires json');
+?>
+--FILE--
+<?php
+
+declare(strict_types=1);
+
+function dump($value) {
+    if (is_object($value)) {
+        return 'new ' . get_class($value);
+    }
+    if ($value === INF) {
+        return 'INF';
+    }
+    return json_encode($value, JSON_PRESERVE_ZERO_FRACTION);
+}
+
+function test(string $type, array $values) {
+    $alignment = 16;
+
+    echo "\nType $type:\n";
+    $fn = eval("return function($type \$arg) { return \$arg; };");
+    foreach ($values as $value) {
+        echo str_pad(dump($value), $alignment), ' => ';
+
+        try {
+            error_clear_last();
+            $value = @$fn($value);
+            echo dump($value);
+            if ($e = error_get_last()) {
+                echo ' (', $e['message'], ')';
+            }
+        } catch (TypeError $e) {
+            $msg = $e->getMessage();
+            $msg = strstr($msg, ', called in', true);
+            $msg = str_replace('1 passed to {closure}()', '...', $msg);
+            echo $msg;
+        }
+        echo "\n";
+    }
+}
+
+class WithToString {
+    public function __toString() {
+        return "__toString()";
+    }
+}
+
+$values = [
+    42, 42.0, INF, "42", "42.0", "42x", "x", "",
+    true, false, null, [], new stdClass, new WithToString,
+];
+test('int|float', $values);
+test('int|float|false', $values);
+test('int|float|bool', $values);
+test('int|bool', $values);
+test('int|string|null', $values);
+test('string|bool', $values);
+test('float|array', $values);
+test('string|array', $values);
+test('bool|array', $values);
+
+?>
+--EXPECT--
+Type int|float:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => Argument ... must be of type int|float, string given
+"42.0"           => Argument ... must be of type int|float, string given
+"42x"            => Argument ... must be of type int|float, string given
+"x"              => Argument ... must be of type int|float, string given
+""               => Argument ... must be of type int|float, string given
+true             => Argument ... must be of type int|float, bool given
+false            => Argument ... must be of type int|float, bool given
+null             => Argument ... must be of type int|float, null given
+[]               => Argument ... must be of type int|float, array given
+new stdClass     => Argument ... must be of type int|float, object given
+new WithToString => Argument ... must be of type int|float, object given
+
+Type int|float|false:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => Argument ... must be of type int|float|false, string given
+"42.0"           => Argument ... must be of type int|float|false, string given
+"42x"            => Argument ... must be of type int|float|false, string given
+"x"              => Argument ... must be of type int|float|false, string given
+""               => Argument ... must be of type int|float|false, string given
+true             => Argument ... must be of type int|float|false, bool given
+false            => false
+null             => Argument ... must be of type int|float|false, null given
+[]               => Argument ... must be of type int|float|false, array given
+new stdClass     => Argument ... must be of type int|float|false, object given
+new WithToString => Argument ... must be of type int|float|false, object given
+
+Type int|float|bool:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => Argument ... must be of type int|float|bool, string given
+"42.0"           => Argument ... must be of type int|float|bool, string given
+"42x"            => Argument ... must be of type int|float|bool, string given
+"x"              => Argument ... must be of type int|float|bool, string given
+""               => Argument ... must be of type int|float|bool, string given
+true             => true
+false            => false
+null             => Argument ... must be of type int|float|bool, null given
+[]               => Argument ... must be of type int|float|bool, array given
+new stdClass     => Argument ... must be of type int|float|bool, object given
+new WithToString => Argument ... must be of type int|float|bool, object given
+
+Type int|bool:
+42               => 42
+42.0             => Argument ... must be of type int|bool, float given
+INF              => Argument ... must be of type int|bool, float given
+"42"             => Argument ... must be of type int|bool, string given
+"42.0"           => Argument ... must be of type int|bool, string given
+"42x"            => Argument ... must be of type int|bool, string given
+"x"              => Argument ... must be of type int|bool, string given
+""               => Argument ... must be of type int|bool, string given
+true             => true
+false            => false
+null             => Argument ... must be of type int|bool, null given
+[]               => Argument ... must be of type int|bool, array given
+new stdClass     => Argument ... must be of type int|bool, object given
+new WithToString => Argument ... must be of type int|bool, object given
+
+Type int|string|null:
+42               => 42
+42.0             => Argument ... must be of type string|int|null, float given
+INF              => Argument ... must be of type string|int|null, float given
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => Argument ... must be of type string|int|null, bool given
+false            => Argument ... must be of type string|int|null, bool given
+null             => null
+[]               => Argument ... must be of type string|int|null, array given
+new stdClass     => Argument ... must be of type string|int|null, object given
+new WithToString => Argument ... must be of type string|int|null, object given
+
+Type string|bool:
+42               => Argument ... must be of type string|bool, int given
+42.0             => Argument ... must be of type string|bool, float given
+INF              => Argument ... must be of type string|bool, float given
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => true
+false            => false
+null             => Argument ... must be of type string|bool, null given
+[]               => Argument ... must be of type string|bool, array given
+new stdClass     => Argument ... must be of type string|bool, object given
+new WithToString => Argument ... must be of type string|bool, object given
+
+Type float|array:
+42               => 42.0
+42.0             => 42.0
+INF              => INF
+"42"             => Argument ... must be of type array|float, string given
+"42.0"           => Argument ... must be of type array|float, string given
+"42x"            => Argument ... must be of type array|float, string given
+"x"              => Argument ... must be of type array|float, string given
+""               => Argument ... must be of type array|float, string given
+true             => Argument ... must be of type array|float, bool given
+false            => Argument ... must be of type array|float, bool given
+null             => Argument ... must be of type array|float, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|float, object given
+new WithToString => Argument ... must be of type array|float, object given
+
+Type string|array:
+42               => Argument ... must be of type array|string, int given
+42.0             => Argument ... must be of type array|string, float given
+INF              => Argument ... must be of type array|string, float given
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => Argument ... must be of type array|string, bool given
+false            => Argument ... must be of type array|string, bool given
+null             => Argument ... must be of type array|string, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|string, object given
+new WithToString => Argument ... must be of type array|string, object given
+
+Type bool|array:
+42               => Argument ... must be of type array|bool, int given
+42.0             => Argument ... must be of type array|bool, float given
+INF              => Argument ... must be of type array|bool, float given
+"42"             => Argument ... must be of type array|bool, string given
+"42.0"           => Argument ... must be of type array|bool, string given
+"42x"            => Argument ... must be of type array|bool, string given
+"x"              => Argument ... must be of type array|bool, string given
+""               => Argument ... must be of type array|bool, string given
+true             => true
+false            => false
+null             => Argument ... must be of type array|bool, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|bool, object given
+new WithToString => Argument ... must be of type array|bool, object given
diff --git a/Zend/tests/type_declarations/union_types/type_checking_weak.phpt b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt
new file mode 100644 (file)
index 0000000..441eaab
--- /dev/null
@@ -0,0 +1,209 @@
+--TEST--
+Behavior of union type checks (weak)
+--SKIPIF--
+<?php
+if (!extension_loaded('json')) die('skip requires json');
+?>
+--FILE--
+<?php
+
+function dump($value) {
+    if (is_object($value)) {
+        return 'new ' . get_class($value);
+    }
+    if ($value === INF) {
+        return 'INF';
+    }
+    return json_encode($value, JSON_PRESERVE_ZERO_FRACTION);
+}
+
+function test(string $type, array $values) {
+    $alignment = 16;
+
+    echo "\nType $type:\n";
+    $fn = eval("return function($type \$arg) { return \$arg; };");
+    foreach ($values as $value) {
+        echo str_pad(dump($value), $alignment), ' => ';
+
+        try {
+            error_clear_last();
+            $value = @$fn($value);
+            echo dump($value);
+            if ($e = error_get_last()) {
+                echo ' (', $e['message'], ')';
+            }
+        } catch (TypeError $e) {
+            $msg = $e->getMessage();
+            $msg = strstr($msg, ', called in', true);
+            $msg = str_replace('1 passed to {closure}()', '...', $msg);
+            echo $msg;
+        }
+        echo "\n";
+    }
+}
+
+class WithToString {
+    public function __toString() {
+        return "__toString()";
+    }
+}
+
+$values = [
+    42, 42.0, INF, "42", "42.0", "42x", "x", "",
+    true, false, null, [], new stdClass, new WithToString,
+];
+test('int|float', $values);
+test('int|float|false', $values);
+test('int|float|bool', $values);
+test('int|bool', $values);
+test('int|string|null', $values);
+test('string|bool', $values);
+test('float|array', $values);
+test('string|array', $values);
+test('bool|array', $values);
+
+?>
+--EXPECT--
+Type int|float:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => 42
+"42.0"           => 42.0
+"42x"            => 42 (A non well formed numeric value encountered)
+"x"              => Argument ... must be of type int|float, string given
+""               => Argument ... must be of type int|float, string given
+true             => 1
+false            => 0
+null             => Argument ... must be of type int|float, null given
+[]               => Argument ... must be of type int|float, array given
+new stdClass     => Argument ... must be of type int|float, object given
+new WithToString => Argument ... must be of type int|float, object given
+
+Type int|float|false:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => 42
+"42.0"           => 42.0
+"42x"            => 42 (A non well formed numeric value encountered)
+"x"              => Argument ... must be of type int|float|false, string given
+""               => Argument ... must be of type int|float|false, string given
+true             => 1
+false            => false
+null             => Argument ... must be of type int|float|false, null given
+[]               => Argument ... must be of type int|float|false, array given
+new stdClass     => Argument ... must be of type int|float|false, object given
+new WithToString => Argument ... must be of type int|float|false, object given
+
+Type int|float|bool:
+42               => 42
+42.0             => 42.0
+INF              => INF
+"42"             => 42
+"42.0"           => 42.0
+"42x"            => 42 (A non well formed numeric value encountered)
+"x"              => true
+""               => false
+true             => true
+false            => false
+null             => Argument ... must be of type int|float|bool, null given
+[]               => Argument ... must be of type int|float|bool, array given
+new stdClass     => Argument ... must be of type int|float|bool, object given
+new WithToString => Argument ... must be of type int|float|bool, object given
+
+Type int|bool:
+42               => 42
+42.0             => 42
+INF              => true
+"42"             => 42
+"42.0"           => 42
+"42x"            => 42 (A non well formed numeric value encountered)
+"x"              => true
+""               => false
+true             => true
+false            => false
+null             => Argument ... must be of type int|bool, null given
+[]               => Argument ... must be of type int|bool, array given
+new stdClass     => Argument ... must be of type int|bool, object given
+new WithToString => Argument ... must be of type int|bool, object given
+
+Type int|string|null:
+42               => 42
+42.0             => 42
+INF              => "INF"
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => 1
+false            => 0
+null             => null
+[]               => Argument ... must be of type string|int|null, array given
+new stdClass     => Argument ... must be of type string|int|null, object given
+new WithToString => "__toString()"
+
+Type string|bool:
+42               => "42"
+42.0             => "42"
+INF              => "INF"
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => true
+false            => false
+null             => Argument ... must be of type string|bool, null given
+[]               => Argument ... must be of type string|bool, array given
+new stdClass     => Argument ... must be of type string|bool, object given
+new WithToString => "__toString()"
+
+Type float|array:
+42               => 42.0
+42.0             => 42.0
+INF              => INF
+"42"             => 42.0
+"42.0"           => 42.0
+"42x"            => 42.0 (A non well formed numeric value encountered)
+"x"              => Argument ... must be of type array|float, string given
+""               => Argument ... must be of type array|float, string given
+true             => 1.0
+false            => 0.0
+null             => Argument ... must be of type array|float, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|float, object given
+new WithToString => Argument ... must be of type array|float, object given
+
+Type string|array:
+42               => "42"
+42.0             => "42"
+INF              => "INF"
+"42"             => "42"
+"42.0"           => "42.0"
+"42x"            => "42x"
+"x"              => "x"
+""               => ""
+true             => "1"
+false            => ""
+null             => Argument ... must be of type array|string, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|string, object given
+new WithToString => "__toString()"
+
+Type bool|array:
+42               => true
+42.0             => true
+INF              => true
+"42"             => true
+"42.0"           => true
+"42x"            => true
+"x"              => true
+""               => false
+true             => true
+false            => false
+null             => Argument ... must be of type array|bool, null given
+[]               => []
+new stdClass     => Argument ... must be of type array|bool, object given
+new WithToString => Argument ... must be of type array|bool, object given
diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt
new file mode 100644 (file)
index 0000000..9e1dcae
--- /dev/null
@@ -0,0 +1,15 @@
+--TEST--
+Invalid union type variance: Adding extra return type
+--FILE--
+<?php
+
+class A {
+    public function method(): int {}
+}
+class B extends A {
+    public function method(): int|float {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Declaration of B::method(): int|float must be compatible with A::method(): int in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt
new file mode 100644 (file)
index 0000000..4448114
--- /dev/null
@@ -0,0 +1,15 @@
+--TEST--
+Invalid union type variance: Removing argument union type
+--FILE--
+<?php
+
+class A {
+    public function method(int|float $a) {}
+}
+class B extends A {
+    public function method(int $a) {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Declaration of B::method(int $a) must be compatible with A::method(int|float $a) in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt
new file mode 100644 (file)
index 0000000..26d9ae3
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Invalid union type variance: Using parent of class in return type
+--FILE--
+<?php
+
+class X {}
+class Y extends X {}
+
+class A {
+    public function method(): Y|string {}
+}
+class B extends A {
+    public function method(): X|string {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Declaration of B::method(): X|string must be compatible with A::method(): Y|string in %s on line %d
diff --git a/Zend/tests/type_declarations/union_types/variance/valid.phpt b/Zend/tests/type_declarations/union_types/variance/valid.phpt
new file mode 100644 (file)
index 0000000..f9e5cc4
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+Valid union type variance
+--FILE--
+<?php
+
+class X {}
+class Y extends X {}
+
+class A {
+    public X|Y $prop;
+    public iterable $prop2;
+
+    public function method(int $a): int|float {}
+    public function method2(B|string $a): A|string {}
+    public function method3(Y|B $a): X|A {}
+    public function method4(Traversable|X $a): iterable|X {}
+}
+class B extends A {
+    public X $prop;
+    public array|Traversable $prop2;
+
+    public function method(int|float $a): int {}
+    public function method2(A|string $a): B|string {}
+    public function method3(A|X $a): B|Y {}
+    public function method4(iterable|X $a): Traversable|X {}
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
diff --git a/Zend/tests/type_declarations/union_types/void_with_class.phpt b/Zend/tests/type_declarations/union_types/void_with_class.phpt
new file mode 100644 (file)
index 0000000..6e1f439
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Combining void with class type
+--FILE--
+<?php
+
+function test(): T|void {}
+
+?>
+--EXPECTF--
+Fatal error: Void can only be used as a standalone type in %s on line %d
index 8c9ae86d747ca45ebf4df60266834e9374f28335..d85135d28713fcaa87f9cf1887ec84a6a7abe428 100644 (file)
@@ -592,7 +592,18 @@ static void function_copy_ctor(zval *zv) /* {{{ */
                new_arg_info = pemalloc(sizeof(zend_arg_info) * num_args, 1);
                memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args);
                for (i = 0 ; i < num_args; i++) {
-                       if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
+                       if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) {
+                               zend_type_list *old_list = ZEND_TYPE_LIST(arg_info[i].type);
+                               zend_type_list *new_list = pemalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types), 1);
+                               memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types));
+                               ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list);
+
+                               void **entry;
+                               ZEND_TYPE_LIST_FOREACH_PTR(new_list, entry) {
+                                       zend_string *name = zend_string_dup(ZEND_TYPE_LIST_GET_NAME(*entry), 1);
+                                       *entry = ZEND_TYPE_LIST_ENCODE_NAME(name);
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) {
                                zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), 1);
                                ZEND_TYPE_SET_PTR(new_arg_info[i].type, name);
                        }
@@ -947,6 +958,15 @@ void zend_register_standard_ini_entries(void) /* {{{ */
 }
 /* }}} */
 
+static zend_class_entry *resolve_type_name(zend_string *type_name) { 
+       zend_string *lc_type_name = zend_string_tolower(type_name);
+       zend_class_entry *ce = zend_hash_find_ptr(CG(class_table), lc_type_name);
+
+       ZEND_ASSERT(ce && ce->type == ZEND_INTERNAL_CLASS);
+       zend_string_release(lc_type_name);
+       return ce;
+}
+
 static void zend_resolve_property_types(void) /* {{{ */
 {
        zend_class_entry *ce;
@@ -959,14 +979,18 @@ static void zend_resolve_property_types(void) /* {{{ */
 
                if (UNEXPECTED(ZEND_CLASS_HAS_TYPE_HINTS(ce))) {
                        ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) {
-                               if (ZEND_TYPE_IS_NAME(prop_info->type)) {
+                               if (ZEND_TYPE_HAS_LIST(prop_info->type)) {
+                                       void **entry;
+                                       ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) {
+                                               if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+                                                       zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                                                       *entry = ZEND_TYPE_LIST_ENCODE_CE(resolve_type_name(type_name));
+                                                       zend_string_release(type_name);
+                                               }
+                                       } ZEND_TYPE_LIST_FOREACH_END();
+                               } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) {
                                        zend_string *type_name = ZEND_TYPE_NAME(prop_info->type);
-                                       zend_string *lc_type_name = zend_string_tolower(type_name);
-                                       zend_class_entry *prop_ce = zend_hash_find_ptr(CG(class_table), lc_type_name);
-
-                                       ZEND_ASSERT(prop_ce && prop_ce->type == ZEND_INTERNAL_CLASS);
-                                       prop_info->type = (zend_type) ZEND_TYPE_INIT_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type), 0);
-                                       zend_string_release(lc_type_name);
+                                       ZEND_TYPE_SET_CE(prop_info->type, resolve_type_name(type_name));
                                        zend_string_release(type_name);
                                }
                        } ZEND_HASH_FOREACH_END();
index 3c1ca4c7188c49595009c8e2f562288067c7a88b..87cfd5f1394bca3916dc08c710ae4b8ef496f73d 100644 (file)
@@ -2054,7 +2054,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
                                internal_function->num_args--;
                        }
                        if (ZEND_TYPE_IS_SET(info->type)) {
-                               if (ZEND_TYPE_IS_CLASS(info->type)) {
+                               if (ZEND_TYPE_HAS_NAME(info->type)) {
                                        const char *type_name = ZEND_TYPE_LITERAL_NAME(info->type);
                                        if (!scope && (!strcasecmp(type_name, "self") || !strcasecmp(type_name, "parent"))) {
                                                zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", type_name);
@@ -2135,7 +2135,9 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
                        memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args);
                        reg_function->common.arg_info = new_arg_info + 1;
                        for (i = 0; i < num_args; i++) {
-                               if (ZEND_TYPE_IS_CLASS(new_arg_info[i].type)) {
+                               if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) {
+                                       ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
+                                               && "Only simple classes are currently supported");
                                        const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
                                        ZEND_TYPE_SET_PTR(new_arg_info[i].type,
                                                zend_string_init_interned(class_name, strlen(class_name), 1));
index f0b524b30e82971f58a2a00fbe572232239ed215..980e23d0555378c1c9fb0b8b6d85aaff4893521d 100644 (file)
@@ -1314,6 +1314,23 @@ static ZEND_COLD void zend_ast_export_class_no_header(smart_str *str, zend_ast_d
        smart_str_appends(str, "}");
 }
 
+static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) {
+       if (ast->kind == ZEND_AST_TYPE_UNION) {
+               zend_ast_list *list = zend_ast_get_list(ast);
+               for (uint32_t i = 0; i < list->children; i++) {
+                       if (i != 0) {
+                               smart_str_appendc(str, '|');
+                       }
+                       zend_ast_export_type(str, list->child[i], indent);
+               }
+               return;
+       }
+       if (ast->attr & ZEND_TYPE_NULLABLE) {
+               smart_str_appendc(str, '?');
+       }
+       zend_ast_export_ns_name(str, ast, 0, indent);
+}
+
 #define BINARY_OP(_op, _p, _pl, _pr) do { \
                op = _op; \
                p = _p; \
@@ -1423,10 +1440,7 @@ tail_call:
                        zend_ast_export_ex(str, decl->child[1], 0, indent);
                        if (decl->child[3]) {
                                smart_str_appends(str, ": ");
-                               if (decl->child[3]->attr & ZEND_TYPE_NULLABLE) {
-                                       smart_str_appendc(str, '?');
-                               }
-                               zend_ast_export_ns_name(str, decl->child[3], 0, indent);
+                               zend_ast_export_type(str, decl->child[3], indent);
                        }
                        if (decl->child[2]) {
                                if (decl->kind == ZEND_AST_ARROW_FUNC) {
@@ -1516,11 +1530,7 @@ simple_list:
                        }
 
                        if (type_ast) {
-                               if (type_ast->attr & ZEND_TYPE_NULLABLE) {
-                                       smart_str_appendc(str, '?');
-                               }
-                               zend_ast_export_ns_name(
-                                       str, type_ast, 0, indent);
+                               zend_ast_export_type(str, type_ast, indent);
                                smart_str_appendc(str, ' ');
                        }
 
@@ -1990,10 +2000,7 @@ simple_list:
                        break;
                case ZEND_AST_PARAM:
                        if (ast->child[0]) {
-                               if (ast->child[0]->attr & ZEND_TYPE_NULLABLE) {
-                                       smart_str_appendc(str, '?');
-                               }
-                               zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+                               zend_ast_export_type(str, ast->child[0], indent);
                                smart_str_appendc(str, ' ');
                        }
                        if (ast->attr & ZEND_PARAM_REF) {
index fd6dd1677a4506165f1d61b3d3c7dff255d04540..5b8aae6f96c25a4c8e9734d216dbc533ca3e28bc 100644 (file)
@@ -61,6 +61,7 @@ enum _zend_ast_kind {
        ZEND_AST_NAME_LIST,
        ZEND_AST_TRAIT_ADAPTATIONS,
        ZEND_AST_USE,
+       ZEND_AST_TYPE_UNION,
 
        /* 0 child nodes */
        ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,
index ea93e878d03a387c0546dbc532fc2ace00990334..53fe9afde8c779fd1dda35feba1fffa6da10cb13 100644 (file)
@@ -60,6 +60,10 @@ typedef struct _zend_loop_var {
 } zend_loop_var;
 
 static inline uint32_t zend_alloc_cache_slots(unsigned count) {
+       if (count == 0) {
+               return (uint32_t) -1;
+       }
+
        zend_op_array *op_array = CG(active_op_array);
        uint32_t ret = op_array->cache_size;
        op_array->cache_size += count * sizeof(void*);
@@ -123,6 +127,7 @@ static void zend_destroy_property_info_internal(zval *zv) /* {{{ */
        zend_property_info *property_info = Z_PTR_P(zv);
 
        zend_string_release_ex(property_info->name, 1);
+       zend_type_release(property_info->type, /* persistent */ 1);
        free(property_info);
 }
 /* }}} */
@@ -211,6 +216,8 @@ typedef struct _builtin_type_info {
 } builtin_type_info;
 
 static const builtin_type_info builtin_types[] = {
+       {ZEND_STRL("null"), IS_NULL},
+       {ZEND_STRL("false"), IS_FALSE},
        {ZEND_STRL("int"), IS_LONG},
        {ZEND_STRL("float"), IS_DOUBLE},
        {ZEND_STRL("string"), IS_STRING},
@@ -1119,62 +1126,94 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */
 }
 /* }}} */
 
+static zend_string *add_type_string(zend_string *type, zend_string *new_type) {
+       zend_string *result;
+       if (type == NULL) {
+               return zend_string_copy(new_type);
+       }
+
+       // TODO: Switch to smart_str?
+       result = zend_string_alloc(ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1, 0);
+       memcpy(ZSTR_VAL(result), ZSTR_VAL(type), ZSTR_LEN(type));
+       ZSTR_VAL(result)[ZSTR_LEN(type)] = '|';
+       memcpy(ZSTR_VAL(result) + ZSTR_LEN(type) + 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type));
+       ZSTR_VAL(result)[ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1] = '\0';
+       zend_string_release(type);
+       return result;
+}
+
+static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scope) {
+       if (scope) {
+               if (zend_string_equals_literal_ci(name, "self")) {
+                       name = scope->name;
+               } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) {
+                       name = scope->parent->name;
+               }
+       }
+       return name;
+}
+
 zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
-       zend_bool nullable = ZEND_TYPE_ALLOW_NULL(type);
-       zend_string *str;
-       if (ZEND_TYPE_IS_NAME(type)) {
-               zend_string *name = ZEND_TYPE_NAME(type);
-               if (scope) {
-                       if (zend_string_equals_literal_ci(name, "self")) {
-                               name = scope->name;
-                       } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) {
-                               name = scope->parent->name;
-                       }
-               }
-               str = zend_string_copy(name);
-       } else if (ZEND_TYPE_IS_CE(type)) {
+       zend_string *str = NULL;
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *elem;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), elem) {
+                       if (ZEND_TYPE_LIST_IS_CE(elem)) {
+                               str = add_type_string(str, ZEND_TYPE_LIST_GET_CE(elem)->name);
+                       } else {
+                               str = add_type_string(str,
+                                       resolve_class_name(ZEND_TYPE_LIST_GET_NAME(elem), scope));
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(type)) {
+               str = zend_string_copy(resolve_class_name(ZEND_TYPE_NAME(type), scope));
+       } else if (ZEND_TYPE_HAS_CE(type)) {
                str = zend_string_copy(ZEND_TYPE_CE(type)->name);
-       } else {
-               uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
-               switch (type_mask) {
-                       case MAY_BE_FALSE|MAY_BE_TRUE:
-                               str = ZSTR_KNOWN(ZEND_STR_BOOL);
-                               break;
-                       case MAY_BE_LONG:
-                               str = ZSTR_KNOWN(ZEND_STR_INT);
-                               break;
-                       case MAY_BE_DOUBLE:
-                               str = ZSTR_KNOWN(ZEND_STR_FLOAT);
-                               break;
-                       case MAY_BE_STRING:
-                               str = ZSTR_KNOWN(ZEND_STR_STRING);
-                               break;
-                       case MAY_BE_ARRAY:
-                               str = ZSTR_KNOWN(ZEND_STR_ARRAY);
-                               break;
-                       case MAY_BE_OBJECT:
-                               str = ZSTR_KNOWN(ZEND_STR_OBJECT);
-                               break;
-                       case MAY_BE_CALLABLE:
-                               str = ZSTR_KNOWN(ZEND_STR_CALLABLE);
-                               break;
-                       case MAY_BE_ITERABLE:
-                               str = ZSTR_KNOWN(ZEND_STR_ITERABLE);
-                               break;
-                       case MAY_BE_VOID:
-                               str = ZSTR_KNOWN(ZEND_STR_VOID);
-                               break;
-                       EMPTY_SWITCH_DEFAULT_CASE()
-               }
        }
 
-       if (nullable) {
-               zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0);
-               ZSTR_VAL(nullable_str)[0] = '?';
-               memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str));
-               ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0';
-               zend_string_release(str);
-               return nullable_str;
+       uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
+       if (type_mask & MAY_BE_CALLABLE) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE));
+       }
+       if (type_mask & MAY_BE_ITERABLE) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE));
+       }
+       if (type_mask & MAY_BE_OBJECT) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT));
+       }
+       if (type_mask & MAY_BE_ARRAY) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY));
+       }
+       if (type_mask & MAY_BE_STRING) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING));
+       }
+       if (type_mask & MAY_BE_LONG) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT));
+       }
+       if (type_mask & MAY_BE_DOUBLE) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT));
+       }
+       if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL));
+       } else if (type_mask & MAY_BE_FALSE) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE));
+       }
+       if (type_mask & MAY_BE_VOID) {
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID));
+       }
+
+       if (type_mask & MAY_BE_NULL) {
+               zend_bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL;
+               if (!is_union) {
+                       zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0);
+                       ZSTR_VAL(nullable_str)[0] = '?';
+                       memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str));
+                       ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0';
+                       zend_string_release(str);
+                       return nullable_str;
+               }
+
+               str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE));
        }
        return str;
 }
@@ -1183,6 +1222,12 @@ zend_string *zend_type_to_string(zend_type type) {
        return zend_type_to_string_resolved(type, NULL);
 }
 
+static zend_bool is_generator_compatible_class_type(zend_string *name) {
+       return zend_string_equals_literal_ci(name, "Traversable")
+               || zend_string_equals_literal_ci(name, "Iterator")
+               || zend_string_equals_literal_ci(name, "Generator");
+}
+
 static void zend_mark_function_as_generator() /* {{{ */
 {
        if (!CG(active_op_array)->function_name) {
@@ -1191,21 +1236,30 @@ static void zend_mark_function_as_generator() /* {{{ */
        }
 
        if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
-               zend_arg_info return_info = CG(active_op_array)->arg_info[-1];
-               zend_bool valid_type;
-               if (ZEND_TYPE_IS_CLASS(return_info.type)) {
-                       zend_string *name = ZEND_TYPE_NAME(return_info.type);
-                       valid_type = zend_string_equals_literal_ci(name, "Traversable")
-                               || zend_string_equals_literal_ci(name, "Iterator")
-                               || zend_string_equals_literal_ci(name, "Generator");
+               zend_type return_type = CG(active_op_array)->arg_info[-1].type;
+               zend_bool valid_type = 0;
+               if (ZEND_TYPE_HAS_CLASS(return_type)) {
+                       if (ZEND_TYPE_HAS_LIST(return_type)) {
+                               void *entry;
+                               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(return_type), entry) {
+                                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                                       if (is_generator_compatible_class_type(ZEND_TYPE_LIST_GET_NAME(entry))) {
+                                               valid_type = 1;
+                                               break;
+                                       }
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else {
+                               ZEND_ASSERT(ZEND_TYPE_HAS_NAME(return_type));
+                               valid_type = is_generator_compatible_class_type(ZEND_TYPE_NAME(return_type));
+                       }
                } else {
-                       valid_type = (ZEND_TYPE_FULL_MASK(return_info.type) & MAY_BE_ITERABLE) != 0;
+                       valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_ITERABLE) != 0;
                }
 
                if (!valid_type) {
-                       zend_string *str = zend_type_to_string(return_info.type);
+                       zend_string *str = zend_type_to_string(return_type);
                        zend_error_noreturn(E_COMPILE_ERROR,
-                               "Generators may only declare a return type of " \
+                               "Generators may only declare a return type containing " \
                                "Generator, Iterator, Traversable, or iterable, %s is not permitted",
                                ZSTR_VAL(str));
                }
@@ -2167,6 +2221,16 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */
 }
 /* }}} */
 
+static size_t zend_type_get_num_classes(zend_type type) {
+       if (!ZEND_TYPE_HAS_CLASS(type)) {
+               return 0;
+       }
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               return ZEND_TYPE_LIST(type)->num_types;
+       }
+       return 1;
+}
+
 static void zend_emit_return_type_check(
                znode *expr, zend_arg_info *return_info, zend_bool implicit) /* {{{ */
 {
@@ -2212,12 +2276,8 @@ static void zend_emit_return_type_check(
                        opline->result_type = expr->op_type = IS_TMP_VAR;
                        opline->result.var = expr->u.op.var = get_temporary_variable();
                }
-               if (ZEND_TYPE_IS_CLASS(return_info->type)) {
-                       opline->op2.num = CG(active_op_array)->cache_size;
-                       CG(active_op_array)->cache_size += sizeof(void*);
-               } else {
-                       opline->op2.num = -1;
-               }
+
+               opline->op2.num = zend_alloc_cache_slots(zend_type_get_num_classes(return_info->type));
        }
 }
 /* }}} */
@@ -5372,16 +5432,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
 }
 /* }}} */
 
-static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */
+static zend_type zend_compile_single_typename(zend_ast *ast)
 {
-       zend_bool allow_null = force_allow_null;
-       if (ast->attr & ZEND_TYPE_NULLABLE) {
-               allow_null = 1;
-               ast->attr &= ~ZEND_TYPE_NULLABLE;
-       }
-
+       ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
        if (ast->kind == ZEND_AST_TYPE) {
-               return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, allow_null, 0);
+               return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
        } else {
                zend_string *class_name = zend_ast_get_str(ast);
                zend_uchar type = zend_lookup_builtin_type_by_name(class_name);
@@ -5392,10 +5447,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null
                                        "Type declaration '%s' must be unqualified",
                                        ZSTR_VAL(zend_string_tolower(class_name)));
                        }
-                       if (type == IS_VOID && allow_null) {
-                               zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable");
-                       }
-                       return (zend_type) ZEND_TYPE_INIT_CODE(type, allow_null, 0);
+                       return (zend_type) ZEND_TYPE_INIT_CODE(type, 0, 0);
                } else {
                        const char *correct_name;
                        zend_string *orig_name = zend_ast_get_str(ast);
@@ -5427,9 +5479,141 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null
                                }
                        }
 
-                       return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, allow_null, 0);
+                       return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0);
+               }
+       }
+}
+
+static zend_bool zend_type_contains_traversable(zend_type type) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                       if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) {
+                               return 1;
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(type)) {
+               return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable");
+       }
+       return 0;
+}
+
+// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially
+// treat it as a built-in type alias.
+static zend_type zend_compile_typename(
+               zend_ast *ast, zend_bool force_allow_null, zend_bool use_arena) /* {{{ */
+{
+       zend_bool allow_null = force_allow_null;
+       zend_type type = ZEND_TYPE_INIT_NONE(0);
+       if (ast->attr & ZEND_TYPE_NULLABLE) {
+               allow_null = 1;
+               ast->attr &= ~ZEND_TYPE_NULLABLE;
+       }
+
+       if (ast->kind == ZEND_AST_TYPE_UNION) {
+               zend_ast_list *list = zend_ast_get_list(ast);
+               for (uint32_t i = 0; i < list->children; i++) {
+                       zend_ast *type_ast = list->child[i];
+                       zend_type single_type = zend_compile_single_typename(type_ast);
+                       uint32_t type_mask_overlap =
+                               ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(single_type);
+                       if (type_mask_overlap) {
+                               zend_type overlap_type = ZEND_TYPE_INIT_MASK(type_mask_overlap);
+                               zend_string *overlap_type_str = zend_type_to_string(overlap_type);
+                               zend_error_noreturn(E_COMPILE_ERROR,
+                                       "Duplicate type %s is redundant", ZSTR_VAL(overlap_type_str));
+                       }
+                       ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type);
+
+                       if (ZEND_TYPE_HAS_CLASS(single_type)) {
+                               if (!ZEND_TYPE_HAS_CLASS(type)) {
+                                       /* The first class type can be stored directly as the type ptr payload. */
+                                       ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type));
+                                       ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT;
+                               } else {
+                                       zend_type_list *list;
+                                       if (ZEND_TYPE_HAS_LIST(type)) {
+                                               /* Add name to existing name list. */
+                                               zend_type_list *old_list = ZEND_TYPE_LIST(type);
+                                               if (use_arena) {
+                                                       // TODO: Add a zend_arena_realloc API?
+                                                       list = zend_arena_alloc(
+                                                               &CG(arena), ZEND_TYPE_LIST_SIZE(old_list->num_types + 1));
+                                                       memcpy(list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types));
+                                               } else {
+                                                       list = erealloc(old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types + 1));
+                                               }
+                                               list->types[list->num_types++] = ZEND_TYPE_NAME(single_type);
+                                       } else {
+                                               /* Switch from single name to name list. */
+                                               size_t size = ZEND_TYPE_LIST_SIZE(2);
+                                               list = use_arena ? zend_arena_alloc(&CG(arena), size) : emalloc(size);
+                                               list->num_types = 2;
+                                               list->types[0] = ZEND_TYPE_NAME(type);
+                                               list->types[1] = ZEND_TYPE_NAME(single_type);
+                                       }
+                                       ZEND_TYPE_SET_LIST(type, list);
+                                       if (use_arena) {
+                                               ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
+                                       }
+
+                                       /* Check for trivially redundant class types */
+                                       for (size_t i = 0; i < list->num_types - 1; i++) {
+                                               if (zend_string_equals_ci(
+                                                               ZEND_TYPE_LIST_GET_NAME(list->types[i]),
+                                                               ZEND_TYPE_NAME(single_type))) {
+                                                       zend_string *single_type_str = zend_type_to_string(single_type);
+                                                       zend_error_noreturn(E_COMPILE_ERROR,
+                                                               "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
+                                               }
+                                       }
+                               }
+                       }
+               }
+       } else {
+               type = zend_compile_single_typename(ast);
+       }
+
+       if (allow_null) {
+               ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL;
+       }
+
+       uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
+       if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) {
+               zend_string *type_str = zend_type_to_string(type);
+               zend_error_noreturn(E_COMPILE_ERROR,
+                       "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str));
+       }
+
+       if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) {
+               zend_string *type_str = zend_type_to_string(type);
+               zend_error_noreturn(E_COMPILE_ERROR,
+                       "Type %s contains both iterable and Traversable, which is redundant",
+                       ZSTR_VAL(type_str));
+       }
+
+       if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) {
+               zend_string *type_str = zend_type_to_string(type);
+               zend_error_noreturn(E_COMPILE_ERROR,
+                       "Type %s contains both object and a class type, which is redundant",
+                       ZSTR_VAL(type_str));
+       }
+
+       if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_VOID)) {
+               zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type");
+       }
+
+       if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE))
+                       && !ZEND_TYPE_HAS_CLASS(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
+               if (type_mask == MAY_BE_NULL) {
+                       zend_error_noreturn(E_COMPILE_ERROR, "Null can not be used as a standalone type");
+               } else {
+                       zend_error_noreturn(E_COMPILE_ERROR, "False can not be used as a standalone type");
                }
        }
+
+       return type;
 }
 /* }}} */
 
@@ -5437,13 +5621,6 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null
 static zend_bool zend_is_valid_default_value(zend_type type, zval *value)
 {
        ZEND_ASSERT(ZEND_TYPE_IS_SET(type));
-       if (Z_TYPE_P(value) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type)) {
-               return 1;
-       }
-
-       if (ZEND_TYPE_IS_CLASS(type)) {
-               return 0;
-       }
        if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(value))) {
                return 1;
        }
@@ -5469,7 +5646,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
                /* Use op_array->arg_info[-1] for return type */
                arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
                arg_infos->name = NULL;
-               arg_infos->type = zend_compile_typename(return_type_ast, 0);
+               arg_infos->type = zend_compile_typename(
+                       return_type_ast, /* force_allow_null */ 0, /* use_arena */ 0);
                ZEND_TYPE_FULL_MASK(arg_infos->type) |= _ZEND_ARG_INFO_FLAGS(
                        (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0, /* is_variadic */ 0);
                arg_infos++;
@@ -5544,13 +5722,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
                if (type_ast) {
                        uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF;
 
-                       zend_bool is_class;
-
                        op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
-                       arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL);
-                       is_class = ZEND_TYPE_IS_CLASS(arg_info->type);
+                       arg_info->type = zend_compile_typename(
+                               type_ast, default_type == IS_NULL, /* use_arena */ 0);
 
-                       if (!is_class && (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID)) {
+                       if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) {
                                zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type");
                        }
 
@@ -5570,9 +5746,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
 
                if (type_ast) {
                        /* Allocate cache slot to speed-up run-time class resolution */
-                       if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
-                               opline->extended_value = zend_alloc_cache_slot();
-                       }
+                       opline->extended_value =
+                               zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type));
                }
 
                ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic);
@@ -6078,7 +6253,7 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) /
                zend_type type = ZEND_TYPE_INIT_NONE(0);
 
                if (type_ast) {
-                       type = zend_compile_typename(type_ast, 0);
+                       type = zend_compile_typename(type_ast, /* force_allow_null */ 0, /* use_arena */ 1);
 
                        if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) {
                                zend_string *str = zend_type_to_string(type);
index 96db1dcb3a695ef76cf18e54d6d032a0c18b01be..a87204d26753b57ce7e213e0b3a5986754d57ef9 100644 (file)
@@ -783,6 +783,8 @@ ZEND_API void destroy_op_array(zend_op_array *op_array);
 ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle);
 ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce);
 ZEND_API void zend_cleanup_internal_classes(void);
+ZEND_API void zend_type_release(zend_type type, zend_bool persistent);
+
 
 ZEND_API ZEND_COLD void zend_user_exception_handler(void);
 
index 097137aa5cff04fbc6167810e9a0766833f43073..153d8e3bc4ea95fd249d749fabad8ea7753b772e 100644 (file)
@@ -39,6 +39,7 @@
 #include "zend_dtrace.h"
 #include "zend_inheritance.h"
 #include "zend_type_info.h"
+#include "zend_smart_str.h"
 
 /* Virtual current working directory support */
 #include "zend_virtual_cwd.h"
@@ -642,14 +643,30 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro
        }
 }
 
+/* Test used to preserve old error messages for non-union types.
+ * We might want to canonicalize all type errors instead. */
+static zend_bool is_union_type(zend_type type) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               return 1;
+       }
+       uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
+       if (ZEND_TYPE_HAS_CLASS(type)) {
+               return type_mask_without_null != 0;
+       }
+       if (type_mask_without_null == MAY_BE_BOOL) {
+               return 0;
+       }
+       /* Check that only one bit is set. */
+       return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
+}
+
 static ZEND_COLD void zend_verify_type_error_common(
                const zend_function *zf, const zend_arg_info *arg_info,
                void **cache_slot, zval *value,
                const char **fname, const char **fsep, const char **fclass,
-               const char **need_msg, const char **need_kind, const char **need_or_null,
-               const char **given_msg, const char **given_kind)
+               zend_string **need_msg, const char **given_msg, const char **given_kind)
 {
-       zend_bool is_interface = 0;
+       smart_str str = {0};
        *fname = ZSTR_VAL(zf->common.function_name);
        if (zf->common.scope) {
                *fsep =  "::";
@@ -659,58 +676,69 @@ static ZEND_COLD void zend_verify_type_error_common(
                *fclass = "";
        }
 
-       if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
+       if (is_union_type(arg_info->type)) {
+               zend_string *type_str = zend_type_to_string(arg_info->type);
+               smart_str_appends(&str, "be of type ");
+               smart_str_append(&str, type_str);
+               zend_string_release(type_str);
+       } else if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+               zend_bool is_interface = 0;
                zend_class_entry *ce = *cache_slot;
+               if (!ce) {
+                       ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type),
+                               (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+               }
                if (ce) {
                        if (ce->ce_flags & ZEND_ACC_INTERFACE) {
-                               *need_msg = "implement interface ";
+                               smart_str_appends(&str, "implement interface ");
                                is_interface = 1;
                        } else {
-                               *need_msg = "be an instance of ";
+                               smart_str_appends(&str, "be an instance of ");
                        }
-                       *need_kind = ZSTR_VAL(ce->name);
+                       smart_str_append(&str, ce->name);
                } else {
                        /* We don't know whether it's a class or interface, assume it's a class */
+                       smart_str_appends(&str, "be an instance of ");
+                       smart_str_append(&str, ZEND_TYPE_NAME(arg_info->type));
+               }
 
-                       *need_msg = "be an instance of ";
-                       *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type));
+               if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
+                       smart_str_appends(&str, is_interface ? " or be null" : " or null");
                }
        } else {
                uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(arg_info->type);
                switch (type_mask) {
                        case MAY_BE_OBJECT:
-                               *need_msg = "be an ";
-                               *need_kind = "object";
+                               smart_str_appends(&str, "be an object");
                                break;
                        case MAY_BE_CALLABLE:
-                               *need_msg = "be callable";
-                               *need_kind = "";
+                               smart_str_appends(&str, "be callable");
                                break;
                        case MAY_BE_ITERABLE:
-                               *need_msg = "be iterable";
-                               *need_kind = "";
+                               smart_str_appends(&str, "be iterable");
                                break;
                        default:
                        {
-                               /* TODO: The zend_type_to_string() result is guaranteed interned here.
-                                * It would be beter to switch all this code to use zend_string though. */
+                               /* Hack to print the type without null */
                                zend_type type = arg_info->type;
                                ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL;
-                               *need_msg = "be of the type ";
-                               *need_kind = ZSTR_VAL(zend_type_to_string(type));
+                               zend_string *type_str = zend_type_to_string(type);
+                               smart_str_appends(&str, "be of the type ");
+                               smart_str_append(&str, type_str);
+                               zend_string_release(type_str);
                                break;
                        }
                }
-       }
 
-       if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
-               *need_or_null = is_interface ? " or be null" : " or null";
-       } else {
-               *need_or_null = "";
+               if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
+                       smart_str_appends(&str, " or null");
+               }
        }
 
+       *need_msg = smart_str_extract(&str);
+
        if (value) {
-               if (ZEND_TYPE_IS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) {
+               if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) {
                        *given_msg = "instance of ";
                        *given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name);
                } else {
@@ -729,7 +757,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
 {
        zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data;
        const char *fname, *fsep, *fclass;
-       const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind;
+       zend_string *need_msg;
+       const char *given_msg, *given_kind;
 
        if (EG(exception)) {
                /* The type verification itself might have already thrown an exception
@@ -740,19 +769,21 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
        if (value) {
                zend_verify_type_error_common(
                        zf, arg_info, cache_slot, value,
-                       &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind);
+                       &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind);
 
                if (zf->common.type == ZEND_USER_FUNCTION) {
                        if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) {
-                               zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given, called in %s on line %d",
-                                               arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind,
+                               zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given, called in %s on line %d",
+                                               arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind,
                                                ZSTR_VAL(ptr->func->op_array.filename), ptr->opline->lineno);
                        } else {
-                               zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
+                               zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind);
                        }
                } else {
-                       zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
+                       zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind);
                }
+
+               zend_string_release(need_msg);
        } else {
                zend_missing_arg_error(ptr);
        }
@@ -760,42 +791,47 @@ ZEND_API ZEND_COLD void zend_verify_arg_error(
 
 static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg)
 {
-       if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) {
-               zend_bool dest;
+       zend_long lval;
+       double dval;
+       zend_string *str;
+       zend_bool bval;
 
-               if (!zend_parse_arg_bool_weak(arg, &dest)) {
-                       return 0;
+       /* Type preference order: int -> float -> string -> bool */
+       if (type_mask & MAY_BE_LONG) {
+               /* For an int|float union type and string value,
+                * determine chosen type by is_numeric_string() semantics. */
+               if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) {
+                       zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &lval, &dval, -1);
+                       if (type == IS_LONG) {
+                               zend_string_release(Z_STR_P(arg));
+                               ZVAL_LONG(arg, lval);
+                               return 1;
+                       }
+                       if (type == IS_DOUBLE) {
+                               zend_string_release(Z_STR_P(arg));
+                               ZVAL_DOUBLE(arg, dval);
+                               return 1;
+                       }
+               } else if (zend_parse_arg_long_weak(arg, &lval)) {
+                       zval_ptr_dtor(arg);
+                       ZVAL_LONG(arg, lval);
+                       return 1;
                }
+       }
+       if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval)) {
                zval_ptr_dtor(arg);
-               ZVAL_BOOL(arg, dest);
+               ZVAL_DOUBLE(arg, dval);
                return 1;
        }
-       if (type_mask & MAY_BE_LONG) {
-               zend_long dest;
-
-               if (!zend_parse_arg_long_weak(arg, &dest)) {
-                       return 0;
-               }
-               zval_ptr_dtor(arg);
-               ZVAL_LONG(arg, dest);
+       if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str)) {
+               /* on success "arg" is converted to IS_STRING */
                return 1;
        }
-       if (type_mask & MAY_BE_DOUBLE) {
-               double dest;
-
-               if (!zend_parse_arg_double_weak(arg, &dest)) {
-                       return 0;
-               }
+       if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) {
                zval_ptr_dtor(arg);
-               ZVAL_DOUBLE(arg, dest);
+               ZVAL_BOOL(arg, bval);
                return 1;
        }
-       if (type_mask & MAY_BE_STRING) {
-               zend_string *dest;
-
-               /* on success "arg" is converted to IS_STRING */
-               return zend_parse_arg_str_weak(arg, &dest);
-       }
        return 0;
 }
 
@@ -803,40 +839,48 @@ static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg
 /* Used to sanity-check internal arginfo types without performing any actual type conversions. */
 static zend_bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_mask, zval *arg)
 {
-       if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) {
-               zend_bool dest;
-               return zend_parse_arg_bool_weak(arg, &dest);
-       }
+       zend_long lval;
+       double dval;
+       zend_bool bval;
+
        if (type_mask & MAY_BE_LONG) {
-               zend_long dest;
                if (Z_TYPE_P(arg) == IS_STRING) {
                        /* Handle this case separately to avoid the "non well-formed" warning */
-                       double dval;
                        zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, &dval, 1);
                        if (type == IS_LONG) {
                                return 1;
                        }
                        if (type == IS_DOUBLE) {
-                               return !zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval);
+                               if ((type_mask & MAY_BE_DOUBLE)
+                                               || (!zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval))) {
+                                       return 1;
+                               }
 
                        }
-                       return 0;
                }
-               return zend_parse_arg_long_weak(arg, &dest);
+               if (zend_parse_arg_long_weak(arg, &lval)) {
+                       return 1;
+               }
        }
        if (type_mask & MAY_BE_DOUBLE) {
-               double dest;
                if (Z_TYPE_P(arg) == IS_STRING) {
                        /* Handle this case separately to avoid the "non well-formed" warning */
-                       return is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0;
+                       if (is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0) {
+                               return 1;
+                       }
+               }
+               if (zend_parse_arg_double_weak(arg, &dval)) {
+                       return 1;
                }
-               return zend_parse_arg_double_weak(arg, &dest);
        }
-       if (type_mask & MAY_BE_STRING) {
-               /* We don't call cast_object here, because this check must be side-effect free. As this
-                * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept
-                * more than actually allowed here. */
-               return Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT;
+       /* We don't call cast_object here, because this check must be side-effect free. As this
+        * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept
+        * more than actually allowed here. */
+       if ((type_mask & MAY_BE_STRING) && (Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT)) {
+               return 1;
+       }
+       if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) {
+               return 1;
        }
        return 0;
 }
@@ -850,12 +894,10 @@ ZEND_API zend_bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, z
                        return 0;
                }
        } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) {
-               /* NULL may be accepted only by nullable hints (this is already checked) */
-               if (is_internal_arg && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) {
-                       /* As an exception, null is allowed for scalar types in weak mode. */
-                       return 1;
-               }
-               return 0;
+               /* NULL may be accepted only by nullable hints (this is already checked).
+                * As an exception for internal functions, null is allowed for scalar types in weak mode. */
+               return is_internal_arg
+                       && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING));
        }
 #if ZEND_DEBUG
        if (is_internal_arg) {
@@ -883,59 +925,77 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i
        zend_string_release(type_str);
 }
 
-static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self_ce) {
-       zend_class_entry *ce;
-       zend_string *name = ZEND_TYPE_NAME(*type);
+static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) {
        if (zend_string_equals_literal_ci(name, "self")) {
                /* We need to explicitly check for this here, to avoid updating the type in the trait and
                 * later using the wrong "self" when the trait is used in a class. */
                if (UNEXPECTED((self_ce->ce_flags & ZEND_ACC_TRAIT) != 0)) {
-                       zend_throw_error(NULL, "Cannot write a%s value to a 'self' typed static property of a trait", ZEND_TYPE_ALLOW_NULL(*type) ? " non-null" : "");
-                       return 0;
+                       return NULL;
                }
-               ce = self_ce;
+               return self_ce;
        } else if (zend_string_equals_literal_ci(name, "parent")) {
-               if (UNEXPECTED(!self_ce->parent)) {
-                       zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent");
-                       return 0;
-               }
-               ce = self_ce->parent;
+               return self_ce->parent;
        } else {
-               ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
-               if (UNEXPECTED(!ce)) {
-                       return 0;
-               }
+               return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
        }
-
-       zend_string_release(name);
-       *type = (zend_type) ZEND_TYPE_INIT_CE(ce, ZEND_TYPE_ALLOW_NULL(*type), 0);
-       return 1;
 }
 
+static zend_bool zend_check_and_resolve_property_class_type(
+               zend_property_info *info, zend_class_entry *object_ce) {
+       zend_class_entry *ce;
+       if (ZEND_TYPE_HAS_LIST(info->type)) {
+               void **entry;
+               ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(info->type), entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+                               zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                               ce = resolve_single_class_type(name, info->ce);
+                               if (!ce) {
+                                       continue;
+                               }
+                               zend_string_release(name);
+                               *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+                       } else {
+                               ce = ZEND_TYPE_LIST_GET_CE(*entry);
+                       }
+                       if (instanceof_function(object_ce, ce)) {
+                               return 1;
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+               return 0;
+       } else {
+               if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) {
+                       zend_string *name = ZEND_TYPE_NAME(info->type);
+                       ce = resolve_single_class_type(name, info->ce);
+                       if (UNEXPECTED(!ce)) {
+                               return 0;
+                       }
+
+                       zend_string_release(name);
+                       ZEND_TYPE_SET_CE(info->type, ce);
+               } else {
+                       ce = ZEND_TYPE_CE(info->type);
+               }
+               return instanceof_function(object_ce, ce);
+       }
+}
 
 static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict)
 {
        ZEND_ASSERT(!Z_ISREF_P(property));
-       if (ZEND_TYPE_IS_CLASS(info->type)) {
-               if (UNEXPECTED(Z_TYPE_P(property) != IS_OBJECT)) {
-                       return Z_TYPE_P(property) == IS_NULL && ZEND_TYPE_ALLOW_NULL(info->type);
-               }
-
-               if (UNEXPECTED(!ZEND_TYPE_IS_CE(info->type)) && UNEXPECTED(!zend_resolve_class_type(&info->type, info->ce))) {
-                       return 0;
-               }
+       if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) {
+               return 1;
+       }
 
-               return instanceof_function(Z_OBJCE_P(property), ZEND_TYPE_CE(info->type));
+       if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT
+                       && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) {
+               return 1;
        }
 
        ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE));
-       if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) {
+       if ((ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
                return 1;
-       } else if (ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) {
-               return zend_is_iterable(property);
-       } else {
-               return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0);
        }
+       return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0);
 }
 
 static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict)
@@ -981,43 +1041,70 @@ static zend_always_inline zend_bool zend_check_type(
                arg = Z_REFVAL_P(arg);
        }
 
-       if (ZEND_TYPE_IS_CLASS(type)) {
+       if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) {
+               return 1;
+       }
+
+       if (ZEND_TYPE_HAS_CLASS(type) && Z_TYPE_P(arg) == IS_OBJECT) {
                zend_class_entry *ce;
-               if (EXPECTED(*cache_slot)) {
-                       ce = (zend_class_entry *) *cache_slot;
+               if (ZEND_TYPE_HAS_LIST(type)) {
+                       void *entry;
+                       ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                               if (*cache_slot) {
+                                       ce = *cache_slot;
+                               } else {
+                                       ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry),
+                                               (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+                                       if (!ce) {
+                                               cache_slot++;
+                                               continue;
+                                       }
+                                       *cache_slot = ce;
+                               }
+                               if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+                                       return 1;
+                               }
+                               cache_slot++;
+                       } ZEND_TYPE_LIST_FOREACH_END();
                } else {
-                       ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
-                       if (UNEXPECTED(!ce)) {
-                               return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type);
+                       if (EXPECTED(*cache_slot)) {
+                               ce = (zend_class_entry *) *cache_slot;
+                       } else {
+                               ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+                               if (UNEXPECTED(!ce)) {
+                                       goto builtin_types;
+                               }
+                               *cache_slot = (void *) ce;
+                       }
+                       if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+                               return 1;
                        }
-                       *cache_slot = (void *) ce;
-               }
-               if (EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
-                       return instanceof_function(Z_OBJCE_P(arg), ce);
                }
-               return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type);
-       } else if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) {
-               return 1;
        }
 
+builtin_types:
        type_mask = ZEND_TYPE_FULL_MASK(type);
-       if (type_mask & MAY_BE_CALLABLE) {
-               return zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL);
-       } else if (type_mask & MAY_BE_ITERABLE) {
-               return zend_is_iterable(arg);
-       } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
-               return 0; /* we cannot have conversions for typed refs */
-       } else if (is_internal && is_return_type) {
+       if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) {
+               return 1;
+       }
+       if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
+               return 1;
+       }
+       if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
+               /* We cannot have conversions for typed refs. */
+               return 0;
+       }
+       if (is_internal && is_return_type) {
                /* For internal returns, the type has to match exactly, because we're not
                 * going to check it for non-debug builds, and there will be no chance to
                 * apply coercions. */
                return 0;
-       } else {
-               return zend_verify_scalar_type_hint(type_mask, arg,
-                       is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(),
-                       is_internal);
        }
 
+       return zend_verify_scalar_type_hint(type_mask, arg,
+               is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(),
+               is_internal);
+
        /* Special handling for IS_VOID is not necessary (for return types),
         * because this case is already checked at compile-time. */
 }
@@ -1139,14 +1226,17 @@ static ZEND_COLD void zend_verify_return_error(
 {
        const zend_arg_info *arg_info = &zf->common.arg_info[-1];
        const char *fname, *fsep, *fclass;
-       const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind;
+       zend_string *need_msg;
+       const char *given_msg, *given_kind;
 
        zend_verify_type_error_common(
                zf, arg_info, cache_slot, value,
-               &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind);
+               &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind);
+
+       zend_type_error("Return value of %s%s%s() must %s, %s%s returned",
+               fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind);
 
-       zend_type_error("Return value of %s%s%s() must %s%s%s, %s%s returned",
-               fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
+       zend_string_release(need_msg);
 }
 
 #if ZEND_DEBUG
@@ -1155,14 +1245,15 @@ static ZEND_COLD void zend_verify_internal_return_error(
 {
        const zend_arg_info *arg_info = &zf->common.arg_info[-1];
        const char *fname, *fsep, *fclass;
-       const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind;
+       zend_string *need_msg;
+       const char *given_msg, *given_kind;
 
        zend_verify_type_error_common(
                zf, arg_info, cache_slot, value,
-               &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind);
+               &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind);
 
-       zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s%s%s, %s%s returned",
-               fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
+       zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s, %s%s returned",
+               fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind);
 }
 
 static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind)
@@ -1217,18 +1308,6 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval *
 static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, void **cache_slot)
 {
        /* VERIFY_RETURN_TYPE is not emitted for "void" functions, so this is always an error. */
-       zend_arg_info *ret_info = zf->common.arg_info - 1;
-
-       // TODO: Eliminate this!
-       zend_class_entry *ce = NULL;
-       if (ZEND_TYPE_IS_CLASS(ret_info->type)) {
-               if (UNEXPECTED(!*cache_slot)) {
-                       zend_class_entry *ce = zend_fetch_class(ZEND_TYPE_NAME(ret_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
-                       if (ce) {
-                               *cache_slot = (void *) ce;
-                       }
-               }
-       }
        zend_verify_return_error(zf, cache_slot, NULL);
        return 0;
 }
@@ -1572,25 +1651,25 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re
        return NULL;
 }
 
-static ZEND_COLD zend_long zend_throw_incdec_ref_error(zend_reference *ref OPLINE_DC)
+static ZEND_COLD zend_long zend_throw_incdec_ref_error(
+               zend_reference *ref, zend_property_info *error_prop OPLINE_DC)
 {
-       zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref);
-       /* Currently there should be no way for a typed reference to accept both int and double.
-        * Generalize this and the related property code once this becomes possible. */
-       ZEND_ASSERT(error_prop);
+       zend_string *type_str = zend_type_to_string(error_prop->type);
        if (ZEND_IS_INCREMENT(opline->opcode)) {
                zend_type_error(
-                       "Cannot increment a reference held by property %s::$%s of type %sint past its maximal value",
+                       "Cannot increment a reference held by property %s::$%s of type %s past its maximal value",
                        ZSTR_VAL(error_prop->ce->name),
                        zend_get_unmangled_property_name(error_prop->name),
-                       ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : "");
+                       ZSTR_VAL(type_str));
+               zend_string_release(type_str);
                return ZEND_LONG_MAX;
        } else {
                zend_type_error(
-                       "Cannot decrement a reference held by property %s::$%s of type %sint past its minimal value",
+                       "Cannot decrement a reference held by property %s::$%s of type %s past its minimal value",
                        ZSTR_VAL(error_prop->ce->name),
                        zend_get_unmangled_property_name(error_prop->name),
-                       ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : "");
+                       ZSTR_VAL(type_str));
+               zend_string_release(type_str);
                return ZEND_LONG_MIN;
        }
 }
@@ -1632,8 +1711,11 @@ static void zend_incdec_typed_ref(zend_reference *ref, zval *copy OPLINE_DC EXEC
        }
 
        if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) {
-               zend_long val = zend_throw_incdec_ref_error(ref OPLINE_CC);
-               ZVAL_LONG(var_ptr, val);
+               zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref);
+               if (UNEXPECTED(error_prop)) {
+                       zend_long val = zend_throw_incdec_ref_error(ref, error_prop OPLINE_CC);
+                       ZVAL_LONG(var_ptr, val);
+               }
        } else if (UNEXPECTED(!zend_verify_ref_assignable_zval(ref, var_ptr, EX_USES_STRICT_TYPES()))) {
                zval_ptr_dtor(var_ptr);
                ZVAL_COPY_VALUE(var_ptr, copy);
@@ -1660,8 +1742,10 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr,
        }
 
        if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) {
-               zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC);
-               ZVAL_LONG(var_ptr, val);
+               if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) {
+                       zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC);
+                       ZVAL_LONG(var_ptr, val);
+               }
        } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) {
                zval_ptr_dtor(var_ptr);
                ZVAL_COPY_VALUE(var_ptr, copy);
@@ -1679,7 +1763,8 @@ static void zend_pre_incdec_property_zval(zval *prop, zend_property_info *prop_i
                } else {
                        fast_long_decrement_function(prop);
                }
-               if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) {
+               if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)
+                               && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) {
                        zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC);
                        ZVAL_LONG(prop, val);
                }
@@ -1717,7 +1802,8 @@ static void zend_post_incdec_property_zval(zval *prop, zend_property_info *prop_
                } else {
                        fast_long_decrement_function(prop);
                }
-               if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) {
+               if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)
+                               && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) {
                        zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC);
                        ZVAL_LONG(prop, val);
                }
@@ -2942,29 +3028,22 @@ ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(zend_property_info
 
 /* 1: valid, 0: invalid, -1: may be valid after type coercion */
 static zend_always_inline int i_zend_verify_type_assignable_zval(
-               zend_type *type_ptr, zend_class_entry *self_ce, zval *zv, zend_bool strict) {
-       zend_type type = *type_ptr;
+               zend_property_info *info, zval *zv, zend_bool strict) {
+       zend_type type = info->type;
        uint32_t type_mask;
        zend_uchar zv_type = Z_TYPE_P(zv);
 
-       if (ZEND_TYPE_IS_CLASS(type)) {
-               if (ZEND_TYPE_ALLOW_NULL(type) && zv_type == IS_NULL) {
-                       return 1;
-               }
-               if (!ZEND_TYPE_IS_CE(type)) {
-                       if (!zend_resolve_class_type(type_ptr, self_ce)) {
-                               return 0;
-                       }
-                       type = *type_ptr;
-               }
-               return zv_type == IS_OBJECT && instanceof_function(Z_OBJCE_P(zv), ZEND_TYPE_CE(type));
+       if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, zv_type))) {
+               return 1;
        }
 
-       if (ZEND_TYPE_CONTAINS_CODE(type, zv_type)) {
+       if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT
+                       && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) {
                return 1;
        }
 
        type_mask = ZEND_TYPE_FULL_MASK(type);
+       ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE));
        if (type_mask & MAY_BE_ITERABLE) {
                return zend_is_iterable(zv);
        }
@@ -2977,13 +3056,14 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
                return 0;
        }
 
-       /* No weak conversions for arrays and objects */
-       if (type_mask & (MAY_BE_ARRAY|MAY_BE_OBJECT)) {
+       /* NULL may be accepted only by nullable hints (this is already checked) */
+       if (zv_type == IS_NULL) {
                return 0;
        }
 
-       /* NULL may be accepted only by nullable hints (this is already checked) */
-       if (zv_type == IS_NULL) {
+       /* Does not contain any type to which a coercion is possible */
+       if (!(type_mask & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))
+                       && (type_mask & MAY_BE_BOOL) != MAY_BE_BOOL) {
                return 0;
        }
 
@@ -2996,39 +3076,62 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference
        zend_property_info *prop;
 
        /* The value must satisfy each property type, and coerce to the same value for each property
-        * type. Right now, the latter rule means that *if* coercion is necessary, then all types
-        * must be the same (modulo nullability). To handle this, remember the first type we see and
-        * compare against it when coercion becomes necessary. */
-       zend_property_info *seen_prop = NULL;
-       uint32_t seen_type_mask;
-       zend_bool needs_coercion = 0;
+        * type. Remember the first coerced type and value we've seen for this purpose. */
+       zend_property_info *first_prop = NULL;
+       zval coerced_value;
+       ZVAL_UNDEF(&coerced_value);
 
        ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE);
        ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) {
-               int result = i_zend_verify_type_assignable_zval(&prop->type, prop->ce, zv, strict);
+               int result = i_zend_verify_type_assignable_zval(prop, zv, strict);
                if (result == 0) {
+type_error:
                        zend_throw_ref_type_error_zval(prop, zv);
+                       zval_ptr_dtor(&coerced_value);
                        return 0;
                }
 
                if (result < 0) {
-                       needs_coercion = 1;
-               }
-
-               if (!seen_prop) {
-                       seen_prop = prop;
-                       seen_type_mask = ZEND_TYPE_IS_CLASS(prop->type)
-                               ? MAY_BE_OBJECT : ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type);
-               } else if (needs_coercion
-                               && seen_type_mask != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop->type)) {
-                       zend_throw_conflicting_coercion_error(seen_prop, prop, zv);
-                       return 0;
+                       if (!first_prop) {
+                               first_prop = prop;
+                               ZVAL_COPY(&coerced_value, zv);
+                               if (!zend_verify_weak_scalar_type_hint(
+                                               ZEND_TYPE_FULL_MASK(prop->type), &coerced_value)) {
+                                       goto type_error;
+                               }
+                       } else if (Z_ISUNDEF(coerced_value)) {
+                               /* A previous property did not require coercion, but this one does,
+                                * so they are incompatible. */
+                               goto conflicting_coercion_error;
+                       } else {
+                               zval tmp;
+                               ZVAL_COPY(&tmp, zv);
+                               if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) {
+                                       zval_ptr_dtor(&tmp);
+                                       goto type_error;
+                               }
+                               if (!zend_is_identical(&coerced_value, &tmp)) {
+                                       zval_ptr_dtor(&tmp);
+                                       goto conflicting_coercion_error;
+                               }
+                       }
+               } else {
+                       if (!first_prop) {
+                               first_prop = prop;
+                       } else if (!Z_ISUNDEF(coerced_value)) {
+                               /* A previous property required coercion, but this one doesn't,
+                                * so they are incompatible. */
+conflicting_coercion_error:
+                               zend_throw_conflicting_coercion_error(first_prop, prop, zv);
+                               zval_ptr_dtor(&coerced_value);
+                               return 0;
+                       }
                }
        } ZEND_REF_FOREACH_TYPE_SOURCES_END();
 
-       if (UNEXPECTED(needs_coercion && !zend_verify_weak_scalar_type_hint(seen_type_mask, zv))) {
-               zend_throw_ref_type_error_zval(seen_prop, zv);
-               return 0;
+       if (!Z_ISUNDEF(coerced_value)) {
+               zval_ptr_dtor(zv);
+               ZVAL_COPY_VALUE(zv, &coerced_value);
        }
 
        return 1;
@@ -3080,22 +3183,23 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert
                int result;
 
                val = Z_REFVAL_P(val);
-               result = i_zend_verify_type_assignable_zval(&prop_info->type, prop_info->ce, val, strict);
+               result = i_zend_verify_type_assignable_zval(prop_info, val, strict);
                if (result > 0) {
                        return 1;
                }
 
                if (result < 0) {
-                       zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val));
-                       if (ZEND_TYPE_PURE_MASK_WITHOUT_NULL(prop_info->type)
-                                       != ZEND_TYPE_PURE_MASK_WITHOUT_NULL(ref_prop->type)) {
-                               /* Invalid due to conflicting coercion */
+                       /* This is definitely an error, but we still need to determined why: Either because
+                        * the value is simply illegal for the type, or because or a conflicting coercion. */
+                       zval tmp;
+                       ZVAL_COPY(&tmp, val);
+                       if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), &tmp)) {
+                               zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val));
                                zend_throw_ref_type_error_type(ref_prop, prop_info, val);
+                               zval_ptr_dtor(&tmp);
                                return 0;
                        }
-                       if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), val)) {
-                               return 1;
-                       }
+                       zval_ptr_dtor(&tmp);
                }
        } else {
                ZVAL_DEREF(val);
index af81c327e158690b71b2be155079227d6ce3ac5f..9b8a47f365ba5542dfd61f27b4b4935aa5b082b9 100644 (file)
@@ -40,14 +40,31 @@ static void overridden_ptr_dtor(zval *zv) /* {{{ */
 }
 /* }}} */
 
+static void zend_type_copy_ctor(zend_type *type, zend_bool persistent) {
+       if (ZEND_TYPE_HAS_LIST(*type)) {
+               zend_type_list *old_list = ZEND_TYPE_LIST(*type);
+               size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types);
+               zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type)
+                       ? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent);
+               memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types));
+               ZEND_TYPE_SET_PTR(*type, new_list);
+
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(new_list, entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                       zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry));
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(*type)) {
+               zend_string_addref(ZEND_TYPE_NAME(*type));
+       }
+}
+
 static zend_property_info *zend_duplicate_property_info_internal(zend_property_info *property_info) /* {{{ */
 {
        zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1);
        memcpy(new_property_info, property_info, sizeof(zend_property_info));
        zend_string_addref(new_property_info->name);
-       if (ZEND_TYPE_IS_NAME(new_property_info->type)) {
-               zend_string_addref(ZEND_TYPE_NAME(new_property_info->type));
-       }
+       zend_type_copy_ctor(&new_property_info->type, /* persistent */ 1);
 
        return new_property_info;
 }
@@ -219,7 +236,8 @@ static zend_bool class_visible(zend_class_entry *ce) {
        }
 }
 
-static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name) {
+static zend_class_entry *lookup_class(
+               zend_class_entry *scope, zend_string *name, zend_bool register_unresolved) {
        zend_class_entry *ce;
        if (!CG(in_compilation)) {
                uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD;
@@ -228,12 +246,14 @@ static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name
                        return ce;
                }
 
-               /* We'll autoload this class and process delayed variance obligations later. */
-               if (!CG(delayed_autoloads)) {
-                       ALLOC_HASHTABLE(CG(delayed_autoloads));
-                       zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0);
+               if (register_unresolved) {
+                       /* We'll autoload this class and process delayed variance obligations later. */
+                       if (!CG(delayed_autoloads)) {
+                               ALLOC_HASHTABLE(CG(delayed_autoloads));
+                               zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0);
+                       }
+                       zend_hash_add_empty_element(CG(delayed_autoloads), name);
                }
-               zend_hash_add_empty_element(CG(delayed_autoloads), name);
        } else {
                ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
                if (ce && class_visible(ce)) {
@@ -302,6 +322,23 @@ static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce
        return 0;
 }
 
+static zend_bool zend_type_contains_traversable(zend_type type) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                       if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) {
+                               return 1;
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+               return 0;
+       }
+       if (ZEND_TYPE_HAS_NAME(type)) {
+               return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable");
+       }
+       return 0;
+}
+
 /* Unresolved means that class declarations that are currently not available are needed to
  * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated
  * as an ERROR. */
@@ -311,86 +348,150 @@ typedef enum {
        INHERITANCE_SUCCESS = 1,
 } inheritance_status;
 
-static inheritance_status zend_perform_covariant_type_check(
-               zend_string **unresolved_class,
-               const zend_function *fe, zend_arg_info *fe_arg_info,
-               const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
-{
-       zend_type fe_type = fe_arg_info->type, proto_type = proto_arg_info->type;
-       ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type));
-
-       if (ZEND_TYPE_ALLOW_NULL(fe_type) && !ZEND_TYPE_ALLOW_NULL(proto_type)) {
-               return INHERITANCE_ERROR;
+static inheritance_status zend_perform_covariant_class_type_check(
+               zend_class_entry *fe_scope, zend_string *fe_class_name,
+               zend_class_entry *proto_scope, zend_type proto_type,
+               zend_bool register_unresolved) {
+       zend_bool have_unresolved = 0;
+       zend_class_entry *fe_ce = NULL;
+       if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) {
+               /* Currently, any class name would be allowed here. We still perform a class lookup
+                * for forward-compatibility reasons, as we may have named types in the future that
+                * are not classes (such as enums or typedefs). */
+               if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
+               if (!fe_ce) {
+                       have_unresolved = 1;
+               } else {
+                       return INHERITANCE_SUCCESS;
+               }
        }
-
-       if (ZEND_TYPE_IS_CLASS(proto_type)) {
-               zend_string *fe_class_name, *proto_class_name;
-               zend_class_entry *fe_ce, *proto_ce;
-               if (!ZEND_TYPE_IS_CLASS(fe_type)) {
-                       return INHERITANCE_ERROR;
+       if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) {
+               if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
+               if (!fe_ce) {
+                       have_unresolved = 1;
+               } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) {
+                       return INHERITANCE_SUCCESS;
                }
-
-               fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
-               proto_class_name = resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type));
+       }
+       if (ZEND_TYPE_HAS_NAME(proto_type)) {
+               zend_string *proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(proto_type));
                if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
                        return INHERITANCE_SUCCESS;
                }
 
                /* Make sure to always load both classes, to avoid only registering one of them as
                 * a delayed autoload. */
-               fe_ce = lookup_class(fe->common.scope, fe_class_name);
-               proto_ce = lookup_class(proto->common.scope, proto_class_name);
-               if (!fe_ce) {
-                       *unresolved_class = fe_class_name;
-                       return INHERITANCE_UNRESOLVED;
-               }
-               if (!proto_ce) {
-                       *unresolved_class = proto_class_name;
-                       return INHERITANCE_UNRESOLVED;
+               if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
+               zend_class_entry *proto_ce =
+                       lookup_class(proto_scope, proto_class_name, register_unresolved);
+               if (!fe_ce || !proto_ce) {
+                       have_unresolved = 1;
+               } else if (unlinked_instanceof(fe_ce, proto_ce)) {
+                       return INHERITANCE_SUCCESS;
                }
+       }
+       if (ZEND_TYPE_HAS_LIST(proto_type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(proto_type), entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                       zend_string *proto_class_name =
+                               resolve_class_name(proto_scope, ZEND_TYPE_LIST_GET_NAME(entry));
+                       if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
+                               return INHERITANCE_SUCCESS;
+                       }
 
-               return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
-       } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) {
-               if (ZEND_TYPE_IS_CLASS(fe_type)) {
-                       zend_string *fe_class_name =
-                               resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
-                       zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name);
-                       if (!fe_ce) {
-                               *unresolved_class = fe_class_name;
-                               return INHERITANCE_UNRESOLVED;
+                       if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved);
+                       zend_class_entry *proto_ce =
+                               lookup_class(proto_scope, proto_class_name, register_unresolved);
+                       if (!fe_ce || !proto_ce) {
+                               have_unresolved = 1;
+                       } else if (unlinked_instanceof(fe_ce, proto_ce)) {
+                               return INHERITANCE_SUCCESS;
                        }
-                       return unlinked_instanceof(fe_ce, zend_ce_traversable)
-                               ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
+               } ZEND_TYPE_LIST_FOREACH_END();
+       }
+       return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
+}
+
+static inheritance_status zend_perform_covariant_type_check(
+               zend_class_entry *fe_scope, zend_type fe_type,
+               zend_class_entry *proto_scope, zend_type proto_type) /* {{{ */
+{
+       ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type));
+
+       /* Builtin types may be removed, but not added */
+       uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type);
+       uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type);
+       uint32_t added_types = fe_type_mask & ~proto_type_mask;
+       if (added_types) {
+               // TODO: Make "iterable" an alias of "array|Traversable" instead,
+               // so these special cases will be handled automatically.
+               if (added_types == MAY_BE_ITERABLE
+                               && (proto_type_mask & MAY_BE_ARRAY)
+                               && zend_type_contains_traversable(proto_type)) {
+                       /* Replacing array|Traversable with iterable is okay */
+               } else if (added_types == MAY_BE_ARRAY && (proto_type_mask & MAY_BE_ITERABLE)) {
+                       /* Replacing iterable with array is okay */
+               } else {
+                       /* Otherwise adding new types is illegal */
+                       return INHERITANCE_ERROR;
+               }
+       }
+
+       if (ZEND_TYPE_HAS_NAME(fe_type)) {
+               zend_string *fe_class_name = resolve_class_name(fe_scope, ZEND_TYPE_NAME(fe_type));
+               inheritance_status status = zend_perform_covariant_class_type_check(
+                       fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0);
+               if (status != INHERITANCE_UNRESOLVED) {
+                       return status;
                }
 
-               return ZEND_TYPE_FULL_MASK(fe_type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE)
-                       ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
-       } else if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) {
-               if (ZEND_TYPE_IS_CLASS(fe_type)) {
-                       /* Currently, any class name would be allowed here. We still perform a class lookup
-                        * for forward-compatibility reasons, as we may have named types in the future that
-                        * are not classes (such as enums or typedefs). */
+               zend_perform_covariant_class_type_check(
+                       fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1);
+               return INHERITANCE_UNRESOLVED;
+       }
+
+       if (ZEND_TYPE_HAS_LIST(fe_type)) {
+               void *entry;
+               zend_bool all_success = 1;
+
+               /* First try to check whether we can succeed without resolving anything */
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
                        zend_string *fe_class_name =
-                               resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
-                       zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name);
-                       if (!fe_ce) {
-                               *unresolved_class = fe_class_name;
-                               return INHERITANCE_UNRESOLVED;
+                               resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry));
+                       inheritance_status status = zend_perform_covariant_class_type_check(
+                               fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0);
+                       if (status == INHERITANCE_ERROR) {
+                               return INHERITANCE_ERROR;
+                       }
+
+                       if (status != INHERITANCE_SUCCESS) {
+                               all_success = 0;
                        }
+               } ZEND_TYPE_LIST_FOREACH_END();
+
+               /* All individual checks suceeded, overall success */
+               if (all_success) {
                        return INHERITANCE_SUCCESS;
                }
 
-               return ZEND_TYPE_FULL_MASK(fe_type) & MAY_BE_OBJECT
-                       ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
-       } else {
-               return ZEND_TYPE_PURE_MASK_WITHOUT_NULL(fe_type) == ZEND_TYPE_PURE_MASK_WITHOUT_NULL(proto_type)
-                       ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
+               /* Register all classes that may have to be resolved */
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) {
+                       ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry));
+                       zend_string *fe_class_name =
+                               resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry));
+                       zend_perform_covariant_class_type_check(
+                               fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1);
+               } ZEND_TYPE_LIST_FOREACH_END();
+               return INHERITANCE_UNRESOLVED;
        }
+
+       return INHERITANCE_SUCCESS;
 }
 /* }}} */
 
 static inheritance_status zend_do_perform_arg_type_hint_check(
-               zend_string **unresolved_class,
                const zend_function *fe, zend_arg_info *fe_arg_info,
                const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
 {
@@ -407,12 +508,12 @@ static inheritance_status zend_do_perform_arg_type_hint_check(
        /* Contravariant type check is performed as a covariant type check with swapped
         * argument order. */
        return zend_perform_covariant_type_check(
-               unresolved_class, proto, proto_arg_info, fe, fe_arg_info);
+               proto->common.scope, proto_arg_info->type, fe->common.scope, fe_arg_info->type);
 }
 /* }}} */
 
 static inheritance_status zend_do_perform_implementation_check(
-               zend_string **unresolved_class, const zend_function *fe, const zend_function *proto) /* {{{ */
+               const zend_function *fe, const zend_function *proto) /* {{{ */
 {
        uint32_t i, num_args;
        inheritance_status status, local_status;
@@ -478,8 +579,7 @@ static inheritance_status zend_do_perform_implementation_check(
                        proto_arg_info = &proto->common.arg_info[proto->common.num_args];
                }
 
-               local_status = zend_do_perform_arg_type_hint_check(
-                       unresolved_class, fe, fe_arg_info, proto, proto_arg_info);
+               local_status = zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info);
 
                if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) {
                        if (UNEXPECTED(local_status == INHERITANCE_ERROR)) {
@@ -504,7 +604,8 @@ static inheritance_status zend_do_perform_implementation_check(
                }
 
                local_status = zend_perform_covariant_type_check(
-                       unresolved_class, fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1);
+                       fe->common.scope, fe->common.arg_info[-1].type,
+                       proto->common.scope, proto->common.arg_info[-1].type);
 
                if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) {
                        if (UNEXPECTED(local_status == INHERITANCE_ERROR)) {
@@ -663,10 +764,17 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) {
 
 static void ZEND_COLD emit_incompatible_method_error(
                const zend_function *child, const zend_function *parent,
-               inheritance_status status, zend_string *unresolved_class) {
+               inheritance_status status) {
        zend_string *parent_prototype = zend_get_function_declaration(parent);
        zend_string *child_prototype = zend_get_function_declaration(child);
        if (status == INHERITANCE_UNRESOLVED) {
+               /* Fetch the first unresolved class from registered autoloads */
+               zend_string *unresolved_class = NULL;
+               ZEND_HASH_FOREACH_STR_KEY(CG(delayed_autoloads), unresolved_class) {
+                       break;
+               } ZEND_HASH_FOREACH_END();
+               ZEND_ASSERT(unresolved_class);
+
                zend_error_at(E_COMPILE_ERROR, NULL, func_lineno(child),
                        "Could not check compatibility between %s and %s, because class %s is not available",
                        ZSTR_VAL(child_prototype), ZSTR_VAL(parent_prototype), ZSTR_VAL(unresolved_class));
@@ -683,17 +791,13 @@ static void perform_delayable_implementation_check(
                zend_class_entry *ce, const zend_function *fe,
                const zend_function *proto)
 {
-       zend_string *unresolved_class;
-       inheritance_status status = zend_do_perform_implementation_check(
-               &unresolved_class, fe, proto);
-
+       inheritance_status status = zend_do_perform_implementation_check(fe, proto);
        if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
                if (EXPECTED(status == INHERITANCE_UNRESOLVED)) {
                        add_compatibility_obligation(ce, fe, proto);
                } else {
                        ZEND_ASSERT(status == INHERITANCE_ERROR);
-                       emit_incompatible_method_error(
-                               fe, proto, status, unresolved_class);
+                       emit_incompatible_method_error(fe, proto, status);
                }
        }
 }
@@ -792,10 +896,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(z
 
        if (!checked) {
                if (check_only) {
-                       zend_string *unresolved_class;
-
-                       return zend_do_perform_implementation_check(
-                               &unresolved_class, child, parent);
+                       return zend_do_perform_implementation_check(child, parent);
                }
                perform_delayable_implementation_check(ce, child, parent);
        }
@@ -845,39 +946,28 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function
 
 inheritance_status property_types_compatible(
                const zend_property_info *parent_info, const zend_property_info *child_info) {
-       zend_string *parent_name, *child_name;
-       zend_class_entry *parent_type_ce, *child_type_ce;
        if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type)
                        && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) {
                return INHERITANCE_SUCCESS;
        }
 
-       if (!ZEND_TYPE_IS_CLASS(parent_info->type) || !ZEND_TYPE_IS_CLASS(child_info->type) ||
-                       ZEND_TYPE_ALLOW_NULL(parent_info->type) != ZEND_TYPE_ALLOW_NULL(child_info->type)) {
+       if (ZEND_TYPE_IS_SET(parent_info->type) != ZEND_TYPE_IS_SET(child_info->type)) {
                return INHERITANCE_ERROR;
        }
 
-       parent_name = ZEND_TYPE_IS_CE(parent_info->type)
-               ? ZEND_TYPE_CE(parent_info->type)->name
-               : resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type));
-       child_name = ZEND_TYPE_IS_CE(child_info->type)
-               ? ZEND_TYPE_CE(child_info->type)->name
-               : resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type));
-       if (zend_string_equals_ci(parent_name, child_name)) {
+       /* Perform a covariant type check in both directions to determined invariance. */
+       inheritance_status status1 = zend_perform_covariant_type_check(
+               child_info->ce, child_info->type, parent_info->ce, parent_info->type);
+       inheritance_status status2 = zend_perform_covariant_type_check(
+               parent_info->ce, parent_info->type, child_info->ce, child_info->type);
+       if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) {
                return INHERITANCE_SUCCESS;
        }
-
-       /* Check for class aliases */
-       parent_type_ce = ZEND_TYPE_IS_CE(parent_info->type)
-               ? ZEND_TYPE_CE(parent_info->type)
-               : lookup_class(parent_info->ce, parent_name);
-       child_type_ce = ZEND_TYPE_IS_CE(child_info->type)
-               ? ZEND_TYPE_CE(child_info->type)
-               : lookup_class(child_info->ce, child_name);
-       if (!parent_type_ce || !child_type_ce) {
-               return INHERITANCE_UNRESOLVED;
+       if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) {
+               return INHERITANCE_ERROR;
        }
-       return parent_type_ce == child_type_ce ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
+       ZEND_ASSERT(status1 == INHERITANCE_UNRESOLVED && status2 == INHERITANCE_UNRESOLVED);
+       return INHERITANCE_UNRESOLVED;
 }
 
 static void emit_incompatible_property_error(
@@ -1958,9 +2048,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
 
                        Z_TRY_ADDREF_P(prop_value);
                        doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL;
-                       if (ZEND_TYPE_IS_NAME(property_info->type)) {
-                               zend_string_addref(ZEND_TYPE_NAME(property_info->type));
-                       }
+                       zend_type_copy_ctor(&property_info->type, /* persistent */ 0);
                        zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type);
                        zend_string_release_ex(prop_name, 0);
                } ZEND_HASH_FOREACH_END();
@@ -2225,16 +2313,14 @@ static int check_variance_obligation(zval *zv) {
                        return ZEND_HASH_APPLY_KEEP;
                }
        } else if (obligation->type == OBLIGATION_COMPATIBILITY) {
-               zend_string *unresolved_class;
                inheritance_status status = zend_do_perform_implementation_check(
-                       &unresolved_class, obligation->child_fn, obligation->parent_fn);
+                       obligation->child_fn, obligation->parent_fn);
                if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
                        if (EXPECTED(status == INHERITANCE_UNRESOLVED)) {
                                return ZEND_HASH_APPLY_KEEP;
                        }
                        ZEND_ASSERT(status == INHERITANCE_ERROR);
-                       emit_incompatible_method_error(
-                               obligation->child_fn, obligation->parent_fn, status, unresolved_class);
+                       emit_incompatible_method_error(obligation->child_fn, obligation->parent_fn, status);
                }
                /* Either the compatibility check was successful or only threw a warning. */
        } else {
@@ -2297,16 +2383,14 @@ static void report_variance_errors(zend_class_entry *ce) {
        ZEND_ASSERT(obligations != NULL);
 
        ZEND_HASH_FOREACH_PTR(obligations, obligation) {
-               inheritance_status status;
-               zend_string *unresolved_class;
-
                if (obligation->type == OBLIGATION_COMPATIBILITY) {
-                       /* Just used to fetch the unresolved_class in this case. */
-                       status = zend_do_perform_implementation_check(
-                               &unresolved_class, obligation->child_fn, obligation->parent_fn);
+                       /* Just used to populate the delayed_autoloads table,
+                        * which will be used when printing the "unresolved" error. */
+                       inheritance_status status = zend_do_perform_implementation_check(
+                               obligation->child_fn, obligation->parent_fn);
                        ZEND_ASSERT(status == INHERITANCE_UNRESOLVED);
                        emit_incompatible_method_error(
-                               obligation->child_fn, obligation->parent_fn, status, unresolved_class);
+                               obligation->child_fn, obligation->parent_fn, status);
                } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
                        emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
                } else {
index 0f35b69adf93fe06289c81eac600b5a3369c6c16..57e88e630dee6e020a90609aabf7e8c25e591dba 100644 (file)
@@ -255,7 +255,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %type <ast> array_pair non_empty_array_pair_list array_pair_list possible_array_pair
 %type <ast> isset_variable type return_type type_expr
 %type <ast> identifier
-%type <ast> inline_function
+%type <ast> inline_function union_type
 
 %type <num> returns_ref function fn is_reference is_variadic variable_modifiers
 %type <num> method_modifiers non_empty_member_modifiers member_modifier
@@ -660,6 +660,7 @@ optional_type:
 type_expr:
                type            { $$ = $1; }
        |       '?' type        { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
+       |       union_type      { $$ = $1; }
 ;
 
 type:
@@ -668,6 +669,11 @@ type:
        |       name            { $$ = $1; }
 ;
 
+union_type:
+               type '|' type       { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); }
+       |       union_type '|' type { $$ = zend_ast_list_add($1, $3); }
+;
+
 return_type:
                /* empty */     { $$ = NULL; }
        |       ':' type_expr   { $$ = $2; }
index 626eacd35d199966ebeb6fc55e8bd1f4c65944f6..e197e9bf16ab5b8a277d4d2b62da05131a59c0f3 100644 (file)
@@ -102,6 +102,22 @@ ZEND_API void destroy_zend_function(zend_function *function)
        zend_function_dtor(&tmp);
 }
 
+ZEND_API void zend_type_release(zend_type type, zend_bool persistent) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+                               zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry));
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+               if (!ZEND_TYPE_USES_ARENA(type)) {
+                       pefree(ZEND_TYPE_LIST(type), persistent);
+               }
+       } else if (ZEND_TYPE_HAS_NAME(type)) {
+               zend_string_release(ZEND_TYPE_NAME(type));
+       }
+}
+
 void zend_free_internal_arg_info(zend_internal_function *function) {
        if ((function->fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS)) &&
                function->arg_info) {
@@ -114,9 +130,7 @@ void zend_free_internal_arg_info(zend_internal_function *function) {
                        num_args++;
                }
                for (i = 0 ; i < num_args; i++) {
-                       if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
-                               zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 1);
-                       }
+                       zend_type_release(arg_info[i].type, /* persistent */ 1);
                }
                free(arg_info);
        }
@@ -303,9 +317,7 @@ ZEND_API void destroy_zend_class(zval *zv)
                                        if (prop_info->doc_comment) {
                                                zend_string_release_ex(prop_info->doc_comment, 0);
                                        }
-                                       if (ZEND_TYPE_IS_NAME(prop_info->type)) {
-                                               zend_string_release(ZEND_TYPE_NAME(prop_info->type));
-                                       }
+                                       zend_type_release(prop_info->type, /* persistent */ 0);
                                }
                        } ZEND_HASH_FOREACH_END();
                        zend_hash_destroy(&ce->properties_info);
@@ -496,9 +508,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
                        if (arg_info[i].name) {
                                zend_string_release_ex(arg_info[i].name, 0);
                        }
-                       if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
-                               zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 0);
-                       }
+                       zend_type_release(arg_info[i].type, /* persistent */ 0);
                }
                efree(arg_info);
        }
index a38c1cae8c5521d67242898c6c995a2a9a395a03..f2076beee10c836749dcabbd8a94b9004568003e 100644 (file)
@@ -512,6 +512,8 @@ EMPTY_SWITCH_DEFAULT_CASE()
        _(ZEND_STR_CALLABLE,               "callable") \
        _(ZEND_STR_ITERABLE,               "iterable") \
        _(ZEND_STR_VOID,                   "void") \
+       _(ZEND_STR_FALSE,                  "false") \
+       _(ZEND_STR_NULL_LOWERCASE,         "null") \
 
 
 typedef enum _zend_known_string_id {
index 9479d5aad2c03d2add15cca54e830f72dc044189..ef2c6a19bc8e57fc97250cb70a63c954dc2e4d4c 100644 (file)
@@ -25,6 +25,7 @@
 #define MAY_BE_NULL                        (1 << IS_NULL)
 #define MAY_BE_FALSE               (1 << IS_FALSE)
 #define MAY_BE_TRUE                        (1 << IS_TRUE)
+#define MAY_BE_BOOL                 (MAY_BE_FALSE|MAY_BE_TRUE)
 #define MAY_BE_LONG                        (1 << IS_LONG)
 #define MAY_BE_DOUBLE              (1 << IS_DOUBLE)
 #define MAY_BE_STRING              (1 << IS_STRING)
index d77fbc68a371891e9c8e770cc3ffa0429daf26ce..4bfe335e0a667cba2593a779e2c38e8acd9304f2 100644 (file)
@@ -105,11 +105,11 @@ typedef void (*copy_ctor_func_t)(zval *pElement);
  * zend_type - is an abstraction layer to represent information about type hint.
  * It shouldn't be used directly. Only through ZEND_TYPE_* macros.
  *
- * ZEND_TYPE_IS_SET()     - checks if type-hint exists
- * ZEND_TYPE_IS_ONLY_MASK() - checks if type-hint refer to standard type
- * ZEND_TYPE_IS_CLASS()   - checks if type-hint refer to some class
- * ZEND_TYPE_IS_CE()      - checks if type-hint refer to some class by zend_class_entry *
- * ZEND_TYPE_IS_NAME()    - checks if type-hint refer to some class by zend_string *
+ * ZEND_TYPE_IS_SET()        - checks if there is a type-hint
+ * ZEND_TYPE_HAS_ONLY_MASK() - checks if type-hint refer to standard type only
+ * ZEND_TYPE_HAS_CLASS()     - checks if type-hint contains some class
+ * ZEND_TYPE_HAS_CE()        - checks if type-hint contains some class as zend_class_entry *
+ * ZEND_TYPE_HAS_NAME()      - checks if type-hint contains some class as zend_string *
  *
  * ZEND_TYPE_NAME()       - returns referenced class name
  * ZEND_TYPE_CE()         - returns referenced class entry
@@ -130,25 +130,41 @@ typedef struct {
        /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */
 } zend_type;
 
+typedef struct {
+       size_t num_types;
+       void *types[1];
+} zend_type_list;
+
 #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 24
 #define _ZEND_TYPE_MASK ((1u << 24) - 1)
 #define _ZEND_TYPE_MAY_BE_MASK ((1u << (IS_VOID+1)) - 1)
-#define _ZEND_TYPE_CE_BIT (1u << 22)
+/* Only one of these bits may be set. */
 #define _ZEND_TYPE_NAME_BIT (1u << 23)
+#define _ZEND_TYPE_CE_BIT   (1u << 22)
+#define _ZEND_TYPE_LIST_BIT (1u << 21)
+#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT)
+/* Whether the type list is arena allocated */
+#define _ZEND_TYPE_ARENA_BIT (1u << 20)
 /* Must have same value as MAY_BE_NULL */
 #define _ZEND_TYPE_NULLABLE_BIT 0x2
 
 #define ZEND_TYPE_IS_SET(t) \
        (((t).type_mask & _ZEND_TYPE_MASK) != 0)
 
-#define ZEND_TYPE_IS_CLASS(t) \
-       (((t.type_mask) & (_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_CE_BIT)) != 0)
+#define ZEND_TYPE_HAS_CLASS(t) \
+       ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0)
+
+#define ZEND_TYPE_HAS_CE(t) \
+       ((((t).type_mask) & _ZEND_TYPE_CE_BIT) != 0)
 
-#define ZEND_TYPE_IS_CE(t) \
-       (((t.type_mask) & _ZEND_TYPE_CE_BIT) != 0)
+#define ZEND_TYPE_HAS_NAME(t) \
+       ((((t).type_mask) & _ZEND_TYPE_NAME_BIT) != 0)
 
-#define ZEND_TYPE_IS_NAME(t) \
-       (((t.type_mask) & _ZEND_TYPE_NAME_BIT) != 0)
+#define ZEND_TYPE_HAS_LIST(t) \
+       ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
+
+#define ZEND_TYPE_USES_ARENA(t) \
+       ((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0)
 
 #define ZEND_TYPE_IS_ONLY_MASK(t) \
        (ZEND_TYPE_IS_SET(t) && (t).ptr == NULL)
@@ -162,9 +178,63 @@ typedef struct {
 #define ZEND_TYPE_CE(t) \
        ((zend_class_entry *) (t).ptr)
 
+#define ZEND_TYPE_LIST(t) \
+       ((zend_type_list *) (t).ptr)
+
+/* Type lists use the low bit to distinguish NAME and CE entries,
+ * both of which may exist in the same list. */
+#define ZEND_TYPE_LIST_IS_CE(entry) \
+       (((uintptr_t) (entry)) & 1)
+
+#define ZEND_TYPE_LIST_IS_NAME(entry) \
+       !ZEND_TYPE_LIST_IS_CE(entry)
+
+#define ZEND_TYPE_LIST_GET_NAME(entry) \
+       ((zend_string *) (entry))
+
+#define ZEND_TYPE_LIST_GET_CE(entry) \
+       ((zend_class_entry *) ((uintptr_t) (entry) & ~1))
+
+#define ZEND_TYPE_LIST_ENCODE_NAME(name) \
+       ((void *) (name))
+
+#define ZEND_TYPE_LIST_ENCODE_CE(ce) \
+       ((void *) (((uintptr_t) ce) | 1))
+
+#define ZEND_TYPE_LIST_SIZE(num_types) \
+       (sizeof(zend_type_list) + ((num_types) - 1) * sizeof(void *))
+
+#define ZEND_TYPE_LIST_FOREACH_PTR(list, entry_ptr) do { \
+       void **_list = (list)->types; \
+       void **_end = _list + (list)->num_types; \
+       for (; _list < _end; _list++) { \
+               entry_ptr = _list;
+
+#define ZEND_TYPE_LIST_FOREACH(list, entry) do { \
+       void **_list = (list)->types; \
+       void **_end = _list + (list)->num_types; \
+       for (; _list < _end; _list++) { \
+               entry = *_list;
+
+#define ZEND_TYPE_LIST_FOREACH_END() \
+       } \
+} while (0)
+
 #define ZEND_TYPE_SET_PTR(t, _ptr) \
        ((t).ptr = (_ptr))
 
+#define ZEND_TYPE_SET_PTR_AND_KIND(t, _ptr, kind_bit) do { \
+       (t).ptr = (_ptr); \
+       (t).type_mask &= ~_ZEND_TYPE_KIND_MASK; \
+       (t).type_mask |= (kind_bit); \
+} while (0)
+
+#define ZEND_TYPE_SET_CE(t, ce) \
+       ZEND_TYPE_SET_PTR_AND_KIND(t, ce, _ZEND_TYPE_CE_BIT)
+
+#define ZEND_TYPE_SET_LIST(t, list) \
+       ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_LIST_BIT)
+
 /* FULL_MASK() includes the MAY_BE_* type mask, the CE/NAME bits, as well as extra reserved bits.
  * The PURE_MASK() only includes the MAY_BE_* type mask. */
 #define ZEND_TYPE_FULL_MASK(t) \
@@ -192,20 +262,21 @@ typedef struct {
        { NULL, (_type_mask) }
 
 #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \
-       ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \
+       ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : (1 << (code))) \
                | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags))
 
+#define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \
+       { (void *) (ptr), \
+               (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) }
+
 #define ZEND_TYPE_INIT_CE(_ce, allow_null, extra_flags) \
-       { (void *) (_ce), \
-               _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) }
+       ZEND_TYPE_INIT_PTR(_ce, _ZEND_TYPE_CE_BIT, allow_null, extra_flags)
 
 #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \
-       { (void *) (class_name), \
-               _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) }
+       ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags)
 
 #define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \
-       { (void *) (class_name), \
-               _ZEND_TYPE_NAME_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) }
+       ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags)
 
 typedef union _zend_value {
        zend_long         lval;                         /* long value */
index a36458b8995c3f02ba33398e1afe1bb8ff7c0c1d..01b3bcf9f2d72d07fe457105bac1e7f2fb2b2956 100644 (file)
@@ -4108,8 +4108,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
index be26c30765806204a548cca5fb20ef60a4ce281d..a5b1e2e75244e67c5248d0473e301b854698791a 100644 (file)
@@ -8738,8 +8738,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
@@ -18668,8 +18667,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
@@ -26095,8 +26093,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
@@ -32717,8 +32714,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
@@ -44160,8 +44156,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU
                        ZVAL_DEREF(retval_ptr);
                }
 
-               if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type)
-                       && !(ZEND_TYPE_FULL_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))
+               if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY)
                        && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr))
                        && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                        && retval_ref != retval_ptr)
index 0a99ac4140f8e069944600dc97ad685dcc122941..4aabe04c6e1203f8546a92091ca2170f9ec93ff7 100644 (file)
@@ -56,25 +56,31 @@ typedef struct _literal_info {
                info[n].flags = ((kind) | (related)); \
        } while (0)
 
-static zend_bool class_name_type_hint(const zend_op_array *op_array, uint32_t arg_num)
+static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
 {
        zend_arg_info *arg_info;
-
        if (arg_num > 0) {
-               if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
-                       if (EXPECTED(arg_num <= op_array->num_args)) {
-                               arg_info = &op_array->arg_info[arg_num-1];
-                       } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
-                               arg_info = &op_array->arg_info[op_array->num_args];
-                       } else {
-                               return 0;
-                       }
-                       return ZEND_TYPE_IS_CLASS(arg_info->type);
+               if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+                       return 0;
+               }
+               if (EXPECTED(arg_num <= op_array->num_args)) {
+                       arg_info = &op_array->arg_info[arg_num-1];
+               } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
+                       arg_info = &op_array->arg_info[op_array->num_args];
+               } else {
+                       return 0;
                }
        } else {
                arg_info = op_array->arg_info - 1;
-               return ZEND_TYPE_IS_CLASS(arg_info->type);
        }
+
+       if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+               if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
+                       return ZEND_TYPE_LIST(arg_info->type)->num_types;
+               }
+               return 1;
+       }
+
        return 0;
 }
 
@@ -505,17 +511,23 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
                                case ZEND_RECV_INIT:
                                case ZEND_RECV:
                                case ZEND_RECV_VARIADIC:
-                                       if (class_name_type_hint(op_array, opline->op1.num)) {
+                               {
+                                       size_t num_classes = type_num_classes(op_array, opline->op1.num);
+                                       if (num_classes) {
                                                opline->extended_value = cache_size;
-                                               cache_size += sizeof(void *);
+                                               cache_size += num_classes * sizeof(void *);
                                        }
                                        break;
+                               }
                                case ZEND_VERIFY_RETURN_TYPE:
-                                       if (class_name_type_hint(op_array, 0)) {
+                               {
+                                       size_t num_classes = type_num_classes(op_array, 0);
+                                       if (num_classes) {
                                                opline->op2.num = cache_size;
-                                               cache_size += sizeof(void *);
+                                               cache_size += num_classes * sizeof(void *);
                                        }
                                        break;
+                               }
                                case ZEND_ASSIGN_STATIC_PROP_OP:
                                        if (opline->op1_type == IS_CONST) {
                                                // op1 static property
index 59c562425f13b32e0ddcf619298a920f0e803d3e..8802577154269efed92f5d10d2067f46668f00fd 100644 (file)
@@ -311,7 +311,7 @@ static inline zend_bool can_elide_return_type_check(
                return 0;
        }
 
-       if (ZEND_TYPE_IS_CLASS(info->type)) {
+       if (ZEND_TYPE_HAS_CLASS(info->type)) {
                if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) {
                        return 0;
                }
index 8fc92f842c0705723092b2dc51ebac1c293fe2b7..cb31ea21f2cba812f1fb57b4ca7e5eec24f92c67 100644 (file)
@@ -1427,7 +1427,7 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
                                                tmp->max = ZEND_LONG_MAX;
                                                tmp->overflow = 0;
                                                return 1;
-                                       } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) {
+                                       } else if (mask == MAY_BE_BOOL) {
                                                tmp->underflow = 0;
                                                tmp->min = 0;
                                                tmp->max = 1;
@@ -2254,11 +2254,14 @@ uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_
 
        tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type));
        *pce = NULL;
-       if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
-               zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+       if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
                tmp |= MAY_BE_OBJECT;
-               *pce = get_class_entry(script, lcname);
-               zend_string_release_ex(lcname, 0);
+               /* 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)) {
+                       zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+                       *pce = get_class_entry(script, lcname);
+                       zend_string_release_ex(lcname, 0);
+               }
        }
        if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
                tmp |= MAY_BE_RC1 | MAY_BE_RCN;
@@ -2354,33 +2357,29 @@ static zend_property_info *zend_fetch_static_prop_info(const zend_script *script
 
 static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce)
 {
+       if (pce) {
+               *pce = NULL;
+       }
        if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
-               uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type)
-                       ? MAY_BE_OBJECT
-                       : zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
+               uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
 
-               if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
-                       type |= MAY_BE_NULL;
-               }
                if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
                        type |= MAY_BE_RC1 | MAY_BE_RCN;
                }
-               if (pce) {
-                       if (ZEND_TYPE_IS_CE(prop_info->type)) {
-                               *pce = ZEND_TYPE_CE(prop_info->type);
-                       } else if (ZEND_TYPE_IS_NAME(prop_info->type)) {
-                               zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
-                               *pce = get_class_entry(script, lcname);
-                               zend_string_release(lcname);
-                       } else {
-                               *pce = NULL;
+               if (ZEND_TYPE_HAS_CLASS(prop_info->type)) {
+                       type |= MAY_BE_OBJECT;
+                       if (pce) {
+                               if (ZEND_TYPE_HAS_CE(prop_info->type)) {
+                                       *pce = ZEND_TYPE_CE(prop_info->type);
+                               } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) {
+                                       zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
+                                       *pce = get_class_entry(script, lcname);
+                                       zend_string_release(lcname);
+                               }
                        }
                }
                return type;
        }
-       if (pce) {
-               *pce = NULL;
-       }
        return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
 }
 
index 68be526892b3b7b482127ce2ded27a0b2ad8d389..08fca1e3fa90f4adc66399b1aa60eaa67f1fbb03 100644 (file)
@@ -601,7 +601,13 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int
                                num_args++;
                        }
                        for (i = 0 ; i < num_args; i++) {
-                               if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
+                               if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) {
+                                       void **entry;
+                                       ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(arg_info[i].type), entry) {
+                                               ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(*entry));
+                                               *entry = zend_new_interned_string(ZEND_TYPE_LIST_GET_NAME(*entry));
+                                       } ZEND_TYPE_LIST_FOREACH_END();
+                               } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) {
                                        ZEND_TYPE_SET_PTR(arg_info[i].type,
                                                new_interned_string(ZEND_TYPE_NAME(arg_info[i].type)));
                                }
@@ -3539,6 +3545,32 @@ static zend_bool preload_try_resolve_constants(zend_class_entry *ce)
        return ok;
 }
 
+static zend_class_entry *preload_fetch_resolved_ce(zend_string *name, zend_class_entry *self_ce) {
+       zend_string *lcname = zend_string_tolower(name);
+       zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lcname);
+       zend_string_release(lcname);
+       if (!ce) {
+               return NULL;
+       }
+       if (ce == self_ce) {
+               /* Ignore the following requirements if this is the class referring to itself */
+               return ce;
+       }
+#ifdef ZEND_WIN32
+       /* On Windows we can't link with internal class, because of ASLR */
+       if (ce->type == ZEND_INTERNAL_CLASS) {
+               return NULL;
+       }
+#endif
+       if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
+               return NULL;
+       }
+       if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
+               return NULL;
+       }
+       return ce;
+}
+
 static zend_bool preload_try_resolve_property_types(zend_class_entry *ce)
 {
        zend_bool ok = 1;
@@ -3547,66 +3579,61 @@ static zend_bool preload_try_resolve_property_types(zend_class_entry *ce)
 
        if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
                ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
-                       zend_string *name, *lcname;
-
-                       if (!ZEND_TYPE_IS_NAME(prop->type)) {
-                               continue;
-                       }
-
-                       name = ZEND_TYPE_NAME(prop->type);
-                       lcname = zend_string_tolower(name);
-                       p = zend_hash_find_ptr(EG(class_table), lcname);
-                       zend_string_release(lcname);
-                       if (!p) {
-                               ok = 0;
-                               continue;
-                       }
-                       if (p != ce) {
-#ifdef ZEND_WIN32
-                               /* On Windows we can't link with internal class, because of ASLR */
-                               if (p->type == ZEND_INTERNAL_CLASS) {
-                                       ok = 0;
-                                       continue;
-                               }
-#endif
-                               if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
-                                       ok = 0;
-                                       continue;
-                               }
-                               if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
+                       if (ZEND_TYPE_HAS_LIST(prop->type)) {
+                               void **entry;
+                               ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) {
+                                       if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+                                               p = preload_fetch_resolved_ce(ZEND_TYPE_LIST_GET_NAME(*entry), ce);
+                                               if (!p) {
+                                                       ok = 0;
+                                                       continue;
+                                               }
+                                               *entry = ZEND_TYPE_LIST_ENCODE_CE(p);
+                                       }
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else if (ZEND_TYPE_HAS_NAME(prop->type)) {
+                               p = preload_fetch_resolved_ce(ZEND_TYPE_NAME(prop->type), ce);
+                               if (!p) {
                                        ok = 0;
                                        continue;
                                }
+                               ZEND_TYPE_SET_CE(prop->type, p);
                        }
-
-                       zend_string_release(name);
-                       prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type), 0);
                } ZEND_HASH_FOREACH_END();
        }
 
        return ok;
 }
 
-static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) {
-       zend_string *name, *lcname;
-       zend_bool known;
-       if (!ZEND_TYPE_IS_NAME(type)) {
-               return 1;
-       }
-
-       name = ZEND_TYPE_NAME(type);
+static zend_bool preload_is_class_type_known(zend_class_entry *ce, zend_string *name) {
        if (zend_string_equals_literal_ci(name, "self") ||
                zend_string_equals_literal_ci(name, "parent") ||
                zend_string_equals_ci(name, ce->name)) {
                return 1;
        }
 
-       lcname = zend_string_tolower(name);
-       known = zend_hash_exists(EG(class_table), lcname);
+       zend_string *lcname = zend_string_tolower(name);
+       zend_bool known = zend_hash_exists(EG(class_table), lcname);
        zend_string_release(lcname);
        return known;
 }
 
+static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(entry)
+                                       && !preload_is_class_type_known(ce, ZEND_TYPE_LIST_GET_NAME(entry))) {
+                               return 0;
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       }
+       if (ZEND_TYPE_HAS_NAME(type)) {
+               return preload_is_class_type_known(ce, ZEND_TYPE_NAME(type));
+       }
+       return 1;
+}
+
 static zend_bool preload_is_method_maybe_override(zend_class_entry *ce, zend_string *lcname) {
        zend_class_entry *p;
        if (ce->trait_aliases || ce->trait_precedences) {
index 70708729b49eda07f8074ad01ab1f9554d820bfe..df6c4ab1bab0119aeab34e9b0f3bec7c5ba02674 100644 (file)
@@ -424,7 +424,6 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_zval_copy_deref_helper)
        REGISTER_HELPER(zend_jit_new_ref_helper);
        REGISTER_HELPER(zend_jit_fetch_global_helper);
-       REGISTER_HELPER(zend_jit_verify_arg_object);
        REGISTER_HELPER(zend_jit_verify_arg_slow);
        REGISTER_HELPER(zend_jit_fetch_obj_r_slow);
        REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic);
index 658350794ed110fce0bb21ad8a0979d899e6de97..0efd07c3dce6cdae8e199325b79d63fb1d661006 100644 (file)
@@ -1136,64 +1136,59 @@ static zval* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_execute_data *execu
        return value;
 }
 
-static void ZEND_FASTCALL zend_jit_verify_arg_object(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot)
-{
-       zend_class_entry *ce;
-       if (EXPECTED(*cache_slot)) {
-               ce = (zend_class_entry *)*cache_slot;
-       } else {
-               ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
-               if (UNEXPECTED(!ce)) {
-                       zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
-                       return;
-               }
-               *cache_slot = (void *)ce;
-       }
-       if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(arg), ce))) {
-               zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
-       }
-}
-
 static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot)
 {
        uint32_t type_mask;
 
-       if (UNEXPECTED(ZEND_TYPE_IS_CLASS(arg_info->type))) {
+       if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) {
                zend_class_entry *ce;
-               if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
-                       /* Null passed to nullable type */
-                       return;
-               }
-
-               /* This is always an error - we fetch the class name for the error message here */
-               if (EXPECTED(*cache_slot)) {
-                       ce = (zend_class_entry *) *cache_slot;
+               if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
+                       void *entry;
+                       ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), entry) {
+                               if (*cache_slot) {
+                                       ce = *cache_slot;
+                               } else {
+                                       ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry),
+                                               (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+                                       if (!ce) {
+                                               cache_slot++;
+                                               continue;
+                                       }
+                                       *cache_slot = ce;
+                               }
+                               if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+                                       return;
+                               }
+                               cache_slot++;
+                       } ZEND_TYPE_LIST_FOREACH_END();
                } else {
-                       ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
-                       if (ce) {
-                               *cache_slot = (void *)ce;
+                       if (EXPECTED(*cache_slot)) {
+                               ce = (zend_class_entry *) *cache_slot;
+                       } else {
+                               ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+                               if (UNEXPECTED(!ce)) {
+                                       goto builtin_types;
+                               }
+                               *cache_slot = (void *) ce;
+                       }
+                       if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+                               return;
                        }
                }
-               goto err;
        }
 
+builtin_types:
        type_mask = ZEND_TYPE_FULL_MASK(arg_info->type);
-       if (type_mask & MAY_BE_CALLABLE) {
-               if (zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) == 0) {
-                       goto err;
-               }
-       } else if (type_mask & MAY_BE_ITERABLE) {
-               if (zend_is_iterable(arg) == 0) {
-                       goto err;
-               }
-       } else  {
-               if (Z_ISUNDEF_P(arg) ||
-                   zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) {
-                       goto err;
-               }
+       if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) {
+               return;
        }
-       return;
-err:
+       if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
+               return;
+       }
+       if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) {
+               return;
+       }
+
        zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
 }
 
index 0681a450641cc4d713da1ef0c4aade43cfe7d2bd..6f1913a1a1e0db3605b44ee178cfef4eee88751e 100644 (file)
@@ -9036,54 +9036,23 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_
                                        |       GET_Z_PTR r0, r0
                                        |       add r0, offsetof(zend_reference, val)
                                }
-                               if (!ZEND_TYPE_IS_CLASS(type)) {
-                                       uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
-                                       if (is_power_of_two(type_mask)) {
-                                               uint32_t type_code = concrete_type(type_mask);
-                                               |       cmp byte [r0 + 8], type_code
-                                               |       jne >8
-                                       } else {
-                                               |       mov edx, 1
-                                               |       mov cl, byte [r0 + 8]
-                                               |       shl edx, cl
-                                               |       test edx, type_mask
-                                               |       je >8
-                                       }
+
+                               uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
+                               if (is_power_of_two(type_mask)) {
+                                       uint32_t type_code = concrete_type(type_mask);
+                                       |       cmp byte [r0 + 8], type_code
+                                       |       jne >8
                                } else {
-                                       |       SAVE_VALID_OPLINE opline
-                                       |       cmp byte [r0 + 8], IS_OBJECT
-                                       |       jne >9
-                                       |       mov FCARG1a, r0
-                                       |       mov r0, EX->run_time_cache
-                                       |       add r0, opline->extended_value
-                                       |       LOAD_ADDR FCARG2a, (ptrdiff_t)op_array
-                                       |.if X64WIN
-                                               |       mov CARG3, arg_num
-                                               |       LOAD_ADDR CARG4, (ptrdiff_t)arg_info
-                                               |       mov aword A5, r0
-                                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                                       |.elif X64
-                                               |       mov CARG3, arg_num
-                                               |       LOAD_ADDR CARG4, (ptrdiff_t)arg_info
-                                               |       mov CARG5, r0
-                                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                                       |.else
-                                               |       sub r4, 4
-                                               |       push r0
-                                               |       push (ptrdiff_t)arg_info
-                                               |       push arg_num
-                                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                                               |       add r4, 4
-                                       |.endif
-                                       if (!zend_jit_check_exception(Dst)) {
-                                               return 0;
-                                       }
+                                       |       mov edx, 1
+                                       |       mov cl, byte [r0 + 8]
+                                       |       shl edx, cl
+                                       |       test edx, type_mask
+                                       |       je >8
                                }
 
                                |.cold_code
                                |8:
                                |       SAVE_VALID_OPLINE opline
-                               |9:
                                |       mov FCARG1a, r0
                                |       mov r0, EX->run_time_cache
                                |       add r0, opline->extended_value
@@ -9185,47 +9154,18 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen
                        has_slow += 2;
                        |       LOAD_ZVAL_ADDR r0, res_addr
                        |       ZVAL_DEREF r0, MAY_BE_REF
-                       if (!ZEND_TYPE_IS_CLASS(arg_info->type)) {
-                               uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
-                               if (is_power_of_two(type_mask)) {
-                                       uint32_t type_code = concrete_type(type_mask);
-                                       |       cmp byte [r0 + 8], type_code
-                                       |       jne >8
-                               } else {
-                                       |       mov edx, 1
-                                       |       mov cl, byte [r0 + 8]
-                                       |       shl edx, cl
-                                       |       test edx, type_mask
-                                       |       je >8
-                               }
+
+                       uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
+                       if (is_power_of_two(type_mask)) {
+                               uint32_t type_code = concrete_type(type_mask);
+                               |       cmp byte [r0 + 8], type_code
+                               |       jne >8
                        } else {
-                       |       cmp byte [r0 + 8], IS_OBJECT
-                       |       jne >8
-                       |       mov FCARG1a, r0
-                       |       mov r0, EX->run_time_cache
-                       |       lea r0, [r0 + opline->extended_value]
-                       |       LOAD_ADDR FCARG2a, (ptrdiff_t)op_array
-                       |.if X64WIN
-                               |       mov CARG3, arg_num
-                               |       LOAD_ADDR CARG4, (ptrdiff_t)arg_info
-                               |       mov aword A5, r0
-                               |       SAVE_VALID_OPLINE opline
-                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                       |.elif X64
-                               |       mov CARG3, arg_num
-                               |       LOAD_ADDR CARG4, (ptrdiff_t)arg_info
-                               |       mov CARG5, r0
-                               |       SAVE_VALID_OPLINE opline
-                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                       |.else
-                               |       sub r4, 4
-                               |       push r0
-                               |       push (ptrdiff_t)arg_info
-                               |       push arg_num
-                               |       SAVE_VALID_OPLINE opline
-                               |       EXT_CALL zend_jit_verify_arg_object, r0
-                               |       add r4, 4
-                       |.endif
+                               |       mov edx, 1
+                               |       mov cl, byte [r0 + 8]
+                               |       shl edx, cl
+                               |       test edx, type_mask
+                               |       je >8
                        }
                } while (0);
        }
index e74b7bb6689b782b3bbf108f6737a8ffefc6751a..9eb6745478f654b84ee81f2c99b98a07e47a351a 100644 (file)
@@ -233,7 +233,24 @@ static void zend_hash_clone_prop_info(HashTable *ht)
                                prop_info->ce = ARENA_REALLOC(prop_info->ce);
                        }
 
-                       if (ZEND_TYPE_IS_CE(prop_info->type)) {
+                       if (ZEND_TYPE_HAS_LIST(prop_info->type)) {
+                               zend_type_list *list = ZEND_TYPE_LIST(prop_info->type);
+                               if (IN_ARENA(list)) {
+                                       list = ARENA_REALLOC(list);
+                                       ZEND_TYPE_SET_PTR(prop_info->type, list);
+
+                                       void **entry;
+                                       ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) {
+                                               if (ZEND_TYPE_LIST_IS_CE(*entry)) {
+                                                       zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+                                                       if (IN_ARENA(ce)) {
+                                                               ce = ARENA_REALLOC(ce);
+                                                               *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+                                                       }
+                                               }
+                                       } ZEND_TYPE_LIST_FOREACH_END();
+                               }
+                       } else if (ZEND_TYPE_HAS_CE(prop_info->type)) {
                                zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type);
                                if (IN_ARENA(ce)) {
                                        ce = ARENA_REALLOC(ce);
index 6e4d52cee6ffce8c4c2e613edc79323eecc426a1..049b0e9b48780b121920178dfef118321f60fe3a 100644 (file)
@@ -371,6 +371,38 @@ static void zend_file_cache_serialize_zval(zval                     *zv,
        }
 }
 
+static void zend_file_cache_serialize_type(
+               zend_type *type, zend_persistent_script *script, zend_file_cache_metainfo *info, void *buf)
+{
+       if (ZEND_TYPE_HAS_LIST(*type)) {
+               zend_type_list *list = ZEND_TYPE_LIST(*type);
+               SERIALIZE_PTR(list);
+               ZEND_TYPE_SET_PTR(*type, list);
+               UNSERIALIZE_PTR(list);
+
+               void **entry;
+               ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+                               zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                               SERIALIZE_STR(name);
+                               *entry = ZEND_TYPE_LIST_ENCODE_NAME(name);
+                       } else {
+                               zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+                               SERIALIZE_PTR(ce);
+                               *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(*type)) {
+               zend_string *type_name = ZEND_TYPE_NAME(*type);
+               SERIALIZE_STR(type_name);
+               ZEND_TYPE_SET_PTR(*type, type_name);
+       } else if (ZEND_TYPE_HAS_CE(*type)) {
+               zend_class_entry *ce = ZEND_TYPE_CE(*type);
+               SERIALIZE_PTR(ce);
+               ZEND_TYPE_SET_PTR(*type, ce);
+       }
+}
+
 static void zend_file_cache_serialize_op_array(zend_op_array            *op_array,
                                                zend_persistent_script   *script,
                                                zend_file_cache_metainfo *info,
@@ -498,11 +530,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array            *op_arra
                                if (!IS_SERIALIZED(p->name)) {
                                        SERIALIZE_STR(p->name);
                                }
-                               if (ZEND_TYPE_IS_CLASS(p->type)) {
-                                       zend_string *type_name = ZEND_TYPE_NAME(p->type);
-                                       SERIALIZE_STR(type_name);
-                                       ZEND_TYPE_SET_PTR(p->type, type_name);
-                               }
+                               zend_file_cache_serialize_type(&p->type, script, info, buf);
                                p++;
                        }
                }
@@ -571,15 +599,7 @@ static void zend_file_cache_serialize_prop_info(zval                     *zv,
                        if (prop->doc_comment) {
                                SERIALIZE_STR(prop->doc_comment);
                        }
-               }
-               if (ZEND_TYPE_IS_NAME(prop->type)) {
-                       zend_string *name = ZEND_TYPE_NAME(prop->type);
-                       SERIALIZE_STR(name);
-                       ZEND_TYPE_SET_PTR(prop->type, name);
-               } else if (ZEND_TYPE_IS_CE(prop->type)) {
-                       zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
-                       SERIALIZE_PTR(ce);
-                       ZEND_TYPE_SET_PTR(prop->type, ce);
+                       zend_file_cache_serialize_type(&prop->type, script, info, buf);
                }
        }
 }
@@ -1080,6 +1100,37 @@ static void zend_file_cache_unserialize_zval(zval                    *zv,
        }
 }
 
+static void zend_file_cache_unserialize_type(
+               zend_type *type, zend_persistent_script *script, void *buf)
+{
+       if (ZEND_TYPE_HAS_LIST(*type)) {
+               zend_type_list *list = ZEND_TYPE_LIST(*type);
+               UNSERIALIZE_PTR(list);
+               ZEND_TYPE_SET_PTR(*type, list);
+
+               void **entry;
+               ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+                               zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                               UNSERIALIZE_STR(name);
+                               *entry = ZEND_TYPE_LIST_ENCODE_NAME(name);
+                       } else {
+                               zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+                               UNSERIALIZE_PTR(ce);
+                               *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(*type)) {
+               zend_string *type_name = ZEND_TYPE_NAME(*type);
+               UNSERIALIZE_STR(type_name);
+               ZEND_TYPE_SET_PTR(*type, type_name);
+       } else if (ZEND_TYPE_HAS_CE(*type)) {
+               zend_class_entry *ce = ZEND_TYPE_CE(*type);
+               UNSERIALIZE_PTR(ce);
+               ZEND_TYPE_SET_PTR(*type, ce);
+       }
+}
+
 static void zend_file_cache_unserialize_op_array(zend_op_array           *op_array,
                                                  zend_persistent_script  *script,
                                                  void                    *buf)
@@ -1195,11 +1246,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array           *op_arr
                                if (!IS_UNSERIALIZED(p->name)) {
                                        UNSERIALIZE_STR(p->name);
                                }
-                               if (ZEND_TYPE_IS_CLASS(p->type)) {
-                                       zend_string *type_name = ZEND_TYPE_NAME(p->type);
-                                       UNSERIALIZE_STR(type_name);
-                                       ZEND_TYPE_SET_PTR(p->type, type_name);
-                               }
+                               zend_file_cache_unserialize_type(&p->type, script, buf);
                                p++;
                        }
                }
@@ -1268,15 +1315,7 @@ static void zend_file_cache_unserialize_prop_info(zval                    *zv,
                        if (prop->doc_comment) {
                                UNSERIALIZE_STR(prop->doc_comment);
                        }
-               }
-               if (ZEND_TYPE_IS_NAME(prop->type)) {
-                       zend_string *name = ZEND_TYPE_NAME(prop->type);
-                       UNSERIALIZE_STR(name);
-                       ZEND_TYPE_SET_PTR(prop->type, name);
-               } else if (ZEND_TYPE_IS_CE(prop->type)) {
-                       zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
-                       UNSERIALIZE_PTR(ce);
-                       ZEND_TYPE_SET_PTR(prop->type, ce);
+                       zend_file_cache_unserialize_type(&prop->type, script, buf);
                }
        }
 }
index 56c64dc87d1a8f34ef03d3351c82bc233bf32deb..ec5b9d417a1a0d7e6a9430438c366f9501e8351e 100644 (file)
@@ -258,6 +258,35 @@ static void zend_persist_zval(zval *z)
        }
 }
 
+static void zend_persist_type(zend_type *type) {
+       if (ZEND_TYPE_HAS_LIST(*type)) {
+               void **entry;
+               zend_type_list *list = ZEND_TYPE_LIST(*type);
+               if (ZEND_TYPE_USES_ARENA(*type)) {
+                       if (!ZCG(is_immutable_class)) {
+                               list = zend_shared_memdup_arena_put(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+                       } else {
+                               /* Moved from arena to SHM because type list was fully resolved. */
+                               list = zend_shared_memdup_put(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+                               ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT;
+                       }
+               } else {
+                       list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+               }
+               ZEND_TYPE_SET_PTR(*type, list);
+
+               ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+                       zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                       zend_accel_store_interned_string(type_name);
+                       *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name);
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(*type)) {
+               zend_string *type_name = ZEND_TYPE_NAME(*type);
+               zend_accel_store_interned_string(type_name);
+               ZEND_TYPE_SET_PTR(*type, type_name);
+       }
+}
+
 static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script)
 {
        zend_op *persist_ptr;
@@ -499,11 +528,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
                        if (arg_info[i].name) {
                                zend_accel_store_interned_string(arg_info[i].name);
                        }
-                       if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
-                               zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type);
-                               zend_accel_store_interned_string(type_name);
-                               ZEND_TYPE_SET_PTR(arg_info[i].type, type_name);
-                       }
+                       zend_persist_type(&arg_info[i].type);
                }
                if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
                        arg_info++;
@@ -658,12 +683,7 @@ static void zend_persist_property_info(zval *zv)
                        prop->doc_comment = NULL;
                }
        }
-
-       if (ZEND_TYPE_IS_NAME(prop->type)) {
-               zend_string *class_name = ZEND_TYPE_NAME(prop->type);
-               zend_accel_store_interned_string(class_name);
-               ZEND_TYPE_SET_PTR(prop->type, class_name);
-       }
+       zend_persist_type(&prop->type);
 }
 
 static void zend_persist_class_constant(zval *zv)
@@ -937,7 +957,20 @@ static void zend_update_parent_ce(zend_class_entry *ce)
        if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
                zend_property_info *prop;
                ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
-                       if (ZEND_TYPE_IS_CE(prop->type)) {
+                       if (ZEND_TYPE_HAS_LIST(prop->type)) {
+                               void **entry;
+                               ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) {
+                                       if (ZEND_TYPE_LIST_IS_CE(*entry)) {
+                                               zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+                                               if (ce->type == ZEND_USER_CLASS) {
+                                                       ce = zend_shared_alloc_get_xlat_entry(ce);
+                                                       if (ce) {
+                                                               *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+                                                       }
+                                               }
+                                       }
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else if (ZEND_TYPE_HAS_CE(prop->type)) {
                                zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
                                if (ce->type == ZEND_USER_CLASS) {
                                        ce = zend_shared_alloc_get_xlat_entry(ce);
index 62a3deaea0053f28a1bb960d5ce208238ab12bc2..c4aa4adf5eacacbd5a07623bc5cdc8044ac64606 100644 (file)
@@ -148,6 +148,27 @@ static void zend_persist_zval_calc(zval *z)
        }
 }
 
+static void zend_persist_type_calc(zend_type *type)
+{
+       if (ZEND_TYPE_HAS_LIST(*type)) {
+               void **entry;
+               if (ZEND_TYPE_USES_ARENA(*type) && !ZCG(is_immutable_class)) {
+                       ADD_ARENA_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types));
+               } else {
+                       ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types));
+               }
+               ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(*type), entry) {
+                       zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry);
+                       ADD_INTERNED_STRING(type_name);
+                       *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name);
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(*type)) {
+               zend_string *type_name = ZEND_TYPE_NAME(*type);
+               ADD_INTERNED_STRING(type_name);
+               ZEND_TYPE_SET_PTR(*type, type_name);
+       }
+}
+
 static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
 {
        if (op_array->scope && zend_shared_alloc_get_xlat_entry(op_array->opcodes)) {
@@ -222,11 +243,7 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
                        if (arg_info[i].name) {
                                ADD_INTERNED_STRING(arg_info[i].name);
                        }
-                       if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
-                               zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type);
-                               ADD_INTERNED_STRING(type_name);
-                               ZEND_TYPE_SET_PTR(arg_info[i].type, type_name);
-                       }
+                       zend_persist_type_calc(&arg_info[i].type);
                }
        }
 
@@ -302,11 +319,7 @@ static void zend_persist_property_info_calc(zval *zv)
                zend_shared_alloc_register_xlat_entry(prop, prop);
                ADD_SIZE_EX(sizeof(zend_property_info));
                ADD_INTERNED_STRING(prop->name);
-               if (ZEND_TYPE_IS_NAME(prop->type)) {
-                       zend_string *class_name = ZEND_TYPE_NAME(prop->type);
-                       ADD_INTERNED_STRING(class_name);
-                       ZEND_TYPE_SET_PTR(prop->type, class_name);
-               }
+               zend_persist_type_calc(&prop->type);
                if (ZCG(accel_directives).save_comments && prop->doc_comment) {
                        ADD_STRING(prop->doc_comment);
                }
@@ -336,7 +349,14 @@ static void check_property_type_resolution(zend_class_entry *ce) {
 
        if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
                ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
-                       if (ZEND_TYPE_IS_NAME(prop->type)) {
+                       if (ZEND_TYPE_HAS_LIST(prop->type)) {
+                               void *entry;
+                               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(prop->type), entry) {
+                                       if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+                                               return;
+                                       }
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else if (ZEND_TYPE_HAS_NAME(prop->type)) {
                                return;
                        }
                } ZEND_HASH_FOREACH_END();
index 522b9d211d32a4cae2ceeed24dd1602767ce8437..4b3851e9e0361bd9e6e65de32ff923ca709805bf 100644 (file)
@@ -75,6 +75,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr;
 PHPAPI zend_class_entry *reflection_parameter_ptr;
 PHPAPI zend_class_entry *reflection_type_ptr;
 PHPAPI zend_class_entry *reflection_named_type_ptr;
+PHPAPI zend_class_entry *reflection_union_type_ptr;
 PHPAPI zend_class_entry *reflection_class_ptr;
 PHPAPI zend_class_entry *reflection_object_ptr;
 PHPAPI zend_class_entry *reflection_method_ptr;
@@ -127,6 +128,8 @@ typedef struct _parameter_reference {
 /* Struct for type hints */
 typedef struct _type_reference {
        zend_type type;
+       /* Whether to use backwards compatible null representation */
+       zend_bool legacy_behavior;
 } type_reference;
 
 typedef enum {
@@ -228,7 +231,14 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */
                case REF_TYPE_TYPE:
                {
                        type_reference *type_ref = intern->ptr;
-                       if (ZEND_TYPE_IS_NAME(type_ref->type)) {
+                       if (ZEND_TYPE_HAS_LIST(type_ref->type)) {
+                               void *entry;
+                               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type_ref->type), entry) {
+                                       if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+                                               zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry));
+                                       }
+                               } ZEND_TYPE_LIST_FOREACH_END();
+                       } else if (ZEND_TYPE_HAS_NAME(type_ref->type)) {
                                zend_string_release(ZEND_TYPE_NAME(type_ref->type));
                        }
                        efree(type_ref);
@@ -1130,22 +1140,50 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje
 }
 /* }}} */
 
+/* For backwards compatibility reasons, we need to return T|null style unions
+ * as a ReflectionNamedType. Here we determine what counts as a union type and
+ * what doesn't. */
+static zend_bool is_union_type(zend_type type) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               return 1;
+       }
+       uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
+       if (ZEND_TYPE_HAS_CLASS(type)) {
+               return type_mask_without_null != 0;
+       }
+       if (type_mask_without_null == MAY_BE_BOOL) {
+               return 0;
+       }
+       /* Check that only one bit is set. */
+       return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
+}
+
 /* {{{ reflection_type_factory */
-static void reflection_type_factory(zend_type type, zval *object)
+static void reflection_type_factory(zend_type type, zval *object, zend_bool legacy_behavior)
 {
        reflection_object *intern;
        type_reference *reference;
+       zend_bool is_union = is_union_type(type);
 
-       reflection_instantiate(reflection_named_type_ptr, object);
+       reflection_instantiate(
+               is_union ? reflection_union_type_ptr : reflection_named_type_ptr, object);
        intern = Z_REFLECTION_P(object);
        reference = (type_reference*) emalloc(sizeof(type_reference));
        reference->type = type;
+       reference->legacy_behavior = legacy_behavior && !is_union;
        intern->ptr = reference;
        intern->ref_type = REF_TYPE_TYPE;
 
        /* Property types may be resolved during the lifetime of the ReflectionType,
         * so we need to make sure that the strings we reference are not released. */
-       if (ZEND_TYPE_IS_NAME(type)) {
+       if (ZEND_TYPE_HAS_LIST(type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+                               zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry));
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(type)) {
                zend_string_addref(ZEND_TYPE_NAME(type));
        }
 }
@@ -2482,7 +2520,8 @@ ZEND_METHOD(reflection_parameter, getClass)
        }
        GET_REFLECTION_OBJECT_PTR(param);
 
-       if (ZEND_TYPE_IS_CLASS(param->arg_info->type)) {
+       // TODO: This is going to return null for union types, which is rather odd.
+       if (ZEND_TYPE_HAS_NAME(param->arg_info->type)) {
                /* Class name is stored as a string, we might also get "self" or "parent"
                 * - For "self", simply use the function scope. If scope is NULL then
                 *   the function is global and thus self does not make any sense
@@ -2562,7 +2601,7 @@ ZEND_METHOD(reflection_parameter, getType)
        if (!ZEND_TYPE_IS_SET(param->arg_info->type)) {
                RETURN_NULL();
        }
-       reflection_type_factory(param->arg_info->type, return_value);
+       reflection_type_factory(param->arg_info->type, return_value, 1);
 }
 /* }}} */
 
@@ -2862,7 +2901,10 @@ ZEND_METHOD(reflection_named_type, getName)
        }
        GET_REFLECTION_OBJECT_PTR(param);
 
-       RETURN_STR(zend_type_to_string_without_null(param->type));
+       if (param->legacy_behavior) {
+               RETURN_STR(zend_type_to_string_without_null(param->type));
+       }
+       RETURN_STR(zend_type_to_string(param->type));
 }
 /* }}} */
 
@@ -2882,6 +2924,83 @@ ZEND_METHOD(reflection_named_type, isBuiltin)
 }
 /* }}} */
 
+static void append_type(zval *return_value, zend_type type) {
+       zval reflection_type;
+       reflection_type_factory(type, &reflection_type, 0);
+       zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type);
+}
+
+static void append_type_mask(zval *return_value, uint32_t type_mask) {
+       append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask));
+}
+
+/* {{{ proto public string ReflectionUnionType::getTypes()
+   Returns the types that are part of this union type */
+ZEND_METHOD(reflection_union_type, getTypes)
+{
+       reflection_object *intern;
+       type_reference *param;
+       uint32_t type_mask;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+       GET_REFLECTION_OBJECT_PTR(param);
+
+       array_init(return_value);
+       if (ZEND_TYPE_HAS_LIST(param->type)) {
+               void *entry;
+               ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), entry) {
+                       if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+                               append_type(return_value,
+                                       (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_LIST_GET_NAME(entry), 0, 0));
+                       } else {
+                               append_type(return_value,
+                                       (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_LIST_GET_CE(entry), 0, 0));
+                       }
+               } ZEND_TYPE_LIST_FOREACH_END();
+       } else if (ZEND_TYPE_HAS_NAME(param->type)) {
+               append_type(return_value,
+                       (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), 0, 0));
+       } else if (ZEND_TYPE_HAS_CE(param->type)) {
+               append_type(return_value,
+                       (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_CE(param->type), 0, 0));
+       }
+
+       type_mask = ZEND_TYPE_PURE_MASK(param->type);
+       ZEND_ASSERT(!(type_mask & MAY_BE_VOID));
+       if (type_mask & MAY_BE_CALLABLE) {
+               append_type_mask(return_value, MAY_BE_CALLABLE);
+       }
+       if (type_mask & MAY_BE_ITERABLE) {
+               append_type_mask(return_value, MAY_BE_ITERABLE);
+       }
+       if (type_mask & MAY_BE_OBJECT) {
+               append_type_mask(return_value, MAY_BE_OBJECT);
+       }
+       if (type_mask & MAY_BE_ARRAY) {
+               append_type_mask(return_value, MAY_BE_ARRAY);
+       }
+       if (type_mask & MAY_BE_STRING) {
+               append_type_mask(return_value, MAY_BE_STRING);
+       }
+       if (type_mask & MAY_BE_LONG) {
+               append_type_mask(return_value, MAY_BE_LONG);
+       }
+       if (type_mask & MAY_BE_DOUBLE) {
+               append_type_mask(return_value, MAY_BE_DOUBLE);
+       }
+       if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
+               append_type_mask(return_value, MAY_BE_BOOL);
+       } else if (type_mask & MAY_BE_FALSE) {
+               append_type_mask(return_value, MAY_BE_FALSE);
+       }
+       if (type_mask & MAY_BE_NULL) {
+               append_type_mask(return_value, MAY_BE_NULL);
+       }
+}
+/* }}} */
+
 /* {{{ proto public static mixed ReflectionMethod::export(mixed class, string name [, bool return]) throws ReflectionException
    Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */
 ZEND_METHOD(reflection_method, export)
@@ -3347,7 +3466,7 @@ ZEND_METHOD(reflection_function, getReturnType)
                RETURN_NULL();
        }
 
-       reflection_type_factory(fptr->common.arg_info[-1].type, return_value);
+       reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1);
 }
 /* }}} */
 
@@ -5575,7 +5694,7 @@ ZEND_METHOD(reflection_property, getType)
                RETURN_NULL();
        }
 
-       reflection_type_factory(ref->prop->type, return_value);
+       reflection_type_factory(ref->prop->type, return_value, 1);
 }
 /* }}} */
 
@@ -6429,6 +6548,11 @@ static const zend_function_entry reflection_named_type_functions[] = {
        PHP_FE_END
 };
 
+static const zend_function_entry reflection_union_type_functions[] = {
+       ZEND_ME(reflection_union_type, getTypes, arginfo_class_ReflectionUnionType_getTypes, 0)
+       PHP_FE_END
+};
+
 static const zend_function_entry reflection_extension_functions[] = {
        ZEND_ME(reflection, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL)
        ZEND_DEP_ME(reflection_extension, export, arginfo_class_ReflectionExtension_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC)
@@ -6552,6 +6676,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
        reflection_init_class_handlers(&_reflection_entry);
        reflection_named_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
 
+       INIT_CLASS_ENTRY(_reflection_entry, "ReflectionUnionType", reflection_union_type_functions);
+       reflection_init_class_handlers(&_reflection_entry);
+       reflection_union_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
+
        INIT_CLASS_ENTRY(_reflection_entry, "ReflectionMethod", reflection_method_functions);
        reflection_init_class_handlers(&_reflection_entry);
        reflection_method_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_function_abstract_ptr);
index bfd5f0caf952be4aae3213dd0c42c145ab73cd3c..b9cb156e7da5634673ba5615fa860cda1012e67d 100644 (file)
@@ -552,6 +552,11 @@ class ReflectionNamedType extends ReflectionType
     public function isBuiltin() {}
 }
 
+class ReflectionUnionType extends ReflectionType
+{
+    public function getTypes(): array {}
+}
+
 class ReflectionExtension implements Reflector
 {
     final private function __clone() {}
index 517668bd629d2d1c89ae3614ebfa9ff54e467010..d5404d9b3157ad9a6750406810c4b41657588fe6 100644 (file)
@@ -428,6 +428,9 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_Reflector___toString
 
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+
 #define arginfo_class_ReflectionExtension___clone arginfo_class_Reflector___toString
 
 #define arginfo_class_ReflectionExtension_export arginfo_class_ReflectionFunction_export
index d05c381a82bc4f9c5add57d865ad99e05266729e..61a0d24b0887ad1223cc7ac1b28d65c1365d7c93 100644 (file)
@@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection');
 var_dump($ext->getClasses());
 ?>
 --EXPECT--
-array(17) {
+array(18) {
   ["ReflectionException"]=>
   object(ReflectionClass)#2 (1) {
     ["name"]=>
@@ -54,43 +54,48 @@ array(17) {
     ["name"]=>
     string(19) "ReflectionNamedType"
   }
-  ["ReflectionMethod"]=>
+  ["ReflectionUnionType"]=>
   object(ReflectionClass)#11 (1) {
+    ["name"]=>
+    string(19) "ReflectionUnionType"
+  }
+  ["ReflectionMethod"]=>
+  object(ReflectionClass)#12 (1) {
     ["name"]=>
     string(16) "ReflectionMethod"
   }
   ["ReflectionClass"]=>
-  object(ReflectionClass)#12 (1) {
+  object(ReflectionClass)#13 (1) {
     ["name"]=>
     string(15) "ReflectionClass"
   }
   ["ReflectionObject"]=>
-  object(ReflectionClass)#13 (1) {
+  object(ReflectionClass)#14 (1) {
     ["name"]=>
     string(16) "ReflectionObject"
   }
   ["ReflectionProperty"]=>
-  object(ReflectionClass)#14 (1) {
+  object(ReflectionClass)#15 (1) {
     ["name"]=>
     string(18) "ReflectionProperty"
   }
   ["ReflectionClassConstant"]=>
-  object(ReflectionClass)#15 (1) {
+  object(ReflectionClass)#16 (1) {
     ["name"]=>
     string(23) "ReflectionClassConstant"
   }
   ["ReflectionExtension"]=>
-  object(ReflectionClass)#16 (1) {
+  object(ReflectionClass)#17 (1) {
     ["name"]=>
     string(19) "ReflectionExtension"
   }
   ["ReflectionZendExtension"]=>
-  object(ReflectionClass)#17 (1) {
+  object(ReflectionClass)#18 (1) {
     ["name"]=>
     string(23) "ReflectionZendExtension"
   }
   ["ReflectionReference"]=>
-  object(ReflectionClass)#18 (1) {
+  object(ReflectionClass)#19 (1) {
     ["name"]=>
     string(19) "ReflectionReference"
   }
diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt
new file mode 100644 (file)
index 0000000..bb568a0
--- /dev/null
@@ -0,0 +1,112 @@
+--TEST--
+Union types in reflection
+--INI--
+error_reporting=E_ALL&~E_DEPRECATED
+--FILE--
+<?php
+
+function dumpType(ReflectionUnionType $rt) {
+    echo "Type $rt:\n";
+    echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
+    foreach ($rt->getTypes() as $type) {
+        echo "  Name: " . $type->getName() . "\n";
+        echo "  String: " . (string) $type . "\n";
+        echo "  Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n";
+    }
+}
+function test1(): X|Y|int|float|false|null { }
+function test2(): X|iterable|bool { }
+
+class Test {
+    public X|Y|int $prop;
+}
+
+dumpType((new ReflectionFunction('test1'))->getReturnType());
+dumpType((new ReflectionFunction('test2'))->getReturnType());
+
+$rc = new ReflectionClass(Test::class);
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+/* Force CE resolution of the property type */
+
+class x {}
+$test = new Test;
+$test->prop = new x;
+
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+class y {}
+$test->prop = new y;
+
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+?>
+--EXPECT--
+Type X|Y|int|float|false|null:
+Allows null: true
+  Name: X
+  String: X
+  Allows Null: false
+  Name: Y
+  String: Y
+  Allows Null: false
+  Name: int
+  String: int
+  Allows Null: false
+  Name: float
+  String: float
+  Allows Null: false
+  Name: false
+  String: false
+  Allows Null: false
+  Name: null
+  String: null
+  Allows Null: true
+Type X|iterable|bool:
+Allows null: false
+  Name: X
+  String: X
+  Allows Null: false
+  Name: iterable
+  String: iterable
+  Allows Null: false
+  Name: bool
+  String: bool
+  Allows Null: false
+Type X|Y|int:
+Allows null: false
+  Name: X
+  String: X
+  Allows Null: false
+  Name: Y
+  String: Y
+  Allows Null: false
+  Name: int
+  String: int
+  Allows Null: false
+Type x|Y|int:
+Allows null: false
+  Name: x
+  String: x
+  Allows Null: false
+  Name: Y
+  String: Y
+  Allows Null: false
+  Name: int
+  String: int
+  Allows Null: false
+Type x|y|int:
+Allows null: false
+  Name: x
+  String: x
+  Allows Null: false
+  Name: y
+  String: y
+  Allows Null: false
+  Name: int
+  String: int
+  Allows Null: false
index 3b2cb7376e039ba77c27fd854ee6735fe547d5ed..318419ba3e37b90ebaeed29eafd253a202f0501e 100644 (file)
@@ -253,6 +253,21 @@ PHP_MINIT_FUNCTION(zend_test)
                zend_string_release(name);
        }
 
+       {
+               zend_string *name = zend_string_init("classUnionProp", sizeof("classUnionProp") - 1, 1);
+               zend_string *class_name1 = zend_string_init("stdClass", sizeof("stdClass") - 1, 1);
+               zend_string *class_name2 = zend_string_init("Iterator", sizeof("Iterator") - 1, 1);
+               zend_type_list *type_list = malloc(ZEND_TYPE_LIST_SIZE(2));
+               type_list->num_types = 2;
+               type_list->types[0] = ZEND_TYPE_LIST_ENCODE_NAME(class_name1);
+               type_list->types[1] = ZEND_TYPE_LIST_ENCODE_NAME(class_name2);
+               zend_type type = ZEND_TYPE_INIT_PTR(type_list, _ZEND_TYPE_LIST_BIT, 1, 0);
+               zval val;
+               ZVAL_NULL(&val);
+               zend_declare_typed_property(zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, type);
+               zend_string_release(name);
+       }
+
        {
                zend_string *name = zend_string_init("staticIntProp", sizeof("staticIntProp") - 1, 1);
                zval val;