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:
George Peter Banyard 2021-07-05 14:11:03 +02:00 committed by GitHub
parent 629965c80f
commit 069a9fa5e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1640 additions and 330 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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,

View File

@ -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) {
}

View File

@ -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) {
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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===

View File

@ -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

View File

@ -0,0 +1,15 @@
--TEST--
Intersection with child class
--FILE--
<?php
class A {}
class B extends A {}
function test(): A&B {
}
?>
===DONE===
--EXPECT--
===DONE===

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {
}

View File

@ -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===

View File

@ -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===

View File

@ -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===

View File

@ -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===

View File

@ -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===

View File

@ -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===

View File

@ -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===

View File

@ -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);

View File

@ -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, '?');
}

View File

@ -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,

View File

@ -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",

View File

@ -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;
}

View File

@ -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 */

View File

@ -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);

View File

@ -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) {

View File

@ -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]);

View File

@ -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)

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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"
}

View 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

View File

@ -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]=>

View File

@ -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";
}