Skip to content

Commit 79d2425

Browse files
committed
Add ReflectionIntersectionType
1 parent 4fa3019 commit 79d2425

File tree

5 files changed

+188
-24
lines changed

5 files changed

+188
-24
lines changed

ext/reflection/php_reflection.c

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr;
7878
PHPAPI zend_class_entry *reflection_parameter_ptr;
7979
PHPAPI zend_class_entry *reflection_type_ptr;
8080
PHPAPI zend_class_entry *reflection_named_type_ptr;
81+
PHPAPI zend_class_entry *reflection_intersection_type_ptr;
8182
PHPAPI zend_class_entry *reflection_union_type_ptr;
8283
PHPAPI zend_class_entry *reflection_class_ptr;
8384
PHPAPI zend_class_entry *reflection_object_ptr;
@@ -1325,37 +1326,67 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje
13251326
}
13261327
/* }}} */
13271328

1329+
typedef enum reflection_type_kind {
1330+
NAMED_TYPE = 0,
1331+
UNION_TYPE = 1,
1332+
INTERSECTION_TYPE = 2
1333+
} reflection_type_kind;
1334+
13281335
/* For backwards compatibility reasons, we need to return T|null style unions
13291336
* as a ReflectionNamedType. Here we determine what counts as a union type and
13301337
* what doesn't. */
1331-
static bool is_union_type(zend_type type) {
1338+
static reflection_type_kind get_type_kind(zend_type type) {
1339+
uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
1340+
13321341
if (ZEND_TYPE_HAS_LIST(type)) {
1333-
return 1;
1342+
if (ZEND_TYPE_HAS_INTERSECTION(type)) {
1343+
return INTERSECTION_TYPE;
1344+
}
1345+
ZEND_ASSERT(ZEND_TYPE_HAS_UNION(type));
1346+
return UNION_TYPE;
13341347
}
1335-
uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
1348+
13361349
if (ZEND_TYPE_HAS_CLASS(type)) {
1337-
return type_mask_without_null != 0;
1350+
if (type_mask_without_null != 0) {
1351+
return UNION_TYPE;
1352+
}
1353+
return NAMED_TYPE;
13381354
}
1339-
if (type_mask_without_null == MAY_BE_BOOL) {
1340-
return 0;
1355+
if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) {
1356+
return NAMED_TYPE;
13411357
}
13421358
/* Check that only one bit is set. */
1343-
return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
1359+
if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) {
1360+
return UNION_TYPE;
1361+
}
1362+
return NAMED_TYPE;
13441363
}
13451364

13461365
/* {{{ reflection_type_factory */
13471366
static void reflection_type_factory(zend_type type, zval *object, bool legacy_behavior)
13481367
{
13491368
reflection_object *intern;
13501369
type_reference *reference;
1351-
bool is_union = is_union_type(type);
1370+
reflection_type_kind type_kind = get_type_kind(type);
13521371
bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY;
13531372

1354-
reflection_instantiate(is_union && !is_mixed ? reflection_union_type_ptr : reflection_named_type_ptr, object);
1373+
switch (type_kind) {
1374+
case INTERSECTION_TYPE:
1375+
reflection_instantiate(reflection_intersection_type_ptr, object);
1376+
break;
1377+
case UNION_TYPE:
1378+
reflection_instantiate(reflection_union_type_ptr, object);
1379+
break;
1380+
case NAMED_TYPE:
1381+
reflection_instantiate(reflection_named_type_ptr, object);
1382+
break;
1383+
EMPTY_SWITCH_DEFAULT_CASE();
1384+
}
1385+
13551386
intern = Z_REFLECTION_P(object);
13561387
reference = (type_reference*) emalloc(sizeof(type_reference));
13571388
reference->type = type;
1358-
reference->legacy_behavior = legacy_behavior && !is_union && !is_mixed;
1389+
reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed;
13591390
intern->ptr = reference;
13601391
intern->ref_type = REF_TYPE_TYPE;
13611392

@@ -3043,6 +3074,27 @@ ZEND_METHOD(ReflectionUnionType, getTypes)
30433074
}
30443075
/* }}} */
30453076

3077+
/* {{{ Returns the types that are part of this intersection type */
3078+
ZEND_METHOD(ReflectionIntersectionType, getTypes)
3079+
{
3080+
reflection_object *intern;
3081+
type_reference *param;
3082+
zend_type *list_type;
3083+
3084+
if (zend_parse_parameters_none() == FAILURE) {
3085+
RETURN_THROWS();
3086+
}
3087+
GET_REFLECTION_OBJECT_PTR(param);
3088+
3089+
ZEND_ASSERT(ZEND_TYPE_HAS_LIST(param->type));
3090+
3091+
array_init(return_value);
3092+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) {
3093+
append_type(return_value, *list_type);
3094+
} ZEND_TYPE_LIST_FOREACH_END();
3095+
}
3096+
/* }}} */
3097+
30463098
/* {{{ Constructor. Throws an Exception in case the given method does not exist */
30473099
ZEND_METHOD(ReflectionMethod, __construct)
30483100
{
@@ -6795,6 +6847,9 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
67956847
reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr);
67966848
reflection_init_class_handlers(reflection_union_type_ptr);
67976849

6850+
reflection_intersection_type_ptr = register_class_ReflectionIntersectionType(reflection_type_ptr);
6851+
reflection_init_class_handlers(reflection_intersection_type_ptr);
6852+
67986853
reflection_method_ptr = register_class_ReflectionMethod(reflection_function_abstract_ptr);
67996854
reflection_init_class_handlers(reflection_method_ptr);
68006855

ext/reflection/php_reflection.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ class ReflectionUnionType extends ReflectionType
591591
public function getTypes(): array {}
592592
}
593593

594+
class ReflectionIntersectionType extends ReflectionType
595+
{
596+
public function getTypes(): array {}
597+
}
598+
594599
class ReflectionExtension implements Reflector
595600
{
596601
public string $name;

ext/reflection/php_reflection_arginfo.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 3594ec0b0c3ed7266223be9c6b426aac56e3aabe */
2+
* Stub hash: 55c35924ce3b13fb3a6199a565e02ddf5f1606f0 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@@ -422,6 +422,8 @@ ZEND_END_ARG_INFO()
422422
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0)
423423
ZEND_END_ARG_INFO()
424424

425+
#define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionUnionType_getTypes
426+
425427
#define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone
426428

427429
#define arginfo_class_ReflectionExtension___construct arginfo_class_ReflectionClass_hasMethod
@@ -696,6 +698,7 @@ ZEND_METHOD(ReflectionType, __toString);
696698
ZEND_METHOD(ReflectionNamedType, getName);
697699
ZEND_METHOD(ReflectionNamedType, isBuiltin);
698700
ZEND_METHOD(ReflectionUnionType, getTypes);
701+
ZEND_METHOD(ReflectionIntersectionType, getTypes);
699702
ZEND_METHOD(ReflectionExtension, __construct);
700703
ZEND_METHOD(ReflectionExtension, __toString);
701704
ZEND_METHOD(ReflectionExtension, getName);
@@ -991,6 +994,12 @@ static const zend_function_entry class_ReflectionUnionType_methods[] = {
991994
};
992995

993996

997+
static const zend_function_entry class_ReflectionIntersectionType_methods[] = {
998+
ZEND_ME(ReflectionIntersectionType, getTypes, arginfo_class_ReflectionIntersectionType_getTypes, ZEND_ACC_PUBLIC)
999+
ZEND_FE_END
1000+
};
1001+
1002+
9941003
static const zend_function_entry class_ReflectionExtension_methods[] = {
9951004
ZEND_MALIAS(ReflectionClass, __clone, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL)
9961005
ZEND_ME(ReflectionExtension, __construct, arginfo_class_ReflectionExtension___construct, ZEND_ACC_PUBLIC)
@@ -1277,6 +1286,16 @@ static zend_class_entry *register_class_ReflectionUnionType(zend_class_entry *cl
12771286
return class_entry;
12781287
}
12791288

1289+
static zend_class_entry *register_class_ReflectionIntersectionType(zend_class_entry *class_entry_ReflectionType)
1290+
{
1291+
zend_class_entry ce, *class_entry;
1292+
1293+
INIT_CLASS_ENTRY(ce, "ReflectionIntersectionType", class_ReflectionIntersectionType_methods);
1294+
class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionType);
1295+
1296+
return class_entry;
1297+
}
1298+
12801299
static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *class_entry_Reflector)
12811300
{
12821301
zend_class_entry ce, *class_entry;

ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection');
88
var_dump($ext->getClasses());
99
?>
1010
--EXPECT--
11-
array(22) {
11+
array(23) {
1212
["ReflectionException"]=>
1313
object(ReflectionClass)#2 (1) {
1414
["name"]=>
@@ -59,63 +59,68 @@ array(22) {
5959
["name"]=>
6060
string(19) "ReflectionUnionType"
6161
}
62-
["ReflectionMethod"]=>
62+
["ReflectionIntersectionType"]=>
6363
object(ReflectionClass)#12 (1) {
64+
["name"]=>
65+
string(26) "ReflectionIntersectionType"
66+
}
67+
["ReflectionMethod"]=>
68+
object(ReflectionClass)#13 (1) {
6469
["name"]=>
6570
string(16) "ReflectionMethod"
6671
}
6772
["ReflectionClass"]=>
68-
object(ReflectionClass)#13 (1) {
73+
object(ReflectionClass)#14 (1) {
6974
["name"]=>
7075
string(15) "ReflectionClass"
7176
}
7277
["ReflectionObject"]=>
73-
object(ReflectionClass)#14 (1) {
78+
object(ReflectionClass)#15 (1) {
7479
["name"]=>
7580
string(16) "ReflectionObject"
7681
}
7782
["ReflectionProperty"]=>
78-
object(ReflectionClass)#15 (1) {
83+
object(ReflectionClass)#16 (1) {
7984
["name"]=>
8085
string(18) "ReflectionProperty"
8186
}
8287
["ReflectionClassConstant"]=>
83-
object(ReflectionClass)#16 (1) {
88+
object(ReflectionClass)#17 (1) {
8489
["name"]=>
8590
string(23) "ReflectionClassConstant"
8691
}
8792
["ReflectionExtension"]=>
88-
object(ReflectionClass)#17 (1) {
93+
object(ReflectionClass)#18 (1) {
8994
["name"]=>
9095
string(19) "ReflectionExtension"
9196
}
9297
["ReflectionZendExtension"]=>
93-
object(ReflectionClass)#18 (1) {
98+
object(ReflectionClass)#19 (1) {
9499
["name"]=>
95100
string(23) "ReflectionZendExtension"
96101
}
97102
["ReflectionReference"]=>
98-
object(ReflectionClass)#19 (1) {
103+
object(ReflectionClass)#20 (1) {
99104
["name"]=>
100105
string(19) "ReflectionReference"
101106
}
102107
["ReflectionAttribute"]=>
103-
object(ReflectionClass)#20 (1) {
108+
object(ReflectionClass)#21 (1) {
104109
["name"]=>
105110
string(19) "ReflectionAttribute"
106111
}
107112
["ReflectionEnum"]=>
108-
object(ReflectionClass)#21 (1) {
113+
object(ReflectionClass)#22 (1) {
109114
["name"]=>
110115
string(14) "ReflectionEnum"
111116
}
112117
["ReflectionEnumUnitCase"]=>
113-
object(ReflectionClass)#22 (1) {
118+
object(ReflectionClass)#23 (1) {
114119
["name"]=>
115120
string(22) "ReflectionEnumUnitCase"
116121
}
117122
["ReflectionEnumBackedCase"]=>
118-
object(ReflectionClass)#23 (1) {
123+
object(ReflectionClass)#24 (1) {
119124
["name"]=>
120125
string(24) "ReflectionEnumBackedCase"
121126
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--TEST--
2+
Intersection types in reflection
3+
--FILE--
4+
<?php
5+
6+
function dumpType(ReflectionIntersectionType $rt) {
7+
echo "Type $rt:\n";
8+
echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
9+
foreach ($rt->getTypes() as $type) {
10+
echo " Name: " . $type->getName() . "\n";
11+
echo " String: " . (string) $type . "\n";
12+
echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n";
13+
}
14+
}
15+
16+
function test1(): X&Y&Z&Traversable&Countable { }
17+
18+
class Test {
19+
public X&Y&Countable $prop;
20+
}
21+
22+
dumpType((new ReflectionFunction('test1'))->getReturnType());
23+
24+
$rc = new ReflectionClass(Test::class);
25+
$rp = $rc->getProperty('prop');
26+
dumpType($rp->getType());
27+
28+
/* Force CE resolution of the property type */
29+
30+
interface y {}
31+
class x implements Y, Countable {
32+
public function count() {}
33+
}
34+
$test = new Test;
35+
$test->prop = new x;
36+
37+
$rp = $rc->getProperty('prop');
38+
dumpType($rp->getType());
39+
40+
?>
41+
--EXPECT--
42+
Type X&Y&Z&Traversable&Countable:
43+
Allows null: false
44+
Name: X
45+
String: X
46+
Allows Null: false
47+
Name: Y
48+
String: Y
49+
Allows Null: false
50+
Name: Z
51+
String: Z
52+
Allows Null: false
53+
Name: Traversable
54+
String: Traversable
55+
Allows Null: false
56+
Name: Countable
57+
String: Countable
58+
Allows Null: false
59+
Type X&Y&Countable:
60+
Allows null: false
61+
Name: X
62+
String: X
63+
Allows Null: false
64+
Name: Y
65+
String: Y
66+
Allows Null: false
67+
Name: Countable
68+
String: Countable
69+
Allows Null: false
70+
Type x&y&Countable:
71+
Allows null: false
72+
Name: x
73+
String: x
74+
Allows Null: false
75+
Name: y
76+
String: y
77+
Allows Null: false
78+
Name: Countable
79+
String: Countable
80+
Allows Null: false

0 commit comments

Comments
 (0)