Skip to content

Commit 4476b7a

Browse files
Introduce reportCastedArrayKey parameter
1 parent 0d410e6 commit 4476b7a

File tree

9 files changed

+144
-7
lines changed

9 files changed

+144
-7
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ parameters:
7171
reportStaticMethodSignatures: false
7272
reportWrongPhpDocTypeInVarTag: false
7373
reportAnyTypeWideningInVarTag: false
74+
reportArrayKeyCast: false
7475
reportPossiblyNonexistentGeneralArrayOffset: false
7576
reportPossiblyNonexistentConstantArrayOffset: false
7677
checkMissingOverrideMethodAttribute: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ parametersSchema:
8080
reportStaticMethodSignatures: bool()
8181
reportWrongPhpDocTypeInVarTag: bool()
8282
reportAnyTypeWideningInVarTag: bool()
83+
reportArrayKeyCast: bool()
8384
reportPossiblyNonexistentGeneralArrayOffset: bool()
8485
reportPossiblyNonexistentConstantArrayOffset: bool()
8586
checkMissingOverrideMethodAttribute: bool()

src/Rules/Arrays/AllowedArrayKeysTypes.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@
2121
final class AllowedArrayKeysTypes
2222
{
2323

24-
public static function getType(): Type
24+
public static function getType(bool $strict = false): Type
2525
{
26+
if ($strict) {
27+
return new UnionType([
28+
new IntegerType(),
29+
new StringType(),
30+
]);
31+
}
32+
2633
return new UnionType([
2734
new IntegerType(),
2835
new StringType(),

src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public function __construct(
2525
private RuleLevelHelper $ruleLevelHelper,
2626
#[AutowiredParameter]
2727
private bool $reportMaybes,
28+
#[AutowiredParameter]
29+
private bool $reportArrayKeyCast,
2830
)
2931
{
3032
}
@@ -56,17 +58,18 @@ public function processNode(Node $node, Scope $scope): array
5658
return [];
5759
}
5860

61+
$reportArrayKeyCast = $this->reportArrayKeyCast;
5962
$dimensionType = $this->ruleLevelHelper->findTypeToCheck(
6063
$scope,
6164
$node->dim,
6265
'',
63-
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimType)->yes(),
66+
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($reportArrayKeyCast)->isSuperTypeOf($dimType)->yes(),
6467
)->getType();
6568
if ($dimensionType instanceof ErrorType) {
6669
return [];
6770
}
6871

69-
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
72+
$isSuperType = AllowedArrayKeysTypes::getType($this->reportArrayKeyCast)->isSuperTypeOf($dimensionType);
7073
if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) {
7174
return [];
7275
}

src/Rules/Arrays/InvalidKeyInArrayItemRule.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
use PHPStan\DependencyInjection\RegisteredRule;
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
11+
use PHPStan\Rules\RuleLevelHelper;
1112
use PHPStan\Type\MixedType;
13+
use PHPStan\Type\Type;
1214
use PHPStan\Type\VerbosityLevel;
1315
use function sprintf;
1416

@@ -20,8 +22,11 @@ final class InvalidKeyInArrayItemRule implements Rule
2022
{
2123

2224
public function __construct(
25+
private RuleLevelHelper $ruleLevelHelper,
2326
#[AutowiredParameter]
2427
private bool $reportMaybes,
28+
#[AutowiredParameter]
29+
private bool $reportArrayKeyCast,
2530
)
2631
{
2732
}
@@ -37,8 +42,15 @@ public function processNode(Node $node, Scope $scope): array
3742
return [];
3843
}
3944

40-
$dimensionType = $scope->getType($node->key);
41-
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
45+
$reportArrayKeyCast = $this->reportArrayKeyCast;
46+
$dimensionType = $this->ruleLevelHelper->findTypeToCheck(
47+
$scope,
48+
$node->key,
49+
'',
50+
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($reportArrayKeyCast)->isSuperTypeOf($dimType)->yes(),
51+
)->getType();
52+
53+
$isSuperType = AllowedArrayKeysTypes::getType($reportArrayKeyCast)->isSuperTypeOf($dimensionType);
4254
if ($isSuperType->no()) {
4355
return [
4456
RuleErrorBuilder::message(

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase
1414
{
1515

16+
private bool $reportCastedArrayKey = false;
17+
1618
protected function getRule(): Rule
1719
{
1820
$ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), true, false, true, true, true, false, true);
19-
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true);
21+
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true, $this->reportCastedArrayKey);
2022
}
2123

2224
public function testInvalidKey(): void
@@ -116,4 +118,20 @@ public function testBug12273(): void
116118
]);
117119
}
118120

121+
public function testUnsetFalseKey(): void
122+
{
123+
$this->reportCastedArrayKey = true;
124+
125+
$this->analyse([__DIR__ . '/data/unset-false-key.php'], [
126+
[
127+
'Invalid array key type false.',
128+
6,
129+
],
130+
[
131+
'Invalid array key type false.',
132+
13,
133+
],
134+
]);
135+
}
136+
119137
}

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Arrays;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67
use PHPStan\Testing\RuleTestCase;
78
use PHPUnit\Framework\Attributes\RequiresPhp;
89

@@ -12,9 +13,16 @@
1213
class InvalidKeyInArrayItemRuleTest extends RuleTestCase
1314
{
1415

16+
private bool $reportCastedArrayKey = false;
17+
18+
private bool $checkUnionType = true;
19+
20+
private bool $checkNullable = true;
21+
1522
protected function getRule(): Rule
1623
{
17-
return new InvalidKeyInArrayItemRule(true);
24+
$ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), $this->checkNullable, false, $this->checkUnionType, false, false, false, true);
25+
return new InvalidKeyInArrayItemRule($ruleLevelHelper, true, $this->reportCastedArrayKey);
1826
}
1927

2028
public function testInvalidKey(): void
@@ -35,6 +43,70 @@ public function testInvalidKey(): void
3543
]);
3644
}
3745

46+
public function testInvalidKeyOnLevel6(): void
47+
{
48+
$this->checkNullable = false;
49+
$this->checkUnionType = false;
50+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
51+
[
52+
'Invalid array key type DateTimeImmutable.',
53+
13,
54+
],
55+
[
56+
'Invalid array key type array.',
57+
14,
58+
],
59+
]);
60+
}
61+
62+
public function testInvalidKeyReportingCastedArrayKey(): void
63+
{
64+
$this->reportCastedArrayKey = true;
65+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
66+
[
67+
'Invalid array key type null.',
68+
12,
69+
],
70+
[
71+
'Invalid array key type DateTimeImmutable.',
72+
13,
73+
],
74+
[
75+
'Invalid array key type array.',
76+
14,
77+
],
78+
[
79+
'Possibly invalid array key type stdClass|string.',
80+
15,
81+
],
82+
[
83+
'Possibly invalid array key type string|null.',
84+
22,
85+
],
86+
]);
87+
}
88+
89+
public function testInvalidKeyReportingCastedArrayKeyOnLevel6(): void
90+
{
91+
$this->checkNullable = false;
92+
$this->checkUnionType = false;
93+
$this->reportCastedArrayKey = true;
94+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
95+
[
96+
'Invalid array key type null.',
97+
12,
98+
],
99+
[
100+
'Invalid array key type DateTimeImmutable.',
101+
13,
102+
],
103+
[
104+
'Invalid array key type array.',
105+
14,
106+
],
107+
]);
108+
}
109+
38110
public function testInvalidKeyInList(): void
39111
{
40112
$this->analyse([__DIR__ . '/data/invalid-key-list.php'], [

tests/PHPStan/Rules/Arrays/data/invalid-key-array-item.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@
1414
[] => 'bbb',
1515
$stringOrObject => 'aaa',
1616
];
17+
18+
/** @var string|null $stringOrNull */
19+
$stringOrNull = doFoo();
20+
21+
$b = [
22+
$stringOrNull => 'aaa',
23+
];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace UnsetFalseKey;
4+
5+
/** @var array<int, int> $data */
6+
unset($data[false]);
7+
8+
function test_remove_element(): void {
9+
$modified = [1, 4, 6, 8];
10+
11+
// this would happen in the SUT
12+
unset($modified[array_search(4, $modified, true)]);
13+
unset($modified[array_search(5, $modified, true)]); // bug is here - will unset key `0` by accident
14+
15+
assert([1, 6, 8] === $modified); // actually is [6, 8]
16+
}

0 commit comments

Comments
 (0)