Skip to content

Commit

Permalink
Add new ArrayUnpackingRule
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed Mar 28, 2022
1 parent 6700eb6 commit 0b2cc87
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Expand Up @@ -4,5 +4,6 @@ parameters:
skipCheckGenericClasses: []
explicitMixedInUnknownGenericNew: true
arrayFilter: true
arrayUnpacking: true
stubFiles:
- ../stubs/bleedingEdge/Countable.stub
7 changes: 7 additions & 0 deletions conf/config.level3.neon
@@ -1,6 +1,10 @@
includes:
- config.level2.neon

conditionalTags:
PHPStan\Rules\Arrays\ArrayUnpackingRule:
phpstan.rules.rule: %featureToggles.arrayUnpacking%

rules:
- PHPStan\Rules\Arrays\ArrayDestructuringRule
- PHPStan\Rules\Arrays\IterableInForeachRule
Expand Down Expand Up @@ -74,3 +78,6 @@ services:
reportMaybes: %reportMaybes%
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\Arrays\ArrayUnpackingRule
2 changes: 2 additions & 0 deletions conf/config.neon
Expand Up @@ -29,6 +29,7 @@ parameters:
- RecursiveCallbackFilterIterator
explicitMixedInUnknownGenericNew: false
arrayFilter: false
arrayUnpacking: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down Expand Up @@ -207,6 +208,7 @@ parametersSchema:
skipCheckGenericClasses: listOf(string()),
explicitMixedInUnknownGenericNew: bool(),
arrayFilter: bool(),
arrayUnpacking: bool(),
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
43 changes: 43 additions & 0 deletions src/Rules/Arrays/ArrayUnpackingRule.php
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Arrays;

use PhpParser\Node;
use PhpParser\Node\Expr\ArrayItem;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\StringType;

/**
* @implements Rule<ArrayItem>
*/
class ArrayUnpackingRule implements Rule
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function getNodeType(): string
{
return ArrayItem::class;
}

public function processNode(Node $node, Scope $scope): array
{
if ($node->unpack === false || $this->phpVersion->supportsArrayUnpackingWithStringKeys()) {
return [];
}

$valueType = $scope->getType($node->value);

if ((new StringType())->isSuperTypeOf($valueType->getIterableKeyType())->no()) {
return [];
}

return [RuleErrorBuilder::message('Array unpacking cannot be used on array that potentially has string keys.')->build()];
}

}
68 changes: 68 additions & 0 deletions tests/PHPStan/Rules/Arrays/ArrayUnpackingRuleTest.php
@@ -0,0 +1,68 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Arrays;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<ArrayUnpackingRule>
*/
class ArrayUnpackingRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new ArrayUnpackingRule(self::getContainer()->getByType(PhpVersion::class));
}

public function testRule(): void
{
if (PHP_VERSION_ID >= 80100) {
$this->markTestSkipped('Test requires PHP version <= 8.0');
}

$this->analyse([__DIR__ . '/data/array-unpacking.php'], [
[
'Array unpacking cannot be used on array that potentially has string keys.',
7,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
18,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
24,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
29,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
40,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
52,
],
[
'Array unpacking cannot be used on array that potentially has string keys.',
63,
],
]);
}

public function testRuleOnPHP81(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1+');
}

$this->analyse([__DIR__ . '/data/array-unpacking.php'], []);
}

}
66 changes: 66 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/array-unpacking.php
@@ -0,0 +1,66 @@
<?php // lint >= 7.4

namespace ArrayUnpacking;

$foo = ['foo' => 'bar', 1, 2, 3];

$bar = [...$foo];

/** @param array<int, string> $bar */
function intKeyedArray(array $bar)
{
$baz = [...$bar];
}

/** @param array<string, string> $bar */
function stringKeyedArray(array $bar)
{
$baz = [...$bar];
}

/** @param array<array-key, string> $bar */
function unionKeyedArray(array $bar)
{
$baz = [...$bar];
}

function mixedKeyedArray(array $bar)
{
$baz = [...$bar];
}

/**
* @param array<mixed, string> $foo
* @param array<int, string> $bar
*/
function multipleUnpacking(array $foo, array $bar)
{
$baz = [
...$bar,
...$foo,
];
}

/**
* @param array<mixed, string> $foo
* @param array<string, string> $bar
*/
function foo(array $foo, array $bar)
{
$baz = [
$bar,
...$foo
];
}

/**
* @param array{foo: string, bar:int} $foo
* @param array{1, 2, 3, 4} $bar
*/
function unpackingArrayShapes(array $foo, array $bar)
{
$baz = [
...$foo,
...$bar,
];
}

0 comments on commit 0b2cc87

Please sign in to comment.