Skip to content

Commit 71f7b78

Browse files
Report float and null offset based on PHP version (#4406)
1 parent 2548dbb commit 71f7b78

10 files changed

+141
-30
lines changed

src/Php/PhpVersion.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,14 @@ public function hasPDOSubclasses(): bool
414414
return $this->versionId >= 80400;
415415
}
416416

417+
public function deprecatesImplicitlyFloatConversionToInt(): bool
418+
{
419+
return $this->versionId >= 80100;
420+
}
421+
422+
public function deprecatesNullArrayOffset(): bool
423+
{
424+
return $this->versionId >= 80500;
425+
}
426+
417427
}

src/Rules/Arrays/AllowedArrayKeysTypes.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Arrays;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Type\ArrayType;
67
use PHPStan\Type\BooleanType;
78
use PHPStan\Type\Constant\ConstantBooleanType;
@@ -21,15 +22,22 @@
2122
final class AllowedArrayKeysTypes
2223
{
2324

24-
public static function getType(): Type
25+
public static function getType(?PhpVersion $phpVersion = null): Type
2526
{
26-
return new UnionType([
27+
$types = [
2728
new IntegerType(),
2829
new StringType(),
29-
new FloatType(),
3030
new BooleanType(),
31-
new NullType(),
32-
]);
31+
];
32+
33+
if ($phpVersion === null || !$phpVersion->deprecatesImplicitlyFloatConversionToInt()) {
34+
$types[] = new FloatType();
35+
}
36+
if ($phpVersion === null || !$phpVersion->deprecatesNullArrayOffset()) {
37+
$types[] = new NullType();
38+
}
39+
40+
return new UnionType($types);
3341
}
3442

3543
public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type

src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use function array_search;
1616
use function count;
1717
use function implode;
18+
use function is_bool;
19+
use function is_float;
1820
use function is_int;
1921
use function max;
2022
use function sprintf;
@@ -128,6 +130,13 @@ public function processNode(Node $node, Scope $scope): array
128130
}
129131

130132
foreach ($keyValues as $value) {
133+
// Prevent php warning by manually casting array keys
134+
if (is_bool($value) || is_float($value)) {
135+
$value = (int) $value;
136+
} elseif ($value === null) {
137+
$value = (string) $value;
138+
}
139+
131140
$printedValue = $key !== null
132141
? $this->exprPrinter->printExpr($key)
133142
: $value;

src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\AutowiredParameter;
88
use PHPStan\DependencyInjection\RegisteredRule;
9+
use PHPStan\Php\PhpVersion;
910
use PHPStan\Rules\Rule;
1011
use PHPStan\Rules\RuleErrorBuilder;
1112
use PHPStan\Rules\RuleLevelHelper;
@@ -23,6 +24,7 @@ final class InvalidKeyInArrayDimFetchRule implements Rule
2324

2425
public function __construct(
2526
private RuleLevelHelper $ruleLevelHelper,
27+
private PhpVersion $phpVersion,
2628
#[AutowiredParameter]
2729
private bool $reportMaybes,
2830
)
@@ -56,17 +58,18 @@ public function processNode(Node $node, Scope $scope): array
5658
return [];
5759
}
5860

61+
$phpVersion = $this->phpVersion;
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($phpVersion)->isSuperTypeOf($dimType)->yes(),
6467
)->getType();
6568
if ($dimensionType instanceof ErrorType) {
6669
return [];
6770
}
6871

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

src/Rules/Arrays/InvalidKeyInArrayItemRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
8+
use PHPStan\Php\PhpVersion;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\Rules\RuleLevelHelper;
@@ -22,6 +23,7 @@ final class InvalidKeyInArrayItemRule implements Rule
2223

2324
public function __construct(
2425
private RuleLevelHelper $ruleLevelHelper,
26+
private PhpVersion $phpVersion,
2527
)
2628
{
2729
}
@@ -37,17 +39,18 @@ public function processNode(Node $node, Scope $scope): array
3739
return [];
3840
}
3941

42+
$phpVersion = $this->phpVersion;
4043
$dimensionType = $this->ruleLevelHelper->findTypeToCheck(
4144
$scope,
4245
$node->key,
4346
'',
44-
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimType)->yes(),
47+
static fn (Type $dimType): bool => AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimType)->yes(),
4548
)->getType();
4649
if ($dimensionType instanceof ErrorType) {
4750
return [];
4851
}
4952

50-
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
53+
$isSuperType = AllowedArrayKeysTypes::getType($phpVersion)->isSuperTypeOf($dimensionType);
5154
if ($isSuperType->yes()) {
5255
return [];
5356
}

tests/PHPStan/Levels/data/stringOffsetAccess-7.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
"line": 31,
1515
"ignorable": true
1616
},
17+
{
18+
"message": "Possibly invalid array key type float.",
19+
"line": 31,
20+
"ignorable": true
21+
},
1722
{
1823
"message": "Offset int|object might not exist on array|string.",
1924
"line": 35,

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace PHPStan\Rules\Arrays;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Rules\RuleLevelHelper;
78
use PHPStan\Testing\RuleTestCase;
89
use PHPUnit\Framework\Attributes\RequiresPhp;
10+
use const PHP_VERSION_ID;
911

1012
/**
1113
* @extends RuleTestCase<InvalidKeyInArrayDimFetchRule>
@@ -16,12 +18,16 @@ class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase
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(
22+
$ruleLevelHelper,
23+
self::getContainer()->getByType(PhpVersion::class),
24+
true,
25+
);
2026
}
2127

2228
public function testInvalidKey(): void
2329
{
24-
$this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [
30+
$errors = [
2531
[
2632
'Invalid array key type DateTimeImmutable.',
2733
7,
@@ -62,7 +68,26 @@ public function testInvalidKey(): void
6268
'Invalid array key type DateTimeImmutable.',
6369
48,
6470
],
65-
]);
71+
];
72+
73+
if (PHP_VERSION_ID >= 80100) {
74+
$errors[] = [
75+
'Invalid array key type float.',
76+
51,
77+
];
78+
}
79+
if (PHP_VERSION_ID >= 80500) {
80+
$errors[] = [
81+
'Invalid array key type null.',
82+
52,
83+
];
84+
$errors[] = [
85+
'Possibly invalid array key type string|null.',
86+
56,
87+
];
88+
}
89+
90+
$this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], $errors);
6691
}
6792

6893
#[RequiresPhp('>= 8.1')]

tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace PHPStan\Rules\Arrays;
44

5+
use PHPStan\Php\PhpVersion;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Rules\RuleLevelHelper;
78
use PHPStan\Testing\RuleTestCase;
89
use PHPUnit\Framework\Attributes\RequiresPhp;
10+
use const PHP_VERSION_ID;
911

1012
/**
1113
* @extends RuleTestCase<InvalidKeyInArrayItemRule>
@@ -21,50 +23,83 @@ protected function getRule(): Rule
2123
{
2224
$ruleLevelHelper = new RuleLevelHelper(self::createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, false, true);
2325

24-
return new InvalidKeyInArrayItemRule($ruleLevelHelper);
26+
return new InvalidKeyInArrayItemRule(
27+
$ruleLevelHelper,
28+
self::getContainer()->getByType(PhpVersion::class),
29+
);
2530
}
2631

2732
public function testInvalidKey(): void
2833
{
29-
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
34+
$errors = [
3035
[
3136
'Invalid array key type DateTimeImmutable.',
32-
13,
37+
12,
3338
],
3439
[
3540
'Invalid array key type array.',
36-
14,
41+
13,
3742
],
3843
[
3944
'Possibly invalid array key type stdClass|string.',
40-
15,
45+
14,
4146
],
42-
]);
47+
];
48+
49+
if (PHP_VERSION_ID >= 80100) {
50+
$errors[] = [
51+
'Invalid array key type float.',
52+
26,
53+
];
54+
}
55+
if (PHP_VERSION_ID >= 80500) {
56+
$errors[] = [
57+
'Invalid array key type null.',
58+
27,
59+
];
60+
}
61+
62+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], $errors);
4363
}
4464

4565
public function testInvalidMixedKey(): void
4666
{
4767
$this->checkExplicitMixed = true;
4868
$this->checkImplicitMixed = true;
4969

50-
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
70+
$errors = [
5171
[
5272
'Invalid array key type DateTimeImmutable.',
53-
13,
73+
12,
5474
],
5575
[
5676
'Invalid array key type array.',
57-
14,
77+
13,
5878
],
5979
[
6080
'Possibly invalid array key type stdClass|string.',
61-
15,
81+
14,
6282
],
6383
[
6484
'Possibly invalid array key type mixed.',
65-
22,
85+
21,
6686
],
67-
]);
87+
];
88+
89+
if (PHP_VERSION_ID >= 80100) {
90+
$errors[] = [
91+
'Invalid array key type float.',
92+
26,
93+
];
94+
}
95+
if (PHP_VERSION_ID >= 80500) {
96+
$errors[] = [
97+
'Invalid array key type null.',
98+
27,
99+
];
100+
}
101+
102+
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], $errors);
68103
}
69104

70105
public function testInvalidKeyInList(): void

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
namespace InvalidKeyArrayDimFetch;
44

55
$a = [];
6-
$foo = $a[null];
6+
77
$foo = $a[new \DateTimeImmutable()];
88
$a[[]] = $foo;
99
$a[1];
10-
$a[1.0];
10+
1111
$a['1'];
1212
$a[true];
1313
$a[false];
1414

15-
/** @var string|null $stringOrNull */
16-
$stringOrNull = doFoo();
17-
$a[$stringOrNull];
15+
/** @var string|int $stringOrInt */
16+
$stringOrInt = doFoo();
17+
$a[$stringOrInt];
1818

1919
$obj = new \SplObjectStorage();
2020
$obj[new \stdClass()] = 1;
@@ -46,3 +46,11 @@
4646
$array[5][new \DateTimeImmutable()];
4747
$array[new \stdClass()][new \DateTimeImmutable()];
4848
$array[new \DateTimeImmutable()][] = 5;
49+
50+
// Php version dependant
51+
$a[1.0];
52+
$foo = $a[null];
53+
54+
/** @var string|null $stringOrNull */
55+
$stringOrNull = doFoo();
56+
$a[$stringOrNull];

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
'foo',
1010
1 => 'aaa',
1111
'1' => 'aaa',
12-
null => 'aaa',
1312
new \DateTimeImmutable() => 'aaa',
1413
[] => 'bbb',
1514
$stringOrObject => 'aaa',
@@ -21,3 +20,9 @@
2120
$b = [
2221
$mixed => 'foo',
2322
];
23+
24+
// PHP version dependent
25+
$c = [
26+
1.0 => 'aaa',
27+
null => 'aaa',
28+
];

0 commit comments

Comments
 (0)