Skip to content

Introduce reportCastedArrayKey parameter #4012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions conf/config.level3.neon
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ services:
class: PHPStan\Rules\Arrays\InvalidKeyInArrayDimFetchRule
arguments:
reportMaybes: %reportMaybes%
reportCastedArrayKey: %reportCastedArrayKey%
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\Arrays\InvalidKeyInArrayItemRule
arguments:
reportMaybes: %reportMaybes%
reportCastedArrayKey: %reportCastedArrayKey%
tags:
- phpstan.rules.rule

Expand Down
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ parameters:
reportStaticMethodSignatures: false
reportWrongPhpDocTypeInVarTag: false
reportAnyTypeWideningInVarTag: false
reportCastedArrayKey: false
reportPossiblyNonexistentGeneralArrayOffset: false
reportPossiblyNonexistentConstantArrayOffset: false
checkMissingOverrideMethodAttribute: false
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ parametersSchema:
reportStaticMethodSignatures: bool()
reportWrongPhpDocTypeInVarTag: bool()
reportAnyTypeWideningInVarTag: bool()
reportCastedArrayKey: bool()
reportPossiblyNonexistentGeneralArrayOffset: bool()
reportPossiblyNonexistentConstantArrayOffset: bool()
checkMissingOverrideMethodAttribute: bool()
Expand Down
9 changes: 8 additions & 1 deletion src/Rules/Arrays/AllowedArrayKeysTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@
final class AllowedArrayKeysTypes
{

public static function getType(): Type
public static function getType(bool $strict = false): Type
{
if ($strict) {
return new UnionType([
new IntegerType(),
new StringType(),
]);
}

return new UnionType([
new IntegerType(),
new StringType(),
Expand Down
6 changes: 4 additions & 2 deletions src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final class InvalidKeyInArrayDimFetchRule implements Rule
public function __construct(
private RuleLevelHelper $ruleLevelHelper,
private bool $reportMaybes,
private bool $reportCastedArrayKey,
)
{
}
Expand All @@ -42,18 +43,19 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$reportCastedArrayKey = $this->reportCastedArrayKey;
$varType = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$node->var,
'',
static fn (Type $varType): bool => $varType->isArray()->no() || AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType)->yes(),
static fn (Type $varType): bool => $varType->isArray()->no() || AllowedArrayKeysTypes::getType($reportCastedArrayKey)->isSuperTypeOf($dimensionType)->yes(),
)->getType();

if ($varType instanceof ErrorType || $varType->isArray()->no()) {
return [];
}

$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
$isSuperType = AllowedArrayKeysTypes::getType($this->reportCastedArrayKey)->isSuperTypeOf($dimensionType);
if ($isSuperType->yes() || ($isSuperType->maybe() && !$this->reportMaybes)) {
return [];
}
Expand Down
4 changes: 2 additions & 2 deletions src/Rules/Arrays/InvalidKeyInArrayItemRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
final class InvalidKeyInArrayItemRule implements Rule
{

public function __construct(private bool $reportMaybes)
public function __construct(private bool $reportMaybes, private bool $reportCastedArrayKey)
{
}

Expand All @@ -32,7 +32,7 @@ public function processNode(Node $node, Scope $scope): array
}

$dimensionType = $scope->getType($node->key);
$isSuperType = AllowedArrayKeysTypes::getType()->isSuperTypeOf($dimensionType);
$isSuperType = AllowedArrayKeysTypes::getType($this->reportCastedArrayKey)->isSuperTypeOf($dimensionType);
if ($isSuperType->no()) {
return [
RuleErrorBuilder::message(
Expand Down
83 changes: 82 additions & 1 deletion tests/PHPStan/Rules/Arrays/InvalidKeyInArrayDimFetchRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
class InvalidKeyInArrayDimFetchRuleTest extends RuleTestCase
{

private bool $reportCastedArrayKey = false;

protected function getRule(): Rule
{
$ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false, false, false, true);
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true);
return new InvalidKeyInArrayDimFetchRule($ruleLevelHelper, true, $this->reportCastedArrayKey);
}

public function testInvalidKey(): void
Expand Down Expand Up @@ -61,6 +63,69 @@ public function testInvalidKey(): void
]);
}

public function testInvalidKeyReportingCastedArrayKey(): void
{
$this->reportCastedArrayKey = true;
$this->analyse([__DIR__ . '/data/invalid-key-array-dim-fetch.php'], [
[
'Invalid array key type null.',
6,
],
[
'Invalid array key type DateTimeImmutable.',
7,
],
[
'Invalid array key type array.',
8,
],
[
'Invalid array key type float.',
10,
],
[
'Invalid array key type true.',
12,
],
[
'Invalid array key type false.',
13,
],
[
'Possibly invalid array key type string|null.',
17,
],
[
'Possibly invalid array key type stdClass|string.',
24,
],
[
'Invalid array key type DateTimeImmutable.',
31,
],
[
'Invalid array key type DateTimeImmutable.',
45,
],
[
'Invalid array key type DateTimeImmutable.',
46,
],
[
'Invalid array key type DateTimeImmutable.',
47,
],
[
'Invalid array key type stdClass.',
47,
],
[
'Invalid array key type DateTimeImmutable.',
48,
],
]);
}

public function testBug6315(): void
{
if (PHP_VERSION_ID < 80100) {
Expand Down Expand Up @@ -95,4 +160,20 @@ public function testBug6315(): void
]);
}

public function testUnsetFalseKey(): void
{
$this->reportCastedArrayKey = true;

$this->analyse([__DIR__ . '/data/unset-false-key.php'], [
[
'Invalid array key type false.',
6,
],
[
'Invalid array key type false.',
13,
],
]);
}

}
27 changes: 26 additions & 1 deletion tests/PHPStan/Rules/Arrays/InvalidKeyInArrayItemRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
class InvalidKeyInArrayItemRuleTest extends RuleTestCase
{

private bool $reportCastedArrayKey = false;

protected function getRule(): Rule
{
return new InvalidKeyInArrayItemRule(true);
return new InvalidKeyInArrayItemRule(true, $this->reportCastedArrayKey);
}

public function testInvalidKey(): void
Expand All @@ -35,6 +37,29 @@ public function testInvalidKey(): void
]);
}

public function testInvalidKeyReportingCastedArrayKey(): void
{
$this->reportCastedArrayKey = true;
$this->analyse([__DIR__ . '/data/invalid-key-array-item.php'], [
[
'Invalid array key type null.',
12,
],
[
'Invalid array key type DateTimeImmutable.',
13,
],
[
'Invalid array key type array.',
14,
],
[
'Possibly invalid array key type stdClass|string.',
15,
],
]);
}

public function testInvalidKeyInList(): void
{
$this->analyse([__DIR__ . '/data/invalid-key-list.php'], [
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/unset-false-key.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);

namespace UnsetFalseKey;

/** @var array<int, int> $data */
unset($data[false]);

function test_remove_element(): void {
$modified = [1, 4, 6, 8];

// this would happen in the SUT
unset($modified[array_search(4, $modified, true)]);
unset($modified[array_search(5, $modified, true)]); // bug is here - will unset key `0` by accident

assert([1, 6, 8] === $modified); // actually is [6, 8]
}
Loading