Skip to content

Commit 89f4394

Browse files
Introduce reportCastedArrayKey parameter
1 parent 71f7b78 commit 89f4394

9 files changed

+97
-11
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ parameters:
7676
reportStaticMethodSignatures: false
7777
reportWrongPhpDocTypeInVarTag: false
7878
reportAnyTypeWideningInVarTag: false
79+
reportArrayKeyCast: false
7980
reportPossiblyNonexistentGeneralArrayOffset: false
8081
reportPossiblyNonexistentConstantArrayOffset: false
8182
checkMissingOverrideMethodAttribute: false

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ parametersSchema:
8585
reportStaticMethodSignatures: bool()
8686
reportWrongPhpDocTypeInVarTag: bool()
8787
reportAnyTypeWideningInVarTag: bool()
88+
reportArrayKeyCast: bool()
8889
reportPossiblyNonexistentGeneralArrayOffset: bool()
8990
reportPossiblyNonexistentConstantArrayOffset: bool()
9091
checkMissingOverrideMethodAttribute: bool()

src/Rules/Arrays/AllowedArrayKeysTypes.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@
2222
final class AllowedArrayKeysTypes
2323
{
2424

25-
public static function getType(?PhpVersion $phpVersion = null): Type
25+
public static function getType(?PhpVersion $phpVersion = null, bool $strict = false): Type
2626
{
2727
$types = [
2828
new IntegerType(),
2929
new StringType(),
30-
new BooleanType(),
3130
];
3231

33-
if ($phpVersion === null || !$phpVersion->deprecatesImplicitlyFloatConversionToInt()) {
34-
$types[] = new FloatType();
35-
}
36-
if ($phpVersion === null || !$phpVersion->deprecatesNullArrayOffset()) {
37-
$types[] = new NullType();
32+
if (!$strict) {
33+
$types[] = new BooleanType();
34+
35+
if ($phpVersion === null || !$phpVersion->deprecatesImplicitlyFloatConversionToInt()) {
36+
$types[] = new FloatType();
37+
}
38+
if ($phpVersion === null || !$phpVersion->deprecatesNullArrayOffset()) {
39+
$types[] = new NullType();
40+
}
3841
}
3942

4043
return new UnionType($types);

src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public function __construct(
2727
private PhpVersion $phpVersion,
2828
#[AutowiredParameter]
2929
private bool $reportMaybes,
30+
#[AutowiredParameter]
31+
private bool $reportArrayKeyCast,
3032
)
3133
{
3234
}
@@ -59,17 +61,18 @@ public function processNode(Node $node, Scope $scope): array
5961
}
6062

6163
$phpVersion = $this->phpVersion;
64+
$reportArrayKeyCast = $this->reportArrayKeyCast;
6265
$dimensionType = $this->ruleLevelHelper->findTypeToCheck(
6366
$scope,
6467
$node->dim,
6568
'',
66-
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimType)->yes(),
69+
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($phpVersion, $reportArrayKeyCast)->isSuperTypeOf($dimType)->yes(),
6770
)->getType();
6871
if ($dimensionType instanceof ErrorType) {
6972
return [];
7073
}
7174

72-
$isSuperType = AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimensionType);
75+
$isSuperType = AllowedArrayKeysTypes::getType($phpVersion, $reportArrayKeyCast)->isSuperTypeOf($dimensionType);
7376
if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) {
7477
return [];
7578
}

src/Rules/Arrays/InvalidKeyInArrayItemRule.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredParameter;
78
use PHPStan\DependencyInjection\RegisteredRule;
89
use PHPStan\Php\PhpVersion;
910
use PHPStan\Rules\Rule;
@@ -24,6 +25,8 @@ final class InvalidKeyInArrayItemRule implements Rule
2425
public function __construct(
2526
private RuleLevelHelper $ruleLevelHelper,
2627
private PhpVersion $phpVersion,
28+
#[AutowiredParameter]
29+
private bool $reportArrayKeyCast,
2730
)
2831
{
2932
}
@@ -40,17 +43,18 @@ public function processNode(Node $node, Scope $scope): array
4043
}
4144

4245
$phpVersion = $this->phpVersion;
46+
$reportArrayKeyCast = $this->reportArrayKeyCast;
4347
$dimensionType = $this->ruleLevelHelper->findTypeToCheck(
4448
$scope,
4549
$node->key,
4650
'',
47-
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimType)->yes(),
51+
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($phpVersion, $reportArrayKeyCast)->isSuperTypeOf($dimType)->yes(),
4852
)->getType();
4953
if ($dimensionType instanceof ErrorType) {
5054
return [];
5155
}
5256

53-
$isSuperType = AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimensionType);
57+
$isSuperType = AllowedArrayKeysTypes::getType($phpVersion, $reportArrayKeyCast)->isSuperTypeOf($dimensionType);
5458
if ($isSuperType->yes()) {
5559
return [];
5660
}

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase
1616
{
1717

18+
private bool $reportCastedArrayKey = false;
19+
1820
protected function getRule(): Rule
1921
{
2022
$ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), true, false, true, true, true, false, true);
2123
return new InvalidKeyInArrayDimFetchRule(
2224
$ruleLevelHelper,
2325
self::getContainer()->getByType(PhpVersion::class),
2426
true,
27+
$this->reportCastedArrayKey,
2528
);
2629
}
2730

@@ -163,4 +166,20 @@ public function testBug12981(): void
163166
]);
164167
}
165168

169+
public function testUnsetFalseKey(): void
170+
{
171+
$this->reportCastedArrayKey = true;
172+
173+
$this->analyse([__DIR__ . '/data/unset-false-key.php'], [
174+
[
175+
'Invalid array key type false.',
176+
6,
177+
],
178+
[
179+
'Invalid array key type false.',
180+
13,
181+
],
182+
]);
183+
}
184+
166185
}

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
class InvalidKeyInArrayItemRuleTest extends RuleTestCase
1616
{
1717

18+
private bool $reportCastedArrayKey = false;
19+
1820
private bool $checkExplicitMixed = false;
1921

2022
private bool $checkImplicitMixed = false;
@@ -26,6 +28,7 @@ protected function getRule(): Rule
2628
return new InvalidKeyInArrayItemRule(
2729
$ruleLevelHelper,
2830
self::getContainer()->getByType(PhpVersion::class),
31+
$this->reportCastedArrayKey,
2932
);
3033
}
3134

@@ -102,6 +105,38 @@ public function testInvalidMixedKey(): void
102105
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], $errors);
103106
}
104107

108+
public function testInvalidKeyReportingCastedArrayKey(): void
109+
{
110+
$this->reportCastedArrayKey = true;
111+
112+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
113+
[
114+
'Invalid array key type DateTimeImmutable.',
115+
12,
116+
],
117+
[
118+
'Invalid array key type array.',
119+
13,
120+
],
121+
[
122+
'Possibly invalid array key type stdClass|string.',
123+
14,
124+
],
125+
[
126+
'Invalid array key type float.',
127+
26,
128+
],
129+
[
130+
'Invalid array key type null.',
131+
27,
132+
],
133+
[
134+
'Invalid array key type false.',
135+
31,
136+
],
137+
]);
138+
}
139+
105140
public function testInvalidKeyInList(): void
106141
{
107142
$this->analyse([__DIR__ . '/data/invalid-key-list.php'], [

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@
2626
1.0 => 'aaa',
2727
null => 'aaa',
2828
];
29+
30+
$d = [
31+
false => 'aaa',
32+
];
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)