Skip to content

Commit 6bde68d

Browse files
committed
feat(reflection): expose generic parameters and bounds via Reflection API
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent 04db944 commit 6bde68d

6 files changed

Lines changed: 589 additions & 10 deletions

ext/reflection/php_reflection.c

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ PHPAPI zend_class_entry *reflection_enum_unit_case_ptr;
102102
PHPAPI zend_class_entry *reflection_enum_backed_case_ptr;
103103
PHPAPI zend_class_entry *reflection_fiber_ptr;
104104
PHPAPI zend_class_entry *reflection_constant_ptr;
105+
PHPAPI zend_class_entry *reflection_generic_type_parameter_ptr;
106+
PHPAPI zend_class_entry *reflection_type_parameter_reference_ptr;
107+
PHPAPI zend_class_entry *reflection_generic_variance_ptr;
105108
PHPAPI zend_class_entry *reflection_property_hook_type_ptr;
106109

107110
#define GET_REFLECTION_OBJECT() do { \
@@ -144,6 +147,15 @@ typedef struct _type_reference {
144147
bool legacy_behavior;
145148
} type_reference;
146149

150+
/* Struct for generic type-parameter reflection. Points back at a parameter slot
151+
* inside the declaring entity's zend_generic_parameter_list. Lifetime is bound
152+
* to the declaring class_entry / op_array. */
153+
typedef struct _generic_parameter_reference {
154+
zend_generic_parameter *param; /* points into a zend_generic_parameter_list */
155+
uint32_t index; /* position in the list */
156+
zval declaring; /* zval-wrapped ReflectionClass / ReflectionFunctionAbstract */
157+
} generic_parameter_reference;
158+
147159
/* Struct for attributes */
148160
typedef struct _attribute_reference {
149161
HashTable *attributes;
@@ -8156,6 +8168,309 @@ ZEND_METHOD(ReflectionConstant, __toString)
81568168
RETURN_STR(smart_str_extract(&str));
81578169
}
81588170

8171+
ZEND_METHOD(ReflectionGenericTypeParameter, __construct)
8172+
{
8173+
zend_throw_exception(reflection_exception_ptr,
8174+
"Cannot directly instantiate ReflectionGenericTypeParameter", 0);
8175+
}
8176+
8177+
ZEND_METHOD(ReflectionTypeParameterReference, __construct)
8178+
{
8179+
zend_throw_exception(reflection_exception_ptr,
8180+
"Cannot directly instantiate ReflectionTypeParameterReference", 0);
8181+
}
8182+
8183+
8184+
static void reflection_generic_type_parameter_factory(
8185+
zend_generic_parameter *param, uint32_t index, zval *declaring, zval *object)
8186+
{
8187+
reflection_object *intern;
8188+
generic_parameter_reference *reference;
8189+
8190+
object_init_ex(object, reflection_generic_type_parameter_ptr);
8191+
intern = Z_REFLECTION_P(object);
8192+
reference = emalloc(sizeof(generic_parameter_reference));
8193+
reference->param = param;
8194+
reference->index = index;
8195+
ZVAL_COPY(&reference->declaring, declaring);
8196+
intern->ptr = reference;
8197+
intern->ref_type = REF_TYPE_OTHER;
8198+
8199+
ZVAL_STR_COPY(reflection_prop_name(object), param->name);
8200+
}
8201+
8202+
static void reflection_build_generic_parameters_array(
8203+
zend_generic_parameter_list *list, zval *declaring, zval *return_value)
8204+
{
8205+
if (!list) {
8206+
array_init(return_value);
8207+
return;
8208+
}
8209+
array_init_size(return_value, list->count);
8210+
for (uint32_t i = 0; i < list->count; i++) {
8211+
zval entry;
8212+
reflection_generic_type_parameter_factory(&list->parameters[i], i, declaring, &entry);
8213+
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &entry);
8214+
}
8215+
}
8216+
8217+
ZEND_METHOD(ReflectionFunctionAbstract, isGeneric)
8218+
{
8219+
reflection_object *intern;
8220+
zend_function *fptr;
8221+
8222+
ZEND_PARSE_PARAMETERS_NONE();
8223+
GET_REFLECTION_OBJECT_PTR(fptr);
8224+
8225+
if (fptr->type != ZEND_USER_FUNCTION) {
8226+
RETURN_FALSE;
8227+
}
8228+
RETURN_BOOL(fptr->op_array.generic_parameters != NULL);
8229+
}
8230+
8231+
ZEND_METHOD(ReflectionFunctionAbstract, getGenericParameters)
8232+
{
8233+
reflection_object *intern;
8234+
zend_function *fptr;
8235+
8236+
ZEND_PARSE_PARAMETERS_NONE();
8237+
GET_REFLECTION_OBJECT_PTR(fptr);
8238+
8239+
if (fptr->type != ZEND_USER_FUNCTION) {
8240+
array_init(return_value);
8241+
return;
8242+
}
8243+
reflection_build_generic_parameters_array(fptr->op_array.generic_parameters, ZEND_THIS, return_value);
8244+
}
8245+
8246+
ZEND_METHOD(ReflectionClass, isGeneric)
8247+
{
8248+
reflection_object *intern;
8249+
zend_class_entry *ce;
8250+
8251+
ZEND_PARSE_PARAMETERS_NONE();
8252+
GET_REFLECTION_OBJECT_PTR(ce);
8253+
8254+
RETURN_BOOL(ce->generic_parameters != NULL);
8255+
}
8256+
8257+
ZEND_METHOD(ReflectionClass, getGenericParameters)
8258+
{
8259+
reflection_object *intern;
8260+
zend_class_entry *ce;
8261+
8262+
ZEND_PARSE_PARAMETERS_NONE();
8263+
GET_REFLECTION_OBJECT_PTR(ce);
8264+
8265+
reflection_build_generic_parameters_array(ce->generic_parameters, ZEND_THIS, return_value);
8266+
}
8267+
8268+
#define GET_GENERIC_PARAMETER_REFERENCE(intern, param_ref) \
8269+
do { \
8270+
(intern) = Z_REFLECTION_P(getThis()); \
8271+
if ((intern)->ptr == NULL) { \
8272+
zend_throw_error(NULL, "Internal error: Failed to retrieve the type parameter reference"); \
8273+
RETURN_THROWS(); \
8274+
} \
8275+
(param_ref) = (generic_parameter_reference *) (intern)->ptr; \
8276+
} while (0)
8277+
8278+
ZEND_METHOD(ReflectionGenericTypeParameter, getName)
8279+
{
8280+
reflection_object *intern;
8281+
generic_parameter_reference *ref;
8282+
8283+
ZEND_PARSE_PARAMETERS_NONE();
8284+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8285+
RETURN_STR_COPY(ref->param->name);
8286+
}
8287+
8288+
ZEND_METHOD(ReflectionGenericTypeParameter, getPosition)
8289+
{
8290+
reflection_object *intern;
8291+
generic_parameter_reference *ref;
8292+
8293+
ZEND_PARSE_PARAMETERS_NONE();
8294+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8295+
RETURN_LONG((zend_long) ref->index);
8296+
}
8297+
8298+
ZEND_METHOD(ReflectionGenericTypeParameter, getVariance)
8299+
{
8300+
reflection_object *intern;
8301+
generic_parameter_reference *ref;
8302+
8303+
ZEND_PARSE_PARAMETERS_NONE();
8304+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8305+
8306+
const char *case_name;
8307+
switch (ref->param->variance) {
8308+
case 1: case_name = "Covariant"; break;
8309+
case 2: case_name = "Contravariant"; break;
8310+
default: case_name = "Invariant"; break;
8311+
}
8312+
zend_object *case_obj = zend_enum_get_case_cstr(reflection_generic_variance_ptr, case_name);
8313+
if (!case_obj) {
8314+
zend_throw_error(NULL, "Internal error: ReflectionGenericVariance enum case not found");
8315+
RETURN_THROWS();
8316+
}
8317+
ZVAL_OBJ_COPY(return_value, case_obj);
8318+
}
8319+
8320+
ZEND_METHOD(ReflectionGenericTypeParameter, hasBound)
8321+
{
8322+
reflection_object *intern;
8323+
generic_parameter_reference *ref;
8324+
8325+
ZEND_PARSE_PARAMETERS_NONE();
8326+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8327+
RETURN_BOOL(ZEND_TYPE_IS_SET(ref->param->bound));
8328+
}
8329+
8330+
ZEND_METHOD(ReflectionGenericTypeParameter, getBound)
8331+
{
8332+
reflection_object *intern;
8333+
generic_parameter_reference *ref;
8334+
8335+
ZEND_PARSE_PARAMETERS_NONE();
8336+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8337+
8338+
if (!ZEND_TYPE_IS_SET(ref->param->bound)) {
8339+
RETURN_NULL();
8340+
}
8341+
reflection_type_factory(ref->param->bound, return_value, false);
8342+
}
8343+
8344+
ZEND_METHOD(ReflectionGenericTypeParameter, hasDefault)
8345+
{
8346+
reflection_object *intern;
8347+
generic_parameter_reference *ref;
8348+
8349+
ZEND_PARSE_PARAMETERS_NONE();
8350+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8351+
RETURN_BOOL(ZEND_TYPE_IS_SET(ref->param->default_type));
8352+
}
8353+
8354+
ZEND_METHOD(ReflectionGenericTypeParameter, getDefault)
8355+
{
8356+
reflection_object *intern;
8357+
generic_parameter_reference *ref;
8358+
8359+
ZEND_PARSE_PARAMETERS_NONE();
8360+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8361+
8362+
if (!ZEND_TYPE_IS_SET(ref->param->default_type)) {
8363+
RETURN_NULL();
8364+
}
8365+
reflection_type_factory(ref->param->default_type, return_value, false);
8366+
}
8367+
8368+
ZEND_METHOD(ReflectionGenericTypeParameter, getDeclaringEntity)
8369+
{
8370+
reflection_object *intern;
8371+
generic_parameter_reference *ref;
8372+
8373+
ZEND_PARSE_PARAMETERS_NONE();
8374+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8375+
ZVAL_COPY(return_value, &ref->declaring);
8376+
}
8377+
8378+
ZEND_METHOD(ReflectionGenericTypeParameter, __toString)
8379+
{
8380+
reflection_object *intern;
8381+
generic_parameter_reference *ref;
8382+
smart_str str = {0};
8383+
8384+
ZEND_PARSE_PARAMETERS_NONE();
8385+
GET_GENERIC_PARAMETER_REFERENCE(intern, ref);
8386+
8387+
switch (ref->param->variance) {
8388+
case 1:
8389+
smart_str_appendc(&str, '+');
8390+
break;
8391+
case 2:
8392+
smart_str_appendc(&str, '-');
8393+
break;
8394+
default:
8395+
break;
8396+
}
8397+
smart_str_append(&str, ref->param->name);
8398+
if (ZEND_TYPE_IS_SET(ref->param->bound)) {
8399+
zend_string *bound = zend_type_to_string(ref->param->bound);
8400+
smart_str_appends(&str, " : ");
8401+
smart_str_append(&str, bound);
8402+
zend_string_release(bound);
8403+
}
8404+
if (ZEND_TYPE_IS_SET(ref->param->default_type)) {
8405+
zend_string *def = zend_type_to_string(ref->param->default_type);
8406+
smart_str_appends(&str, " = ");
8407+
smart_str_append(&str, def);
8408+
zend_string_release(def);
8409+
}
8410+
RETURN_NEW_STR(smart_str_extract(&str));
8411+
}
8412+
8413+
ZEND_METHOD(ReflectionTypeParameterReference, getName)
8414+
{
8415+
reflection_object *intern;
8416+
type_reference *ref;
8417+
8418+
ZEND_PARSE_PARAMETERS_NONE();
8419+
GET_REFLECTION_OBJECT_PTR(ref);
8420+
8421+
if (!ZEND_TYPE_HAS_TYPE_PARAMETER(ref->type)) {
8422+
zend_throw_error(NULL, "Type parameter reference has no parameter");
8423+
RETURN_THROWS();
8424+
}
8425+
zend_type_parameter_ref *tp = ZEND_TYPE_TYPE_PARAMETER(ref->type);
8426+
RETURN_STR_COPY(tp->name);
8427+
}
8428+
8429+
ZEND_METHOD(ReflectionTypeParameterReference, getTypeParameter)
8430+
{
8431+
/* TODO(azjezz): resolution to the originating ReflectionGenericTypeParameter requires the
8432+
* declaring entity, which is not currently threaded through type_reference. */
8433+
zend_throw_error(NULL,
8434+
"ReflectionTypeParameterReference::getTypeParameter() requires the declaring "
8435+
"entity to be threaded through; not yet implemented");
8436+
RETURN_THROWS();
8437+
}
8438+
8439+
ZEND_METHOD(ReflectionTypeParameterReference, allowsNull)
8440+
{
8441+
ZEND_PARSE_PARAMETERS_NONE();
8442+
RETURN_TRUE;
8443+
}
8444+
8445+
ZEND_METHOD(ReflectionTypeParameterReference, __toString)
8446+
{
8447+
reflection_object *intern;
8448+
type_reference *ref;
8449+
8450+
ZEND_PARSE_PARAMETERS_NONE();
8451+
GET_REFLECTION_OBJECT_PTR(ref);
8452+
8453+
if (!ZEND_TYPE_HAS_TYPE_PARAMETER(ref->type)) {
8454+
RETURN_EMPTY_STRING();
8455+
}
8456+
zend_type_parameter_ref *tp = ZEND_TYPE_TYPE_PARAMETER(ref->type);
8457+
RETURN_STR_COPY(tp->name);
8458+
}
8459+
8460+
ZEND_METHOD(ReflectionNamedType, hasGenericArguments)
8461+
{
8462+
ZEND_PARSE_PARAMETERS_NONE();
8463+
/* TODO(azjezz): pre-erasure type-argument exposure on use-site named types requires
8464+
* pre-erasure storage in the generic side table to be threaded into reflection_type_factory. */
8465+
RETURN_FALSE;
8466+
}
8467+
8468+
ZEND_METHOD(ReflectionNamedType, getGenericArguments)
8469+
{
8470+
ZEND_PARSE_PARAMETERS_NONE();
8471+
array_init(return_value);
8472+
}
8473+
81598474
PHP_MINIT_FUNCTION(reflection) /* {{{ */
81608475
{
81618476
memcpy(&reflection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
@@ -8261,6 +8576,16 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
82618576

82628577
reflection_property_hook_type_ptr = register_class_PropertyHookType();
82638578

8579+
reflection_generic_variance_ptr = register_class_ReflectionGenericVariance();
8580+
8581+
reflection_generic_type_parameter_ptr = register_class_ReflectionGenericTypeParameter(reflector_ptr);
8582+
reflection_generic_type_parameter_ptr->create_object = reflection_objects_new;
8583+
reflection_generic_type_parameter_ptr->default_object_handlers = &reflection_object_handlers;
8584+
8585+
reflection_type_parameter_reference_ptr = register_class_ReflectionTypeParameterReference(reflection_type_ptr);
8586+
reflection_type_parameter_reference_ptr->create_object = reflection_objects_new;
8587+
reflection_type_parameter_reference_ptr->default_object_handlers = &reflection_object_handlers;
8588+
82648589
REFLECTION_G(key_initialized) = false;
82658590

82668591
return SUCCESS;

0 commit comments

Comments
 (0)