Skip to content

User Defined Operator Overloads #7388

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

Closed
wants to merge 42 commits into from
Closed

Conversation

JordanRL
Copy link

@JordanRL JordanRL commented Aug 18, 2021

This is a draft PR so that early feedback on implementation can be received, or errors can be caught earlier.

Draft RFC for reference: https://wiki.php.net/rfc/user_defined_operator_overloads

Implementation checklist:

  • Moving ZEND_AST_GREATER to its own opcode is unfinished
  • Implementation of Z_OBJECT_HANDLER(obj, compare) is still unfinished
    • As part of this, normalization of returned int values needs to be done
    • Additionally, right hand operator handling (multiply by -1) needs to be done
  • Different methods for all comparisons are still in the magic methods definition, though the RFC is moving away from that strategy
  • Optimizations haven't yet been considered
  • Cleanup of abandoned implementation strategies needs to be done
  • More test cases are required
  • Broken behavior that needs special cases
    • Enum comparison is broken, must fix
    • Extending internal classes (like DateTime) no longer inherits the overload behavior. Perhaps special case DateTime?
  • InvalidOperatorError implemented
  • Magic methods described in RFC are implemented
  • Working do_operation handler implemented
  • Reflection
    • Changes to ReflectionClass
    • Changes to ReflectionMethod
  • Add new attribute target
  • Restrict calls to operator function handlers on objects/classes

@HallofFamer
Copy link

HallofFamer commented Aug 19, 2021

Reduces Need For Scalar Objects

Interesting RFC, but I dont think the need for opeator overloading is the reason why developers want Scalar Objects. There are more important reasons and I completely disagree that this RFC will reduce the need for scalar objects even if it passes.

@JordanRL
Copy link
Author

JordanRL commented Aug 20, 2021

Interesting RFC, but I dont think the need for opeator overloading is the reason why developers want Scalar Objects. There are more important reasons and I completely disagree that this RFC will reduce the need for scalar objects even if it passes.

This obviously doesn't eliminate the need for scalar objects. My point was that it reduces the need for them by enabling community created scalar objects to be acceptable solutions in some circumstances. Obviously that doesn't address the issue of re-standardizing the standard library which some people want to see as part of scalar objects, because that would necessarily require a standard implementation of them from core.

@oprypkhantc
Copy link

Just my off-topic 2 cents regarding the scalar objects:

I don't believe implementing them in PHP core by themselves would benefit the community much. For example, if String scalar object was implemented in PHP core, chances are it would have the same set of standard methods that PHP currently has as str_* functions. While there are a lot of them, there are usually not enough, so you would either end up using custom scalar objects from 3rd party or using ugly helper functions like str_pad_left(StringScalar, int): StringScalar.

So IMHO before even thinking of adding scalar objects, we need extension methods from C#/Kotlin. This in turn would mean PHP could implement barebones scalar objects that just have operators overloaded and a set of few very basic methods, nothing more. Community could then implement the rest. Every projects would then use an extensions library they like and it could be then backported to PHP once the community decides what's best.

@krakjoe krakjoe added the RFC label Aug 31, 2021
@@ -286,3 +286,10 @@ tmp-php.ini
!/ext/fileinfo/magicdata.patch
!/ext/pcre/pcre2lib/config.h
!/win32/build/Makefile

# -------
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @iluuu1994. I was going to take care of this before I opened the PR from draft.

Copy link
Member

Choose a reason for hiding this comment

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

Np, I thought maybe you didn't know about global gitignores.

Copy link
Author

Choose a reason for hiding this comment

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

I actually didn't, so the comment was definitely helpful. :)

@KapitanOczywisty
Copy link

And another attempt on this which is failing. I can bet that in this form, overloads will never make into php.

A while back I've made extension for fun, in which I had Operator abstract class. When extending this class you could define methods like __add, __sub. It was more or less working, and you can look it up here: https://gist.github.com/KapitanOczywisty/8c63177650f4e1f581393471e1ebc388

Implementing such overloads for any object makes a big mess, especially for static analysis. It's a way better to make overloads into interface - similar to ArrayAccess. Then is crystal-clear which objects are affected. Of course you can't set argument type in that case, but in ArrayAccess and similar ones you can't either, so let user validate what is allowed as operand.

Also adding another syntax to handle operations is unnecessary complexity, just use magic methods like __add and don't touch tokenizer.

@oprypkhantc
Copy link

@KapitanOczywisty

Implementing such overloads for any object makes a big mess, especially for static analysis

It's in no way different to magic methods or an interface. Checking for an existing operator method overload is just as easy as checking for magic method existence or an interface supertype.

Then is crystal-clear which objects are affected

It's already crystal clear - you have a list of all operator overload methods at your disposal.

Also adding another syntax to handle operations is unnecessary complexity, just use magic methods like __add and don't touch tokenizer.

Read the internals and the RFC. There are plenty of arguments why magic methods make less sense than a separate keyword. Interfaces don't make any sense at all. It's possible that PHP will want to get rid of ArrayAccess at some point too.

@cmb69
Copy link
Member

cmb69 commented Dec 16, 2021

@KapitanOczywisty, please do any non implementation related discussion on the internal mailing list. Otherwise it gets unwieldy here (and for any other RFC PR). Thank you!

@KapitanOczywisty
Copy link

@cmb69 That mailing list is the reason why php is 5 to 10 years behind modern programming languages.

@cmb69
Copy link
Member

cmb69 commented Dec 16, 2021

@KapitanOczywisty, if that is the case, I wonder why you waste your time with such ancient tech. ;)

@KapitanOczywisty
Copy link

@cmb69 Only to maintain old projects, but otherwise typescript + nodejs whenever possible. There is noticeable decrease in php popularity. Updates could be summarized as "too late too little".

@skrtdev
Copy link

skrtdev commented Dec 27, 2021

I like this PR

/* }}} */

static void zend_compile_unary_op(znode *result, zend_ast *ast) /* {{{ */
void zend_compile_unary_op(znode *result, zend_ast *ast) /* {{{ */
Copy link
Member

Choose a reason for hiding this comment

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

Why did you remove the static here?

{
case LeftSide;
case RightSide;
}
Copy link
Member

Choose a reason for hiding this comment

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

Nit: EOL

%type <num> method_modifiers non_empty_member_modifiers member_modifier
%type <num> optional_property_modifiers property_modifier
%type <num> class_modifiers class_modifier use_type backup_fn_flags

%type <ptr> backup_lex_pos
%type <str> backup_doc_comment

%type <ident> reserved_non_modifiers semi_reserved
%type <ident> reserved_non_modifiers semi_reserved ampersand overloadable_operator_list
Copy link
Member

Choose a reason for hiding this comment

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

Isn't the ampersand redundant here?

Comment on lines +267 to +275
/** @tentative-return-type */
public function hasOperator(string $name): bool {}

/** @tentative-return-type */
public function getOperator(string $name): ReflectionMethod {}

/** @tentative-return-type */
public function getOperators(?int $filter = null): array {}

Copy link
Member

Choose a reason for hiding this comment

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

These are new functions, therefore they can just have the return type be normal and not be tentative IMHO.

@@ -218,6 +219,193 @@ static void zend_std_call_issetter(zend_object *zobj, zend_string *prop_name, zv
}
/* }}} */

static int zend_std_call_op_override(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
static int zend_std_call_op_override(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */
static zend_result zend_std_call_op_override(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */

@@ -0,0 +1,80 @@
--TEST--
operator overload: reflection for operators
Copy link
Member

Choose a reason for hiding this comment

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

Reflection tests should be in ext/reflection/

Comment on lines +9 to +15
if ($left == OperandPosition::RightSide) {
echo "Right side".PHP_EOL;
} elseif ($left == OperandPosition::LeftSide) {
echo "Left side".PHP_EOL;
} else {
echo "Unknown enum case!".PHP_EOL;
}
Copy link
Member

Choose a reason for hiding this comment

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

Use match?

Suggested change
if ($left == OperandPosition::RightSide) {
echo "Right side".PHP_EOL;
} elseif ($left == OperandPosition::LeftSide) {
echo "Left side".PHP_EOL;
} else {
echo "Unknown enum case!".PHP_EOL;
}
echo match($left) {
OperandPosition::RightSide => 'Right side'
OperandPosition::LeftSide => 'Left side'
}, PHP_EOL;

(Haven't tried this just typed it out)

$return = new A();
$return->value = $this->value + $other;

return $return;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: indent

$return->value = $other / $this->value;
}

return $return;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: indent


public operator <=>(mixed $other): int
{
return ($this->value <=> $other);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: indent

Copy link
Member

Choose a reason for hiding this comment

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

Was being confused because the file view was showing them misalligned, but it's because you're mixing tabs and spaces, PHPT files should be space indented.

@moniang
Copy link

moniang commented Jan 14, 2022

I hope it will pass the voting phase, although it is unlikely.

@JordanRL JordanRL closed this Jan 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.