Skip to content

Commit 43dc704

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix BenevolentUnionType with generics producing false positive errors
- Fixed ArrayType constructor converting StrictMixedType key to regular UnionType instead of BenevolentUnionType, which caused loss of benevolent semantics during RuleLevelHelper type transformation - Added BenevolentUnionType handling in TemplateTypeTrait::inferTemplateTypes() to filter matching types from a benevolent union when resolving template bounds - New regression test in tests/PHPStan/Rules/Methods/data/bug-9732.php Closes phpstan/phpstan#9732
1 parent ba4fe14 commit 43dc704

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

src/Type/ArrayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function __construct(Type $keyType, private Type $itemType)
5454
$keyType = new MixedType();
5555
}
5656
if ($keyType instanceof StrictMixedType && !$keyType instanceof TemplateStrictMixedType) {
57-
$keyType = new UnionType([new StringType(), new IntegerType()]);
57+
$keyType = new BenevolentUnionType([new IntegerType(), new StringType()]);
5858
}
5959

6060
$this->keyType = $keyType;

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
77
use PHPStan\TrinaryLogic;
88
use PHPStan\Type\AcceptsResult;
9+
use PHPStan\Type\BenevolentUnionType;
910
use PHPStan\Type\ErrorType;
1011
use PHPStan\Type\IntersectionType;
1112
use PHPStan\Type\IsSuperTypeOfResult;
@@ -18,6 +19,7 @@
1819
use PHPStan\Type\TypeUtils;
1920
use PHPStan\Type\UnionType;
2021
use PHPStan\Type\VerbosityLevel;
22+
use function count;
2123
use function sprintf;
2224

2325
/**
@@ -287,6 +289,23 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
287289
]))->union($map);
288290
}
289291

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

tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,15 @@ 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+
934943
#[RequiresPhp('>= 8.5')]
935944
public function testPipeOperator(): void
936945
{
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)