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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
58a1456
Initial commit of user defined operator overloads
JordanRL Aug 11, 2021
c46492d
Added InvalidOperator exception and fixed error handling of operator …
JordanRL Aug 11, 2021
6811080
Started on ZEND_IS_LARGER work
JordanRL Aug 13, 2021
4a84a91
Further implementation of op overloads
JordanRL Aug 18, 2021
6be3d1a
Finished implementation of compare and updated several tests; added s…
JordanRL Aug 19, 2021
5fe2ad1
Fixing various comparison issues and making comparison optional
JordanRL Aug 20, 2021
ff79f94
Added bitwise operators and updated several tests
JordanRL Aug 22, 2021
88bd550
Interim commit
JordanRL Nov 26, 2021
0a0d9ec
Resolving merge conflicts
JordanRL Nov 26, 2021
2fa6d98
Improvements to operator overloads and IS_LARGER opcode
JordanRL Nov 27, 2021
a2c3f15
Fixing some bugs
JordanRL Nov 29, 2021
80497fc
Updating extensions for changes to objects and ZEND_UNCOMPARABLE
JordanRL Nov 29, 2021
3953f37
Resolving failing tests for operator overloads
JordanRL Nov 29, 2021
6fe6fe2
Fixing extension handling for object compare handler
JordanRL Nov 29, 2021
ae584fb
Updating pow test
JordanRL Nov 29, 2021
0ca2ad0
Fixing fallback from __equals to __compareto, and fixing named_params…
JordanRL Nov 30, 2021
ce042d1
Resolving some build errors in CI
JordanRL Nov 30, 2021
0139183
Resolving a missed build error in CI
JordanRL Nov 30, 2021
1e3038f
Resolving SIGSEGV
JordanRL Nov 30, 2021
f3a44b8
removing ZEND_USER_FUNCTION set
JordanRL Nov 30, 2021
96d72ce
Removing test code
JordanRL Nov 30, 2021
cfda296
Removing old comment
JordanRL Nov 30, 2021
bbcaf38
Merge branch 'master' into operator-overloads
JordanRL Dec 1, 2021
b23f4e7
Trying JIT handling
JordanRL Dec 2, 2021
5ebaed4
Fixing typo in case
JordanRL Dec 2, 2021
b042c7d
Another case typo
JordanRL Dec 2, 2021
29ddc0d
Use operator keyword instead of magic methods
bwoebi Dec 3, 2021
faab4f2
Merge pull request #3 from bwoebi/operator-overloads
JordanRL Dec 3, 2021
3697f39
Use operator keyword instead of magic methods
bwoebi Dec 3, 2021
52f3f09
Merge pull request #4 from bwoebi/operator-overloads
JordanRL Dec 3, 2021
fc3a89c
Updating InvalidOperatorError name
JordanRL Dec 4, 2021
9afc789
Adding more tests for op overloads
JordanRL Dec 4, 2021
e044f53
Resolving more memory leaks and segfaults
JordanRL Dec 5, 2021
3bae673
Switched to OperandPosition enum
JordanRL Dec 9, 2021
280ad16
Updates
JordanRL Dec 9, 2021
7e1601b
Fix enum case usage in zend_std_call_op_override
bwoebi Dec 10, 2021
5f500bc
Merge pull request #5 from bwoebi/operator-overloads
JordanRL Dec 10, 2021
b0492c4
Adding helper functions for extensions
JordanRL Dec 10, 2021
3d33ddd
Disallow direct method calls to operators
JordanRL Dec 11, 2021
6395654
Reflection support for operator overloads
JordanRL Dec 11, 2021
814d9a2
Merge branch 'master' into operator-overloads
JordanRL Dec 11, 2021
8d1994f
Allowing direct calls via closures
JordanRL Dec 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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. :)

# Special cases to not be merged
# -------

.idea
compile_commands.json
40 changes: 21 additions & 19 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -505,16 +505,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
break;
case ZEND_IS_SMALLER:
if (opline->opcode == ZEND_BOOL_NOT) {
zend_uchar tmp_type;
uint32_t tmp;

src->opcode = ZEND_IS_SMALLER_OR_EQUAL;
tmp_type = src->op1_type;
src->op1_type = src->op2_type;
src->op2_type = tmp_type;
tmp = src->op1.num;
src->op1.num = src->op2.num;
src->op2.num = tmp;
src->opcode = ZEND_IS_LARGER_OR_EQUAL;
}
COPY_NODE(src->result, opline->result);
SET_VAR_SOURCE(src);
Expand All @@ -523,16 +514,25 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
break;
case ZEND_IS_SMALLER_OR_EQUAL:
if (opline->opcode == ZEND_BOOL_NOT) {
zend_uchar tmp_type;
uint32_t tmp;

src->opcode = ZEND_IS_LARGER;
}
COPY_NODE(src->result, opline->result);
SET_VAR_SOURCE(src);
MAKE_NOP(opline);
++(*opt_count);
break;
case ZEND_IS_LARGER:
if (opline->opcode == ZEND_BOOL_NOT) {
src->opcode = ZEND_IS_SMALLER_OR_EQUAL;
}
COPY_NODE(src->result, opline->result);
SET_VAR_SOURCE(src);
MAKE_NOP(opline);
++(*opt_count);
break;
case ZEND_IS_LARGER_OR_EQUAL:
if (opline->opcode == ZEND_BOOL_NOT) {
src->opcode = ZEND_IS_SMALLER;
tmp_type = src->op1_type;
src->op1_type = src->op2_type;
src->op2_type = tmp_type;
tmp = src->op1.num;
src->op1.num = src->op2.num;
src->op2.num = tmp;
}
COPY_NODE(src->result, opline->result);
SET_VAR_SOURCE(src);
Expand Down Expand Up @@ -820,6 +820,8 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
case ZEND_SR:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_BOOL_XOR:
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ static inline bool may_have_side_effects(
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
case ZEND_CAST:
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,8 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|| opline->opcode == ZEND_IS_NOT_EQUAL
|| opline->opcode == ZEND_IS_SMALLER
|| opline->opcode == ZEND_IS_SMALLER_OR_EQUAL
|| opline->opcode == ZEND_IS_LARGER
|| opline->opcode == ZEND_IS_LARGER_OR_EQUAL
) {

if (opline->op1_type == IS_CONST && opline->op2_type != IS_CONST) {
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/pass1.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_BOOL_XOR:
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,8 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
case ZEND_BW_OR:
Expand Down
4 changes: 4 additions & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2344,6 +2344,8 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_INSTANCEOF:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
Expand Down Expand Up @@ -4599,6 +4601,8 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_CASE:
case ZEND_SPACESHIP:
if ((t1 & MAY_BE_ANY) == MAY_BE_NULL
Expand Down
4 changes: 4 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,8 @@ static void zend_redo_pass_two(zend_op_array *op_array)
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
case ZEND_ISSET_ISEMPTY_CV:
Expand Down Expand Up @@ -1233,6 +1235,8 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
case ZEND_IS_NOT_EQUAL:
case ZEND_IS_SMALLER:
case ZEND_IS_SMALLER_OR_EQUAL:
case ZEND_IS_LARGER:
case ZEND_IS_LARGER_OR_EQUAL:
case ZEND_CASE:
case ZEND_CASE_STRICT:
case ZEND_ISSET_ISEMPTY_CV:
Expand Down
40 changes: 39 additions & 1 deletion Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,9 @@ static void place_essa_pis(
((opline-1)->opcode == ZEND_IS_EQUAL ||
(opline-1)->opcode == ZEND_IS_NOT_EQUAL ||
(opline-1)->opcode == ZEND_IS_SMALLER ||
(opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) &&
(opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL ||
(opline-1)->opcode == ZEND_IS_LARGER ||
(opline-1)->opcode == ZEND_IS_LARGER_OR_EQUAL) &&
opline->op1.var == (opline-1)->result.var) {
int var1 = -1;
int var2 = -1;
Expand Down Expand Up @@ -397,6 +399,24 @@ static void place_essa_pis(
pi_range_min(pi, var2, val2+1);
}
}
} else if ((opline-1)->opcode == ZEND_IS_LARGER) {
if (val2 > ZEND_LONG_MIN) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
pi_range_min(pi, var2, val2-1);
}
}
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
pi_range_max(pi, var2, val2);
}
} else if ((opline-1)->opcode == ZEND_IS_LARGER_OR_EQUAL) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) {
pi_range_min(pi, var2, val2);
}
if (val2 < ZEND_LONG_MAX) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) {
pi_range_max(pi, var2, val2+1);
}
}
}
}
if (var2 >= 0) {
Expand Down Expand Up @@ -432,6 +452,24 @@ static void place_essa_pis(
pi_range_max(pi, var1, val1-1);
}
}
} else if ((opline-1)->opcode == ZEND_IS_LARGER) {
if (val1 < ZEND_LONG_MAX) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
pi_range_max(pi, var1, val1+1);
}
}
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
pi_range_min(pi, var1, val1);
}
} else if ((opline-1)->opcode == ZEND_IS_LARGER_OR_EQUAL) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) {
pi_range_max(pi, var1, val1);
}
if (val1 > ZEND_LONG_MIN) {
if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) {
pi_range_min(pi, var1, val1-1);
}
}
}
}
} else if (opline->op1_type == IS_TMP_VAR &&
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/bug63882.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ $testobj2 = new Test;
$testobj1->x = $testobj1;
$testobj2->x = $testobj2;

var_dump($testobj1 == $testobj2);
var_dump($testobj1 <= $testobj2);
?>
--EXPECTF--
Fatal error: Nesting level too deep - recursive dependency? in %sbug63882.php on line 9
4 changes: 3 additions & 1 deletion Zend/tests/clone_002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ var_dump($a == $b, $b == $c);
class foo { }

$d = clone $a = $b = new foo;
var_dump($a == $d, $b == $d, $c == $a);
var_dump($a == $d);
var_dump($b == $d);
var_dump($c == $a);

?>
--EXPECT--
Expand Down
19 changes: 8 additions & 11 deletions Zend/tests/objects_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ comparing objects to other types
--FILE--
<?php

class Bar {
}

$b = new Bar;
$b = new stdClass;

var_dump($b == NULL);
var_dump($b != NULL);
Expand Down Expand Up @@ -37,24 +34,24 @@ bool(true)
bool(false)
bool(true)

Notice: Object of class Bar could not be converted to int in %s on line %d
Notice: Object of class stdClass could not be converted to int in %s on line %d
bool(false)

Notice: Object of class Bar could not be converted to int in %s on line %d
Notice: Object of class stdClass could not be converted to int in %s on line %d
bool(true)

Notice: Object of class Bar could not be converted to int in %s on line %d
Notice: Object of class stdClass could not be converted to int in %s on line %d
bool(true)

Notice: Object of class Bar could not be converted to int in %s on line %d
Notice: Object of class stdClass could not be converted to int in %s on line %d
bool(false)

Notice: Object of class Bar could not be converted to float in %s on line %d
Notice: Object of class stdClass could not be converted to float in %s on line %d
bool(true)

Notice: Object of class Bar could not be converted to float in %s on line %d
Notice: Object of class stdClass could not be converted to float in %s on line %d
bool(false)

Notice: Object of class Bar could not be converted to int in %s on line %d
Notice: Object of class stdClass could not be converted to int in %s on line %d
bool(true)
Done
31 changes: 31 additions & 0 deletions Zend/tests/operator_overloads/add_operator.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
operator overload: add operator with scalars
--FILE--
<?php

class A {
public int $value;

public operator +(int $other, OperandPosition $left): A
{
$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

}
}

$obj = new A();
$obj->value = 3;

$num1 = 2 + $obj;

var_dump($num1->value);

$num2 = $obj + 3;

var_dump($num2->value);
?>
--EXPECT--
int(5)
int(6)
36 changes: 36 additions & 0 deletions Zend/tests/operator_overloads/div_operator.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
operator overload: div operator with scalars
--FILE--
<?php

class A {
public $value;

public operator /(int|float $other, OperandPosition $left): A
{
$return = new A();

if ($left == OperandPosition::LeftSide) {
$return->value = $this->value / $other;
} else {
$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

}
}

$obj = new A();
$obj->value = 6;

$num1 = 12 / $obj;

var_dump($num1->value);

$num2 = $obj / 2;

var_dump($num2->value);
?>
--EXPECT--
int(2)
int(3)
43 changes: 43 additions & 0 deletions Zend/tests/operator_overloads/equals_fallback_comparison.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
operator overload: equals fallback to comparison operator
--FILE--
<?php

class A {
public int $value;

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.

}
}

$obj = new A();
$obj->value = 6;

$bool1 = 12 == $obj;

var_dump($bool1);

$bool2 = $obj == 2;

var_dump($bool2);

$bool3 = $obj == 6;

var_dump($bool3);

$bool4 = $obj == 6.0;

var_dump($bool4);

$bool5 = $obj != 6.0;

var_dump($bool5);
?>
--EXPECT--
bool(false)
bool(false)
bool(true)
bool(true)
bool(false)
Loading