Skip to content

Commit a535a22

Browse files
authored
Fix phpstan/phpstan#12964: Support for covariant templates in interface properties in 8.4 (#5415)
1 parent 378f49c commit a535a22

File tree

3 files changed

+229
-1
lines changed

3 files changed

+229
-1
lines changed

src/Rules/Generics/PropertyVarianceRule.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function processNode(Node $node, Scope $scope): array
4545
return [];
4646
}
4747

48-
$variance = $node->isReadOnly() || $node->isReadOnlyByPhpDoc()
48+
$variance = $this->hasRestrictedWriteAccess($node)
4949
? TemplateTypeVariance::createCovariant()
5050
: TemplateTypeVariance::createInvariant();
5151

@@ -56,4 +56,28 @@ public function processNode(Node $node, Scope $scope): array
5656
);
5757
}
5858

59+
private function hasRestrictedWriteAccess(ClassPropertyNode $node): bool
60+
{
61+
if ($node->isReadOnly() || $node->isReadOnlyByPhpDoc()) {
62+
return true;
63+
}
64+
65+
if ($node->isPrivateSet()) {
66+
return true;
67+
}
68+
69+
$hooks = $node->getHooks();
70+
if ($hooks === []) {
71+
return false;
72+
}
73+
74+
foreach ($hooks as $hook) {
75+
if ($hook->name->name === 'set') {
76+
return false;
77+
}
78+
}
79+
80+
return true;
81+
}
82+
5983
}

tests/PHPStan/Rules/Generics/PropertyVarianceRuleTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,71 @@ public function testBug13049(): void
137137
$this->analyse([__DIR__ . '/data/bug-13049.php'], []);
138138
}
139139

140+
#[RequiresPhp('>= 8.4')]
141+
public function testBug12964(): void
142+
{
143+
$this->analyse([__DIR__ . '/data/bug-12964.php'], [
144+
[
145+
'Template type X is declared as covariant, but occurs in contravariant position in property Bug12964\C::$b.',
146+
51,
147+
],
148+
[
149+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\C::$d.',
150+
57,
151+
],
152+
[
153+
'Template type X is declared as contravariant, but occurs in covariant position in property Bug12964\D::$a.',
154+
65,
155+
],
156+
[
157+
'Template type X is declared as contravariant, but occurs in covariant position in property Bug12964\D::$c.',
158+
71,
159+
],
160+
[
161+
'Template type X is declared as contravariant, but occurs in invariant position in property Bug12964\D::$d.',
162+
74,
163+
],
164+
[
165+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\E::$a.',
166+
82,
167+
],
168+
[
169+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\E::$b.',
170+
85,
171+
],
172+
[
173+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\E::$c.',
174+
88,
175+
],
176+
[
177+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\E::$d.',
178+
91,
179+
],
180+
[
181+
'Template type X is declared as covariant, but occurs in contravariant position in property Bug12964\F::$b.',
182+
103,
183+
],
184+
[
185+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\F::$d.',
186+
109,
187+
],
188+
[
189+
'Template type X is declared as contravariant, but occurs in covariant position in property Bug12964\G::$a.',
190+
118,
191+
],
192+
[
193+
'Template type X is declared as contravariant, but occurs in covariant position in property Bug12964\G::$c.',
194+
124,
195+
],
196+
[
197+
'Template type X is declared as contravariant, but occurs in invariant position in property Bug12964\G::$d.',
198+
127,
199+
],
200+
[
201+
'Template type X is declared as covariant, but occurs in invariant position in property Bug12964\H::$a.',
202+
136,
203+
],
204+
]);
205+
}
206+
140207
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php // lint >= 8.4
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug12964;
6+
7+
/** @template-contravariant T */
8+
interface In {
9+
}
10+
11+
/** @template-covariant T */
12+
interface Out {
13+
}
14+
15+
/** @template T */
16+
interface Invariant {
17+
}
18+
19+
/**
20+
* @template-covariant T
21+
*/
22+
interface A
23+
{
24+
/**
25+
* @var T
26+
*/
27+
public mixed $b { get; }
28+
}
29+
30+
/**
31+
* @template-covariant T
32+
*/
33+
final class B
34+
{
35+
/**
36+
* @param T $data
37+
*/
38+
public function __construct(
39+
public private(set) mixed $data,
40+
) {}
41+
}
42+
43+
/**
44+
* @template-covariant X
45+
*/
46+
class C {
47+
/** @var X */
48+
public private(set) mixed $a;
49+
50+
/** @var In<X> */
51+
public private(set) mixed $b;
52+
53+
/** @var Out<X> */
54+
public private(set) mixed $c;
55+
56+
/** @var Invariant<X> */
57+
public private(set) mixed $d;
58+
}
59+
60+
/**
61+
* @template-contravariant X
62+
*/
63+
class D {
64+
/** @var X */
65+
public private(set) mixed $a;
66+
67+
/** @var In<X> */
68+
public private(set) mixed $b;
69+
70+
/** @var Out<X> */
71+
public private(set) mixed $c;
72+
73+
/** @var Invariant<X> */
74+
public private(set) mixed $d;
75+
}
76+
77+
/**
78+
* @template-covariant X
79+
*/
80+
class E {
81+
/** @var X */
82+
public protected(set) mixed $a;
83+
84+
/** @var In<X> */
85+
public protected(set) mixed $b;
86+
87+
/** @var Out<X> */
88+
public protected(set) mixed $c;
89+
90+
/** @var Invariant<X> */
91+
public protected(set) mixed $d;
92+
}
93+
94+
/**
95+
* @template-covariant X
96+
*/
97+
interface F
98+
{
99+
/** @var X */
100+
public mixed $a { get; }
101+
102+
/** @var In<X> */
103+
public mixed $b { get; }
104+
105+
/** @var Out<X> */
106+
public mixed $c { get; }
107+
108+
/** @var Invariant<X> */
109+
public mixed $d { get; }
110+
}
111+
112+
/**
113+
* @template-contravariant X
114+
*/
115+
interface G
116+
{
117+
/** @var X */
118+
public mixed $a { get; }
119+
120+
/** @var In<X> */
121+
public mixed $b { get; }
122+
123+
/** @var Out<X> */
124+
public mixed $c { get; }
125+
126+
/** @var Invariant<X> */
127+
public mixed $d { get; }
128+
}
129+
130+
/**
131+
* @template-covariant X
132+
*/
133+
interface H
134+
{
135+
/** @var X */
136+
public mixed $a { get; set; }
137+
}

0 commit comments

Comments
 (0)