Skip to content

Commit cabe851

Browse files
Do not report non existent offset when it s an array creation (#4204)
1 parent 1ac1259 commit cabe851

File tree

10 files changed

+160
-3
lines changed

10 files changed

+160
-3
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5683,10 +5683,9 @@ private function processAssignVar(
56835683
$varNativeType = $scope->getNativeType($var);
56845684

56855685
// 4. compose types
5686-
if ($varType instanceof ErrorType) {
5686+
$isImplicitArrayCreation = $this->isImplicitArrayCreation($dimFetchStack, $scope);
5687+
if ($isImplicitArrayCreation->yes()) {
56875688
$varType = new ConstantArrayType([], []);
5688-
}
5689-
if ($varNativeType instanceof ErrorType) {
56905689
$varNativeType = new ConstantArrayType([], []);
56915690
}
56925691
$offsetValueType = $varType;
@@ -6073,6 +6072,27 @@ static function (): void {
60736072
return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints);
60746073
}
60756074

6075+
/**
6076+
* @param list<ArrayDimFetch> $dimFetchStack
6077+
*/
6078+
private function isImplicitArrayCreation(array $dimFetchStack, Scope $scope): TrinaryLogic
6079+
{
6080+
if (count($dimFetchStack) === 0) {
6081+
return TrinaryLogic::createNo();
6082+
}
6083+
6084+
$varNode = $dimFetchStack[0]->var;
6085+
if (!$varNode instanceof Variable) {
6086+
return TrinaryLogic::createNo();
6087+
}
6088+
6089+
if (!is_string($varNode->name)) {
6090+
return TrinaryLogic::createNo();
6091+
}
6092+
6093+
return $scope->hasVariableType($varNode->name)->negate();
6094+
}
6095+
60766096
/**
60776097
* @param list<ArrayDimFetch> $dimFetchStack
60786098
* @param list<Type|null> $offsetTypes

src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace PHPStan\Rules\Arrays;
44

55
use PhpParser\Node;
6+
use PhpParser\Node\Expr\ArrayDimFetch;
7+
use PhpParser\Node\Expr\Variable;
68
use PHPStan\Analyser\NullsafeOperatorHelper;
79
use PHPStan\Analyser\Scope;
810
use PHPStan\DependencyInjection\AutowiredParameter;
@@ -11,6 +13,7 @@
1113
use PHPStan\Rules\Rule;
1214
use PHPStan\Rules\RuleErrorBuilder;
1315
use PHPStan\Rules\RuleLevelHelper;
16+
use PHPStan\TrinaryLogic;
1417
use PHPStan\Type\ErrorType;
1518
use PHPStan\Type\Type;
1619
use PHPStan\Type\VerbosityLevel;
@@ -42,6 +45,10 @@ public function getNodeType(): string
4245

4346
public function processNode(Node $node, Scope $scope): array
4447
{
48+
if ($this->isImplicitArrayCreation($node, $scope)->yes()) {
49+
return [];
50+
}
51+
4552
if ($node->dim !== null) {
4653
$dimType = $scope->getType($node->dim);
4754
$unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', SprintfHelper::escapeFormatString($dimType->describe(VerbosityLevel::value())));
@@ -153,4 +160,22 @@ public function processNode(Node $node, Scope $scope): array
153160
);
154161
}
155162

163+
private function isImplicitArrayCreation(Node\Expr\ArrayDimFetch $node, Scope $scope): TrinaryLogic
164+
{
165+
$varNode = $node->var;
166+
while ($varNode instanceof ArrayDimFetch) {
167+
$varNode = $varNode->var;
168+
}
169+
170+
if (!$varNode instanceof Variable) {
171+
return TrinaryLogic::createNo();
172+
}
173+
174+
if (!is_string($varNode->name)) {
175+
return TrinaryLogic::createNo();
176+
}
177+
178+
return $scope->hasVariableType($varNode->name)->negate();
179+
}
180+
156181
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12447Bis;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface Foo
8+
{
9+
10+
}
11+
12+
class HelloWorld
13+
{
14+
15+
public function __invoke(Foo $foo): void
16+
{
17+
$a = $foo->doFoo();
18+
assertType('*ERROR*', $a);
19+
$a[] = 5;
20+
assertType('mixed', $a);
21+
}
22+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12447;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
assertType('mixed', $data);
8+
$data[] = 1;
9+
assertType('mixed', $data);
10+
11+
function (): void {
12+
assertType('*ERROR*', $data);
13+
$data[] = 1;
14+
assertType('array{1}', $data);
15+
};

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,4 +944,17 @@ public function testBug3747(): void
944944
$this->analyse([__DIR__ . '/data/bug-3747.php'], []);
945945
}
946946

947+
public function testBug12447(): void
948+
{
949+
$this->checkExplicitMixed = true;
950+
$this->checkImplicitMixed = true;
951+
952+
$this->analyse([__DIR__ . '/data/bug-12447.php'], [
953+
[
954+
'Cannot access an offset on mixed.',
955+
5,
956+
],
957+
]);
958+
}
959+
947960
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12447;
4+
5+
$data[] = 1;
6+
7+
function (): void {
8+
$data[] = 1;
9+
};

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,11 @@ public function testBug12748(): void
999999
$this->analyse([__DIR__ . '/data/bug-12748.php'], []);
10001000
}
10011001

1002+
public function testBug3803(): void
1003+
{
1004+
$this->analyse([__DIR__ . '/data/bug-3803.php'], []);
1005+
}
1006+
10021007
public function testBug11019(): void
10031008
{
10041009
$this->analyse([__DIR__ . '/data/bug-11019.php'], []);
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 Bug3803;
4+
5+
/** @param array<string> $chars */
6+
function fun(array $chars) : void{
7+
$string = "";
8+
foreach($chars as $k => $v){
9+
$string[$k] = $v;
10+
}
11+
if($string === "wheee"){
12+
var_dump("yes");
13+
}
14+
}
15+
16+
fun(["w", "h", "e", "e", "e"]);

tests/PHPStan/Rules/Variables/UnsetRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public function testBug4289(): void
6565
$this->analyse([__DIR__ . '/data/bug-4289.php'], []);
6666
}
6767

68+
public function testBug4204(): void
69+
{
70+
$this->analyse([__DIR__ . '/data/bug-4204.php'], []);
71+
}
72+
6873
public function testBug5223(): void
6974
{
7075
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-5223.php'], [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug4204;
4+
5+
interface Block
6+
{
7+
public function getSettings(): mixed;
8+
}
9+
10+
class HelloWorld
11+
{
12+
/**
13+
* @param array<int, object> $blocks
14+
*/
15+
public function sayHello(array $blocks): void
16+
{
17+
foreach ($blocks as $block) {
18+
$settings = $block->getSettings();
19+
20+
if (isset($settings['name'])) {
21+
// switch name with code key
22+
$settings['code'] = $settings['name'];
23+
unset($settings['name']);
24+
}
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)