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 297b2d0 commit a582a5d
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level3.neon
Expand Up @@ -3,6 +3,7 @@ includes:

rules:
- PHPStan\Rules\Arrays\ArrayDestructuringRule
- PHPStan\Rules\Arrays\ArrayUnpackingRule
- PHPStan\Rules\Arrays\IterableInForeachRule
- PHPStan\Rules\Arrays\OffsetAccessAssignmentRule
- PHPStan\Rules\Arrays\OffsetAccessAssignOpRule
Expand Down
46 changes: 46 additions & 0 deletions src/Rules/Arrays/ArrayUnpackingRule.php
@@ -0,0 +1,46 @@
<?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
{

private PhpVersion $phpVersion;

public function __construct(PhpVersion $phpVersion)
{
$this->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

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 a582a5d

Please sign in to comment.