-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Userspace operator overloading #5156
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
Conversation
Zend/zend_object_handlers.c
Outdated
|
||
if (!EG(exception) && (Z_TYPE_P(result) == IS_UNDEF || Z_TYPE_P(result) == IS_NULL)) | ||
{ | ||
zend_error(E_ERROR, "Method %s::%s must return a non-null value", ZSTR_VAL(ce->name), Z_STRVAL(fci.function_name)); |
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.
This should use zend_type_error()
.
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.
Though I'm wondering: Might it make sense to allow a null return value to indicate that this operand combination is not supported? In particular, I'm thinking that if you have $a * $b
and A::__mul($a, $b)
returns null, then B::__mul($a, $b)
would be tried.
So if some library that provides A implements __mul but does not know about B, then B still has a chance to implement a __mul behavior if it knows about A.
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.
This should use
zend_type_error()
.
Thank you for the hint.
Might it make sense to allow a null return value to indicate that this operand combination is not supported?
I thought about this too. I think some mechanisms like this is highly useful, but i wonder if returning null value is the most intuitive way for that (returning null looks like the operator would return null). Maybe introducing a special Exception/Throwable, like OperandsNotSupportedException, that will be thrown in that cases and signal to use the other operands handler.
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.
Throwing does carry overhead, perhaps a better option is a separate ce that can only be constructed via a call to an internal constant or method?
It could then return that, instead of throwing.
`
class Foo {
public static function __add(Foo $x, $y): Foo|ArithmeticOperationUnsupported {
if (!some_condition($y)) {
return Arithmetic::OPERATION_UNSUPPORTED;
}
return new Foo(...);
}
}
`
{ | ||
case ZEND_ADD: | ||
fcic.function_handler = ce->__add; | ||
ZVAL_STRING(&fci.function_name, ZEND_ADD_FUNC_NAME); |
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.
Is this actually needed? If the ce->__add
etc are properly pre-initialized, then it should not be necessary to set the function name here.
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 idea behind this, was to save the name of magic function called, so it can be for the usage notice below.
Maybe I should move this to the zend_error(E_NOTICE, ..)
line, so the var is only created if really needed.
…port the given types by returning null or throwing a TypeError.
I have added the possibility that an operator handler can signal, that it does not support the given operand types by returning the new constant The handler is also skipped if an TypeError occurs. This has the advantage that you can specify (in simple cases) what types are accepted by using typehints: So if a class can specify a handler like I have some question: Is there any possibility to differentiate between, if a function has returned null or returned nothing (void)? I have tried to check the type of the retval for IS_UNDEF, but the type is always null. (I have even tried to specify the called function as ZEND_USER_FUNCTION, so that zend_call_function() does set the return value to null, but that did not work. It seems that the null value is set on some other layer then). |
I don't think this is a good idea. The TypeError is not necessarily coming from the call to the operator method, it might also occur as part of the implementation, and we wouldn't want those to get lost.
No, this is not possible. |
…f catching TypeError.
…ggered now (we can not differentiate between void and null return value).
Okay I agree. I have changed my implementation the way that it checks if the given operand types match the handler's signature (the logic from zend_execute is reused), before calling the operand. This way it is possible to specify the acceptable types in signature without catching TypeErrors. What do you think about this approach? |
Why calling unimplemented operator just does not throw an exception instead of returning constant? |
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.
Er… I did not mean to make a review actually, just want to comment the RFC. Not pretty sure what this review feature does, so I apologize if I broke some standard process by using it. 🙂
Zend/zend.h
Outdated
zend_function *__sr; | ||
zend_function *__or; | ||
zend_function *__and; | ||
zend_function *__xor; |
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.
My two bits for better developer experience:
I suggest to use full names of methods, this is just not readable. __add
, __subtract
, __multiply
etc. is much clearer.
Also, on*
method naming convention came to my mind (__onAddition
, __onMultiplication
etc.) You can think about the operator as an event emitter ($a + $b
) and the magic method as a handler. Not sure if it is a good idea, but I guess it is more explicit about when the method is run actually.
If the operator is not implemented or the types are not supported, an exception is thrown. But this is done by the engine and not by the user, as the engine has overview about the operator implementations on both (left and right hand) objects. |
Fixed. Thank you for the hint. |
…his would prevent the combination of objects of different libraries for operator overloading.
I found a strange memory leak ocurring, when you call $a++ (or $a--) on an object (see https://travis-ci.org/github/php/php-src/jobs/665462666#L714). Interesting is that this does not happen when you call $a + 1, so I think this is not related to my code, as PHP translates $a++ to $a + 1 before even calling my handler. My guess would be that it is somehow related to the fact, that $a is overridden immediately (it get assigned the value the __add handler returns). |
Hi @jbtronics , I just wanted to let you know (but I didn't want to write a mail) that I really appreciate that you put so much effort into the implementation as well as the design of this RFC. The main reason why I voted no is because I don't agree with the feature itself, primarily the possible side-effects operations could have. |
@kocsismate just out of interest, what side-effects are you talking about? Are you talking here about the user-defined side effects that might occur when they implement some weird logic themselves, or side effects under the hood which developers cannot avoid? Was personally very excited to see this RFC, and I'm really wondering at the moment why this is being declined |
@NickvdMeij it's maybe because people with voting rights are narrow-minded (including me)... So I can imagine most people are not very familiar with this concept and have bad preconceptions for operator overloading, and even the fact that this RFC seems like a very detailed one can't change that. The very first RFC of my own is also failing so I know it's difficult :( Apart from negative bias, I don't like that I'm not always sure if an object supports operator overloading or not. Also, it still feels very weird for me that you can compare P.S. I meant the user-defined side effects. It might be some exaggeration, but I don't know. |
@kocsismate I didn't mean to be negative, but I like your humor :) You guys have all the right to decline, thats the reason you guys have the voting rights in the first place.
The reason it is weird, in my opinion at least, is because it's different from other behaviours that already exist in PHP. By making it user-defined, and well documentated, you can explain this behaviour and educate people in how to use it properly. You can even use the
User-defined side effects, or to generalize a bit; "problems", have been around since programming has been invented. I'm sure the PHP community can handle this problem and again educate people on the use of overloading properties. But thats just my opinion ;-). Thanks anyways for your response, was just curious about the "why" the RFC was declines. Keep on doing the good work 👏 |
@NickvdMeij Don't worry, I did't take your question as an offense. And I didn't mean to be sarcastic with my first sentence :) It's just that there are some things for which I'm not easy to persuade. And apparently the majority of voters think similarly about this topic. You make a good point about the uniformity of internal and userland classes though (actually, Nikita also came up with the same argument on the internals mailing list). What I can say from my side is that I'll keep my eyes on the vote, and re-read the proposal sometime soon so that I might find more things I like about the feature. :) |
I feel like this feature would greatly improve developer experience in (quite rare) cases where it is needed (matrix/vector libraries etc) and there is really no need to be afraid of it at all. I hope this RFC will pass. |
@@ -20,10 +20,14 @@ var_dump($c); | |||
echo "Done\n"; | |||
?> | |||
--EXPECTF-- | |||
Notice: You have to implement the __add function in class stdClass to use this operator with an object in %sadd_002.php on line %d |
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 notice doesn't make sense for non-user defined classes!
I see no point in emitting it here.
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 agree that this message does not make sense here, but I'm fine with having a notice on object to int conversion (which is undefined behavior) here.
continue; | ||
} | ||
|
||
zend_error(E_NOTICE, "You have to implement the %s function in class %s to use this operator with an object", |
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.
A check here like if (ce->type != ZEND_INTERNAL_CLASS)
before zend_error()
would do the job for notices on user classes only.
As the RFC was declined, I close this PR... |
So, this funcionality is rejected ? :( |
It's so pity to see that operators overloading STILL rejected. :( |
sooo I see on the RFC page, that this proposal have 38 Yes and 28 no. How is this rejected? PS: would love to see that implemented... |
New language features (like most changes to the PHP language) need a 2/3 majority for their RFCs, which was not reached here. |
What is the process of reopening an RFC for another PHP version? 🤔 |
Yes. But it would not make sense to just repropose the same RFC again. I guess without some changes it will be rejected again. And even then. As far as I remember it, some of the voters how voted against it, were against the introduction of operator overloading in general. So even with the best implementation of operating overloading you might not be able to convince them. So there is the question, if it is worth to invest work into proposing a new RFC. |
People come and go or change their minds on topics. This was a rather close vote and had a positive community feedback, so it is safe to say it is not pointless to reopen the discussion |
This is an draft implementation for this RFC.
@nikic suggested opening a pull request so this implementation can be discussed (see here.