Skip to content

Commit f7c2d72

Browse files
committed
Fix phpstan/phpstan#9732: BenevolentUnionType with generics produce confusing errors (phpstan#5070)
1 parent a797a5e commit f7c2d72

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\Type\TypeUtils;
1919
use PHPStan\Type\UnionType;
2020
use PHPStan\Type\VerbosityLevel;
21+
use function count;
2122
use function sprintf;
2223

2324
/**
@@ -287,6 +288,23 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
287288
]))->union($map);
288289
}
289290

291+
if ($receivedType instanceof UnionType) {
292+
$matchingTypes = [];
293+
foreach ($receivedType->getTypes() as $innerType) {
294+
if (!$resolvedBound->isSuperTypeOf($innerType)->yes()) {
295+
continue;
296+
}
297+
298+
$matchingTypes[] = $innerType;
299+
}
300+
if (count($matchingTypes) > 0) {
301+
$filteredType = TypeCombinator::union(...$matchingTypes);
302+
return (new TemplateTypeMap([
303+
$this->name => $filteredType,
304+
]))->union($map);
305+
}
306+
}
307+
290308
return $map;
291309
}
292310

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,38 @@ public function testBug13556(): void
931931
$this->analyse([__DIR__ . '/data/bug-13556.php'], []);
932932
}
933933

934+
public function testBug9732(): void
935+
{
936+
$this->checkThisOnly = false;
937+
$this->checkExplicitMixed = true;
938+
$this->checkImplicitMixed = true;
939+
940+
$this->analyse([__DIR__ . '/data/bug-9732.php'], [
941+
[
942+
'Parameter #1 $array of static method Bug9732\HelloWorld::stringifyKeys() expects array<string, mixed>, array<mixed> given.',
943+
21,
944+
],
945+
]);
946+
}
947+
948+
public function testBug12558(): void
949+
{
950+
$this->checkThisOnly = false;
951+
$this->checkExplicitMixed = true;
952+
$this->checkImplicitMixed = true;
953+
954+
$this->analyse([__DIR__ . '/data/bug-12558.php'], [
955+
[
956+
'Parameter #1 $object of static method Bug12558\Foo::assertStatic() expects Bug12558\Foo, Bug12558\Foo|null given.',
957+
45,
958+
],
959+
[
960+
'Parameter #1 $object of static method Bug12558\Foo::assertStatic() expects Bug12558\Foo, bool|Bug12558\Foo given.',
961+
46,
962+
],
963+
]);
964+
}
965+
934966
#[RequiresPhp('>= 8.5')]
935967
public function testPipeOperator(): void
936968
{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12558;
4+
5+
class Foo
6+
{
7+
/**
8+
* @template T of object
9+
*
10+
* @param T $object
11+
*
12+
* @return (T is static ? T : static)
13+
*/
14+
public static function assertStatic(object $object)
15+
{
16+
if (!$object instanceof static) {
17+
throw new \Error('Object is not an instance of static class');
18+
}
19+
20+
return $object;
21+
}
22+
23+
protected function createFoo(): self
24+
{
25+
return new Foo();
26+
}
27+
28+
protected function createFooNullable(): ?self
29+
{
30+
return new Foo();
31+
}
32+
33+
protected function createFooUnionedWithBool(): self|bool
34+
{
35+
return new Foo();
36+
}
37+
38+
protected function foo(): void
39+
{
40+
}
41+
42+
public function testAssertInstanceOf(): void
43+
{
44+
(static::class)::assertStatic($this->createFoo())->foo();
45+
(static::class)::assertStatic($this->createFooNullable())->foo();
46+
(static::class)::assertStatic($this->createFooUnionedWithBool())->foo();
47+
}
48+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9732;
4+
5+
class HelloWorld
6+
{
7+
/**
8+
* @phpstan-template TKeyType of string
9+
* @phpstan-template TValueType
10+
* @phpstan-param array<TKeyType, TValueType> $array
11+
* @phpstan-return \Generator<TKeyType, TValueType, void, void>
12+
*/
13+
public static function stringifyKeys(array $array) : \Generator{
14+
foreach($array as $key => $value){
15+
yield (string) $key => $value;
16+
}
17+
}
18+
19+
public function sayHello(): void
20+
{
21+
self::stringifyKeys($GLOBALS);
22+
}
23+
}

0 commit comments

Comments
 (0)