Skip to content

Commit 236a72f

Browse files
committed
Add ReflectionUnionType
1 parent 02cc6c2 commit 236a72f

File tree

4 files changed

+248
-17
lines changed

4 files changed

+248
-17
lines changed

Zend/zend_compile.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,9 +1164,8 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
11641164
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID));
11651165
}
11661166

1167-
ZEND_ASSERT(str && "There should be at least one type!");
11681167
if (type_mask & MAY_BE_NULL) {
1169-
zend_bool is_union = memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL;
1168+
zend_bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL;
11701169
if (!is_union) {
11711170
zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0);
11721171
ZSTR_VAL(nullable_str)[0] = '?';

ext/reflection/php_reflection.c

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr;
7474
PHPAPI zend_class_entry *reflection_parameter_ptr;
7575
PHPAPI zend_class_entry *reflection_type_ptr;
7676
PHPAPI zend_class_entry *reflection_named_type_ptr;
77+
PHPAPI zend_class_entry *reflection_union_type_ptr;
7778
PHPAPI zend_class_entry *reflection_class_ptr;
7879
PHPAPI zend_class_entry *reflection_object_ptr;
7980
PHPAPI zend_class_entry *reflection_method_ptr;
@@ -127,6 +128,8 @@ typedef struct _parameter_reference {
127128
/* Struct for type hints */
128129
typedef struct _type_reference {
129130
zend_type type;
131+
/* Whether to use backwards compatible null representation */
132+
zend_bool legacy_behavior;
130133
} type_reference;
131134

132135
typedef enum {
@@ -1124,16 +1127,37 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje
11241127
}
11251128
/* }}} */
11261129

1130+
/* For backwards compatibility reasons, we need to return T|null style unions
1131+
* as a ReflectionNamedType. Here we determine what counts as a union type and
1132+
* what doesn't. */
1133+
static zend_bool is_union_type(zend_type type) {
1134+
if (ZEND_TYPE_HAS_LIST(type)) {
1135+
return 1;
1136+
}
1137+
uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
1138+
if (ZEND_TYPE_HAS_CLASS(type)) {
1139+
return type_mask_without_null != 0;
1140+
}
1141+
if (type_mask_without_null == MAY_BE_BOOL) {
1142+
return 0;
1143+
}
1144+
/* Check that only one bit is set. */
1145+
return (type_mask_without_null & (type_mask_without_null - 1)) != 0;
1146+
}
1147+
11271148
/* {{{ reflection_type_factory */
1128-
static void reflection_type_factory(zend_type type, zval *object)
1149+
static void reflection_type_factory(zend_type type, zval *object, zend_bool legacy_behavior)
11291150
{
11301151
reflection_object *intern;
11311152
type_reference *reference;
1153+
zend_bool is_union = is_union_type(type);
11321154

1133-
reflection_instantiate(reflection_named_type_ptr, object);
1155+
reflection_instantiate(
1156+
is_union ? reflection_union_type_ptr : reflection_named_type_ptr, object);
11341157
intern = Z_REFLECTION_P(object);
11351158
reference = (type_reference*) emalloc(sizeof(type_reference));
11361159
reference->type = type;
1160+
reference->legacy_behavior = legacy_behavior && !is_union;
11371161
intern->ptr = reference;
11381162
intern->ref_type = REF_TYPE_TYPE;
11391163
}
@@ -2540,7 +2564,7 @@ ZEND_METHOD(reflection_parameter, getType)
25402564
if (!ZEND_TYPE_IS_SET(param->arg_info->type)) {
25412565
RETURN_NULL();
25422566
}
2543-
reflection_type_factory(param->arg_info->type, return_value);
2567+
reflection_type_factory(param->arg_info->type, return_value, 1);
25442568
}
25452569
/* }}} */
25462570

@@ -2840,7 +2864,10 @@ ZEND_METHOD(reflection_named_type, getName)
28402864
}
28412865
GET_REFLECTION_OBJECT_PTR(param);
28422866

2843-
RETURN_STR(zend_type_to_string_without_null(param->type));
2867+
if (param->legacy_behavior) {
2868+
RETURN_STR(zend_type_to_string_without_null(param->type));
2869+
}
2870+
RETURN_STR(zend_type_to_string(param->type));
28442871
}
28452872
/* }}} */
28462873

@@ -2860,6 +2887,83 @@ ZEND_METHOD(reflection_named_type, isBuiltin)
28602887
}
28612888
/* }}} */
28622889

2890+
static void append_type(zval *return_value, zend_type type) {
2891+
zval reflection_type;
2892+
reflection_type_factory(type, &reflection_type, 0);
2893+
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type);
2894+
}
2895+
2896+
static void append_type_mask(zval *return_value, uint32_t type_mask) {
2897+
append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask));
2898+
}
2899+
2900+
/* {{{ proto public string ReflectionUnionType::getTypes()
2901+
Returns the types that are part of this union type */
2902+
ZEND_METHOD(reflection_union_type, getTypes)
2903+
{
2904+
reflection_object *intern;
2905+
type_reference *param;
2906+
uint32_t type_mask;
2907+
2908+
if (zend_parse_parameters_none() == FAILURE) {
2909+
return;
2910+
}
2911+
GET_REFLECTION_OBJECT_PTR(param);
2912+
2913+
array_init(return_value);
2914+
if (ZEND_TYPE_HAS_LIST(param->type)) {
2915+
void *entry;
2916+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), entry) {
2917+
if (ZEND_TYPE_LIST_IS_NAME(entry)) {
2918+
append_type(return_value,
2919+
(zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_LIST_GET_NAME(entry), 0, 0));
2920+
} else {
2921+
append_type(return_value,
2922+
(zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_LIST_GET_CE(entry), 0, 0));
2923+
}
2924+
} ZEND_TYPE_LIST_FOREACH_END();
2925+
} else if (ZEND_TYPE_HAS_NAME(param->type)) {
2926+
append_type(return_value,
2927+
(zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), 0, 0));
2928+
} else if (ZEND_TYPE_HAS_CE(param->type)) {
2929+
append_type(return_value,
2930+
(zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_CE(param->type), 0, 0));
2931+
}
2932+
2933+
type_mask = ZEND_TYPE_PURE_MASK(param->type);
2934+
ZEND_ASSERT(!(type_mask & MAY_BE_VOID));
2935+
if (type_mask & MAY_BE_CALLABLE) {
2936+
append_type_mask(return_value, MAY_BE_CALLABLE);
2937+
}
2938+
if (type_mask & MAY_BE_ITERABLE) {
2939+
append_type_mask(return_value, MAY_BE_ITERABLE);
2940+
}
2941+
if (type_mask & MAY_BE_OBJECT) {
2942+
append_type_mask(return_value, MAY_BE_OBJECT);
2943+
}
2944+
if (type_mask & MAY_BE_ARRAY) {
2945+
append_type_mask(return_value, MAY_BE_ARRAY);
2946+
}
2947+
if (type_mask & MAY_BE_STRING) {
2948+
append_type_mask(return_value, MAY_BE_STRING);
2949+
}
2950+
if (type_mask & MAY_BE_LONG) {
2951+
append_type_mask(return_value, MAY_BE_LONG);
2952+
}
2953+
if (type_mask & MAY_BE_DOUBLE) {
2954+
append_type_mask(return_value, MAY_BE_DOUBLE);
2955+
}
2956+
if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) {
2957+
append_type_mask(return_value, MAY_BE_BOOL);
2958+
} else if (type_mask & MAY_BE_FALSE) {
2959+
append_type_mask(return_value, MAY_BE_FALSE);
2960+
}
2961+
if (type_mask & MAY_BE_NULL) {
2962+
append_type_mask(return_value, MAY_BE_NULL);
2963+
}
2964+
}
2965+
/* }}} */
2966+
28632967
/* {{{ proto public static mixed ReflectionMethod::export(mixed class, string name [, bool return]) throws ReflectionException
28642968
Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */
28652969
ZEND_METHOD(reflection_method, export)
@@ -3325,7 +3429,7 @@ ZEND_METHOD(reflection_function, getReturnType)
33253429
RETURN_NULL();
33263430
}
33273431

3328-
reflection_type_factory(fptr->common.arg_info[-1].type, return_value);
3432+
reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1);
33293433
}
33303434
/* }}} */
33313435

@@ -5580,7 +5684,7 @@ ZEND_METHOD(reflection_property, getType)
55805684
RETURN_NULL();
55815685
}
55825686

5583-
reflection_type_factory(ref->prop.type, return_value);
5687+
reflection_type_factory(ref->prop.type, return_value, 1);
55845688
}
55855689
/* }}} */
55865690

@@ -6637,6 +6741,11 @@ static const zend_function_entry reflection_named_type_functions[] = {
66376741
PHP_FE_END
66386742
};
66396743

6744+
static const zend_function_entry reflection_union_type_functions[] = {
6745+
ZEND_ME(reflection_union_type, getTypes, arginfo_reflection__void, 0)
6746+
PHP_FE_END
6747+
};
6748+
66406749
ZEND_BEGIN_ARG_INFO_EX(arginfo_reflection_extension_export, 0, 0, 1)
66416750
ZEND_ARG_INFO(0, name)
66426751
ZEND_ARG_INFO(0, return)
@@ -6778,6 +6887,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
67786887
reflection_init_class_handlers(&_reflection_entry);
67796888
reflection_named_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
67806889

6890+
INIT_CLASS_ENTRY(_reflection_entry, "ReflectionUnionType", reflection_union_type_functions);
6891+
reflection_init_class_handlers(&_reflection_entry);
6892+
reflection_union_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr);
6893+
67816894
INIT_CLASS_ENTRY(_reflection_entry, "ReflectionMethod", reflection_method_functions);
67826895
reflection_init_class_handlers(&_reflection_entry);
67836896
reflection_method_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_function_abstract_ptr);

ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ var_dump($ext->getClasses());
99
?>
1010
==DONE==
1111
--EXPECT--
12-
array(17) {
12+
array(18) {
1313
["ReflectionException"]=>
1414
object(ReflectionClass)#2 (1) {
1515
["name"]=>
@@ -55,43 +55,48 @@ array(17) {
5555
["name"]=>
5656
string(19) "ReflectionNamedType"
5757
}
58-
["ReflectionMethod"]=>
58+
["ReflectionUnionType"]=>
5959
object(ReflectionClass)#11 (1) {
60+
["name"]=>
61+
string(19) "ReflectionUnionType"
62+
}
63+
["ReflectionMethod"]=>
64+
object(ReflectionClass)#12 (1) {
6065
["name"]=>
6166
string(16) "ReflectionMethod"
6267
}
6368
["ReflectionClass"]=>
64-
object(ReflectionClass)#12 (1) {
69+
object(ReflectionClass)#13 (1) {
6570
["name"]=>
6671
string(15) "ReflectionClass"
6772
}
6873
["ReflectionObject"]=>
69-
object(ReflectionClass)#13 (1) {
74+
object(ReflectionClass)#14 (1) {
7075
["name"]=>
7176
string(16) "ReflectionObject"
7277
}
7378
["ReflectionProperty"]=>
74-
object(ReflectionClass)#14 (1) {
79+
object(ReflectionClass)#15 (1) {
7580
["name"]=>
7681
string(18) "ReflectionProperty"
7782
}
7883
["ReflectionClassConstant"]=>
79-
object(ReflectionClass)#15 (1) {
84+
object(ReflectionClass)#16 (1) {
8085
["name"]=>
8186
string(23) "ReflectionClassConstant"
8287
}
8388
["ReflectionExtension"]=>
84-
object(ReflectionClass)#16 (1) {
89+
object(ReflectionClass)#17 (1) {
8590
["name"]=>
8691
string(19) "ReflectionExtension"
8792
}
8893
["ReflectionZendExtension"]=>
89-
object(ReflectionClass)#17 (1) {
94+
object(ReflectionClass)#18 (1) {
9095
["name"]=>
9196
string(23) "ReflectionZendExtension"
9297
}
9398
["ReflectionReference"]=>
94-
object(ReflectionClass)#18 (1) {
99+
object(ReflectionClass)#19 (1) {
95100
["name"]=>
96101
string(19) "ReflectionReference"
97102
}

ext/reflection/tests/union_types.phpt

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
--TEST--
2+
Union types in reflection
3+
--INI--
4+
error_reporting=E_ALL&~E_DEPRECATED
5+
--FILE--
6+
<?php
7+
8+
// TODO: Fully fix __toString() and remove deprecation
9+
10+
function dumpType(ReflectionUnionType $rt) {
11+
echo "Type $rt:\n";
12+
echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n";
13+
foreach ($rt->getTypes() as $type) {
14+
echo " Name: " . $type->getName() . "\n";
15+
echo " String: " . (string) $type . "\n";
16+
echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n";
17+
}
18+
}
19+
20+
function test1(): X|Y|int|float|false|null { }
21+
function test2(): X|iterable|bool { }
22+
23+
class Test {
24+
public X|Y|int $prop;
25+
}
26+
27+
dumpType((new ReflectionFunction('test1'))->getReturnType());
28+
dumpType((new ReflectionFunction('test2'))->getReturnType());
29+
30+
$rc = new ReflectionClass(Test::class);
31+
$rp = $rc->getProperty('prop');
32+
dumpType($rp->getType());
33+
34+
/* Force CE resolution of the property type */
35+
36+
class x {}
37+
$test = new Test;
38+
$test->prop = new x;
39+
40+
$rp = $rc->getProperty('prop');
41+
dumpType($rp->getType());
42+
43+
class y {}
44+
$test->prop = new y;
45+
46+
$rp = $rc->getProperty('prop');
47+
dumpType($rp->getType());
48+
49+
?>
50+
--EXPECT--
51+
Type X|Y|int|float|false|null:
52+
Allows null: true
53+
Name: X
54+
String: X
55+
Allows Null: false
56+
Name: Y
57+
String: Y
58+
Allows Null: false
59+
Name: int
60+
String: int
61+
Allows Null: false
62+
Name: float
63+
String: float
64+
Allows Null: false
65+
Name: false
66+
String: false
67+
Allows Null: false
68+
Name: null
69+
String: null
70+
Allows Null: true
71+
Type X|iterable|bool:
72+
Allows null: false
73+
Name: X
74+
String: X
75+
Allows Null: false
76+
Name: iterable
77+
String: iterable
78+
Allows Null: false
79+
Name: bool
80+
String: bool
81+
Allows Null: false
82+
Type X|Y|int:
83+
Allows null: false
84+
Name: X
85+
String: X
86+
Allows Null: false
87+
Name: Y
88+
String: Y
89+
Allows Null: false
90+
Name: int
91+
String: int
92+
Allows Null: false
93+
Type x|Y|int:
94+
Allows null: false
95+
Name: x
96+
String: x
97+
Allows Null: false
98+
Name: Y
99+
String: Y
100+
Allows Null: false
101+
Name: int
102+
String: int
103+
Allows Null: false
104+
Type x|y|int:
105+
Allows null: false
106+
Name: x
107+
String: x
108+
Allows Null: false
109+
Name: y
110+
String: y
111+
Allows Null: false
112+
Name: int
113+
String: int
114+
Allows Null: false

0 commit comments

Comments
 (0)