Skip to content
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

[RFC] Implement structs (WIP) #13800

Draft
wants to merge 30 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c0351ab
Implement data classes
iluuu1994 Mar 21, 2024
4371a00
Support data classes in references
iluuu1994 Mar 22, 2024
b947423
Implement ++/--
iluuu1994 Mar 22, 2024
18b43a0
Implement op assign
iluuu1994 Mar 22, 2024
5c5172f
Test assign ref
iluuu1994 Mar 22, 2024
7688be0
Test sending
iluuu1994 Mar 22, 2024
512917d
Tweak identical check for data classes
iluuu1994 Mar 23, 2024
ca2e696
Support separation on assignment to self
iluuu1994 Mar 24, 2024
0c35679
Extend test for refs
iluuu1994 Mar 24, 2024
9810d52
Properly separate on ReflectionProperty::setValue()
iluuu1994 Mar 24, 2024
c9c7455
Respect readonly to prevent interior mutability
iluuu1994 Mar 24, 2024
686cc4d
Test internal classes
iluuu1994 Mar 25, 2024
5c966fb
Reflection test is fixed!
iluuu1994 Mar 25, 2024
3a322cd
Fix incorrect self-separation on ind/dec/assign-op
iluuu1994 Mar 25, 2024
273270e
Separate on FETCH_THIS
iluuu1994 Mar 27, 2024
629a29b
Add mutating keyword
iluuu1994 Mar 28, 2024
512fee9
Disallow mutating keyword outside of data classes
iluuu1994 Mar 29, 2024
22f8a54
Disallow data classes for SplObjectStorage and WeakMap
iluuu1994 Mar 30, 2024
e7f1d8d
Disallow ArrayObject
iluuu1994 Mar 30, 2024
defd8b2
Fix iteration of data class by-ref
iluuu1994 Apr 4, 2024
ca5b8ea
Temporarily disable jit for benchmark
iluuu1994 Apr 4, 2024
59f1896
Improve iter tests
iluuu1994 Apr 4, 2024
b4c3b85
Disallow data classes in ReflectionProperty::setValue() and Reflectio…
iluuu1994 Apr 4, 2024
657126f
Add [UN]EXPECTED hints
iluuu1994 Apr 4, 2024
0551cdb
Commit generated file
iluuu1994 Apr 5, 2024
d16ad39
Fix comment for unused ce flags
iluuu1994 Apr 5, 2024
349484a
Simplify expression
iluuu1994 Apr 5, 2024
0c2ed9e
Add test for ArrayAccess
iluuu1994 Apr 7, 2024
911f9fb
Rename to structs
iluuu1994 May 8, 2024
3feaa68
Handle final/abstract/readonly for structs
iluuu1994 May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions Zend/tests/data_classes/assign_op.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Copy link
Contributor

@mvorisek mvorisek Apr 7, 2024

Choose a reason for hiding this comment

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

I would like to see a test with spl_object_id() confirming the expected CoW semantic like:

$o = new data class;
$origId = spl_object_id($o);

$fx = function ($o) use ($origId) {
    var_dump(spl_object_id($o) - $origId);

    $o->x = 2; // mutate the data class instance to enforce object copy
    var_dump(spl_object_id($o) - $origId);
    $o->x = 3;
    var_dump(spl_object_id($o) - $origId);

    $o2 = $o;
    var_dump(spl_object_id($o2) - $origId);
    $o2->x = 3;
    var_dump(spl_object_id($o2) - $origId);
    $o2->x = 12;
    var_dump(spl_object_id($o2) - $origId);
    $o2->x = 13;
    var_dump(spl_object_id($o2) - $origId);
};
$fx();

var_dump(spl_object_id($o) - $origId);

Assign op on data classes
--FILE--
<?php

data class Box {
iluuu1994 marked this conversation as resolved.
Show resolved Hide resolved
public function __construct(
public $value,
) {}
}

$a = new Box(0);
$b = $a;
$b->value += 1;
var_dump($a);
var_dump($b);

?>
--EXPECT--
object(Box)#1 (1) {
["value"]=>
int(0)
}
object(Box)#2 (1) {
["value"]=>
int(1)
}
27 changes: 27 additions & 0 deletions Zend/tests/data_classes/assign_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Assign ref on data classes
--FILE--
<?php

data class Box {
public $value;
}

$a = new Box();
$a->value = 1;
$b = $a;
$c = 2;
$b->value = &$c;
var_dump($a);
var_dump($b);

?>
--EXPECT--
object(Box)#1 (1) {
["value"]=>
int(1)
}
object(Box)#2 (1) {
["value"]=>
&int(2)
}
32 changes: 32 additions & 0 deletions Zend/tests/data_classes/assign_to_self.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Send data classes
--FILE--
<?php

data class Box {
public function __construct(
public $value,
) {}
}

$a = new Box(42);
$a->value = $a;
var_dump($a);

$b = new Box(42);
$b->value = &$b;
var_dump($b);

?>
--EXPECT--
object(Box)#2 (1) {
["value"]=>
object(Box)#1 (1) {
["value"]=>
int(42)
}
}
object(Box)#3 (1) {
["value"]=>
*RECURSION*
}
33 changes: 33 additions & 0 deletions Zend/tests/data_classes/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Basic data classes
--FILE--
<?php

data class Point {
public function __construct(
public int $x,
public int $y,
) {}
}

$a = new Point(1, 1);
$b = $a;
$b->x = 2;
$b->y = 2;
var_dump($a);
var_dump($b);

?>
--EXPECT--
object(Point)#1 (2) {
["x"]=>
int(1)
["y"]=>
int(1)
}
object(Point)#2 (2) {
["x"]=>
int(2)
["y"]=>
int(2)
}
43 changes: 43 additions & 0 deletions Zend/tests/data_classes/interior_immutability.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
Basic data classes
--FILE--
<?php

data class Child {
public function __construct(
public $value,
) {}
}

class Parent_ {
public function __construct(
public Child $writableChild,
public readonly Child $readonlyChild,
) {}
}

$parent = new Parent_(new Child(1), new Child(1));
$parent->writableChild->value = 2;
try {
$parent->readonlyChild->value = 2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

var_dump($parent);

?>
--EXPECT--
Cannot modify readonly property Parent_::$readonlyChild
object(Parent_)#1 (2) {
["writableChild"]=>
object(Child)#2 (1) {
["value"]=>
int(2)
}
["readonlyChild"]=>
object(Child)#3 (1) {
["value"]=>
int(1)
}
}
73 changes: 73 additions & 0 deletions Zend/tests/data_classes/is_identical.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
--TEST--
=== on data classes
--FILE--
<?php

data class Box {
public function __construct(
public $value,
) {}
}

$a = new Box(1);
$b = new Box(2);
$c = $a;
$d = $a;
$d->value++;
$e = new Box('1');

$values = [$a, $b, $c, $d, $e];
foreach ($values as $l) {
foreach ($values as $r) {
echo var_export($l->value, true)
. ' === '
. var_export($r->value, true)
. ' => '
. ($l === $r ? 'true' : 'false')
. "\n";
}
}

#[\AllowDynamicProperties]
data class Point {}

$a = new Point();
$a->x = 1;
$a->y = 1;
$b = new Point();
$b->y = 1;
$b->x = 1;
var_dump($a === $b);
unset($b->y);
$b->y = 1;
var_dump($a === $b);

?>
--EXPECT--
1 === 1 => true
1 === 2 => false
1 === 1 => true
1 === 2 => false
1 === '1' => false
2 === 1 => false
2 === 2 => true
2 === 1 => false
2 === 2 => true
2 === '1' => false
1 === 1 => true
1 === 2 => false
1 === 1 => true
1 === 2 => false
1 === '1' => false
2 === 1 => false
2 === 2 => true
2 === 1 => false
2 === 2 => true
2 === '1' => false
'1' === 1 => false
'1' === 2 => false
'1' === 1 => false
'1' === 2 => false
'1' === '1' => true
bool(false)
bool(true)
84 changes: 84 additions & 0 deletions Zend/tests/data_classes/iter.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--TEST--
Iteration of data classes
--FILE--
<?php

#[AllowDynamicProperties]
data class Box {
public $value;
}

$box = new Box();
$box->value = 1;
$copy = $box;

echo "Iteration by value\n";

foreach ($box as $value) {
var_dump($value);
}

echo "\nIteration by ref\n";

foreach ($box as $prop => &$value) {
if ($prop === 'value' && $value === 1) {
$box->dynamic = 1;
}
$value++;
var_dump($value);
}

echo "\nIteration by ref on copy\n";

$copy2 = $copy;
$copy2Ref = &$copy2;

foreach ($copy2Ref as $prop => &$value) {
$value++;
var_dump($value);
}

echo "\nIteration by ref on ref with separation\n";

function &getBox() {
global $box;
$box2 = $box;
return $box2;
}

foreach (getBox() as $prop => &$value) {
$value++;
var_dump($value);
}

echo "\nDone\n";

var_dump($box, $copy);

?>
--EXPECT--
Iteration by value
int(1)

Iteration by ref
int(2)
int(2)

Iteration by ref on copy
int(2)

Iteration by ref on ref with separation
int(3)
int(3)

Done
object(Box)#2 (2) {
["value"]=>
int(2)
["dynamic"]=>
int(2)
}
object(Box)#1 (1) {
["value"]=>
int(1)
}
12 changes: 12 additions & 0 deletions Zend/tests/data_classes/mutatin_in_ordinary_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Mutating keyword in ordinary class
--FILE--
<?php

class Point {
public mutating function zero() {}
}

?>
--EXPECTF--
Fatal error: Mutating modifier may only be added to data class methods in %s on line %d
38 changes: 38 additions & 0 deletions Zend/tests/data_classes/mutating_call_verification.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Mutating method calls on data classes
--FILE--
<?php

data class D {
public function nonMutating() {
echo __FUNCTION__, "\n";
}

public mutating function mutating() {
echo __FUNCTION__, "\n";
}
}

$d = new D();

$d->nonMutating();
$d->mutating!();

try {
$d->nonMutating!();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
$d->mutating();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
nonMutating
mutating
Non-mutating method must not be called with $object->func!() syntax
Mutating method must be called with $object->func!() syntax