summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-09-25 13:21:13 +0200
committerNikita Popov <nikita.ppv@gmail.com>2019-11-08 15:15:48 +0100
commit999e32b65a8a4bb59e27e538fa68ffae4b99d863 (patch)
tree9b6c69d032b9b69fff2f3b71f95ad2566cdf4acb /Zend
parentac4e0f0852ce780e143013ceff45067a172e8a83 (diff)
downloadphp-git-999e32b65a8a4bb59e27e538fa68ffae4b99d863.tar.gz
Implement union types
According to RFC: https://wiki.php.net/rfc/union_types_v2 The type representation now makes use of both the pointer payload and the type mask at the same time. Additionall, zend_type_list is introduced as a new kind of pointer payload, which is used to store multiple class types. Each of the class types is a tagged pointer, which may be either a class name or class entry. The latter is only used for typed properties, while arguments/returns will instead use cache slots. A type list can contain a mix of both names and CEs at the same time, as not all classes may be resolvable. One thing this is missing is support for union types in arginfo and stubs, which I want to handle separately. I've also dropped the special object code from the JIT implementation for now -- I plan to add this back in a different form at a later time. For now I did not want to include non-trivial JIT changes together with large functional changes. Another possible piece of follow-up work is to implement "iterable" as an internal alias for "array|Traversable". I believe this will eliminate quite a few special-cases that had to be implemented. Closes GH-4838.
Diffstat (limited to 'Zend')
-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
48 files changed, 2082 insertions, 508 deletions
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)