Skip to content

Commit a52ce5f

Browse files
committed
feat(zend): enforce generic type-parameter variance at class declaration
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent c54ea59 commit a52ce5f

23 files changed

Lines changed: 579 additions & 0 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
--TEST--
2+
Variance: full happy-path showing every legal combination
3+
--FILE--
4+
<?php
5+
class Animal {}
6+
class Dog extends Animal {}
7+
8+
interface Producer<+T> { public function produce(): T; }
9+
interface Consumer<-T> { public function consume(T $x): void; }
10+
interface Inv<U> { public function rw(U $x): U; }
11+
12+
class CoProducer<+T> {
13+
public readonly T $tag;
14+
public function __construct(T $tag) { $this->tag = $tag; }
15+
public function get(): T {}
16+
public function makeProducer(): Producer<T> {}
17+
public function makeConsumerOfConsumers(): Consumer<Consumer<T>> {}
18+
}
19+
20+
class ReadOnlyHooked<+T> {
21+
private T $backing;
22+
public function __construct(T $v) { $this->backing = $v; }
23+
public T $val { get => $this->backing; }
24+
}
25+
26+
class CoConsumer<-T> {
27+
public function take(T $x): void {}
28+
public function takeMany(T ...$xs): void {}
29+
public function makeConsumer(): Consumer<T> {}
30+
public function consumerOfProducers(Producer<T> $p): void {}
31+
}
32+
33+
class Holder<T> {
34+
public T $val;
35+
public function __construct(T $v) { $this->val = $v; }
36+
public function get(): T { return $this->val; }
37+
public function set(T $v): void { $this->val = $v; }
38+
public T $hooked {
39+
get => $this->val;
40+
set(T $v) { $this->val = $v; }
41+
}
42+
}
43+
44+
interface Source<+T> { public function fetch(): T; }
45+
class StringSource implements Source<string> {
46+
public function fetch(): string { return "ok"; }
47+
}
48+
49+
class CovariantChain<+T> implements Source<T> {
50+
public function __construct(public readonly T $value) {}
51+
public function fetch(): T { return $this->value; }
52+
}
53+
54+
class MethodLevel<+T> {
55+
public function map<U>(U $key): T {}
56+
}
57+
58+
trait TraitProducer<+U> {
59+
public function produceU(): U {}
60+
}
61+
class UsesProducerTrait {
62+
use TraitProducer<int>;
63+
public function produceU(): int { return 7; }
64+
}
65+
66+
$h = new Holder::<int>(42);
67+
$h->set(7);
68+
echo $h->get(), "\n";
69+
70+
$rh = new ReadOnlyHooked::<string>("hi");
71+
echo $rh->val, "\n";
72+
73+
$cp = new CoProducer::<Dog>(new Dog());
74+
echo get_class($cp->tag), "\n";
75+
76+
echo (new UsesProducerTrait)->produceU(), "\n";
77+
echo (new StringSource)->fetch(), "\n";
78+
79+
echo "all variance combinations OK\n";
80+
?>
81+
--EXPECT--
82+
7
83+
hi
84+
Dog
85+
7
86+
ok
87+
all variance combinations OK
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Variance: -T in a hooked property with set hook (invariant position) is rejected
3+
--FILE--
4+
<?php
5+
class A<-T> {
6+
private T $backing;
7+
public function __construct(T $v) { $this->backing = $v; }
8+
public T $val {
9+
get => $this->backing;
10+
set(T $v) { $this->backing = $v; }
11+
}
12+
}
13+
?>
14+
--EXPECTF--
15+
Fatal error: Type parameter T declared contravariant (-T) cannot appear in invariant position in %s on line %d
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Variance: -T in a readonly property (covariant position) is rejected
3+
--FILE--
4+
<?php
5+
class A<-T> {
6+
public readonly T $val;
7+
public function __construct(T $v) { $this->val = $v; }
8+
}
9+
?>
10+
--EXPECTF--
11+
Fatal error: Type parameter T declared contravariant (-T) cannot appear in covariant position in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Variance: -T in method return (covariant position) is rejected
3+
--FILE--
4+
<?php
5+
class A<-T> {
6+
public function get(): T {}
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Type parameter T declared contravariant (-T) cannot appear in covariant position in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Variance: -T in a read/write typed property (invariant position) is rejected
3+
--FILE--
4+
<?php
5+
class A<-T> {
6+
public T $val;
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Type parameter T declared contravariant (-T) cannot appear in invariant position in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Variance: -T composed through Producer<+U> in return (+ × + = +) is rejected
3+
--FILE--
4+
<?php
5+
interface Producer<+U> { public function produce(): U; }
6+
7+
class A<-T> {
8+
public function makeProducer(): Producer<T> {}
9+
}
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type parameter T declared contravariant (-T) cannot appear in covariant position in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Variance: +T in a hooked property with by-ref get (effectively invariant) is rejected
3+
--FILE--
4+
<?php
5+
class A<+T> {
6+
private T $backing;
7+
public function __construct(T $v) { $this->backing = $v; }
8+
public T $val {
9+
&get => $this->backing;
10+
}
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Type parameter T declared covariant (+T) cannot appear in invariant position in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Variance: +T in a sibling type parameter's default (invariant position) is rejected
3+
--FILE--
4+
<?php
5+
interface Container<U> { public function get(): U; }
6+
7+
class A<+T, U = Container<T>> {}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Type parameter T declared covariant (+T) cannot appear in invariant position in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Variance: +T appearing in its own bound (invariant position) is rejected
3+
--FILE--
4+
<?php
5+
interface Box<U> { public function get(): U; }
6+
7+
class A<+T : Box<T>> {}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Type parameter T declared covariant (+T) cannot appear in invariant position in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Variance: +T in method parameter (contravariant position) is rejected
3+
--FILE--
4+
<?php
5+
class A<+T> {
6+
public function take(T $x): void {}
7+
}
8+
?>
9+
--EXPECTF--
10+
Fatal error: Type parameter T declared covariant (+T) cannot appear in contravariant position in %s on line %d

0 commit comments

Comments
 (0)