mirror of
https://github.com/php/php-src.git
synced 2024-11-27 11:53:33 +08:00
Pure Intersection types (#6799)
Implement pure intersection types RFC RFC: https://wiki.php.net/rfc/pure-intersection-types Co-authored-by: Nikita Popov <nikic@php.net> Co-authored-by: Ilija Tovilo <ilutov@php.net>
This commit is contained in:
parent
629965c80f
commit
069a9fa5e4
@ -64,6 +64,9 @@ PHP 8.1 UPGRADE NOTES
|
||||
PHP cross-version compatibility concerns, a `#[ReturnTypeWillChange]`
|
||||
attribute can be added to silence the deprecation notice.
|
||||
RFC: https://wiki.php.net/rfc/internal_method_return_types
|
||||
. Added support for intersection types.
|
||||
They cannot be combined with union types.
|
||||
RFC: https://wiki.php.net/rfc/pure-intersection-types
|
||||
|
||||
- Fileinfo:
|
||||
. The fileinfo functions now accept and return, respectively, finfo objects
|
||||
@ -307,7 +310,7 @@ PHP 8.1 UPGRADE NOTES
|
||||
Previously, -a without readline had the same behavior as calling php without
|
||||
any arguments, apart from printing an additional "Interactive mode enabled"
|
||||
message. This mode was not, in fact, interactive.
|
||||
|
||||
|
||||
- phpdbg:
|
||||
. Remote functionality from phpdbg has been removed.
|
||||
|
||||
@ -453,7 +456,7 @@ PHP 8.1 UPGRADE NOTES
|
||||
MYSQLI_REFRESH_SLAVE, in line with an upstream change in MySQL. The old
|
||||
constant is still available for backwards-compatibility reasons, but may
|
||||
be deprecated/removed in the future.
|
||||
|
||||
|
||||
- Sockets:
|
||||
. TCP_DEFER_ACCEPT socket option added where available.
|
||||
|
||||
|
@ -73,7 +73,7 @@ static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
|
||||
arg_info = op_array->arg_info - 1;
|
||||
}
|
||||
|
||||
if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
|
||||
if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
|
||||
return ZEND_TYPE_LIST(arg_info->type)->num_types;
|
||||
}
|
||||
|
@ -310,19 +310,23 @@ static inline bool can_elide_return_type_check(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_HAS_CLASS(arg_info->type)) {
|
||||
if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
|
||||
zend_type *single_type;
|
||||
/* For intersection: result==false is failure, default is success.
|
||||
* For union: result==true is success, default is failure. */
|
||||
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(arg_info->type);
|
||||
ZEND_TYPE_FOREACH(arg_info->type, single_type) {
|
||||
if (ZEND_TYPE_HAS_NAME(*single_type)) {
|
||||
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type));
|
||||
zend_class_entry *ce = zend_optimizer_get_class_entry(script, lcname);
|
||||
zend_string_release(lcname);
|
||||
if (ce && safe_instanceof(use_info->ce, ce)) {
|
||||
/* One of the class union types matched. */
|
||||
return true;
|
||||
bool result = ce && safe_instanceof(use_info->ce, ce);
|
||||
if (result == !is_intersection) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
return is_intersection;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -2221,7 +2221,7 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
|
||||
}
|
||||
|
||||
uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type));
|
||||
if (ZEND_TYPE_HAS_CLASS(type)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(type)) {
|
||||
tmp |= MAY_BE_OBJECT;
|
||||
if (pce) {
|
||||
/* As we only have space to store one CE,
|
||||
|
@ -0,0 +1,39 @@
|
||||
--TEST--
|
||||
Added element of intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class A implements X, Y, Z {}
|
||||
|
||||
class Collection {
|
||||
public X&Y $intersect;
|
||||
}
|
||||
|
||||
function foo(): X&Y {
|
||||
return new A();
|
||||
}
|
||||
|
||||
function bar(X&Y $o): void {
|
||||
var_dump($o);
|
||||
}
|
||||
|
||||
$o = foo();
|
||||
var_dump($o);
|
||||
|
||||
$c = new Collection();
|
||||
$a = new A();
|
||||
|
||||
$c->intersect = $a;
|
||||
echo 'OK', \PHP_EOL;
|
||||
bar($a);
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (0) {
|
||||
}
|
||||
OK
|
||||
object(A)#3 (0) {
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
--TEST--
|
||||
Assigning values to intersection types
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class TestParent implements X, Y {}
|
||||
class TestChild extends TestParent implements Z {}
|
||||
|
||||
class A {
|
||||
|
||||
public X&Y&Z $prop;
|
||||
|
||||
public function method1(X&Y $a): X&Y&Z {
|
||||
return new TestChild();
|
||||
}
|
||||
public function method2(X $a): X&Y {
|
||||
return new TestParent();
|
||||
}
|
||||
}
|
||||
|
||||
$tp = new TestParent();
|
||||
$tc = new TestChild();
|
||||
|
||||
$o = new A();
|
||||
try {
|
||||
$o->prop = $tp;
|
||||
} catch (TypeError $e) {
|
||||
echo $e->getMessage(), \PHP_EOL;
|
||||
}
|
||||
|
||||
$o->prop = $tc;
|
||||
|
||||
$r = $o->method1($tp);
|
||||
var_dump($r);
|
||||
$r = $o->method2($tp);
|
||||
var_dump($r);
|
||||
$r = $o->method1($tc);
|
||||
var_dump($r);
|
||||
$r = $o->method2($tc);
|
||||
var_dump($r);
|
||||
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Cannot assign TestParent to property A::$prop of type X&Y&Z
|
||||
object(TestChild)#%d (0) {
|
||||
}
|
||||
object(TestParent)#%d (0) {
|
||||
}
|
||||
object(TestChild)#%d (0) {
|
||||
}
|
||||
object(TestParent)#%d (0) {
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
array type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): array&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type array cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
bool type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): bool&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type bool cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
callable type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): callable&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type callable cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
false type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): false&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type false cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
float type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): float&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type float cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
int type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): int&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type int cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
iterable type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): iterable&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type iterable cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
mixed type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): mixed&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type mixed cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
never type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): never&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type never cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
null type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): null&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type null cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
Intersection type cannot be nullable
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): ?Countable&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
object type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): object&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type object cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
parent type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {}
|
||||
|
||||
class B extends A {
|
||||
public function foo(): parent&Iterator {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type parent cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
self type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public function foo(): self&Iterator {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type self cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
static type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public function foo(): static&Iterator {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type static cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
string type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): string&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type string cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
void type cannot take part in an intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function foo(): void&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type void cannot be part of an intersection type in %s on line %d
|
@ -0,0 +1,50 @@
|
||||
--TEST--
|
||||
Missing one element of intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class A implements X {}
|
||||
|
||||
class Collection {
|
||||
public X&Y $intersect;
|
||||
}
|
||||
|
||||
function foo(): X&Y {
|
||||
return new A();
|
||||
}
|
||||
|
||||
function bar(X&Y $o): void {
|
||||
var_dump($o);
|
||||
}
|
||||
|
||||
try {
|
||||
$o = foo();
|
||||
var_dump($o);
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
$c = new Collection();
|
||||
$a = new A();
|
||||
|
||||
try {
|
||||
$c->intersect = $a;
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
bar($a);
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
foo(): Return value must be of type X&Y, A returned
|
||||
Cannot assign A to property Collection::$intersect of type X&Y
|
||||
bar(): Argument #1 ($o) must be of type X&Y, A given, called in %s on line %d
|
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Intersection types in parameters
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
|
||||
class Foo implements A, B {}
|
||||
class Bar implements A {}
|
||||
|
||||
function foo(A&B $bar) {
|
||||
var_dump($bar);
|
||||
}
|
||||
|
||||
foo(new Foo());
|
||||
|
||||
try {
|
||||
foo(new Bar());
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage(), \PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Foo)#1 (0) {
|
||||
}
|
||||
foo(): Argument #1 ($bar) must be of type A&B, Bar given, called in %s on line %d
|
@ -0,0 +1,8 @@
|
||||
--TEST--
|
||||
Parse error for & not followed by var or vararg
|
||||
--FILE--
|
||||
<?php
|
||||
+&
|
||||
?>
|
||||
--EXPECTF--
|
||||
Parse error: syntax error, unexpected token "&" in %s on line %d
|
@ -0,0 +1,11 @@
|
||||
--TEST--
|
||||
Duplicate class alias type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
use A as B;
|
||||
function foo(): A&B {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Duplicate type A is redundant in %s on line %d
|
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Duplicate class alias type at runtime
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {}
|
||||
|
||||
class_alias('A', 'B');
|
||||
function foo(): A&B {}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,11 @@
|
||||
--TEST--
|
||||
Duplicate class type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function test(): Foo&A&FOO {
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Duplicate type FOO is redundant in %s on line %d
|
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Intersection with child class
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
function test(): A&B {
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Intersection types and typed reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class A implements X, Y, Z {}
|
||||
class B implements X, Y {}
|
||||
|
||||
class Test {
|
||||
public X&Y $y;
|
||||
public X&Z $z;
|
||||
}
|
||||
$test = new Test;
|
||||
$r = new A;
|
||||
$test->y =& $r;
|
||||
$test->z =& $r;
|
||||
|
||||
try {
|
||||
$r = new B;
|
||||
} catch (\TypeError $e) {
|
||||
echo $e->getMessage(), \PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot assign B to reference held by property Test::$z of type X&Z
|
@ -0,0 +1,27 @@
|
||||
--TEST--
|
||||
Co-variance check failure for intersection type where child replace one of intersection type members with a supertype
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B extends A {}
|
||||
interface C {}
|
||||
|
||||
class Test implements B, C {}
|
||||
|
||||
class Foo {
|
||||
public function foo(): B&C {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
/* This fails because A is a parent type for B */
|
||||
class FooChild extends Foo {
|
||||
public function foo(): A&C {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of FooChild::foo(): A&C must be compatible with Foo::foo(): B&C in %s on line %d
|
@ -0,0 +1,26 @@
|
||||
--TEST--
|
||||
Co-variance check failure for intersection type where child replaces it with standard type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
|
||||
class Test implements A, B {}
|
||||
|
||||
class Foo {
|
||||
public function foo(): A&B {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
/* This fails because just A larger than A&B */
|
||||
class FooChild extends Foo {
|
||||
public function foo(): array {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of FooChild::foo(): array must be compatible with Foo::foo(): A&B in %s on line %d
|
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
Replacing int type with intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class Test {
|
||||
function method(): int {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): int in %s on line %d
|
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Replacing object type with not-loadable intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
class Test {
|
||||
function method(): object {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
Fatal error: Could not check compatibility between Test2::method(): X&Y and Test::method(): object, because class X is not available in %s on line %d
|
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
Replacing iterable type with non-Traversable intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class Test {
|
||||
function method(): iterable {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d
|
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Replacing not-loadable parent intersection type with loadable child intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// Let Y and Z be loadable.
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class Test {
|
||||
function method(): X&Y {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): Y&Z {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
Fatal error: Could not check compatibility between Test2::method(): Y&Z and Test::method(): X&Y, because class X is not available in %s on line %d
|
@ -0,0 +1,26 @@
|
||||
--TEST--
|
||||
Co-variance check failure for intersection type where child removes one of intersection type members
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
|
||||
class Test implements A, B {}
|
||||
|
||||
class Foo {
|
||||
public function foo(): A&B {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
/* This fails because just A larger than A&B */
|
||||
class FooChild extends Foo {
|
||||
public function foo(): A {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of FooChild::foo(): A must be compatible with Foo::foo(): A&B in %s on line %d
|
@ -0,0 +1,27 @@
|
||||
--TEST--
|
||||
Co-variance check failure for intersection type where child removes one of intersection type members
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
interface C {}
|
||||
|
||||
class Test implements A, B, C {}
|
||||
|
||||
class Foo {
|
||||
public function foo(): A&B&C {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
/* This fails because just A&B larger than A&B&C */
|
||||
class FooChild extends Foo {
|
||||
public function foo(): A&B {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of FooChild::foo(): A&B must be compatible with Foo::foo(): A&B&C in %s on line %d
|
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class TestOne implements X, Y {}
|
||||
class TestTwo implements X {}
|
||||
|
||||
interface A
|
||||
{
|
||||
public function foo(): X&Y;
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
public function foo(): TestOne|TestTwo;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Y in %s on line %d
|
@ -0,0 +1,23 @@
|
||||
--TEST--
|
||||
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class TestOne implements X, Y {}
|
||||
|
||||
interface A
|
||||
{
|
||||
public function foo(): X&Y;
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
public function foo(): TestOne|int;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::foo(): TestOne|int must be compatible with A::foo(): X&Y in %s on line %d
|
@ -0,0 +1,25 @@
|
||||
--TEST--
|
||||
Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z extends Y {}
|
||||
|
||||
class TestOne implements X, Z {}
|
||||
class TestTwo implements X, Y {}
|
||||
|
||||
interface A
|
||||
{
|
||||
public function foo(): X&Z;
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
public function foo(): TestOne|TestTwo;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Z in %s on line %d
|
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
Property types must be invariant
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class A {
|
||||
public X&Y $prop;
|
||||
}
|
||||
class B extends A {
|
||||
public X&Y&Z $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type of B::$prop must be X&Y (as in class A) in %s on line %d
|
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Intersection type reduction invalid invariant type check
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
class A implements X, Y {}
|
||||
|
||||
class Test {
|
||||
public X&Y $prop;
|
||||
}
|
||||
class Test2 extends Test {
|
||||
public A $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECTF--
|
||||
Fatal error: Type of Test2::$prop must be X&Y (as in class Test) in %s on line %d
|
@ -0,0 +1,40 @@
|
||||
--TEST--
|
||||
Valid inheritence - co-variance
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
interface C {}
|
||||
|
||||
class Test implements A, B, C {}
|
||||
|
||||
class Foo {
|
||||
public function foo(): A {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
class FooChild extends Foo {
|
||||
public function foo(): A&B {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
class FooSecondChild extends FooChild {
|
||||
public function foo(): A&B&C {
|
||||
return new Test();
|
||||
}
|
||||
}
|
||||
|
||||
$o = new FooChild();
|
||||
var_dump($o->foo());
|
||||
$o = new FooSecondChild();
|
||||
var_dump($o->foo());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Test)#%d (0) {
|
||||
}
|
||||
object(Test)#%d (0) {
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Commutative intersection types
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface A {}
|
||||
interface B {}
|
||||
|
||||
class Foo {
|
||||
public A&B $prop;
|
||||
public function foo(A&B $v): A&B {}
|
||||
}
|
||||
|
||||
class FooChild extends Foo {
|
||||
public B&A $prop;
|
||||
public function foo(B&A $v): B&A {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,29 @@
|
||||
--TEST--
|
||||
Valid intersection type variance
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class TestParent implements X, Y, Z {}
|
||||
class TestChild implements Z {}
|
||||
|
||||
class A {
|
||||
public X&Y $prop;
|
||||
|
||||
public function method1(X&Y&Z $a): X&Y {}
|
||||
public function method2(X&Y $a): X {}
|
||||
}
|
||||
class B extends A {
|
||||
public X&Y $prop;
|
||||
|
||||
public function method1(X&Y $a): X&Y&Z {}
|
||||
public function method2(X $a): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Intersection type reduction valid invariant type check
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {}
|
||||
class B extends A {}
|
||||
|
||||
class Test {
|
||||
public A&B $prop;
|
||||
}
|
||||
class Test2 extends Test {
|
||||
public B $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Replacing union of classes respecting intersection type by intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
|
||||
class TestOne implements X, Y {}
|
||||
class TestTwo implements X, Y {}
|
||||
|
||||
interface A
|
||||
{
|
||||
public function foo(TestOne|TestTwo $param): X&Y;
|
||||
}
|
||||
|
||||
interface B extends A
|
||||
{
|
||||
public function foo(X&Y $param): TestOne|TestTwo;
|
||||
}
|
||||
|
||||
interface C extends A
|
||||
{
|
||||
public function foo(X $param): TestTwo;
|
||||
}
|
||||
|
||||
interface D extends A
|
||||
{
|
||||
public function foo(Y $param): TestOne;
|
||||
}
|
||||
|
||||
interface E extends B
|
||||
{
|
||||
public function foo(X $param): TestTwo;
|
||||
}
|
||||
|
||||
interface F extends B
|
||||
{
|
||||
public function foo(Y $param): TestOne;
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Replacing union type by intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface X {}
|
||||
interface Y {}
|
||||
interface Z {}
|
||||
|
||||
class A {
|
||||
public function test(): X|Z {}
|
||||
}
|
||||
class B extends A {
|
||||
public function test(): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,23 @@
|
||||
--TEST--
|
||||
Replacing object type with intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// It's sufficient that Y is loadable.
|
||||
interface Y {}
|
||||
|
||||
class Test {
|
||||
function method(): object {}
|
||||
function method2(): object|int {}
|
||||
function method3(): Y|int {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): X&Y {}
|
||||
function method2(): X&Y {}
|
||||
function method3(): X&Y {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Replacing iterable type with intersection type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class MyIterator implements Traversable {}
|
||||
|
||||
class Test {
|
||||
function method(): iterable {}
|
||||
function method2(): iterable|int {}
|
||||
function method3(): iterable|Z {}
|
||||
}
|
||||
class Test2 extends Test {
|
||||
function method(): X&Traversable {}
|
||||
function method2(): X&MyIterator {}
|
||||
function method3(): X&Z {}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -2424,14 +2424,14 @@ static void zend_check_magic_method_return_type(const zend_class_entry *ce, cons
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_class_type = ZEND_TYPE_HAS_CLASS(fptr->common.arg_info[-1].type);
|
||||
bool is_complex_type = ZEND_TYPE_IS_COMPLEX(fptr->common.arg_info[-1].type);
|
||||
uint32_t extra_types = ZEND_TYPE_PURE_MASK(fptr->common.arg_info[-1].type) & ~return_type;
|
||||
if (extra_types & MAY_BE_STATIC) {
|
||||
extra_types &= ~MAY_BE_STATIC;
|
||||
has_class_type = 1;
|
||||
is_complex_type = true;
|
||||
}
|
||||
|
||||
if (extra_types || (has_class_type && return_type != MAY_BE_OBJECT)) {
|
||||
if (extra_types || (is_complex_type && return_type != MAY_BE_OBJECT)) {
|
||||
zend_error(error_type, "%s::%s(): Return type must be %s when declared",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name),
|
||||
ZSTR_VAL(zend_type_to_string((zend_type) ZEND_TYPE_INIT_MASK(return_type))));
|
||||
@ -2768,7 +2768,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
|
||||
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_HAS_CLASS(new_arg_info[i].type)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(new_arg_info[i].type)) {
|
||||
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
|
||||
&& "Should be stored as simple name");
|
||||
const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
|
||||
|
@ -1457,6 +1457,16 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ast->kind == ZEND_AST_TYPE_INTERSECTION) {
|
||||
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, '?');
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ enum _zend_ast_kind {
|
||||
ZEND_AST_TRAIT_ADAPTATIONS,
|
||||
ZEND_AST_USE,
|
||||
ZEND_AST_TYPE_UNION,
|
||||
ZEND_AST_TYPE_INTERSECTION,
|
||||
ZEND_AST_ATTRIBUTE_LIST,
|
||||
ZEND_AST_ATTRIBUTE_GROUP,
|
||||
ZEND_AST_MATCH_ARM_LIST,
|
||||
|
@ -1149,15 +1149,21 @@ ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name) /*
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static zend_string *add_type_string(zend_string *type, zend_string *new_type) {
|
||||
static zend_string *add_type_string(zend_string *type, zend_string *new_type, bool is_intersection) {
|
||||
zend_string *result;
|
||||
if (type == NULL) {
|
||||
return zend_string_copy(new_type);
|
||||
}
|
||||
|
||||
result = zend_string_concat3(
|
||||
ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type));
|
||||
zend_string_release(type);
|
||||
if (is_intersection) {
|
||||
result = zend_string_concat3(ZSTR_VAL(type), ZSTR_LEN(type),
|
||||
"&", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type));
|
||||
zend_string_release(type);
|
||||
} else {
|
||||
result = zend_string_concat3(
|
||||
ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type));
|
||||
zend_string_release(type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1185,9 +1191,10 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
|
||||
if (ZEND_TYPE_HAS_LIST(type)) {
|
||||
zend_type *list_type;
|
||||
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type);
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
|
||||
if (ZEND_TYPE_HAS_CE(*list_type)) {
|
||||
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name);
|
||||
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection);
|
||||
} else {
|
||||
zend_string *name = ZEND_TYPE_NAME(*list_type);
|
||||
|
||||
@ -1196,13 +1203,13 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
zend_class_entry *ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
|
||||
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
|
||||
str = add_type_string(str, tmp);
|
||||
str = add_type_string(str, tmp, is_intersection);
|
||||
} else {
|
||||
str = add_type_string(str, ce->name);
|
||||
str = add_type_string(str, ce->name, is_intersection);
|
||||
}
|
||||
} else {
|
||||
zend_string *resolved = resolve_class_name(name, scope);
|
||||
str = add_type_string(str, resolved);
|
||||
str = add_type_string(str, resolved, is_intersection);
|
||||
zend_string_release(resolved);
|
||||
}
|
||||
}
|
||||
@ -1228,7 +1235,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
||||
|
||||
if (type_mask == MAY_BE_ANY) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED), /* is_intersection */ false);
|
||||
|
||||
return str;
|
||||
}
|
||||
@ -1240,39 +1247,39 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
name = called_scope->name;
|
||||
}
|
||||
}
|
||||
str = add_type_string(str, name);
|
||||
str = add_type_string(str, name, /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_CALLABLE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_ITERABLE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_OBJECT) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_ARRAY) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_STRING) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_LONG) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_DOUBLE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT), /* is_intersection */ false);
|
||||
}
|
||||
if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL), /* is_intersection */ false);
|
||||
} else if (type_mask & MAY_BE_FALSE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_VOID) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_NEVER) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER), /* is_intersection */ false);
|
||||
}
|
||||
|
||||
if (type_mask & MAY_BE_NULL) {
|
||||
@ -1283,7 +1290,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
return nullable_str;
|
||||
}
|
||||
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE));
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE), /* is_intersection */ false);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@ -2411,7 +2418,7 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */
|
||||
/* }}} */
|
||||
|
||||
static size_t zend_type_get_num_classes(zend_type type) {
|
||||
if (!ZEND_TYPE_HAS_CLASS(type)) {
|
||||
if (!ZEND_TYPE_IS_COMPLEX(type)) {
|
||||
return 0;
|
||||
}
|
||||
if (ZEND_TYPE_HAS_LIST(type)) {
|
||||
@ -6249,8 +6256,8 @@ static zend_type zend_compile_typename(
|
||||
ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type);
|
||||
ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK;
|
||||
|
||||
if (ZEND_TYPE_HAS_CLASS(single_type)) {
|
||||
if (!ZEND_TYPE_HAS_CLASS(type)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(single_type)) {
|
||||
if (!ZEND_TYPE_IS_COMPLEX(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;
|
||||
@ -6284,9 +6291,60 @@ static zend_type zend_compile_typename(
|
||||
memcpy(list, type_list, ZEND_TYPE_LIST_SIZE(type_list->num_types));
|
||||
ZEND_TYPE_SET_LIST(type, list);
|
||||
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
|
||||
/* Inform that the type list is a union type */
|
||||
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_UNION_BIT;
|
||||
}
|
||||
|
||||
free_alloca(type_list, use_heap);
|
||||
} else if (ast->kind == ZEND_AST_TYPE_INTERSECTION) {
|
||||
zend_ast_list *list = zend_ast_get_list(ast);
|
||||
zend_type_list *type_list;
|
||||
|
||||
/* Allocate the type list directly on the arena as it must be a type
|
||||
* list of the same number of elements as the AST list has children */
|
||||
type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children));
|
||||
type_list->num_types = 0;
|
||||
|
||||
ZEND_ASSERT(list->children > 1);
|
||||
|
||||
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);
|
||||
|
||||
/* An intersection of standard types cannot exist so invalidate it */
|
||||
if (ZEND_TYPE_IS_ONLY_MASK(single_type)) {
|
||||
zend_string *standard_type_str = zend_type_to_string(single_type);
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str));
|
||||
zend_string_release_ex(standard_type_str, false);
|
||||
}
|
||||
/* Check for "self" and "parent" too */
|
||||
if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self")
|
||||
|| zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type)));
|
||||
}
|
||||
|
||||
/* Add type to the type list */
|
||||
type_list->types[type_list->num_types++] = single_type;
|
||||
|
||||
/* Check for trivially redundant class types */
|
||||
for (size_t i = 0; i < type_list->num_types - 1; i++) {
|
||||
if (zend_string_equals_ci(
|
||||
ZEND_TYPE_NAME(type_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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZEND_ASSERT(list->children == type_list->num_types);
|
||||
|
||||
ZEND_TYPE_SET_LIST(type, type_list);
|
||||
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
|
||||
/* Inform that the type list is an intersection type */
|
||||
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
|
||||
} else {
|
||||
type = zend_compile_single_typename(ast);
|
||||
}
|
||||
@ -6313,23 +6371,23 @@ static zend_type zend_compile_typename(
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null");
|
||||
}
|
||||
|
||||
if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) {
|
||||
if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_IS_COMPLEX(type) || (type_mask & MAY_BE_STATIC))) {
|
||||
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)) {
|
||||
if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_IS_COMPLEX(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_NEVER) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_NEVER)) {
|
||||
if ((type_mask & MAY_BE_NEVER) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_NEVER)) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "never 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))) {
|
||||
&& !ZEND_TYPE_IS_COMPLEX(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 {
|
||||
@ -7472,7 +7530,7 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
|
||||
ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM);
|
||||
zend_type type = zend_compile_typename(enum_backing_type_ast, 0);
|
||||
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
||||
if (ZEND_TYPE_HAS_CLASS(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) {
|
||||
zend_string *type_string = zend_type_to_string(type);
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Enum backing type must be int or string, %s given",
|
||||
|
@ -838,67 +838,54 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline zend_class_entry *zend_ce_from_type(
|
||||
zend_property_info *info, zend_type *type) {
|
||||
if (UNEXPECTED(!ZEND_TYPE_HAS_NAME(*type))) {
|
||||
ZEND_ASSERT(ZEND_TYPE_HAS_CE(*type));
|
||||
return ZEND_TYPE_CE(*type);
|
||||
}
|
||||
|
||||
zend_string *name = ZEND_TYPE_NAME(*type);
|
||||
zend_class_entry *ce;
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
}
|
||||
} else {
|
||||
ce = resolve_single_class_type(name, info->ce);
|
||||
if (ce && !(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
||||
zend_string_release(name);
|
||||
ZEND_TYPE_SET_CE(*type, ce);
|
||||
}
|
||||
}
|
||||
return ce;
|
||||
}
|
||||
|
||||
static 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)) {
|
||||
zend_type *list_type;
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
|
||||
if (ZEND_TYPE_HAS_NAME(*list_type)) {
|
||||
zend_string *name = ZEND_TYPE_NAME(*list_type);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = resolve_single_class_type(name, info->ce);
|
||||
if (!ce) {
|
||||
continue;
|
||||
}
|
||||
if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
||||
zend_string_release(name);
|
||||
ZEND_TYPE_SET_CE(*list_type, ce);
|
||||
}
|
||||
if (ZEND_TYPE_IS_INTERSECTION(info->type)) {
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
|
||||
zend_class_entry *ce = zend_ce_from_type(info, list_type);
|
||||
if (!ce || !instanceof_function(object_ce, ce)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ce = ZEND_TYPE_CE(*list_type);
|
||||
}
|
||||
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);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = resolve_single_class_type(name, info->ce);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
return 0;
|
||||
}
|
||||
if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
|
||||
zend_string_release(name);
|
||||
ZEND_TYPE_SET_CE(info->type, ce);
|
||||
}
|
||||
}
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
return true;
|
||||
} else {
|
||||
ce = ZEND_TYPE_CE(info->type);
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
|
||||
zend_class_entry *ce = zend_ce_from_type(info, list_type);
|
||||
if (ce && instanceof_function(object_ce, ce)) {
|
||||
return true;
|
||||
}
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
return false;
|
||||
}
|
||||
return instanceof_function(object_ce, ce);
|
||||
} else {
|
||||
zend_class_entry *ce = zend_ce_from_type(info, &info->type);
|
||||
return ce && instanceof_function(object_ce, ce);
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,7 +896,7 @@ static zend_always_inline bool i_zend_check_property_type(zend_property_info *in
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT
|
||||
if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT
|
||||
&& zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) {
|
||||
return 1;
|
||||
}
|
||||
@ -951,7 +938,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
|
||||
return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES());
|
||||
}
|
||||
|
||||
ZEND_API bool zend_value_instanceof_static(zval *zv) {
|
||||
static zend_always_inline bool zend_value_instanceof_static(zval *zv) {
|
||||
if (Z_TYPE_P(zv) != IS_OBJECT) {
|
||||
return 0;
|
||||
}
|
||||
@ -971,85 +958,81 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) {
|
||||
# define HAVE_CACHE_SLOT 1
|
||||
#endif
|
||||
|
||||
static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
|
||||
void **cache_slot, zend_type *type)
|
||||
{
|
||||
if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
|
||||
return (zend_class_entry *) *cache_slot;
|
||||
}
|
||||
|
||||
zend_string *name = ZEND_TYPE_NAME(*type);
|
||||
zend_class_entry *ce;
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
/* Cannot resolve */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = zend_fetch_class(name,
|
||||
ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
*cache_slot = (void *) ce;
|
||||
}
|
||||
return ce;
|
||||
}
|
||||
|
||||
static zend_always_inline bool zend_check_type_slow(
|
||||
zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope,
|
||||
zend_type *type, zval *arg, zend_reference *ref, void **cache_slot,
|
||||
bool is_return_type, bool is_internal)
|
||||
{
|
||||
uint32_t type_mask;
|
||||
if (ZEND_TYPE_HAS_CLASS(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
|
||||
if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
|
||||
zend_class_entry *ce;
|
||||
if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
|
||||
zend_type *list_type;
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
|
||||
if (HAVE_CACHE_SLOT && *cache_slot) {
|
||||
ce = *cache_slot;
|
||||
} else {
|
||||
zend_string *name = ZEND_TYPE_NAME(*list_type);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (!ce) {
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
cache_slot++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = zend_fetch_class(name,
|
||||
ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT);
|
||||
if (!ce) {
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
cache_slot++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
|
||||
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
|
||||
/* If type is not an instance of one of the types taking part in the
|
||||
* intersection it cannot be a valid instance of the whole intersection type. */
|
||||
if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return false;
|
||||
}
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
*cache_slot = ce;
|
||||
cache_slot++;
|
||||
}
|
||||
}
|
||||
if (instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return 1;
|
||||
}
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
cache_slot++;
|
||||
}
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
} else {
|
||||
if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
|
||||
ce = (zend_class_entry *) *cache_slot;
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
return true;
|
||||
} else {
|
||||
zend_string *name = ZEND_TYPE_NAME(*type);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
goto builtin_types;
|
||||
}
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
|
||||
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
|
||||
/* Instance of a single type part of a union is sufficient to pass the type check */
|
||||
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ce = zend_fetch_class(name,
|
||||
ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
goto builtin_types;
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
cache_slot++;
|
||||
}
|
||||
}
|
||||
if (HAVE_CACHE_SLOT) {
|
||||
*cache_slot = (void *) ce;
|
||||
}
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
}
|
||||
if (instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return 1;
|
||||
} else {
|
||||
ce = zend_fetch_ce_from_cache_slot(cache_slot, type);
|
||||
/* If we have a CE we check if it satisfies the type constraint,
|
||||
* otherwise it will check if a standard type satisfies it. */
|
||||
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builtin_types:
|
||||
type_mask = ZEND_TYPE_FULL_MASK(*type);
|
||||
if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) {
|
||||
return 1;
|
||||
@ -1095,7 +1078,14 @@ static zend_always_inline bool zend_check_type(
|
||||
return 1;
|
||||
}
|
||||
|
||||
return zend_check_type_slow(type, arg, ref, cache_slot, scope, is_return_type, is_internal);
|
||||
return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal);
|
||||
}
|
||||
|
||||
ZEND_API bool zend_check_user_type_slow(
|
||||
zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type)
|
||||
{
|
||||
return zend_check_type_slow(
|
||||
type, arg, ref, cache_slot, is_return_type, /* is_internal */ false);
|
||||
}
|
||||
|
||||
static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
|
||||
@ -3131,7 +3121,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT
|
||||
if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT
|
||||
&& zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) {
|
||||
return 1;
|
||||
}
|
||||
|
@ -76,7 +76,8 @@ ZEND_API ZEND_COLD void zend_verify_return_error(
|
||||
ZEND_API ZEND_COLD void zend_verify_never_error(
|
||||
const zend_function *zf);
|
||||
ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref);
|
||||
ZEND_API bool zend_value_instanceof_static(zval *zv);
|
||||
ZEND_API bool zend_check_user_type_slow(
|
||||
zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type);
|
||||
|
||||
|
||||
#define ZEND_REF_TYPE_SOURCES(ref) \
|
||||
@ -459,7 +460,6 @@ ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *p
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
END_EXTERN_C()
|
||||
|
||||
#endif /* ZEND_EXECUTE_H */
|
||||
|
@ -403,14 +403,64 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name
|
||||
zend_hash_add_ptr(ht, class_name, ce);
|
||||
}
|
||||
|
||||
static inheritance_status zend_perform_covariant_class_type_check(
|
||||
/* Check whether any type in the fe_type intersection type is a subtype of the proto class. */
|
||||
static inheritance_status zend_is_intersection_subtype_of_class(
|
||||
zend_class_entry *fe_scope, zend_type fe_type,
|
||||
zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce)
|
||||
{
|
||||
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type));
|
||||
bool have_unresolved = false;
|
||||
zend_type *single_type;
|
||||
|
||||
/* Traverse the list of child types and check that at least one is
|
||||
* a subtype of the parent type being checked */
|
||||
ZEND_TYPE_FOREACH(fe_type, single_type) {
|
||||
zend_class_entry *fe_ce;
|
||||
zend_string *fe_class_name = NULL;
|
||||
if (ZEND_TYPE_HAS_NAME(*single_type)) {
|
||||
fe_class_name =
|
||||
resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type));
|
||||
if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
|
||||
if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name);
|
||||
fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
|
||||
if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name);
|
||||
fe_ce = ZEND_TYPE_CE(*single_type);
|
||||
} else {
|
||||
/* standard type in an intersection type is impossible,
|
||||
* because it would be a fatal compile error */
|
||||
ZEND_UNREACHABLE();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fe_ce || !proto_ce) {
|
||||
have_unresolved = true;
|
||||
continue;
|
||||
}
|
||||
if (unlinked_instanceof(fe_ce, proto_ce)) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
track_class_dependency(proto_ce, proto_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
|
||||
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
|
||||
}
|
||||
|
||||
/* Check whether a single class proto type is a subtype of a potentially complex fe_type. */
|
||||
static inheritance_status zend_is_class_subtype_of_type(
|
||||
zend_class_entry *fe_scope, zend_string *fe_class_name, zend_class_entry *fe_ce,
|
||||
zend_class_entry *proto_scope, zend_type proto_type) {
|
||||
bool have_unresolved = 0;
|
||||
|
||||
/* If the parent has 'object' as a return type, any class satisfies the co-variant check */
|
||||
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). */
|
||||
* are not classes (such as typedefs). */
|
||||
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
if (!fe_ce) {
|
||||
have_unresolved = 1;
|
||||
@ -430,6 +480,10 @@ static inheritance_status zend_perform_covariant_class_type_check(
|
||||
}
|
||||
|
||||
zend_type *single_type;
|
||||
|
||||
/* Traverse the list of parent types and check if the current child (FE)
|
||||
* class is the subtype of at least one of them (union) or all of them (intersection). */
|
||||
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(proto_type);
|
||||
ZEND_TYPE_FOREACH(proto_type, single_type) {
|
||||
zend_class_entry *proto_ce;
|
||||
zend_string *proto_class_name = NULL;
|
||||
@ -437,7 +491,10 @@ static inheritance_status zend_perform_covariant_class_type_check(
|
||||
proto_class_name =
|
||||
resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type));
|
||||
if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
|
||||
return INHERITANCE_SUCCESS;
|
||||
if (!is_intersection) {
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
@ -446,19 +503,45 @@ static inheritance_status zend_perform_covariant_class_type_check(
|
||||
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
proto_ce = ZEND_TYPE_CE(*single_type);
|
||||
} else {
|
||||
/* standard type */
|
||||
ZEND_ASSERT(!is_intersection);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fe_ce || !proto_ce) {
|
||||
have_unresolved = 1;
|
||||
} else if (unlinked_instanceof(fe_ce, proto_ce)) {
|
||||
continue;
|
||||
}
|
||||
if (unlinked_instanceof(fe_ce, proto_ce)) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
track_class_dependency(proto_ce, proto_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
if (!is_intersection) {
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
if (is_intersection) {
|
||||
return INHERITANCE_ERROR;
|
||||
}
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
|
||||
return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR;
|
||||
if (have_unresolved) {
|
||||
return INHERITANCE_UNRESOLVED;
|
||||
}
|
||||
return is_intersection ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
|
||||
}
|
||||
|
||||
static zend_string *get_class_from_type(
|
||||
zend_class_entry **ce, zend_class_entry *scope, zend_type single_type) {
|
||||
if (ZEND_TYPE_HAS_NAME(single_type)) {
|
||||
*ce = NULL;
|
||||
return resolve_class_name(scope, ZEND_TYPE_NAME(single_type));
|
||||
}
|
||||
if (ZEND_TYPE_HAS_CE(single_type)) {
|
||||
*ce = ZEND_TYPE_CE(single_type);
|
||||
return (*ce)->name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void register_unresolved_classes(zend_class_entry *scope, zend_type type) {
|
||||
@ -519,35 +602,85 @@ static inheritance_status zend_perform_covariant_type_check(
|
||||
}
|
||||
|
||||
zend_type *single_type;
|
||||
bool all_success = 1;
|
||||
inheritance_status early_exit_status;
|
||||
bool have_unresolved = false;
|
||||
|
||||
/* First try to check whether we can succeed without resolving anything */
|
||||
ZEND_TYPE_FOREACH(fe_type, single_type) {
|
||||
inheritance_status status;
|
||||
if (ZEND_TYPE_HAS_NAME(*single_type)) {
|
||||
zend_string *fe_class_name =
|
||||
resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type));
|
||||
status = zend_perform_covariant_class_type_check(
|
||||
fe_scope, fe_class_name, NULL, proto_scope, proto_type);
|
||||
} else if (ZEND_TYPE_HAS_CE(*single_type)) {
|
||||
zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type);
|
||||
status = zend_perform_covariant_class_type_check(
|
||||
fe_scope, fe_ce->name, fe_ce, proto_scope, proto_type);
|
||||
} else {
|
||||
continue;
|
||||
if (ZEND_TYPE_IS_INTERSECTION(fe_type)) {
|
||||
/* Currently, for object type 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 typedefs). */
|
||||
if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) {
|
||||
bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0;
|
||||
ZEND_TYPE_FOREACH(fe_type, single_type) {
|
||||
zend_class_entry *fe_ce;
|
||||
zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type);
|
||||
if (!fe_class_name) {
|
||||
continue;
|
||||
}
|
||||
if (!fe_ce) {
|
||||
fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
}
|
||||
if (fe_ce) {
|
||||
if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
have_unresolved = true;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
}
|
||||
|
||||
if (status == INHERITANCE_ERROR) {
|
||||
return INHERITANCE_ERROR;
|
||||
}
|
||||
if (status != INHERITANCE_SUCCESS) {
|
||||
all_success = 0;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
/* U_1&...&U_n < V_1&...&V_m if forall V_j. exists U_i. U_i < V_j.
|
||||
* U_1&...&U_n < V_1|...|V_m if exists V_j. exists U_i. U_i < V_j.
|
||||
* As such, we need to iterate over proto_type (V_j) first and use a different
|
||||
* quantifier depending on whether fe_type is a union or an intersection. */
|
||||
early_exit_status =
|
||||
ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS;
|
||||
ZEND_TYPE_FOREACH(proto_type, single_type) {
|
||||
zend_class_entry *proto_ce;
|
||||
zend_string *proto_class_name =
|
||||
get_class_from_type(&proto_ce, proto_scope, *single_type);
|
||||
if (!proto_class_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* All individual checks succeeded, overall success */
|
||||
if (all_success) {
|
||||
return INHERITANCE_SUCCESS;
|
||||
inheritance_status status = zend_is_intersection_subtype_of_class(
|
||||
fe_scope, fe_type, proto_scope, proto_class_name, proto_ce);
|
||||
if (status == early_exit_status) {
|
||||
return status;
|
||||
}
|
||||
if (status == INHERITANCE_UNRESOLVED) {
|
||||
have_unresolved = true;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
} else {
|
||||
/* U_1|...|U_n < V_1|...|V_m if forall U_i. exists V_j. U_i < V_j.
|
||||
* U_1|...|U_n < V_1&...&V_m if forall U_i. forall V_j. U_i < V_j.
|
||||
* We need to iterate over fe_type (U_i) first and the logic is independent of
|
||||
* whether proto_type is a union or intersection (only the inner check differs). */
|
||||
early_exit_status = INHERITANCE_ERROR;
|
||||
ZEND_TYPE_FOREACH(fe_type, single_type) {
|
||||
zend_class_entry *fe_ce;
|
||||
zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type);
|
||||
if (!fe_class_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inheritance_status status = zend_is_class_subtype_of_type(
|
||||
fe_scope, fe_class_name, fe_ce, proto_scope, proto_type);
|
||||
if (status == early_exit_status) {
|
||||
return status;
|
||||
}
|
||||
if (status == INHERITANCE_UNRESOLVED) {
|
||||
have_unresolved = true;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
}
|
||||
|
||||
if (!have_unresolved) {
|
||||
return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
|
||||
}
|
||||
|
||||
register_unresolved_classes(fe_scope, fe_type);
|
||||
|
@ -68,7 +68,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%left T_BOOLEAN_AND
|
||||
%left '|'
|
||||
%left '^'
|
||||
%left '&'
|
||||
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
||||
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
||||
%left '.'
|
||||
@ -231,6 +231,13 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%token T_COALESCE "'??'"
|
||||
%token T_POW "'**'"
|
||||
%token T_POW_EQUAL "'**='"
|
||||
/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2,
|
||||
* with only one token lookahead, bison does not know whether to reduce T1 as a complete type,
|
||||
* or shift to continue parsing an intersection type. */
|
||||
%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&'"
|
||||
/* Bison warns on duplicate token literals, so use a different dummy value here.
|
||||
* It will be fixed up by zend_yytnamerr() later. */
|
||||
%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "amp"
|
||||
%token T_BAD_CHARACTER "invalid character"
|
||||
|
||||
/* Token used to force a parse error from the lexer */
|
||||
@ -264,8 +271,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%type <ast> lexical_var_list encaps_list
|
||||
%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_without_static
|
||||
%type <ast> identifier type_expr_without_static union_type_without_static
|
||||
%type <ast> inline_function union_type
|
||||
%type <ast> identifier type_expr_without_static union_type_without_static intersection_type_without_static
|
||||
%type <ast> inline_function union_type intersection_type
|
||||
%type <ast> attributed_statement attributed_class_statement attributed_parameter
|
||||
%type <ast> attribute_decl attribute attributes attribute_group namespace_declaration_name
|
||||
%type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
|
||||
@ -301,6 +308,11 @@ semi_reserved:
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
||||
;
|
||||
|
||||
ampersand:
|
||||
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
|
||||
;
|
||||
|
||||
identifier:
|
||||
T_STRING { $$ = $1; }
|
||||
| semi_reserved {
|
||||
@ -555,7 +567,7 @@ function_declaration_statement:
|
||||
|
||||
is_reference:
|
||||
%empty { $$ = 0; }
|
||||
| '&' { $$ = ZEND_PARAM_REF; }
|
||||
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = ZEND_PARAM_REF; }
|
||||
;
|
||||
|
||||
is_variadic:
|
||||
@ -633,7 +645,7 @@ implements_list:
|
||||
|
||||
foreach_variable:
|
||||
variable { $$ = $1; }
|
||||
| '&' variable { $$ = zend_ast_create(ZEND_AST_REF, $2); }
|
||||
| ampersand variable { $$ = zend_ast_create(ZEND_AST_REF, $2); }
|
||||
| T_LIST '(' array_pair_list ')' { $$ = $3; $$->attr = ZEND_ARRAY_SYNTAX_LIST; }
|
||||
| '[' array_pair_list ']' { $$ = $2; $$->attr = ZEND_ARRAY_SYNTAX_SHORT; }
|
||||
;
|
||||
@ -785,6 +797,7 @@ type_expr:
|
||||
type { $$ = $1; }
|
||||
| '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
|
||||
| union_type { $$ = $1; }
|
||||
| intersection_type { $$ = $1; }
|
||||
;
|
||||
|
||||
type:
|
||||
@ -797,6 +810,11 @@ union_type:
|
||||
| union_type '|' type { $$ = zend_ast_list_add($1, $3); }
|
||||
;
|
||||
|
||||
intersection_type:
|
||||
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); }
|
||||
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_list_add($1, $3); }
|
||||
;
|
||||
|
||||
/* Duplicate the type rules without "static",
|
||||
* to avoid conflicts with "static" modifier for properties. */
|
||||
|
||||
@ -804,6 +822,7 @@ type_expr_without_static:
|
||||
type_without_static { $$ = $1; }
|
||||
| '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; }
|
||||
| union_type_without_static { $$ = $1; }
|
||||
| intersection_type_without_static { $$ = $1; }
|
||||
;
|
||||
|
||||
type_without_static:
|
||||
@ -819,6 +838,13 @@ union_type_without_static:
|
||||
{ $$ = zend_ast_list_add($1, $3); }
|
||||
;
|
||||
|
||||
intersection_type_without_static:
|
||||
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||
{ $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); }
|
||||
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||
{ $$ = zend_ast_list_add($1, $3); }
|
||||
;
|
||||
|
||||
return_type:
|
||||
%empty { $$ = NULL; }
|
||||
| ':' type_expr { $$ = $2; }
|
||||
@ -1047,7 +1073,7 @@ expr:
|
||||
{ $2->attr = ZEND_ARRAY_SYNTAX_SHORT; $$ = zend_ast_create(ZEND_AST_ASSIGN, $2, $5); }
|
||||
| variable '=' expr
|
||||
{ $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); }
|
||||
| variable '=' '&' variable
|
||||
| variable '=' ampersand variable
|
||||
{ $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); }
|
||||
| T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2); }
|
||||
| variable T_PLUS_EQUAL expr
|
||||
@ -1091,7 +1117,8 @@ expr:
|
||||
| expr T_LOGICAL_XOR expr
|
||||
{ $$ = zend_ast_create_binary_op(ZEND_BOOL_XOR, $1, $3); }
|
||||
| expr '|' expr { $$ = zend_ast_create_binary_op(ZEND_BW_OR, $1, $3); }
|
||||
| expr '&' expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); }
|
||||
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); }
|
||||
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); }
|
||||
| expr '^' expr { $$ = zend_ast_create_binary_op(ZEND_BW_XOR, $1, $3); }
|
||||
| expr '.' expr { $$ = zend_ast_create_binary_op(ZEND_CONCAT, $1, $3); }
|
||||
| expr '+' expr { $$ = zend_ast_create_binary_op(ZEND_ADD, $1, $3); }
|
||||
@ -1201,7 +1228,7 @@ backup_lex_pos:
|
||||
|
||||
returns_ref:
|
||||
%empty { $$ = 0; }
|
||||
| '&' { $$ = ZEND_ACC_RETURN_REFERENCE; }
|
||||
| ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; }
|
||||
;
|
||||
|
||||
lexical_vars:
|
||||
@ -1216,7 +1243,7 @@ lexical_var_list:
|
||||
|
||||
lexical_var:
|
||||
T_VARIABLE { $$ = $1; }
|
||||
| '&' T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; }
|
||||
| ampersand T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
@ -1416,9 +1443,9 @@ array_pair:
|
||||
{ $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); }
|
||||
| expr
|
||||
{ $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); }
|
||||
| expr T_DOUBLE_ARROW '&' variable
|
||||
| expr T_DOUBLE_ARROW ampersand variable
|
||||
{ $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $4, $1); }
|
||||
| '&' variable
|
||||
| ampersand variable
|
||||
{ $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $2, NULL); }
|
||||
| T_ELLIPSIS expr
|
||||
{ $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
|
||||
@ -1543,6 +1570,14 @@ static YYSIZE_T zend_yytnamerr(char *yyres, const char *yystr)
|
||||
return sizeof("token \"\\\"")-1;
|
||||
}
|
||||
|
||||
/* We used "amp" as a dummy label to avoid a duplicate token literal warning. */
|
||||
if (strcmp(toktype, "\"amp\"") == 0) {
|
||||
if (yyres) {
|
||||
yystpcpy(yyres, "token \"&\"");
|
||||
}
|
||||
return sizeof("token \"&\"")-1;
|
||||
}
|
||||
|
||||
/* Avoid unreadable """ */
|
||||
/* "'" would theoretically be just as bad, but is never currently parsed as a separate token */
|
||||
if (strcmp(toktype, "'\"'") == 0) {
|
||||
|
@ -1854,6 +1854,15 @@ NEWLINE ("\r"|"\n"|"\r\n")
|
||||
RETURN_TOKEN(T_SR);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"&"{TABS_AND_SPACES}("$"|"...") {
|
||||
yyless(1);
|
||||
RETURN_TOKEN(T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"&" {
|
||||
RETURN_TOKEN(T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"]"|")" {
|
||||
/* Check that ] and ) match up properly with a preceding [ or ( */
|
||||
RETURN_EXIT_NESTING_TOKEN(yytext[0]);
|
||||
|
@ -109,7 +109,7 @@ typedef void (*copy_ctor_func_t)(zval *pElement);
|
||||
* It shouldn't be used directly. Only through ZEND_TYPE_* macros.
|
||||
*
|
||||
* 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_IS_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 *
|
||||
@ -148,15 +148,21 @@ typedef struct {
|
||||
/* TODO: bit 21 is not used */
|
||||
/* Whether the type list is arena allocated */
|
||||
#define _ZEND_TYPE_ARENA_BIT (1u << 20)
|
||||
/* Whether the type list is an intersection type */
|
||||
#define _ZEND_TYPE_INTERSECTION_BIT (1u << 19)
|
||||
/* Whether the type is a union type */
|
||||
#define _ZEND_TYPE_UNION_BIT (1u << 18)
|
||||
/* Type mask excluding the flags above. */
|
||||
#define _ZEND_TYPE_MAY_BE_MASK ((1u << 20) - 1)
|
||||
#define _ZEND_TYPE_MAY_BE_MASK ((1u << 18) - 1)
|
||||
/* Must have same value as MAY_BE_NULL */
|
||||
#define _ZEND_TYPE_NULLABLE_BIT 0x2u
|
||||
|
||||
#define ZEND_TYPE_IS_SET(t) \
|
||||
(((t).type_mask & _ZEND_TYPE_MASK) != 0)
|
||||
|
||||
#define ZEND_TYPE_HAS_CLASS(t) \
|
||||
/* If a type is complex it means it's either a list with a union or intersection,
|
||||
* or the void pointer is a CE/Name */
|
||||
#define ZEND_TYPE_IS_COMPLEX(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0)
|
||||
|
||||
#define ZEND_TYPE_HAS_CE(t) \
|
||||
@ -168,6 +174,12 @@ typedef struct {
|
||||
#define ZEND_TYPE_HAS_LIST(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
|
||||
|
||||
#define ZEND_TYPE_IS_INTERSECTION(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_INTERSECTION_BIT) != 0)
|
||||
|
||||
#define ZEND_TYPE_IS_UNION(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_UNION_BIT) != 0)
|
||||
|
||||
#define ZEND_TYPE_USES_ARENA(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0)
|
||||
|
||||
|
@ -4224,7 +4224,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
|
@ -9995,7 +9995,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
@ -20370,7 +20370,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
@ -27901,7 +27901,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
@ -35050,7 +35050,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
@ -46789,7 +46789,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU
|
||||
}
|
||||
|
||||
SAVE_OPLINE();
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) {
|
||||
if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
|
||||
zend_verify_return_error(EX(func), retval_ptr);
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
|
@ -1320,98 +1320,13 @@ check_indirect:
|
||||
return ref;
|
||||
}
|
||||
|
||||
static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_info *arg_info, void **cache_slot)
|
||||
{
|
||||
uint32_t type_mask;
|
||||
|
||||
if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) {
|
||||
zend_class_entry *ce;
|
||||
if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
|
||||
zend_type *list_type;
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) {
|
||||
if (*cache_slot) {
|
||||
ce = *cache_slot;
|
||||
} else {
|
||||
zend_string *name = ZEND_TYPE_NAME(*list_type);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (!ce) {
|
||||
cache_slot++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = zend_fetch_class(name,
|
||||
ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT);
|
||||
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 {
|
||||
if (EXPECTED(*cache_slot)) {
|
||||
ce = (zend_class_entry *) *cache_slot;
|
||||
} else {
|
||||
zend_string *name = ZEND_TYPE_NAME(arg_info->type);
|
||||
|
||||
if (ZSTR_HAS_CE_CACHE(name)) {
|
||||
ce = ZSTR_GET_CE_CACHE(name);
|
||||
if (!ce) {
|
||||
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
goto builtin_types;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ce = zend_fetch_class(name,
|
||||
ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT);
|
||||
if (UNEXPECTED(!ce)) {
|
||||
goto builtin_types;
|
||||
}
|
||||
}
|
||||
*cache_slot = (void *) ce;
|
||||
}
|
||||
if (instanceof_function(Z_OBJCE_P(arg), ce)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builtin_types:
|
||||
type_mask = ZEND_TYPE_FULL_MASK(arg_info->type);
|
||||
if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) {
|
||||
return 1;
|
||||
}
|
||||
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
|
||||
return 1;
|
||||
}
|
||||
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
|
||||
return 1;
|
||||
}
|
||||
if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg_info)
|
||||
{
|
||||
zend_execute_data *execute_data = EG(current_execute_data);
|
||||
const zend_op *opline = EX(opline);
|
||||
void **cache_slot = CACHE_ADDR(opline->extended_value);
|
||||
bool ret;
|
||||
|
||||
ret = zend_jit_verify_type_common(arg, arg_info, cache_slot);
|
||||
bool ret = zend_check_user_type_slow(
|
||||
&arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false);
|
||||
if (UNEXPECTED(!ret)) {
|
||||
zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg);
|
||||
return 0;
|
||||
@ -1421,7 +1336,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg
|
||||
|
||||
static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_array *op_array, zend_arg_info *arg_info, void **cache_slot)
|
||||
{
|
||||
if (UNEXPECTED(!zend_jit_verify_type_common(arg, arg_info, cache_slot))) {
|
||||
if (UNEXPECTED(!zend_check_user_type_slow(
|
||||
&arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) {
|
||||
zend_verify_return_error((zend_function*)op_array, arg);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,10 @@ function getClassUnion(): stdClass|FooBar {
|
||||
return new stdClass;
|
||||
}
|
||||
|
||||
function getClassIntersection(): Traversable&Countable {
|
||||
return new ArrayObject;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
$_main:
|
||||
@ -69,6 +73,16 @@ getClassUnion:
|
||||
LIVE RANGES:
|
||||
0: 0001 - 0002 (new)
|
||||
|
||||
getClassIntersection:
|
||||
; (lines=3, args=0, vars=0, tmps=1)
|
||||
; (after optimizer)
|
||||
; %s
|
||||
0000 V0 = NEW 0 string("ArrayObject")
|
||||
0001 DO_FCALL
|
||||
0002 RETURN V0
|
||||
LIVE RANGES:
|
||||
0: 0001 - 0002 (new)
|
||||
|
||||
Test1::getIntOrFloat:
|
||||
; (lines=2, args=1, vars=1, tmps=0)
|
||||
; (after optimizer)
|
||||
|
@ -79,6 +79,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr;
|
||||
PHPAPI zend_class_entry *reflection_parameter_ptr;
|
||||
PHPAPI zend_class_entry *reflection_type_ptr;
|
||||
PHPAPI zend_class_entry *reflection_named_type_ptr;
|
||||
PHPAPI zend_class_entry *reflection_intersection_type_ptr;
|
||||
PHPAPI zend_class_entry *reflection_union_type_ptr;
|
||||
PHPAPI zend_class_entry *reflection_class_ptr;
|
||||
PHPAPI zend_class_entry *reflection_object_ptr;
|
||||
@ -1319,22 +1320,40 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
typedef enum {
|
||||
NAMED_TYPE = 0,
|
||||
UNION_TYPE = 1,
|
||||
INTERSECTION_TYPE = 2
|
||||
} reflection_type_kind;
|
||||
|
||||
/* 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 bool is_union_type(zend_type type) {
|
||||
if (ZEND_TYPE_HAS_LIST(type)) {
|
||||
return 1;
|
||||
}
|
||||
static reflection_type_kind get_type_kind(zend_type type) {
|
||||
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 (ZEND_TYPE_HAS_LIST(type)) {
|
||||
if (ZEND_TYPE_IS_INTERSECTION(type)) {
|
||||
return INTERSECTION_TYPE;
|
||||
}
|
||||
ZEND_ASSERT(ZEND_TYPE_IS_UNION(type));
|
||||
return UNION_TYPE;
|
||||
}
|
||||
if (type_mask_without_null == MAY_BE_BOOL) {
|
||||
return 0;
|
||||
|
||||
if (ZEND_TYPE_IS_COMPLEX(type)) {
|
||||
if (type_mask_without_null != 0) {
|
||||
return UNION_TYPE;
|
||||
}
|
||||
return NAMED_TYPE;
|
||||
}
|
||||
if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) {
|
||||
return NAMED_TYPE;
|
||||
}
|
||||
/* Check that only one bit is set. */
|
||||
return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
|
||||
if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) {
|
||||
return UNION_TYPE;
|
||||
}
|
||||
return NAMED_TYPE;
|
||||
}
|
||||
|
||||
/* {{{ reflection_type_factory */
|
||||
@ -1342,14 +1361,26 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be
|
||||
{
|
||||
reflection_object *intern;
|
||||
type_reference *reference;
|
||||
bool is_union = is_union_type(type);
|
||||
reflection_type_kind type_kind = get_type_kind(type);
|
||||
bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY;
|
||||
|
||||
reflection_instantiate(is_union && !is_mixed ? reflection_union_type_ptr : reflection_named_type_ptr, object);
|
||||
switch (type_kind) {
|
||||
case INTERSECTION_TYPE:
|
||||
reflection_instantiate(reflection_intersection_type_ptr, object);
|
||||
break;
|
||||
case UNION_TYPE:
|
||||
reflection_instantiate(reflection_union_type_ptr, object);
|
||||
break;
|
||||
case NAMED_TYPE:
|
||||
reflection_instantiate(reflection_named_type_ptr, object);
|
||||
break;
|
||||
EMPTY_SWITCH_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
intern = Z_REFLECTION_P(object);
|
||||
reference = (type_reference*) emalloc(sizeof(type_reference));
|
||||
reference->type = type;
|
||||
reference->legacy_behavior = legacy_behavior && !is_union && !is_mixed;
|
||||
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed;
|
||||
intern->ptr = reference;
|
||||
intern->ref_type = REF_TYPE_TYPE;
|
||||
|
||||
@ -3097,6 +3128,27 @@ ZEND_METHOD(ReflectionUnionType, getTypes)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Returns the types that are part of this intersection type */
|
||||
ZEND_METHOD(ReflectionIntersectionType, getTypes)
|
||||
{
|
||||
reflection_object *intern;
|
||||
type_reference *param;
|
||||
zend_type *list_type;
|
||||
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(param);
|
||||
|
||||
ZEND_ASSERT(ZEND_TYPE_HAS_LIST(param->type));
|
||||
|
||||
array_init(return_value);
|
||||
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) {
|
||||
append_type(return_value, *list_type);
|
||||
} ZEND_TYPE_LIST_FOREACH_END();
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Constructor. Throws an Exception in case the given method does not exist */
|
||||
ZEND_METHOD(ReflectionMethod, __construct)
|
||||
{
|
||||
@ -7017,6 +7069,9 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
|
||||
reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr);
|
||||
reflection_init_class_handlers(reflection_union_type_ptr);
|
||||
|
||||
reflection_intersection_type_ptr = register_class_ReflectionIntersectionType(reflection_type_ptr);
|
||||
reflection_init_class_handlers(reflection_intersection_type_ptr);
|
||||
|
||||
reflection_method_ptr = register_class_ReflectionMethod(reflection_function_abstract_ptr);
|
||||
reflection_init_class_handlers(reflection_method_ptr);
|
||||
|
||||
|
@ -596,6 +596,11 @@ class ReflectionUnionType extends ReflectionType
|
||||
public function getTypes(): array {}
|
||||
}
|
||||
|
||||
class ReflectionIntersectionType extends ReflectionType
|
||||
{
|
||||
public function getTypes(): array {}
|
||||
}
|
||||
|
||||
class ReflectionExtension implements Reflector
|
||||
{
|
||||
public string $name;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: e66c459f457f71cb677a93652364ab7e81be8b0e */
|
||||
* Stub hash: d8e686125cf213e019c1d706867e3c178fa057d2 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
|
||||
@ -468,6 +468,8 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables
|
||||
|
||||
#define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables
|
||||
|
||||
#define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionExtension___construct, 0, 0, 1)
|
||||
@ -766,6 +768,7 @@ ZEND_METHOD(ReflectionType, __toString);
|
||||
ZEND_METHOD(ReflectionNamedType, getName);
|
||||
ZEND_METHOD(ReflectionNamedType, isBuiltin);
|
||||
ZEND_METHOD(ReflectionUnionType, getTypes);
|
||||
ZEND_METHOD(ReflectionIntersectionType, getTypes);
|
||||
ZEND_METHOD(ReflectionExtension, __construct);
|
||||
ZEND_METHOD(ReflectionExtension, __toString);
|
||||
ZEND_METHOD(ReflectionExtension, getName);
|
||||
@ -1070,6 +1073,12 @@ static const zend_function_entry class_ReflectionUnionType_methods[] = {
|
||||
};
|
||||
|
||||
|
||||
static const zend_function_entry class_ReflectionIntersectionType_methods[] = {
|
||||
ZEND_ME(ReflectionIntersectionType, getTypes, arginfo_class_ReflectionIntersectionType_getTypes, ZEND_ACC_PUBLIC)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
|
||||
static const zend_function_entry class_ReflectionExtension_methods[] = {
|
||||
ZEND_MALIAS(ReflectionClass, __clone, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE)
|
||||
ZEND_ME(ReflectionExtension, __construct, arginfo_class_ReflectionExtension___construct, ZEND_ACC_PUBLIC)
|
||||
@ -1367,6 +1376,16 @@ static zend_class_entry *register_class_ReflectionUnionType(zend_class_entry *cl
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
static zend_class_entry *register_class_ReflectionIntersectionType(zend_class_entry *class_entry_ReflectionType)
|
||||
{
|
||||
zend_class_entry ce, *class_entry;
|
||||
|
||||
INIT_CLASS_ENTRY(ce, "ReflectionIntersectionType", class_ReflectionIntersectionType_methods);
|
||||
class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionType);
|
||||
|
||||
return class_entry;
|
||||
}
|
||||
|
||||
static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *class_entry_Reflector)
|
||||
{
|
||||
zend_class_entry ce, *class_entry;
|
||||
|
@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection');
|
||||
var_dump($ext->getClasses());
|
||||
?>
|
||||
--EXPECT--
|
||||
array(23) {
|
||||
array(24) {
|
||||
["ReflectionException"]=>
|
||||
object(ReflectionClass)#2 (1) {
|
||||
["name"]=>
|
||||
@ -59,68 +59,73 @@ array(23) {
|
||||
["name"]=>
|
||||
string(19) "ReflectionUnionType"
|
||||
}
|
||||
["ReflectionMethod"]=>
|
||||
["ReflectionIntersectionType"]=>
|
||||
object(ReflectionClass)#12 (1) {
|
||||
["name"]=>
|
||||
string(26) "ReflectionIntersectionType"
|
||||
}
|
||||
["ReflectionMethod"]=>
|
||||
object(ReflectionClass)#13 (1) {
|
||||
["name"]=>
|
||||
string(16) "ReflectionMethod"
|
||||
}
|
||||
["ReflectionClass"]=>
|
||||
object(ReflectionClass)#13 (1) {
|
||||
object(ReflectionClass)#14 (1) {
|
||||
["name"]=>
|
||||
string(15) "ReflectionClass"
|
||||
}
|
||||
["ReflectionObject"]=>
|
||||
object(ReflectionClass)#14 (1) {
|
||||
object(ReflectionClass)#15 (1) {
|
||||
["name"]=>
|
||||
string(16) "ReflectionObject"
|
||||
}
|
||||
["ReflectionProperty"]=>
|
||||
object(ReflectionClass)#15 (1) {
|
||||
object(ReflectionClass)#16 (1) {
|
||||
["name"]=>
|
||||
string(18) "ReflectionProperty"
|
||||
}
|
||||
["ReflectionClassConstant"]=>
|
||||
object(ReflectionClass)#16 (1) {
|
||||
object(ReflectionClass)#17 (1) {
|
||||
["name"]=>
|
||||
string(23) "ReflectionClassConstant"
|
||||
}
|
||||
["ReflectionExtension"]=>
|
||||
object(ReflectionClass)#17 (1) {
|
||||
object(ReflectionClass)#18 (1) {
|
||||
["name"]=>
|
||||
string(19) "ReflectionExtension"
|
||||
}
|
||||
["ReflectionZendExtension"]=>
|
||||
object(ReflectionClass)#18 (1) {
|
||||
object(ReflectionClass)#19 (1) {
|
||||
["name"]=>
|
||||
string(23) "ReflectionZendExtension"
|
||||
}
|
||||
["ReflectionReference"]=>
|
||||
object(ReflectionClass)#19 (1) {
|
||||
object(ReflectionClass)#20 (1) {
|
||||
["name"]=>
|
||||
string(19) "ReflectionReference"
|
||||
}
|
||||
["ReflectionAttribute"]=>
|
||||
object(ReflectionClass)#20 (1) {
|
||||
object(ReflectionClass)#21 (1) {
|
||||
["name"]=>
|
||||
string(19) "ReflectionAttribute"
|
||||
}
|
||||
["ReflectionEnum"]=>
|
||||
object(ReflectionClass)#21 (1) {
|
||||
object(ReflectionClass)#22 (1) {
|
||||
["name"]=>
|
||||
string(14) "ReflectionEnum"
|
||||
}
|
||||
["ReflectionEnumUnitCase"]=>
|
||||
object(ReflectionClass)#22 (1) {
|
||||
object(ReflectionClass)#23 (1) {
|
||||
["name"]=>
|
||||
string(22) "ReflectionEnumUnitCase"
|
||||
}
|
||||
["ReflectionEnumBackedCase"]=>
|
||||
object(ReflectionClass)#23 (1) {
|
||||
object(ReflectionClass)#24 (1) {
|
||||
["name"]=>
|
||||
string(24) "ReflectionEnumBackedCase"
|
||||
}
|
||||
["ReflectionFiber"]=>
|
||||
object(ReflectionClass)#24 (1) {
|
||||
object(ReflectionClass)#25 (1) {
|
||||
["name"]=>
|
||||
string(15) "ReflectionFiber"
|
||||
}
|
||||
|
80
ext/reflection/tests/intersection_types.phpt
Normal file
80
ext/reflection/tests/intersection_types.phpt
Normal file
@ -0,0 +1,80 @@
|
||||
--TEST--
|
||||
Intersection types in reflection
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function dumpType(ReflectionIntersectionType $rt) {
|
||||
echo "Type $rt:\n";
|
||||
echo "Allows null: " . json_encode($rt->allowsNull()) . "\n";
|
||||
foreach ($rt->getTypes() as $type) {
|
||||
echo " Name: " . $type->getName() . "\n";
|
||||
echo " String: " . (string) $type . "\n";
|
||||
echo " Allows Null: " . json_encode($type->allowsNull()) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
function test1(): X&Y&Z&Traversable&Countable { }
|
||||
|
||||
class Test {
|
||||
public X&Y&Countable $prop;
|
||||
}
|
||||
|
||||
dumpType((new ReflectionFunction('test1'))->getReturnType());
|
||||
|
||||
$rc = new ReflectionClass(Test::class);
|
||||
$rp = $rc->getProperty('prop');
|
||||
dumpType($rp->getType());
|
||||
|
||||
/* Force CE resolution of the property type */
|
||||
|
||||
interface y {}
|
||||
class x implements Y, Countable {
|
||||
public function count() {}
|
||||
}
|
||||
$test = new Test;
|
||||
$test->prop = new x;
|
||||
|
||||
$rp = $rc->getProperty('prop');
|
||||
dumpType($rp->getType());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Type X&Y&Z&Traversable&Countable:
|
||||
Allows null: false
|
||||
Name: X
|
||||
String: X
|
||||
Allows Null: false
|
||||
Name: Y
|
||||
String: Y
|
||||
Allows Null: false
|
||||
Name: Z
|
||||
String: Z
|
||||
Allows Null: false
|
||||
Name: Traversable
|
||||
String: Traversable
|
||||
Allows Null: false
|
||||
Name: Countable
|
||||
String: Countable
|
||||
Allows Null: false
|
||||
Type X&Y&Countable:
|
||||
Allows null: false
|
||||
Name: X
|
||||
String: X
|
||||
Allows Null: false
|
||||
Name: Y
|
||||
String: Y
|
||||
Allows Null: false
|
||||
Name: Countable
|
||||
String: Countable
|
||||
Allows Null: false
|
||||
Type x&y&Countable:
|
||||
Allows null: false
|
||||
Name: x
|
||||
String: x
|
||||
Allows Null: false
|
||||
Name: y
|
||||
String: y
|
||||
Allows Null: false
|
||||
Name: Countable
|
||||
String: Countable
|
||||
Allows Null: false
|
@ -917,7 +917,14 @@ array(47) {
|
||||
int(1)
|
||||
}
|
||||
[42]=>
|
||||
string(1) "&"
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(%d)
|
||||
[1]=>
|
||||
string(1) "&"
|
||||
[2]=>
|
||||
int(1)
|
||||
}
|
||||
[43]=>
|
||||
array(3) {
|
||||
[0]=>
|
||||
|
@ -167,6 +167,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
|
||||
REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT);
|
||||
}
|
||||
@ -317,6 +319,8 @@ char *get_token_type_name(int token_type)
|
||||
case T_COALESCE: return "T_COALESCE";
|
||||
case T_POW: return "T_POW";
|
||||
case T_POW_EQUAL: return "T_POW_EQUAL";
|
||||
case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG";
|
||||
case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG";
|
||||
case T_BAD_CHARACTER: return "T_BAD_CHARACTER";
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user