Skip to content

Commit d968371

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix phpstan/phpstan#4296: false positive on isset() with numeric string offset on string-keyed array
- In ArrayType::hasOffsetValueType() and getOffsetValueType(), numeric string offsets like '1234' are converted to integers by toArrayKey(), then rejected because IntegerType is not a supertype of StringType key - Added check against original (pre-coercion) offset type so numeric strings are recognized as valid string keys - New regression test in tests/PHPStan/Rules/Variables/data/bug-4296.php
1 parent a3fb8e9 commit d968371

4 files changed

Lines changed: 39 additions & 4 deletions

File tree

src/Type/ArrayType.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
268268

269269
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
270270
{
271+
$originalOffsetType = $offsetType;
271272
$offsetArrayKeyType = $offsetType->toArrayKey();
272273
if ($offsetArrayKeyType instanceof ErrorType) {
273274
$allowedArrayKeys = AllowedArrayKeysTypes::getType();
@@ -279,6 +280,7 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
279280
$offsetType = $offsetArrayKeyType;
280281

281282
if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()
283+
&& $this->getKeyType()->isSuperTypeOf($originalOffsetType)->no()
282284
&& ($offsetType->isString()->no() || !$offsetType->isConstantScalarValue()->no())
283285
) {
284286
return TrinaryLogic::createNo();
@@ -289,8 +291,10 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
289291

290292
public function getOffsetValueType(Type $offsetType): Type
291293
{
294+
$originalOffsetType = $offsetType;
292295
$offsetType = $offsetType->toArrayKey();
293296
if ($this->getKeyType()->isSuperTypeOf($offsetType)->no()
297+
&& $this->getKeyType()->isSuperTypeOf($originalOffsetType)->no()
294298
&& ($offsetType->isString()->no() || !$offsetType->isConstantScalarValue()->no())
295299
) {
296300
return new ErrorType();

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,6 @@ public function testRule(): void
6363
'Offset 0 does not exist on array<string, string>.',
6464
111,
6565
],
66-
[
67-
'Offset \'0\' does not exist on array<string, string>.',
68-
112,
69-
],
7066
[
7167
'Offset int does not exist on array<string, string>.',
7268
114,

tests/PHPStan/Rules/Variables/IssetRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,11 @@ public function testBug9503(): void
513513
$this->analyse([__DIR__ . '/data/bug-9503.php'], []);
514514
}
515515

516+
public function testBug4296(): void
517+
{
518+
$this->treatPhpDocTypesAsCertain = true;
519+
520+
$this->analyse([__DIR__ . '/data/bug-4296.php'], []);
521+
}
522+
516523
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug4296;
4+
5+
class Test {
6+
private string $id;
7+
8+
public function __construct(string $id)
9+
{
10+
$this->id = $id;
11+
}
12+
13+
public function getId(): string
14+
{
15+
return $this->id;
16+
}
17+
}
18+
19+
$map = [];
20+
foreach ([new Test('1234')] as $test) {
21+
$map[$test->getId()] = $test;
22+
}
23+
24+
foreach (['1234'] as $value) {
25+
if (isset($map[$value])) {
26+
$found = 1;
27+
}
28+
}

0 commit comments

Comments
 (0)