-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Added fallthrough for == operator with objects for extensions #7973
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the PR! However, I wonder where handling ==
/!=
differently from other comparisons would be desireable. Can you give an example use-case?
In the absence of the operator overloads RFC, I am planning at some point on writing a math extension. This will involve types for matrices and complex numbers, both of which are uncomparable, but can be equivalent. |
Ah, now I see, thanks! But what about introducing a handler which only is called for |
@cmb69 Something like that would have BC implications and require an RFC, wouldn't it? Or am I misunderstanding what you're suggesting? |
@JordanRL, my idea would not break BC; it's basically a new handler which is called, if it is defined; so for classes which don't define the handler, |
@cmb69 alright, I'll switch this to draft. |
Anybody interested in this (or has serious objections)? |
If a new handler was added instead that would avoid the impact on gmp and other extensions, right? I think that was what @cmb69 was originally suggesting. |
Since the last time this was reviewed, the following changes have been made:
|
@dstogov if possible, could you confirm whether the new code for the PR avoids the BC issues you were talking about? |
@JordanRL in general the patch is right, but I don't like it. The new handler solves only specific case - when objects are not ordered, and may be only equal or not equal. However, this is not enough even for comparison of IS_DOUBLE numbers (with NaN). So, later someone will come with new set of object handlers to solve more general problem. May be it's better to allow |
@dstogov Fixing ZEND_UNCOMPARABLE is on my list, yeah. The reason I didn't opt for something like what you're talking about to solve this, is that the compare handler is not given an opcode, because it is called from zend_compare which is also not given an opcode. This makes sense, as zend_compare is called from many locations that don't really have an opcode to pass (such as from the defs for some functions in core). zend_compare can return ZEND_UNCOMPARABLE. But currently, ZEND_UNCOMPARABLE is defined as 1, which means the arguments must be provided such that zend_compare is evaluated as This presented a large problem for the RFC I worked on for operator overloads, since the order of the operands in the original PHP code needed to be preserved so that the order of competing handlers could be determined accurately, which was the original reason I dived into adding ZEND_IS_GREATER and ZEND_IS_GREATER_OR_EQUAL to the ZEND_AST_BINARY_OP which of course resulted in needing updates to JIT and the OpCache (which I was not qualified to do). So, I can certainly look at whether I could modify something through that path, but my previous experience made me worried about handling it that way. |
Oh. right! My previous post doesn't make a lot of sense :( We can't return ZEND_UNCOMPARABLE or throw UNORDERD exception without knowledge about comparison operators. |
Well, I think that you could do something like throw an exception, but it would involve lots of special conditions to check values and types probably. That would maybe be enough for something like complex numbers or matrices, but I saw value in contributing a more general solution for this sort of thing. For instance, once this handler exists, maybe it could be used for some more intelligent handling of the |
@@ -1941,6 +1941,10 @@ ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose | |||
return zend_std_get_properties_for(zobj, purpose); | |||
} | |||
|
|||
ZEND_API zend_result zend_std_equals_objects(zend_uchar opcode, zval *result, zval *op1, zval *op2) { | |||
return FAILURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the fallback to the compare handler be in here?
Do we really need to pass the opcode here? I think it would be better for the return value to indicate whether it equals or not (possibly with an equals/not_equals/uncomparable enum). This would prevent inconsistent ==
and !=
implementations by design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I didn't put the fallback here is that extensions that used this handler would have to manually implement the fallback again (assuming they wanted to use the compare handler). But yeah, if this function returned a result enum instead of zend_result
I think the fallback would have to go in here.
That's just very different from how the rest of the handlers are done (from what I've seen), and I wanted this code to be similar to other code in the engine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I didn't put the fallback here is that extensions that used this handler would have to manually implement the fallback again (assuming they wanted to use the compare handler).
Not sure I get what you mean here -- if they want to use some custom logic and then fall back to the default, they can call zend_std_equals_objects to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I get what you mean here -- if they want to use some custom logic and then fall back to the default, they can call zend_std_equals_objects to do so.
Or yeah... they could do the obvious thing you just suggested...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually though, what about the case where op1 falls back but op2 has custom logic? How would the default handler for op1 handle that?
@@ -501,7 +501,11 @@ ZEND_VM_HELPER(zend_is_equal_helper, ANY, ANY, zval *op_1, zval *op_2) | |||
if (UNEXPECTED(Z_TYPE_INFO_P(op_2) == IS_UNDEF)) { | |||
op_2 = ZVAL_UNDEFINED_OP2(); | |||
} | |||
ret = zend_compare(op_1, op_2); | |||
if (Z_TYPE_P(op_1) == IS_OBJECT || Z_TYPE_P(op_2) == IS_OBJECT) { | |||
ret = zend_equals_object(op_1, op_2, ZEND_IS_EQUAL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you need a separate zend_equals that is pushed through the entire hierarchy. Otherwise objects nested in arrays will not use the correct handler, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, similar to zend_compare
that handles all types and type combinations? I hadn't considered the case of objects in arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, similar to zend_compare that handles all types and type combinations?
Yeah, exactly.
This should allow extensions to handle the == operator differently from the <=> operator.
It accomplishes this by allowing the do_operation handler to look at ZEND_IS_EQUAL and ZEND_IS_NOT_EQUAL prior to calling zend_compare.It accomplishes this by checking a new equals handler on the object for ZEND_IS_EQUAL and ZEND_IS_NOT_EQUAL prior to calling zend_compare.