Skip to content

Resolve relative class type at compile time #11460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from

Conversation

Girgias
Copy link
Member

@Girgias Girgias commented Jun 16, 2023

The motivation for this is to potentially add methods to ReflectionType which can check if it is co/contra-variant to another ReflectionType, however self, parent, and static are problematic in that those types would usually be resolved at run time.

However, in most cases we can resolve at compile time self and parent, the exceptions being traits and unbound closures.
static is resolvable if it comes from an instance or a class, as it will resolve to that name, leaving interfaces to be an issue.

Commits are kinda a mess as it's somewhat W.I.P. as I might still need to tweak the redundancy check for DNF types.

@Girgias Girgias requested a review from arnaud-lb June 16, 2023 04:14
@Girgias Girgias force-pushed the reflection-co-variance branch 3 times, most recently from eda873f to a9b9158 Compare June 20, 2023 14:49
@Girgias Girgias force-pushed the reflection-co-variance branch from a9b9158 to b4a63c5 Compare June 24, 2023 16:09
@Girgias Girgias marked this pull request as ready for review June 24, 2023 20:02
Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a shit-ton of tests 😄 I think we should condense them somehow.

bool allows_null = ZEND_TYPE_PURE_MASK(param->type) & MAY_BE_NULL;
zend_type resolved_type;
/* For static resolved name is the name of the class */
if (ZEND_TYPE_PURE_MASK(param->type) & MAY_BE_STATIC) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused, how can we know static without knowing the context? (i.e. in what object instance it is called)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One cannot create a "proper" ReflectionType without getting it either from a ReflectionParameter, ReflectionProperty, or a ReflectionFunction for the return type.

Part of the change is to propagate forward the object_ce from those prior Reflection objects.

@@ -6495,7 +6532,34 @@ static void zend_is_type_list_redundant_by_single_type(zend_type_list *type_list
}
if (zend_string_equals_ci(ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(type))) {
zend_string *single_type_str = zend_type_to_string(type);
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str));
if (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this special handling for this edge-case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because otherwise you would get an error message like Duplicate type T is redundant when self or parent gets resolved to T and it makes for some confusing stuff, or I'm misunderstanding the question.

fn->common.arg_info = new_arg_infos + has_return_type;
has_resolved_type = true;
}
new_arg_infos[i].type = resolved_type;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the old type be released? I think this might only be reproducible for eval'd code where the type may contain non-interned strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? Haven't thought about something like this as I would usually expect to be allocated on an arena. And releasing the type info for the traits doesn't make a lot of sense as it still needs to keep the unresolved types for itself.

@Girgias Girgias force-pushed the reflection-co-variance branch from c9d830a to 1910966 Compare June 27, 2023 17:40
@Girgias
Copy link
Member Author

Girgias commented Jun 27, 2023

Yes, it is a lot of tests as there are a lot of weird cases I stumbled into what I thought would be relatively easy :|

Not really sure why we need to condense them as I tried to make them all be their own thing somewhat, and even then I've been missing cases (like using eval, or using a trait in two completely unrelated classes).

@Girgias Girgias force-pushed the reflection-co-variance branch from 1910966 to b62f42a Compare July 25, 2023 09:49
@arnaud-lb
Copy link
Member

Resolving self and parent at compile time may be beneficial because the resolved name will have a CE_CACHE, although I would assume that resolving these at runtime is fast already because they are resolved from EG(This). Do you have benchmark numbers?

If we don't do compile time resolution, we could save some _ZEND_TYPE bits (we are running out of them because the 4 higher ones are used by _ZEND_IS_TENTATIVE_BIT, _ZEND_IS_PROMOTED_BIT, etc). An other benefit would be that it would require less changes overall.

@Girgias
Copy link
Member Author

Girgias commented Feb 20, 2024

Resolving self and parent at compile time may be beneficial because the resolved name will have a CE_CACHE, although I would assume that resolving these at runtime is fast already because they are resolved from EG(This). Do you have benchmark numbers?

If we don't do compile time resolution, we could save some _ZEND_TYPE bits (we are running out of them because the 4 higher ones are used by _ZEND_IS_TENTATIVE_BIT, _ZEND_IS_PROMOTED_BIT, etc). An other benefit would be that it would require less changes overall.

The main motivation is not saving time at runtime, but to expose co/contra-variant APIs in Reflection to reduce the need in userland to implement the variance logic (at least for most cases as traits and unbound closures are still impossible)

So no, I didn't benchmark.

However, we could change the error messages to show the resolved types instead of going back to self/parent at the downside of it being a minor BC break.

@Girgias Girgias force-pushed the reflection-co-variance branch from b62f42a to d5a51e6 Compare February 28, 2024 18:05
@Girgias Girgias force-pushed the reflection-co-variance branch from 577eda4 to 616f6e7 Compare March 8, 2024 14:12
@Girgias Girgias force-pushed the reflection-co-variance branch 2 times, most recently from 61b2568 to eafac08 Compare April 15, 2024 14:06
@@ -0,0 +1,19 @@
--TEST--
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in the file name :)

zend_class_entry *called_scope = NULL;
zend_object *this_obj = NULL;

Z_OBJ_HANDLER(intern->obj, get_closure)(Z_OBJ(intern->obj), &called_scope, &fptr, &this_obj, /* check_only */ true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you could avoid calling ->get_closure twice here and in reflection_type_factory:

  • Call ->get_closure in reflection_type_factory and store ce. Set it to called_scope for MAY_BE_STATIC, and fptr->common.scope otherwise
  • Then here, just use intern->ce in all cases.

An additional benefit is that it may be possible to avoid holding the closure in ->obj. For the condition above, you could add a flag in type_reference to indicate this is in a closure context.

@Girgias Girgias force-pushed the reflection-co-variance branch from eafac08 to e77225e Compare April 16, 2024 20:08
@@ -1443,7 +1489,8 @@ static void reflection_method_factory(zend_class_entry *ce, zend_function *metho
intern->ref_type = REF_TYPE_FUNCTION;
intern->ce = ce;
if (closure_object) {
ZVAL_OBJ_COPY(&intern->obj, Z_OBJ_P(closure_object));
intern->ce = zend_ce_closure;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: This looks wrong, and I need to add a test with a Closure bound within a method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants