Skip to content

Commit d999117

Browse files
Preserve correct UnionType subclass in filterTypes()
1 parent d9b383f commit d999117

File tree

8 files changed

+136
-2
lines changed

8 files changed

+136
-2
lines changed

src/Type/BenevolentUnionType.php

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ public function __construct(array $types, bool $normalized = false)
1919
parent::__construct($types, $normalized);
2020
}
2121

22+
public function filterTypes(callable $filterCb): Type
23+
{
24+
$result = parent::filterTypes($filterCb);
25+
if (!$result instanceof self && $result instanceof UnionType) {
26+
return TypeUtils::toBenevolentUnion($result);
27+
}
28+
29+
return $result;
30+
}
31+
2232
public function describe(VerbosityLevel $level): string
2333
{
2434
return '(' . parent::describe($level) . ')';

src/Type/Generic/TemplateBenevolentUnionType.php

+17
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,21 @@ public function withTypes(array $types): self
4747
);
4848
}
4949

50+
public function filterTypes(callable $filterCb): Type
51+
{
52+
$result = parent::filterTypes($filterCb);
53+
if (!$result instanceof TemplateType) {
54+
return TemplateTypeFactory::create(
55+
$this->getScope(),
56+
$this->getName(),
57+
$result,
58+
$this->getVariance(),
59+
$this->getStrategy(),
60+
$this->getDefault(),
61+
);
62+
}
63+
64+
return $result;
65+
}
66+
5067
}

src/Type/Generic/TemplateUnionType.php

+17
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,21 @@ public function __construct(
3434
$this->default = $default;
3535
}
3636

37+
public function filterTypes(callable $filterCb): Type
38+
{
39+
$result = parent::filterTypes($filterCb);
40+
if (!$result instanceof TemplateType) {
41+
return TemplateTypeFactory::create(
42+
$this->getScope(),
43+
$this->getName(),
44+
$result,
45+
$this->getVariance(),
46+
$this->getStrategy(),
47+
$this->getDefault(),
48+
);
49+
}
50+
51+
return $result;
52+
}
53+
3754
}

src/Type/UnionType.php

+6
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,20 @@ public function getTypes(): array
9292
public function filterTypes(callable $filterCb): Type
9393
{
9494
$newTypes = [];
95+
$changed = false;
9596
foreach ($this->getTypes() as $innerType) {
9697
if (!$filterCb($innerType)) {
98+
$changed = true;
9799
continue;
98100
}
99101

100102
$newTypes[] = $innerType;
101103
}
102104

105+
if (!$changed) {
106+
return $this;
107+
}
108+
103109
return TypeCombinator::union(...$newTypes);
104110
}
105111

tests/PHPStan/Analyser/nsrt/bug-6609-83.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) {
5050
*/
5151
function modify3(\DateTimeInterface $date, string $s) {
5252
$date = $date->modify($s);
53-
assertType('DateTime|DateTimeImmutable', $date);
53+
assertType('T of DateTime|DateTimeImmutable (method Bug6609Php83\Foo::modify3(), argument)', $date);
5454

5555
return $date;
5656
}

tests/PHPStan/Analyser/nsrt/bug-6609.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function modify2(\DateTimeInterface $date) {
5050
*/
5151
function modify3(\DateTimeInterface $date, string $s) {
5252
$date = $date->modify($s);
53-
assertType('(DateTime|DateTimeImmutable|false)', $date);
53+
assertType('((T of DateTime|DateTimeImmutable (method Bug6609\Foo::modify3(), argument))|false)', $date);
5454

5555
return $date;
5656
}

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -1054,4 +1054,9 @@ public function testBug10715(): void
10541054
$this->analyse([__DIR__ . '/data/bug-10715.php'], []);
10551055
}
10561056

1057+
public function testBug11663(): void
1058+
{
1059+
$this->analyse([__DIR__ . '/data/bug-11663.php'], []);
1060+
}
1061+
10571062
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11663;
4+
5+
class BuilderA
6+
{
7+
/**
8+
* @param string $test
9+
* @return $this
10+
*/
11+
public function where(string $test)
12+
{
13+
return $this;
14+
}
15+
}
16+
17+
class BuilderB
18+
{
19+
/**
20+
* @param string $test
21+
* @return $this
22+
*/
23+
public function where(string $test)
24+
{
25+
return $this;
26+
}
27+
}
28+
29+
interface A
30+
{
31+
public function doFoo(): static;
32+
}
33+
34+
interface B
35+
{
36+
}
37+
38+
class Test
39+
{
40+
/**
41+
* @template B of BuilderA|BuilderB
42+
* @param B $template
43+
* @return B
44+
*/
45+
public function test($template)
46+
{
47+
return $template->where('test');
48+
}
49+
50+
/**
51+
* @param __benevolent<BuilderA|BuilderB|false> $template
52+
* @return __benevolent<BuilderA|BuilderB>
53+
*/
54+
public function test2($template)
55+
{
56+
return $template->where('test');
57+
}
58+
59+
60+
/**
61+
* @template T of A|B
62+
* @param T $ab
63+
* @return T
64+
*/
65+
function foo(A|B $ab): A|B
66+
{
67+
return $ab->doFoo();
68+
}
69+
70+
/**
71+
* @template T of __benevolent<A|B>
72+
* @param T $ab
73+
* @return T
74+
*/
75+
function foo2(A|B $ab): A|B
76+
{
77+
return $ab->doFoo();
78+
}
79+
}

0 commit comments

Comments
 (0)