========================================
- Core:
+ . Added support for union types.
+ RFC: https://wiki.php.net/rfc/union_types_v2
. Added ValueError class.
========================================
}
}));
-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) {
})) 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) {
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
?>
--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
?>
--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) {
}
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
--- /dev/null
+--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) {
+}
--- /dev/null
+--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===
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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===
--- /dev/null
+--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
--- /dev/null
+--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"
--- /dev/null
+--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) {
+}
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--TEST--
+Duplicate class type
+--FILE--
+<?php
+
+function test(): Foo|int|FOO {
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate type FOO is redundant in %s on line %d
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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
--- /dev/null
+--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===
--- /dev/null
+--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
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);
}
}
/* }}} */
+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;
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();
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);
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));
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; \
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) {
}
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, ' ');
}
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) {
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,
} 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*);
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);
}
/* }}} */
} 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},
}
/* }}} */
+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;
}
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) {
}
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));
}
}
/* }}} */
+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) /* {{{ */
{
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));
}
}
/* }}} */
}
/* }}} */
-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);
"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);
}
}
- 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;
}
/* }}} */
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;
}
/* 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++;
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");
}
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);
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);
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);
#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"
}
}
+/* 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 = "::";
*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 {
{
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
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);
}
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;
}
/* 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;
}
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) {
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)
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. */
}
{
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
{
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)
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;
}
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;
}
}
}
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);
}
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);
} 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);
}
} 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);
}
/* 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);
}
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;
}
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;
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);
}
/* }}} */
+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;
}
}
}
-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;
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)) {
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. */
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) /* {{{ */
{
/* 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;
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)) {
}
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)) {
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));
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);
}
}
}
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);
}
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(
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();
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 {
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 {
%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
type_expr:
type { $$ = $1; }
| '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
+ | union_type { $$ = $1; }
;
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; }
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) {
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);
}
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);
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);
}
_(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 {
#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)
* 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
/* 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)
#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) \
{ 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 */
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)
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)
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)
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)
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)
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)
info[n].flags = ((kind) | (related)); \
} while (0)
-static zend_bool class_name_type_hint(const zend_op_array *op_array, uint32_t arg_num)
+static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
{
zend_arg_info *arg_info;
-
if (arg_num > 0) {
- if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
- if (EXPECTED(arg_num <= op_array->num_args)) {
- arg_info = &op_array->arg_info[arg_num-1];
- } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
- arg_info = &op_array->arg_info[op_array->num_args];
- } else {
- return 0;
- }
- return ZEND_TYPE_IS_CLASS(arg_info->type);
+ if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+ return 0;
+ }
+ if (EXPECTED(arg_num <= op_array->num_args)) {
+ arg_info = &op_array->arg_info[arg_num-1];
+ } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
+ arg_info = &op_array->arg_info[op_array->num_args];
+ } else {
+ return 0;
}
} else {
arg_info = op_array->arg_info - 1;
- return ZEND_TYPE_IS_CLASS(arg_info->type);
}
+
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+ if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
+ return ZEND_TYPE_LIST(arg_info->type)->num_types;
+ }
+ return 1;
+ }
+
return 0;
}
case ZEND_RECV_INIT:
case ZEND_RECV:
case ZEND_RECV_VARIADIC:
- if (class_name_type_hint(op_array, opline->op1.num)) {
+ {
+ size_t num_classes = type_num_classes(op_array, opline->op1.num);
+ if (num_classes) {
opline->extended_value = cache_size;
- cache_size += sizeof(void *);
+ cache_size += num_classes * sizeof(void *);
}
break;
+ }
case ZEND_VERIFY_RETURN_TYPE:
- if (class_name_type_hint(op_array, 0)) {
+ {
+ size_t num_classes = type_num_classes(op_array, 0);
+ if (num_classes) {
opline->op2.num = cache_size;
- cache_size += sizeof(void *);
+ cache_size += num_classes * sizeof(void *);
}
break;
+ }
case ZEND_ASSIGN_STATIC_PROP_OP:
if (opline->op1_type == IS_CONST) {
// op1 static property
return 0;
}
- if (ZEND_TYPE_IS_CLASS(info->type)) {
+ if (ZEND_TYPE_HAS_CLASS(info->type)) {
if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) {
return 0;
}
tmp->max = ZEND_LONG_MAX;
tmp->overflow = 0;
return 1;
- } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) {
+ } else if (mask == MAY_BE_BOOL) {
tmp->underflow = 0;
tmp->min = 0;
tmp->max = 1;
tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type));
*pce = NULL;
- if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
- zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
tmp |= MAY_BE_OBJECT;
- *pce = get_class_entry(script, lcname);
- zend_string_release_ex(lcname, 0);
+ /* As we only have space to store one CE, we use a plain object type for class unions. */
+ if (ZEND_TYPE_HAS_NAME(arg_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release_ex(lcname, 0);
+ }
}
if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
tmp |= MAY_BE_RC1 | MAY_BE_RCN;
static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce)
{
+ if (pce) {
+ *pce = NULL;
+ }
if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
- uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type)
- ? MAY_BE_OBJECT
- : zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
+ uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
- if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
- type |= MAY_BE_NULL;
- }
if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
type |= MAY_BE_RC1 | MAY_BE_RCN;
}
- if (pce) {
- if (ZEND_TYPE_IS_CE(prop_info->type)) {
- *pce = ZEND_TYPE_CE(prop_info->type);
- } else if (ZEND_TYPE_IS_NAME(prop_info->type)) {
- zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
- *pce = get_class_entry(script, lcname);
- zend_string_release(lcname);
- } else {
- *pce = NULL;
+ if (ZEND_TYPE_HAS_CLASS(prop_info->type)) {
+ type |= MAY_BE_OBJECT;
+ if (pce) {
+ if (ZEND_TYPE_HAS_CE(prop_info->type)) {
+ *pce = ZEND_TYPE_CE(prop_info->type);
+ } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release(lcname);
+ }
}
}
return type;
}
- if (pce) {
- *pce = NULL;
- }
return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
}
num_args++;
}
for (i = 0 ; i < num_args; i++) {
- if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
+ if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) {
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(arg_info[i].type), entry) {
+ ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(*entry));
+ *entry = zend_new_interned_string(ZEND_TYPE_LIST_GET_NAME(*entry));
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) {
ZEND_TYPE_SET_PTR(arg_info[i].type,
new_interned_string(ZEND_TYPE_NAME(arg_info[i].type)));
}
return ok;
}
+static zend_class_entry *preload_fetch_resolved_ce(zend_string *name, zend_class_entry *self_ce) {
+ zend_string *lcname = zend_string_tolower(name);
+ zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lcname);
+ zend_string_release(lcname);
+ if (!ce) {
+ return NULL;
+ }
+ if (ce == self_ce) {
+ /* Ignore the following requirements if this is the class referring to itself */
+ return ce;
+ }
+#ifdef ZEND_WIN32
+ /* On Windows we can't link with internal class, because of ASLR */
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ return NULL;
+ }
+#endif
+ if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
+ return NULL;
+ }
+ if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
+ return NULL;
+ }
+ return ce;
+}
+
static zend_bool preload_try_resolve_property_types(zend_class_entry *ce)
{
zend_bool ok = 1;
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
- zend_string *name, *lcname;
-
- if (!ZEND_TYPE_IS_NAME(prop->type)) {
- continue;
- }
-
- name = ZEND_TYPE_NAME(prop->type);
- lcname = zend_string_tolower(name);
- p = zend_hash_find_ptr(EG(class_table), lcname);
- zend_string_release(lcname);
- if (!p) {
- ok = 0;
- continue;
- }
- if (p != ce) {
-#ifdef ZEND_WIN32
- /* On Windows we can't link with internal class, because of ASLR */
- if (p->type == ZEND_INTERNAL_CLASS) {
- ok = 0;
- continue;
- }
-#endif
- if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
- ok = 0;
- continue;
- }
- if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
+ if (ZEND_TYPE_HAS_LIST(prop->type)) {
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+ p = preload_fetch_resolved_ce(ZEND_TYPE_LIST_GET_NAME(*entry), ce);
+ if (!p) {
+ ok = 0;
+ continue;
+ }
+ *entry = ZEND_TYPE_LIST_ENCODE_CE(p);
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(prop->type)) {
+ p = preload_fetch_resolved_ce(ZEND_TYPE_NAME(prop->type), ce);
+ if (!p) {
ok = 0;
continue;
}
+ ZEND_TYPE_SET_CE(prop->type, p);
}
-
- zend_string_release(name);
- prop->type = (zend_type) ZEND_TYPE_INIT_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type), 0);
} ZEND_HASH_FOREACH_END();
}
return ok;
}
-static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) {
- zend_string *name, *lcname;
- zend_bool known;
- if (!ZEND_TYPE_IS_NAME(type)) {
- return 1;
- }
-
- name = ZEND_TYPE_NAME(type);
+static zend_bool preload_is_class_type_known(zend_class_entry *ce, zend_string *name) {
if (zend_string_equals_literal_ci(name, "self") ||
zend_string_equals_literal_ci(name, "parent") ||
zend_string_equals_ci(name, ce->name)) {
return 1;
}
- lcname = zend_string_tolower(name);
- known = zend_hash_exists(EG(class_table), lcname);
+ zend_string *lcname = zend_string_tolower(name);
+ zend_bool known = zend_hash_exists(EG(class_table), lcname);
zend_string_release(lcname);
return known;
}
+static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) {
+ if (ZEND_TYPE_HAS_LIST(type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(entry)
+ && !preload_is_class_type_known(ce, ZEND_TYPE_LIST_GET_NAME(entry))) {
+ return 0;
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ }
+ if (ZEND_TYPE_HAS_NAME(type)) {
+ return preload_is_class_type_known(ce, ZEND_TYPE_NAME(type));
+ }
+ return 1;
+}
+
static zend_bool preload_is_method_maybe_override(zend_class_entry *ce, zend_string *lcname) {
zend_class_entry *p;
if (ce->trait_aliases || ce->trait_precedences) {
REGISTER_HELPER(zend_jit_zval_copy_deref_helper)
REGISTER_HELPER(zend_jit_new_ref_helper);
REGISTER_HELPER(zend_jit_fetch_global_helper);
- REGISTER_HELPER(zend_jit_verify_arg_object);
REGISTER_HELPER(zend_jit_verify_arg_slow);
REGISTER_HELPER(zend_jit_fetch_obj_r_slow);
REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic);
return value;
}
-static void ZEND_FASTCALL zend_jit_verify_arg_object(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot)
-{
- zend_class_entry *ce;
- if (EXPECTED(*cache_slot)) {
- ce = (zend_class_entry *)*cache_slot;
- } else {
- ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
- if (UNEXPECTED(!ce)) {
- zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
- return;
- }
- *cache_slot = (void *)ce;
- }
- if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(arg), ce))) {
- zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
- }
-}
-
static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot)
{
uint32_t type_mask;
- if (UNEXPECTED(ZEND_TYPE_IS_CLASS(arg_info->type))) {
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) {
zend_class_entry *ce;
- if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
- /* Null passed to nullable type */
- return;
- }
-
- /* This is always an error - we fetch the class name for the error message here */
- if (EXPECTED(*cache_slot)) {
- ce = (zend_class_entry *) *cache_slot;
+ if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), entry) {
+ if (*cache_slot) {
+ ce = *cache_slot;
+ } else {
+ ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry),
+ (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+ if (!ce) {
+ cache_slot++;
+ continue;
+ }
+ *cache_slot = ce;
+ }
+ if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+ return;
+ }
+ cache_slot++;
+ } ZEND_TYPE_LIST_FOREACH_END();
} else {
- ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
- if (ce) {
- *cache_slot = (void *)ce;
+ if (EXPECTED(*cache_slot)) {
+ ce = (zend_class_entry *) *cache_slot;
+ } else {
+ ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
+ if (UNEXPECTED(!ce)) {
+ goto builtin_types;
+ }
+ *cache_slot = (void *) ce;
+ }
+ if (instanceof_function(Z_OBJCE_P(arg), ce)) {
+ return;
}
}
- goto err;
}
+builtin_types:
type_mask = ZEND_TYPE_FULL_MASK(arg_info->type);
- if (type_mask & MAY_BE_CALLABLE) {
- if (zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) == 0) {
- goto err;
- }
- } else if (type_mask & MAY_BE_ITERABLE) {
- if (zend_is_iterable(arg) == 0) {
- goto err;
- }
- } else {
- if (Z_ISUNDEF_P(arg) ||
- zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) {
- goto err;
- }
+ if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) {
+ return;
}
- return;
-err:
+ if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
+ return;
+ }
+ if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) {
+ return;
+ }
+
zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg);
}
| GET_Z_PTR r0, r0
| add r0, offsetof(zend_reference, val)
}
- if (!ZEND_TYPE_IS_CLASS(type)) {
- uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
- if (is_power_of_two(type_mask)) {
- uint32_t type_code = concrete_type(type_mask);
- | cmp byte [r0 + 8], type_code
- | jne >8
- } else {
- | mov edx, 1
- | mov cl, byte [r0 + 8]
- | shl edx, cl
- | test edx, type_mask
- | je >8
- }
+
+ uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
+ if (is_power_of_two(type_mask)) {
+ uint32_t type_code = concrete_type(type_mask);
+ | cmp byte [r0 + 8], type_code
+ | jne >8
} else {
- | SAVE_VALID_OPLINE opline
- | cmp byte [r0 + 8], IS_OBJECT
- | jne >9
- | mov FCARG1a, r0
- | mov r0, EX->run_time_cache
- | add r0, opline->extended_value
- | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array
- |.if X64WIN
- | mov CARG3, arg_num
- | LOAD_ADDR CARG4, (ptrdiff_t)arg_info
- | mov aword A5, r0
- | EXT_CALL zend_jit_verify_arg_object, r0
- |.elif X64
- | mov CARG3, arg_num
- | LOAD_ADDR CARG4, (ptrdiff_t)arg_info
- | mov CARG5, r0
- | EXT_CALL zend_jit_verify_arg_object, r0
- |.else
- | sub r4, 4
- | push r0
- | push (ptrdiff_t)arg_info
- | push arg_num
- | EXT_CALL zend_jit_verify_arg_object, r0
- | add r4, 4
- |.endif
- if (!zend_jit_check_exception(Dst)) {
- return 0;
- }
+ | mov edx, 1
+ | mov cl, byte [r0 + 8]
+ | shl edx, cl
+ | test edx, type_mask
+ | je >8
}
|.cold_code
|8:
| SAVE_VALID_OPLINE opline
- |9:
| mov FCARG1a, r0
| mov r0, EX->run_time_cache
| add r0, opline->extended_value
has_slow += 2;
| LOAD_ZVAL_ADDR r0, res_addr
| ZVAL_DEREF r0, MAY_BE_REF
- if (!ZEND_TYPE_IS_CLASS(arg_info->type)) {
- uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
- if (is_power_of_two(type_mask)) {
- uint32_t type_code = concrete_type(type_mask);
- | cmp byte [r0 + 8], type_code
- | jne >8
- } else {
- | mov edx, 1
- | mov cl, byte [r0 + 8]
- | shl edx, cl
- | test edx, type_mask
- | je >8
- }
+
+ uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type);
+ if (is_power_of_two(type_mask)) {
+ uint32_t type_code = concrete_type(type_mask);
+ | cmp byte [r0 + 8], type_code
+ | jne >8
} else {
- | cmp byte [r0 + 8], IS_OBJECT
- | jne >8
- | mov FCARG1a, r0
- | mov r0, EX->run_time_cache
- | lea r0, [r0 + opline->extended_value]
- | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array
- |.if X64WIN
- | mov CARG3, arg_num
- | LOAD_ADDR CARG4, (ptrdiff_t)arg_info
- | mov aword A5, r0
- | SAVE_VALID_OPLINE opline
- | EXT_CALL zend_jit_verify_arg_object, r0
- |.elif X64
- | mov CARG3, arg_num
- | LOAD_ADDR CARG4, (ptrdiff_t)arg_info
- | mov CARG5, r0
- | SAVE_VALID_OPLINE opline
- | EXT_CALL zend_jit_verify_arg_object, r0
- |.else
- | sub r4, 4
- | push r0
- | push (ptrdiff_t)arg_info
- | push arg_num
- | SAVE_VALID_OPLINE opline
- | EXT_CALL zend_jit_verify_arg_object, r0
- | add r4, 4
- |.endif
+ | mov edx, 1
+ | mov cl, byte [r0 + 8]
+ | shl edx, cl
+ | test edx, type_mask
+ | je >8
}
} while (0);
}
prop_info->ce = ARENA_REALLOC(prop_info->ce);
}
- if (ZEND_TYPE_IS_CE(prop_info->type)) {
+ if (ZEND_TYPE_HAS_LIST(prop_info->type)) {
+ zend_type_list *list = ZEND_TYPE_LIST(prop_info->type);
+ if (IN_ARENA(list)) {
+ list = ARENA_REALLOC(list);
+ ZEND_TYPE_SET_PTR(prop_info->type, list);
+
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) {
+ if (ZEND_TYPE_LIST_IS_CE(*entry)) {
+ zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+ if (IN_ARENA(ce)) {
+ ce = ARENA_REALLOC(ce);
+ *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+ }
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ }
+ } else if (ZEND_TYPE_HAS_CE(prop_info->type)) {
zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type);
if (IN_ARENA(ce)) {
ce = ARENA_REALLOC(ce);
}
}
+static void zend_file_cache_serialize_type(
+ zend_type *type, zend_persistent_script *script, zend_file_cache_metainfo *info, void *buf)
+{
+ if (ZEND_TYPE_HAS_LIST(*type)) {
+ zend_type_list *list = ZEND_TYPE_LIST(*type);
+ SERIALIZE_PTR(list);
+ ZEND_TYPE_SET_PTR(*type, list);
+ UNSERIALIZE_PTR(list);
+
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+ zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry);
+ SERIALIZE_STR(name);
+ *entry = ZEND_TYPE_LIST_ENCODE_NAME(name);
+ } else {
+ zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+ SERIALIZE_PTR(ce);
+ *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(*type)) {
+ zend_string *type_name = ZEND_TYPE_NAME(*type);
+ SERIALIZE_STR(type_name);
+ ZEND_TYPE_SET_PTR(*type, type_name);
+ } else if (ZEND_TYPE_HAS_CE(*type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(*type);
+ SERIALIZE_PTR(ce);
+ ZEND_TYPE_SET_PTR(*type, ce);
+ }
+}
+
static void zend_file_cache_serialize_op_array(zend_op_array *op_array,
zend_persistent_script *script,
zend_file_cache_metainfo *info,
if (!IS_SERIALIZED(p->name)) {
SERIALIZE_STR(p->name);
}
- if (ZEND_TYPE_IS_CLASS(p->type)) {
- zend_string *type_name = ZEND_TYPE_NAME(p->type);
- SERIALIZE_STR(type_name);
- ZEND_TYPE_SET_PTR(p->type, type_name);
- }
+ zend_file_cache_serialize_type(&p->type, script, info, buf);
p++;
}
}
if (prop->doc_comment) {
SERIALIZE_STR(prop->doc_comment);
}
- }
- if (ZEND_TYPE_IS_NAME(prop->type)) {
- zend_string *name = ZEND_TYPE_NAME(prop->type);
- SERIALIZE_STR(name);
- ZEND_TYPE_SET_PTR(prop->type, name);
- } else if (ZEND_TYPE_IS_CE(prop->type)) {
- zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
- SERIALIZE_PTR(ce);
- ZEND_TYPE_SET_PTR(prop->type, ce);
+ zend_file_cache_serialize_type(&prop->type, script, info, buf);
}
}
}
}
}
+static void zend_file_cache_unserialize_type(
+ zend_type *type, zend_persistent_script *script, void *buf)
+{
+ if (ZEND_TYPE_HAS_LIST(*type)) {
+ zend_type_list *list = ZEND_TYPE_LIST(*type);
+ UNSERIALIZE_PTR(list);
+ ZEND_TYPE_SET_PTR(*type, list);
+
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
+ zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry);
+ UNSERIALIZE_STR(name);
+ *entry = ZEND_TYPE_LIST_ENCODE_NAME(name);
+ } else {
+ zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+ UNSERIALIZE_PTR(ce);
+ *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(*type)) {
+ zend_string *type_name = ZEND_TYPE_NAME(*type);
+ UNSERIALIZE_STR(type_name);
+ ZEND_TYPE_SET_PTR(*type, type_name);
+ } else if (ZEND_TYPE_HAS_CE(*type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(*type);
+ UNSERIALIZE_PTR(ce);
+ ZEND_TYPE_SET_PTR(*type, ce);
+ }
+}
+
static void zend_file_cache_unserialize_op_array(zend_op_array *op_array,
zend_persistent_script *script,
void *buf)
if (!IS_UNSERIALIZED(p->name)) {
UNSERIALIZE_STR(p->name);
}
- if (ZEND_TYPE_IS_CLASS(p->type)) {
- zend_string *type_name = ZEND_TYPE_NAME(p->type);
- UNSERIALIZE_STR(type_name);
- ZEND_TYPE_SET_PTR(p->type, type_name);
- }
+ zend_file_cache_unserialize_type(&p->type, script, buf);
p++;
}
}
if (prop->doc_comment) {
UNSERIALIZE_STR(prop->doc_comment);
}
- }
- if (ZEND_TYPE_IS_NAME(prop->type)) {
- zend_string *name = ZEND_TYPE_NAME(prop->type);
- UNSERIALIZE_STR(name);
- ZEND_TYPE_SET_PTR(prop->type, name);
- } else if (ZEND_TYPE_IS_CE(prop->type)) {
- zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
- UNSERIALIZE_PTR(ce);
- ZEND_TYPE_SET_PTR(prop->type, ce);
+ zend_file_cache_unserialize_type(&prop->type, script, buf);
}
}
}
}
}
+static void zend_persist_type(zend_type *type) {
+ if (ZEND_TYPE_HAS_LIST(*type)) {
+ void **entry;
+ zend_type_list *list = ZEND_TYPE_LIST(*type);
+ if (ZEND_TYPE_USES_ARENA(*type)) {
+ if (!ZCG(is_immutable_class)) {
+ list = zend_shared_memdup_arena_put(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+ } else {
+ /* Moved from arena to SHM because type list was fully resolved. */
+ list = zend_shared_memdup_put(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+ ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT;
+ }
+ } else {
+ list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types));
+ }
+ ZEND_TYPE_SET_PTR(*type, list);
+
+ ZEND_TYPE_LIST_FOREACH_PTR(list, entry) {
+ zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry);
+ zend_accel_store_interned_string(type_name);
+ *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name);
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(*type)) {
+ zend_string *type_name = ZEND_TYPE_NAME(*type);
+ zend_accel_store_interned_string(type_name);
+ ZEND_TYPE_SET_PTR(*type, type_name);
+ }
+}
+
static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script)
{
zend_op *persist_ptr;
if (arg_info[i].name) {
zend_accel_store_interned_string(arg_info[i].name);
}
- if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
- zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type);
- zend_accel_store_interned_string(type_name);
- ZEND_TYPE_SET_PTR(arg_info[i].type, type_name);
- }
+ zend_persist_type(&arg_info[i].type);
}
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
arg_info++;
prop->doc_comment = NULL;
}
}
-
- if (ZEND_TYPE_IS_NAME(prop->type)) {
- zend_string *class_name = ZEND_TYPE_NAME(prop->type);
- zend_accel_store_interned_string(class_name);
- ZEND_TYPE_SET_PTR(prop->type, class_name);
- }
+ zend_persist_type(&prop->type);
}
static void zend_persist_class_constant(zval *zv)
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
zend_property_info *prop;
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
- if (ZEND_TYPE_IS_CE(prop->type)) {
+ if (ZEND_TYPE_HAS_LIST(prop->type)) {
+ void **entry;
+ ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) {
+ if (ZEND_TYPE_LIST_IS_CE(*entry)) {
+ zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry);
+ if (ce->type == ZEND_USER_CLASS) {
+ ce = zend_shared_alloc_get_xlat_entry(ce);
+ if (ce) {
+ *entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
+ }
+ }
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_CE(prop->type)) {
zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
if (ce->type == ZEND_USER_CLASS) {
ce = zend_shared_alloc_get_xlat_entry(ce);
}
}
+static void zend_persist_type_calc(zend_type *type)
+{
+ if (ZEND_TYPE_HAS_LIST(*type)) {
+ void **entry;
+ if (ZEND_TYPE_USES_ARENA(*type) && !ZCG(is_immutable_class)) {
+ ADD_ARENA_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types));
+ } else {
+ ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types));
+ }
+ ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(*type), entry) {
+ zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry);
+ ADD_INTERNED_STRING(type_name);
+ *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name);
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(*type)) {
+ zend_string *type_name = ZEND_TYPE_NAME(*type);
+ ADD_INTERNED_STRING(type_name);
+ ZEND_TYPE_SET_PTR(*type, type_name);
+ }
+}
+
static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
{
if (op_array->scope && zend_shared_alloc_get_xlat_entry(op_array->opcodes)) {
if (arg_info[i].name) {
ADD_INTERNED_STRING(arg_info[i].name);
}
- if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) {
- zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type);
- ADD_INTERNED_STRING(type_name);
- ZEND_TYPE_SET_PTR(arg_info[i].type, type_name);
- }
+ zend_persist_type_calc(&arg_info[i].type);
}
}
zend_shared_alloc_register_xlat_entry(prop, prop);
ADD_SIZE_EX(sizeof(zend_property_info));
ADD_INTERNED_STRING(prop->name);
- if (ZEND_TYPE_IS_NAME(prop->type)) {
- zend_string *class_name = ZEND_TYPE_NAME(prop->type);
- ADD_INTERNED_STRING(class_name);
- ZEND_TYPE_SET_PTR(prop->type, class_name);
- }
+ zend_persist_type_calc(&prop->type);
if (ZCG(accel_directives).save_comments && prop->doc_comment) {
ADD_STRING(prop->doc_comment);
}
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
- if (ZEND_TYPE_IS_NAME(prop->type)) {
+ if (ZEND_TYPE_HAS_LIST(prop->type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(prop->type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+ return;
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(prop->type)) {
return;
}
} ZEND_HASH_FOREACH_END();
PHPAPI zend_class_entry *reflection_parameter_ptr;
PHPAPI zend_class_entry *reflection_type_ptr;
PHPAPI zend_class_entry *reflection_named_type_ptr;
+PHPAPI zend_class_entry *reflection_union_type_ptr;
PHPAPI zend_class_entry *reflection_class_ptr;
PHPAPI zend_class_entry *reflection_object_ptr;
PHPAPI zend_class_entry *reflection_method_ptr;
/* Struct for type hints */
typedef struct _type_reference {
zend_type type;
+ /* Whether to use backwards compatible null representation */
+ zend_bool legacy_behavior;
} type_reference;
typedef enum {
case REF_TYPE_TYPE:
{
type_reference *type_ref = intern->ptr;
- if (ZEND_TYPE_IS_NAME(type_ref->type)) {
+ if (ZEND_TYPE_HAS_LIST(type_ref->type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type_ref->type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+ zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry));
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(type_ref->type)) {
zend_string_release(ZEND_TYPE_NAME(type_ref->type));
}
efree(type_ref);
}
/* }}} */
+/* For backwards compatibility reasons, we need to return T|null style unions
+ * as a ReflectionNamedType. Here we determine what counts as a union type and
+ * what doesn't. */
+static zend_bool is_union_type(zend_type type) {
+ if (ZEND_TYPE_HAS_LIST(type)) {
+ return 1;
+ }
+ uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
+ if (ZEND_TYPE_HAS_CLASS(type)) {
+ return type_mask_without_null != 0;
+ }
+ if (type_mask_without_null == MAY_BE_BOOL) {
+ return 0;
+ }
+ /* Check that only one bit is set. */
+ return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
+}
+
/* {{{ reflection_type_factory */
-static void reflection_type_factory(zend_type type, zval *object)
+static void reflection_type_factory(zend_type type, zval *object, zend_bool legacy_behavior)
{
reflection_object *intern;
type_reference *reference;
+ zend_bool is_union = is_union_type(type);
- reflection_instantiate(reflection_named_type_ptr, object);
+ reflection_instantiate(
+ is_union ? reflection_union_type_ptr : reflection_named_type_ptr, object);
intern = Z_REFLECTION_P(object);
reference = (type_reference*) emalloc(sizeof(type_reference));
reference->type = type;
+ reference->legacy_behavior = legacy_behavior && !is_union;
intern->ptr = reference;
intern->ref_type = REF_TYPE_TYPE;
/* Property types may be resolved during the lifetime of the ReflectionType,
* so we need to make sure that the strings we reference are not released. */
- if (ZEND_TYPE_IS_NAME(type)) {
+ if (ZEND_TYPE_HAS_LIST(type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+ zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry));
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(type)) {
zend_string_addref(ZEND_TYPE_NAME(type));
}
}
}
GET_REFLECTION_OBJECT_PTR(param);
- if (ZEND_TYPE_IS_CLASS(param->arg_info->type)) {
+ // TODO: This is going to return null for union types, which is rather odd.
+ if (ZEND_TYPE_HAS_NAME(param->arg_info->type)) {
/* Class name is stored as a string, we might also get "self" or "parent"
* - For "self", simply use the function scope. If scope is NULL then
* the function is global and thus self does not make any sense
if (!ZEND_TYPE_IS_SET(param->arg_info->type)) {
RETURN_NULL();
}
- reflection_type_factory(param->arg_info->type, return_value);
+ reflection_type_factory(param->arg_info->type, return_value, 1);
}
/* }}} */
}
GET_REFLECTION_OBJECT_PTR(param);
- RETURN_STR(zend_type_to_string_without_null(param->type));
+ if (param->legacy_behavior) {
+ RETURN_STR(zend_type_to_string_without_null(param->type));
+ }
+ RETURN_STR(zend_type_to_string(param->type));
}
/* }}} */
}
/* }}} */
+static void append_type(zval *return_value, zend_type type) {
+ zval reflection_type;
+ reflection_type_factory(type, &reflection_type, 0);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type);
+}
+
+static void append_type_mask(zval *return_value, uint32_t type_mask) {
+ append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask));
+}
+
+/* {{{ proto public string ReflectionUnionType::getTypes()
+ Returns the types that are part of this union type */
+ZEND_METHOD(reflection_union_type, getTypes)
+{
+ reflection_object *intern;
+ type_reference *param;
+ uint32_t type_mask;
+
+ if (zend_parse_parameters_none() == FAILURE) {
+ return;
+ }
+ GET_REFLECTION_OBJECT_PTR(param);
+
+ array_init(return_value);
+ if (ZEND_TYPE_HAS_LIST(param->type)) {
+ void *entry;
+ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), entry) {
+ if (ZEND_TYPE_LIST_IS_NAME(entry)) {
+ append_type(return_value,
+ (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_LIST_GET_NAME(entry), 0, 0));
+ } else {
+ append_type(return_value,
+ (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_LIST_GET_CE(entry), 0, 0));
+ }
+ } ZEND_TYPE_LIST_FOREACH_END();
+ } else if (ZEND_TYPE_HAS_NAME(param->type)) {
+ append_type(return_value,
+ (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), 0, 0));
+ } else if (ZEND_TYPE_HAS_CE(param->type)) {
+ append_type(return_value,
+ (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_CE(param->type), 0, 0));
+ }
+
+ type_mask = ZEND_TYPE_PURE_MASK(param->type);
+ ZEND_ASSERT(!(type_mask & MAY_BE_VOID));
+ if (type_mask & MAY_BE_CALLABLE) {
+ append_type_mask(return_value, MAY_BE_CALLABLE);
+ }
+ if (type_mask & MAY_BE_ITERABLE) {
+ append_type_mask(return_value, MAY_BE_ITERABLE);
+ }
+ if (type_mask & MAY_BE_OBJECT) {
+ append_type_mask(return_value, MAY_BE_OBJECT);
+ }
+ if (type_mask & MAY_BE_ARRAY) {
+ append_type_mask(return_value, MAY_BE_ARRAY);
+ }
+ if (type_mask & MAY_BE_STRING) {
+ append_type_mask(return_value, MAY_BE_STRING);
+ }
+ if (type_mask & MAY_BE_LONG) {
+ append_type_mask(return_value, MAY_BE_LONG);
+ }
+ if (type_mask & MAY_BE_DOUBLE) {
+ append_type_mask(return_value, MAY_BE_DOUBLE);
+ }
+ if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
+ append_type_mask(return_value, MAY_BE_BOOL);
+ } else if (type_mask & MAY_BE_FALSE) {
+ append_type_mask(return_value, MAY_BE_FALSE);
+ }
+ if (type_mask & MAY_BE_NULL) {
+ append_type_mask(return_value, MAY_BE_NULL);
+ }
+}
+/* }}} */
+
/* {{{ proto public static mixed ReflectionMethod::export(mixed class, string name [, bool return]) throws ReflectionException
Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */
ZEND_METHOD(reflection_method, export)
RETURN_NULL();
}
- reflection_type_factory(fptr->common.arg_info[-1].type, return_value);
+ reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1);
}
/* }}} */
RETURN_NULL();
}
- reflection_type_factory(ref->prop->type, return_value);
+ reflection_type_factory(ref->prop->type, return_value, 1);
}
/* }}} */
PHP_FE_END
};
+static const zend_function_entry reflection_union_type_functions[] = {
+ ZEND_ME(reflection_union_type, getTypes, arginfo_class_ReflectionUnionType_getTypes, 0)
+ PHP_FE_END
+};
+
static const zend_function_entry reflection_extension_functions[] = {
ZEND_ME(reflection, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL)
ZEND_DEP_ME(reflection_extension, export, arginfo_class_ReflectionExtension_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC)
reflection_init_class_handlers(&_reflection_entry);
reflection_named_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
+ INIT_CLASS_ENTRY(_reflection_entry, "ReflectionUnionType", reflection_union_type_functions);
+ reflection_init_class_handlers(&_reflection_entry);
+ reflection_union_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
+
INIT_CLASS_ENTRY(_reflection_entry, "ReflectionMethod", reflection_method_functions);
reflection_init_class_handlers(&_reflection_entry);
reflection_method_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_function_abstract_ptr);
public function isBuiltin() {}
}
+class ReflectionUnionType extends ReflectionType
+{
+ public function getTypes(): array {}
+}
+
class ReflectionExtension implements Reflector
{
final private function __clone() {}
#define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_Reflector___toString
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+
#define arginfo_class_ReflectionExtension___clone arginfo_class_Reflector___toString
#define arginfo_class_ReflectionExtension_export arginfo_class_ReflectionFunction_export
var_dump($ext->getClasses());
?>
--EXPECT--
-array(17) {
+array(18) {
["ReflectionException"]=>
object(ReflectionClass)#2 (1) {
["name"]=>
["name"]=>
string(19) "ReflectionNamedType"
}
- ["ReflectionMethod"]=>
+ ["ReflectionUnionType"]=>
object(ReflectionClass)#11 (1) {
+ ["name"]=>
+ string(19) "ReflectionUnionType"
+ }
+ ["ReflectionMethod"]=>
+ object(ReflectionClass)#12 (1) {
["name"]=>
string(16) "ReflectionMethod"
}
["ReflectionClass"]=>
- object(ReflectionClass)#12 (1) {
+ object(ReflectionClass)#13 (1) {
["name"]=>
string(15) "ReflectionClass"
}
["ReflectionObject"]=>
- object(ReflectionClass)#13 (1) {
+ object(ReflectionClass)#14 (1) {
["name"]=>
string(16) "ReflectionObject"
}
["ReflectionProperty"]=>
- object(ReflectionClass)#14 (1) {
+ object(ReflectionClass)#15 (1) {
["name"]=>
string(18) "ReflectionProperty"
}
["ReflectionClassConstant"]=>
- object(ReflectionClass)#15 (1) {
+ object(ReflectionClass)#16 (1) {
["name"]=>
string(23) "ReflectionClassConstant"
}
["ReflectionExtension"]=>
- object(ReflectionClass)#16 (1) {
+ object(ReflectionClass)#17 (1) {
["name"]=>
string(19) "ReflectionExtension"
}
["ReflectionZendExtension"]=>
- object(ReflectionClass)#17 (1) {
+ object(ReflectionClass)#18 (1) {
["name"]=>
string(23) "ReflectionZendExtension"
}
["ReflectionReference"]=>
- object(ReflectionClass)#18 (1) {
+ object(ReflectionClass)#19 (1) {
["name"]=>
string(19) "ReflectionReference"
}
--- /dev/null
+--TEST--
+Union types in reflection
+--INI--
+error_reporting=E_ALL&~E_DEPRECATED
+--FILE--
+<?php
+
+function dumpType(ReflectionUnionType $rt) {
+ echo "Type $rt:\n";
+ echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
+ foreach ($rt->getTypes() as $type) {
+ echo " Name: " . $type->getName() . "\n";
+ echo " String: " . (string) $type . "\n";
+ echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n";
+ }
+}
+
+function test1(): X|Y|int|float|false|null { }
+function test2(): X|iterable|bool { }
+
+class Test {
+ public X|Y|int $prop;
+}
+
+dumpType((new ReflectionFunction('test1'))->getReturnType());
+dumpType((new ReflectionFunction('test2'))->getReturnType());
+
+$rc = new ReflectionClass(Test::class);
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+/* Force CE resolution of the property type */
+
+class x {}
+$test = new Test;
+$test->prop = new x;
+
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+class y {}
+$test->prop = new y;
+
+$rp = $rc->getProperty('prop');
+dumpType($rp->getType());
+
+?>
+--EXPECT--
+Type X|Y|int|float|false|null:
+Allows null: true
+ Name: X
+ String: X
+ Allows Null: false
+ Name: Y
+ String: Y
+ Allows Null: false
+ Name: int
+ String: int
+ Allows Null: false
+ Name: float
+ String: float
+ Allows Null: false
+ Name: false
+ String: false
+ Allows Null: false
+ Name: null
+ String: null
+ Allows Null: true
+Type X|iterable|bool:
+Allows null: false
+ Name: X
+ String: X
+ Allows Null: false
+ Name: iterable
+ String: iterable
+ Allows Null: false
+ Name: bool
+ String: bool
+ Allows Null: false
+Type X|Y|int:
+Allows null: false
+ Name: X
+ String: X
+ Allows Null: false
+ Name: Y
+ String: Y
+ Allows Null: false
+ Name: int
+ String: int
+ Allows Null: false
+Type x|Y|int:
+Allows null: false
+ Name: x
+ String: x
+ Allows Null: false
+ Name: Y
+ String: Y
+ Allows Null: false
+ Name: int
+ String: int
+ Allows Null: false
+Type x|y|int:
+Allows null: false
+ Name: x
+ String: x
+ Allows Null: false
+ Name: y
+ String: y
+ Allows Null: false
+ Name: int
+ String: int
+ Allows Null: false
zend_string_release(name);
}
+ {
+ zend_string *name = zend_string_init("classUnionProp", sizeof("classUnionProp") - 1, 1);
+ zend_string *class_name1 = zend_string_init("stdClass", sizeof("stdClass") - 1, 1);
+ zend_string *class_name2 = zend_string_init("Iterator", sizeof("Iterator") - 1, 1);
+ zend_type_list *type_list = malloc(ZEND_TYPE_LIST_SIZE(2));
+ type_list->num_types = 2;
+ type_list->types[0] = ZEND_TYPE_LIST_ENCODE_NAME(class_name1);
+ type_list->types[1] = ZEND_TYPE_LIST_ENCODE_NAME(class_name2);
+ zend_type type = ZEND_TYPE_INIT_PTR(type_list, _ZEND_TYPE_LIST_BIT, 1, 0);
+ zval val;
+ ZVAL_NULL(&val);
+ zend_declare_typed_property(zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, type);
+ zend_string_release(name);
+ }
+
{
zend_string *name = zend_string_init("staticIntProp", sizeof("staticIntProp") - 1, 1);
zval val;