summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--UPGRADING2
-rw-r--r--Zend/tests/assert/expect_015.phpt4
-rw-r--r--Zend/tests/return_types/generators002.phpt2
-rw-r--r--Zend/tests/type_declarations/nullable_void.phpt2
-rw-r--r--Zend/tests/type_declarations/typed_properties_043.phpt6
-rw-r--r--Zend/tests/type_declarations/typed_properties_095.phpt8
-rw-r--r--Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt21
-rw-r--r--Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt18
-rw-r--r--Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt12
-rw-r--r--Zend/tests/type_declarations/union_types/incdec_prop.phpt132
-rw-r--r--Zend/tests/type_declarations/union_types/inheritance.phpt46
-rw-r--r--Zend/tests/type_declarations/union_types/inheritance_internal.phpt33
-rw-r--r--Zend/tests/type_declarations/union_types/legal_default_values.phpt45
-rw-r--r--Zend/tests/type_declarations/union_types/multiple_classes.phpt83
-rw-r--r--Zend/tests/type_declarations/union_types/prop_ref_assign.phpt59
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt11
-rw-r--r--Zend/tests/type_declarations/union_types/standalone_false.phpt10
-rw-r--r--Zend/tests/type_declarations/union_types/standalone_null.phpt10
-rw-r--r--Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt10
-rw-r--r--Zend/tests/type_declarations/union_types/type_checking_strict.phpt211
-rw-r--r--Zend/tests/type_declarations/union_types/type_checking_weak.phpt209
-rw-r--r--Zend/tests/type_declarations/union_types/variance/invalid_001.phpt15
-rw-r--r--Zend/tests/type_declarations/union_types/variance/invalid_002.phpt15
-rw-r--r--Zend/tests/type_declarations/union_types/variance/invalid_003.phpt18
-rw-r--r--Zend/tests/type_declarations/union_types/variance/valid.phpt31
-rw-r--r--Zend/tests/type_declarations/union_types/void_with_class.phpt10
-rw-r--r--Zend/zend.c40
-rw-r--r--Zend/zend_API.c6
-rw-r--r--Zend/zend_ast.c33
-rw-r--r--Zend/zend_ast.h1
-rw-r--r--Zend/zend_compile.c371
-rw-r--r--Zend/zend_compile.h2
-rw-r--r--Zend/zend_execute.c542
-rw-r--r--Zend/zend_inheritance.c320
-rw-r--r--Zend/zend_language_parser.y8
-rw-r--r--Zend/zend_opcode.c28
-rw-r--r--Zend/zend_string.h2
-rw-r--r--Zend/zend_type_info.h1
-rw-r--r--Zend/zend_types.h109
-rw-r--r--Zend/zend_vm_def.h3
-rw-r--r--Zend/zend_vm_execute.h15
-rw-r--r--ext/opcache/Optimizer/compact_literals.c44
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c2
-rw-r--r--ext/opcache/Optimizer/zend_inference.c45
-rw-r--r--ext/opcache/ZendAccelerator.c109
-rw-r--r--ext/opcache/jit/zend_jit_disasm_x86.c1
-rw-r--r--ext/opcache/jit/zend_jit_helpers.c87
-rw-r--r--ext/opcache/jit/zend_jit_x86.dasc104
-rw-r--r--ext/opcache/zend_accelerator_util_funcs.c19
-rw-r--r--ext/opcache/zend_file_cache.c95
-rw-r--r--ext/opcache/zend_persist.c57
-rw-r--r--ext/opcache/zend_persist_calc.c42
-rw-r--r--ext/reflection/php_reflection.c146
-rw-r--r--ext/reflection/reflection.stub.php5
-rw-r--r--ext/reflection/reflection_arginfo.h3
-rw-r--r--ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt23
-rw-r--r--ext/reflection/tests/union_types.phpt112
-rw-r--r--ext/zend_test/test.c15
66 files changed, 2713 insertions, 788 deletions
diff --git a/UPGRADING b/UPGRADING
index 1412162b6f..1bb6d93574 100644
--- 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.
========================================
diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt
index 72f13ff83f..769eed8270 100644
--- a/Zend/tests/assert/expect_015.phpt
+++ b/Zend/tests/assert/expect_015.phpt
@@ -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) {
diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt
index 90bada714b..2e42f4b052 100644
--- a/Zend/tests/return_types/generators002.phpt
+++ b/Zend/tests/return_types/generators002.phpt
@@ -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
diff --git a/Zend/tests/type_declarations/nullable_void.phpt b/Zend/tests/type_declarations/nullable_void.phpt
index 4ff0edb0d8..725c11bb59 100644
--- a/Zend/tests/type_declarations/nullable_void.phpt
+++ b/Zend/tests/type_declarations/nullable_void.phpt
@@ -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
diff --git a/Zend/tests/type_declarations/typed_properties_043.phpt b/Zend/tests/type_declarations/typed_properties_043.phpt
index eefe358796..79f01545e1 100644
--- a/Zend/tests/type_declarations/typed_properties_043.phpt
+++ b/Zend/tests/type_declarations/typed_properties_043.phpt
@@ -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) {
}
diff --git a/Zend/tests/type_declarations/typed_properties_095.phpt b/Zend/tests/type_declarations/typed_properties_095.phpt
index 3f1027f08f..8470d4f437 100644
--- a/Zend/tests/type_declarations/typed_properties_095.phpt
+++ b/Zend/tests/type_declarations/typed_properties_095.phpt
@@ -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
index 0000000000..7c369090a5
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt
@@ -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
index 0000000000..8526c65537
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt
@@ -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
index 0000000000..6c40adc8f2
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt
@@ -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
index 0000000000..f5941751dc
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt
@@ -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
index 0000000000..dde6f59526
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/incdec_prop.phpt
@@ -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
index 0000000000..a0a1b65912
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/inheritance.phpt
@@ -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
index 0000000000..bb53411cad
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt
@@ -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
index 0000000000..2807397734
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/legal_default_values.phpt
@@ -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
index 0000000000..aac56c6603
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/multiple_classes.phpt
@@ -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
index 0000000000..a8db8fca9c
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt
@@ -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
index 0000000000..91d2d940f6
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt
@@ -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
index 0000000000..5739f699e7
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt
@@ -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
index 0000000000..f9cd3e01fa
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt
@@ -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
index 0000000000..5b65a33de1
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt
@@ -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
index 0000000000..e3f7c5858b
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt
@@ -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
index 0000000000..c6b0949418
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt
@@ -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
index 0000000000..5597794c86
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt
@@ -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
index 0000000000..e9f785ed17
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt
@@ -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
index 0000000000..3932f929e5
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/standalone_false.phpt
@@ -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
index 0000000000..7a25f9cd05
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/standalone_null.phpt
@@ -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
index 0000000000..1e680249b0
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt
@@ -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
index 0000000000..f098b638dc
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt
@@ -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
index 0000000000..441eaab854
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt
@@ -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
index 0000000000..9e1dcaefa1
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt
@@ -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
index 0000000000..4448114fce
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt
@@ -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
index 0000000000..26d9ae3eb4
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt
@@ -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
index 0000000000..f9e5cc4980
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/variance/valid.phpt
@@ -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
index 0000000000..6e1f439e31
--- /dev/null
+++ b/Zend/tests/type_declarations/union_types/void_with_class.phpt
@@ -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
diff --git a/Zend/zend.c b/Zend/zend.c
index 8c9ae86d74..d85135d287 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -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();
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 3c1ca4c718..87cfd5f139 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -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));
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index f0b524b30e..980e23d055 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -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) {
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index fd6dd1677a..5b8aae6f96 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -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,
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index ea93e878d0..53fe9afde8 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -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);
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 96db1dcb3a..a87204d267 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -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);
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 097137aa5c..153d8e3bc4 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -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);
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index af81c327e1..9b8a47f365 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -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 {
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 0f35b69adf..57e88e630d 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -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; }
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index 626eacd35d..e197e9bf16 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -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);
}
diff --git a/Zend/zend_string.h b/Zend/zend_string.h
index a38c1cae8c..f2076beee1 100644
--- a/Zend/zend_string.h
+++ b/Zend/zend_string.h
@@ -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 {
diff --git a/Zend/zend_type_info.h b/Zend/zend_type_info.h
index 9479d5aad2..ef2c6a19bc 100644
--- a/Zend/zend_type_info.h
+++ b/Zend/zend_type_info.h
@@ -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)
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index d77fbc68a3..4bfe335e0a 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -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 */
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index a36458b899..01b3bcf9f2 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -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)
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index be26c30765..a5b1e2e752 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -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)
diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c
index 0a99ac4140..4aabe04c6e 100644
--- a/ext/opcache/Optimizer/compact_literals.c
+++ b/ext/opcache/Optimizer/compact_literals.c
@@ -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
diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c
index 59c562425f..8802577154 100644
--- a/ext/opcache/Optimizer/dfa_pass.c
+++ b/ext/opcache/Optimizer/dfa_pass.c
@@ -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;
}
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index 8fc92f842c..cb31ea21f2 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -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;
}
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 68be526892..08fca1e3fa 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -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) {
diff --git a/ext/opcache/jit/zend_jit_disasm_x86.c b/ext/opcache/jit/zend_jit_disasm_x86.c
index 70708729b4..df6c4ab1ba 100644
--- a/ext/opcache/jit/zend_jit_disasm_x86.c
+++ b/ext/opcache/jit/zend_jit_disasm_x86.c
@@ -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);
diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c
index 658350794e..0efd07c3dc 100644
--- a/ext/opcache/jit/zend_jit_helpers.c
+++ b/ext/opcache/jit/zend_jit_helpers.c
@@ -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);
}
diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc
index 0681a45064..6f1913a1a1 100644
--- a/ext/opcache/jit/zend_jit_x86.dasc
+++ b/ext/opcache/jit/zend_jit_x86.dasc
@@ -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);
}
diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c
index e74b7bb668..9eb6745478 100644
--- a/ext/opcache/zend_accelerator_util_funcs.c
+++ b/ext/opcache/zend_accelerator_util_funcs.c
@@ -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);
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index 6e4d52cee6..049b0e9b48 100644
--- a/ext/opcache/zend_file_cache.c
+++ b/ext/opcache/zend_file_cache.c
@@ -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);
}
}
}
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index 56c64dc87d..ec5b9d417a 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -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);
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index 62a3deaea0..c4aa4adf5e 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -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();
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 522b9d211d..4b3851e9e0 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -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);
diff --git a/ext/reflection/reflection.stub.php b/ext/reflection/reflection.stub.php
index bfd5f0caf9..b9cb156e7d 100644
--- a/ext/reflection/reflection.stub.php
+++ b/ext/reflection/reflection.stub.php
@@ -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() {}
diff --git a/ext/reflection/reflection_arginfo.h b/ext/reflection/reflection_arginfo.h
index 517668bd62..d5404d9b31 100644
--- a/ext/reflection/reflection_arginfo.h
+++ b/ext/reflection/reflection_arginfo.h
@@ -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
diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
index d05c381a82..61a0d24b08 100644
--- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
+++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt
@@ -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
index 0000000000..bb568a0cf4
--- /dev/null
+++ b/ext/reflection/tests/union_types.phpt
@@ -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
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index 3b2cb7376e..318419ba3e 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -254,6 +254,21 @@ PHP_MINIT_FUNCTION(zend_test)
}
{
+ 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;
ZVAL_LONG(&val, 123);