Skip to content

Commit 1636fb9

Browse files
committed
Array with int|string in prevent mode should accept any string
1 parent 9db66d0 commit 1636fb9

5 files changed

Lines changed: 57 additions & 18 deletions

‎src/PhpDoc/TypeNodeResolver.php‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -988,17 +988,19 @@ static function (string $variance): TemplateTypeVariance {
988988
private function transformUnsafeArrayKey(Type $keyType): Type
989989
{
990990
if ($this->reportUnsafeArrayStringKeyCasting === ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
991-
$keyType = TypeTraverser::map($keyType, static function (Type $type, callable $traverse) {
992-
if ($type instanceof UnionType || $type instanceof IntersectionType) {
993-
return $traverse($type);
994-
}
991+
if (!$keyType->isSuperTypeOf(new IntegerType())->yes()) {
992+
$keyType = TypeTraverser::map($keyType, static function (Type $type, callable $traverse) {
993+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
994+
return $traverse($type);
995+
}
995996

996-
if ($type instanceof StringType) {
997-
return TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true));
998-
}
997+
if ($type instanceof StringType) {
998+
return TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true));
999+
}
9991000

1000-
return $type;
1001-
});
1001+
return $type;
1002+
});
1003+
}
10021004
}
10031005

10041006
return TypeCombinator::intersect($keyType->toArrayKey(), new UnionType([

‎src/Type/UnionType.php‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateTimeInterface;
88
use Error;
99
use Exception;
10+
use PHPStan\DependencyInjection\ReportUnsafeArrayStringKeyCastingToggle;
1011
use PHPStan\Php\PhpVersion;
1112
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
1213
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
@@ -1186,7 +1187,18 @@ public function toArray(): Type
11861187

11871188
public function toArrayKey(): Type
11881189
{
1189-
return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey());
1190+
$level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
1191+
if ($level !== ReportUnsafeArrayStringKeyCastingToggle::PREVENT || $this->isInteger()->no()) {
1192+
return $this->unionTypes(static fn (Type $type): Type => $type->toArrayKey());
1193+
}
1194+
1195+
return $this->unionTypes(static function (Type $type): Type {
1196+
if ($type instanceof StringType) { // @phpstan-ignore phpstanApi.instanceofType
1197+
return $type;
1198+
}
1199+
1200+
return $type->toArrayKey();
1201+
});
11901202
}
11911203

11921204
public function toCoercedArgumentType(bool $strictTypes): Type

‎tests/PHPStan/Analyser/ReportUnsafeArrayStringKeyCastingPreventTypeAcceptanceTest.php‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@
33
namespace PHPStan\Analyser;
44

55
use PHPStan\Rules\Methods\CallMethodsRule;
6+
use PHPStan\Rules\Methods\ReturnTypeRule;
67
use PHPStan\Rules\Rule;
8+
use PHPStan\Testing\CompositeRule;
79
use PHPStan\Testing\RuleTestCase;
810
use function array_merge;
911

1012
/**
11-
* @extends RuleTestCase<CallMethodsRule>
13+
* @extends RuleTestCase<CompositeRule>
1214
*/
1315
class ReportUnsafeArrayStringKeyCastingPreventTypeAcceptanceTest extends RuleTestCase
1416
{
1517

1618
public function getRule(): Rule
1719
{
18-
return self::getContainer()->getByType(CallMethodsRule::class);
20+
// @phpstan-ignore argument.type
21+
return new CompositeRule([
22+
self::getContainer()->getByType(CallMethodsRule::class),
23+
self::getContainer()->getByType(ReturnTypeRule::class),
24+
]);
1925
}
2026

2127
public function testRule(): void

‎tests/PHPStan/Analyser/data/report-unsafe-array-string-key-casting-accepts.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,22 @@ public function doTest(string $s): void
8686
}
8787

8888
}
89+
90+
class ReleaseNoteParser
91+
{
92+
93+
/**
94+
* @param non-empty-string $s
95+
* @return array<int|string, array{section: string, release: string}>
96+
*/
97+
public function buildCommitMap(string $s): array
98+
{
99+
/** @var array<string, array{section: string, release: string}> $map */
100+
$map = [];
101+
102+
$map[$s] = ['section' => 'a', 'release' => 'b'];
103+
104+
return $map;
105+
}
106+
107+
}

‎tests/PHPStan/Analyser/data/report-unsafe-array-string-key-casting-prevent.php‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function doBar(array $a): void
2525
{
2626
assertType('array<ReportUnsafeArrayStringKeyCastingPrevent\Foo>', $a);
2727
foreach ($a as $k => $v) {
28-
assertType('(int|non-decimal-int-string)', $k);
28+
assertType('(int|string)', $k);
2929
}
3030
}
3131

@@ -34,9 +34,9 @@ public function doBar(array $a): void
3434
*/
3535
public function doBaz(array $a): void
3636
{
37-
assertType('array<int|non-decimal-int-string, ReportUnsafeArrayStringKeyCastingPrevent\Foo>', $a);
37+
assertType('array<int|string, ReportUnsafeArrayStringKeyCastingPrevent\Foo>', $a);
3838
foreach ($a as $k => $v) {
39-
assertType('int|non-decimal-int-string', $k);
39+
assertType('int|string', $k);
4040
}
4141
}
4242

@@ -111,7 +111,7 @@ public function doBar(array $a): void
111111
{
112112
assertType('array{a: int, ...<ReportUnsafeArrayStringKeyCastingPrevent\Unsealed>}', $a);
113113
foreach ($a as $k => $v) {
114-
assertType('(int|non-decimal-int-string)', $k);
114+
assertType('(int|string)', $k);
115115
}
116116
}
117117

@@ -120,9 +120,9 @@ public function doBar(array $a): void
120120
*/
121121
public function doBaz(array $a): void
122122
{
123-
assertType('array{a: int, ...<int|non-decimal-int-string, ReportUnsafeArrayStringKeyCastingPrevent\Unsealed>}', $a);
123+
assertType('array{a: int, ...<int|string, ReportUnsafeArrayStringKeyCastingPrevent\Unsealed>}', $a);
124124
foreach ($a as $k => $v) {
125-
assertType('int|non-decimal-int-string', $k);
125+
assertType('int|string', $k);
126126
}
127127
}
128128

0 commit comments

Comments
 (0)