Skip to content

Commit 15247ea

Browse files
php-genericsclaude
andcommitted
Fix opcache persistence bugs, optimize generic type checks, apply PR review feedback
- Fix opcache SHM persistence for interface_bound_generic_args and trait_bound_generic_args HashTables (zend_persist.c, zend_persist_calc.c, zend_file_cache.c) - Fix object type fast-path bug in zend_check_type that skipped instanceof - Optimize generic type checking with inline mask checks to avoid zend_check_type_slow() calls on the hot path - Optimize ZEND_NEW handler to use inlined i_zend_get_current_generic_args() and skip context resolution when not in a generic context - Apply PR review feedback: convert ZEND_GENERIC_VARIANCE_* and ZEND_GENERIC_BOUND_* defines to C23_ENUM, remove underscore prefixes from struct names, use proper enum types for variance/bound fields, use typedefs instead of struct tags in globals Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1dcdf87 commit 15247ea

37 files changed

+2035
-89
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
Generic class: abstract generic class with abstract methods
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
abstract class Repository<T> {
8+
abstract public function find(int $id): T;
9+
abstract public function save(T $entity): void;
10+
11+
public function findAndSave(int $id): T {
12+
$item = $this->find($id);
13+
$this->save($item);
14+
return $item;
15+
}
16+
}
17+
18+
class User {
19+
public function __construct(public int $id, public string $name) {}
20+
}
21+
22+
class UserRepository extends Repository<User> {
23+
private array $store = [];
24+
25+
public function find(int $id): User {
26+
return $this->store[$id] ?? new User($id, "User$id");
27+
}
28+
29+
public function save(User $entity): void {
30+
$this->store[$entity->id] = $entity;
31+
}
32+
}
33+
34+
// 1. Basic usage
35+
$repo = new UserRepository();
36+
$user = $repo->find(1);
37+
echo "1. " . $user->name . "\n";
38+
39+
// 2. Save and retrieve
40+
$repo->save(new User(2, "Alice"));
41+
echo "2. " . $repo->find(2)->name . "\n";
42+
43+
// 3. Template method pattern
44+
$u = $repo->findAndSave(3);
45+
echo "3. " . $u->name . "\n";
46+
47+
// 4. Cannot instantiate abstract generic class
48+
try {
49+
$r = new Repository<User>();
50+
} catch (Error $e) {
51+
echo "4. Error: " . (str_contains($e->getMessage(), 'abstract') ? 'abstract' : $e->getMessage()) . "\n";
52+
}
53+
54+
echo "Done.\n";
55+
?>
56+
--EXPECT--
57+
1. User1
58+
2. Alice
59+
3. User3
60+
4. Error: abstract
61+
Done.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
Generic class: circular references with GC
3+
--FILE--
4+
<?php
5+
6+
class Node<T> {
7+
public T $value;
8+
public ?self $next = null;
9+
10+
public function __construct(T $value) {
11+
$this->value = $value;
12+
}
13+
}
14+
15+
// 1. Simple circular reference
16+
$a = new Node<int>(1);
17+
$b = new Node<int>(2);
18+
$a->next = $b;
19+
$b->next = $a; // circular
20+
21+
echo "1. a=" . $a->value . ", b=" . $b->value . "\n";
22+
echo "1. a->next=" . $a->next->value . "\n";
23+
echo "1. b->next=" . $b->next->value . "\n";
24+
25+
// Break and let GC handle it
26+
unset($a, $b);
27+
gc_collect_cycles();
28+
echo "1. GC OK\n";
29+
30+
// 2. Self-referential
31+
$self = new Node<int>(42);
32+
$self->next = $self;
33+
echo "2. self=" . $self->value . "\n";
34+
echo "2. self->next=" . $self->next->value . "\n";
35+
unset($self);
36+
gc_collect_cycles();
37+
echo "2. GC OK\n";
38+
39+
// 3. Longer cycle
40+
$n1 = new Node<string>("a");
41+
$n2 = new Node<string>("b");
42+
$n3 = new Node<string>("c");
43+
$n1->next = $n2;
44+
$n2->next = $n3;
45+
$n3->next = $n1; // cycle
46+
47+
echo "3. chain: " . $n1->value . "->" . $n2->value . "->" . $n3->value . "\n";
48+
unset($n1, $n2, $n3);
49+
gc_collect_cycles();
50+
echo "3. GC OK\n";
51+
52+
// 4. Many cycles to stress GC
53+
for ($i = 0; $i < 100; $i++) {
54+
$x = new Node<int>($i);
55+
$y = new Node<int>($i + 1);
56+
$x->next = $y;
57+
$y->next = $x;
58+
}
59+
gc_collect_cycles();
60+
echo "4. 100 cycles GC OK\n";
61+
62+
echo "Done.\n";
63+
?>
64+
--EXPECT--
65+
1. a=1, b=2
66+
1. a->next=2
67+
1. b->next=1
68+
1. GC OK
69+
2. self=42
70+
2. self->next=42
71+
2. GC OK
72+
3. chain: a->b->c
73+
3. GC OK
74+
4. 100 cycles GC OK
75+
Done.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
Generic class: Closure::bind and bindTo with generic $this
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
class Box<T> {
8+
private T $value;
9+
public function __construct(T $value) { $this->value = $value; }
10+
public function get(): T { return $this->value; }
11+
}
12+
13+
// 1. Closure reading private property of generic object
14+
$getter = Closure::bind(function() {
15+
return $this->value;
16+
}, new Box<int>(42), Box::class);
17+
18+
echo "1. " . $getter() . "\n";
19+
20+
// 2. Closure bound to different generic instance
21+
$box1 = new Box<string>("hello");
22+
$box2 = new Box<string>("world");
23+
24+
$fn = function() { return $this->value; };
25+
$bound1 = Closure::bind($fn, $box1, Box::class);
26+
$bound2 = Closure::bind($fn, $box2, Box::class);
27+
28+
echo "2. " . $bound1() . "\n";
29+
echo "2. " . $bound2() . "\n";
30+
31+
// 3. bindTo
32+
$box3 = new Box<int>(99);
33+
$rebound = $bound1->bindTo($box3, Box::class);
34+
echo "3. " . $rebound() . "\n";
35+
36+
// 4. Closure with use() capturing generic object
37+
$captured = new Box<int>(7);
38+
$fn = function() use ($captured) {
39+
return $captured->get();
40+
};
41+
echo "4. " . $fn() . "\n";
42+
43+
// 5. Closure modifying generic object
44+
$modifier = Closure::bind(function($val) {
45+
$this->value = $val;
46+
}, new Box<int>(0), Box::class);
47+
48+
$modifier(42);
49+
// Note: the closure's $this is a copy of the original binding
50+
echo "5. OK\n";
51+
52+
echo "Done.\n";
53+
?>
54+
--EXPECT--
55+
1. 42
56+
2. hello
57+
2. world
58+
3. 99
59+
4. 7
60+
5. OK
61+
Done.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
--TEST--
2+
Generic class: generic class defined in included file
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
--FILE--
7+
<?php
8+
declare(strict_types=1);
9+
10+
// Define generic class in a temp file and include it
11+
$tmpDir = sys_get_temp_dir();
12+
$file1 = $tmpDir . '/generic_box_' . getmypid() . '.php';
13+
$file2 = $tmpDir . '/generic_pair_' . getmypid() . '.php';
14+
15+
file_put_contents($file1, '<?php
16+
class Box<T> {
17+
public T $value;
18+
public function __construct(T $value) { $this->value = $value; }
19+
public function get(): T { return $this->value; }
20+
}
21+
');
22+
23+
file_put_contents($file2, '<?php
24+
class Pair<A, B> {
25+
public function __construct(private A $first, private B $second) {}
26+
public function first(): A { return $this->first; }
27+
public function second(): B { return $this->second; }
28+
}
29+
');
30+
31+
// Include both files
32+
require_once $file1;
33+
require_once $file2;
34+
35+
// 1. Use included generic class
36+
$box = new Box<int>(42);
37+
echo "1. " . $box->get() . "\n";
38+
39+
// 2. Type enforcement from included class
40+
try {
41+
$box2 = new Box<int>("bad");
42+
} catch (TypeError $e) {
43+
echo "2. TypeError OK\n";
44+
}
45+
46+
// 3. Second included generic class
47+
$pair = new Pair<string, int>("age", 25);
48+
echo "3. " . $pair->first() . ": " . $pair->second() . "\n";
49+
50+
// 4. Inheritance from included generic class
51+
class IntBox extends Box<int> {}
52+
$ib = new IntBox(99);
53+
echo "4. " . $ib->get() . "\n";
54+
55+
// 5. Type inference from included class
56+
$inferred = new Box("hello");
57+
echo "5. " . $inferred->get() . "\n";
58+
59+
// Cleanup
60+
unlink($file1);
61+
unlink($file2);
62+
63+
echo "Done.\n";
64+
?>
65+
--EXPECT--
66+
1. 42
67+
2. TypeError OK
68+
3. age: 25
69+
4. 99
70+
5. hello
71+
Done.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--TEST--
2+
Generic class: child inherits parent's generic constructor
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
class Box<T> {
8+
private T $value;
9+
public function __construct(T $value) { $this->value = $value; }
10+
public function get(): T { return $this->value; }
11+
}
12+
13+
// Child with no constructor — inherits parent's
14+
class LabeledBox<T> extends Box<T> {
15+
public function label(): string { return "LabeledBox"; }
16+
}
17+
18+
// Child with bound type and no constructor
19+
class IntBox extends Box<int> {}
20+
21+
// Child with own constructor calling parent
22+
class NamedBox<T> extends Box<T> {
23+
private string $name;
24+
public function __construct(string $name, T $value) {
25+
$this->name = $name;
26+
parent::__construct($value);
27+
}
28+
public function getName(): string { return $this->name; }
29+
}
30+
31+
// 1. Inherited constructor with explicit type args
32+
$lb = new LabeledBox<string>("hello");
33+
echo "1. " . $lb->get() . " - " . $lb->label() . "\n";
34+
35+
// 2. Inherited constructor — type enforcement
36+
try {
37+
$lb2 = new LabeledBox<int>("not int");
38+
} catch (TypeError $e) {
39+
echo "2. TypeError OK\n";
40+
}
41+
42+
// 3. Bound type with inherited constructor
43+
$ib = new IntBox(42);
44+
echo "3. " . $ib->get() . "\n";
45+
46+
try {
47+
$ib2 = new IntBox("not int");
48+
} catch (TypeError $e) {
49+
echo "3. TypeError OK\n";
50+
}
51+
52+
// 4. Own constructor calling parent
53+
$nb = new NamedBox<float>("temp", 98.6);
54+
echo "4. " . $nb->getName() . ": " . $nb->get() . "\n";
55+
56+
// 5. Inferred type through explicit type args on child
57+
$lb3 = new LabeledBox<int>(42);
58+
echo "5. " . $lb3->get() . " - " . $lb3->label() . "\n";
59+
60+
try {
61+
$lb3->get(); // returns int, fine
62+
echo "5. class: " . get_class($lb3) . "\n";
63+
} catch (TypeError $e) {
64+
echo "FAIL: " . $e->getMessage() . "\n";
65+
}
66+
67+
echo "Done.\n";
68+
?>
69+
--EXPECT--
70+
1. hello - LabeledBox
71+
2. TypeError OK
72+
3. 42
73+
3. TypeError OK
74+
4. temp: 98.6
75+
5. 42 - LabeledBox
76+
5. class: LabeledBox
77+
Done.

0 commit comments

Comments
 (0)