Skip to content
Closed
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
7 changes: 7 additions & 0 deletions src/Analyser/ExprHandler/ArrayDimFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type
}

$offsetAccessibleType = $scope->getType($expr->var);
if ($offsetAccessibleType instanceof NeverType) {
// never is a subtype of everything (including ArrayAccess), so without
// this short-circuit the fetch would be resolved through offsetGet() and
// produce an *ERROR* type instead of the expected never.
return $offsetAccessibleType;
}

if (
!$offsetAccessibleType->isArray()->yes()
&& (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-9307.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function test(): void
}
}

assertType('array<*ERROR*>', $objects); // could be array<int, Bug9307\Item>
assertType('array<int, Bug9307\Item>', $objects);

$this->acceptObjects($objects);
}
Expand Down
37 changes: 37 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14281.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace Bug14281;

use function PHPStan\Testing\assertType;

function test(): void
{
$array = [
null,
0,
'some-string',
new \stdClass(),
['some' => 'value'],
];

assert($array[0] === null);
assertType("array{null, 0, 'some-string', stdClass, array{some: 'value'}}", $array);

// $array[1] is 0, so this assertion can never hold and collapses the array
assert($array[1] === null);
assertType('*NEVER*', $array);

// offset access on a never array must stay never instead of becoming *ERROR*
assertType('*NEVER*', $array[2]);
assert($array[2] === null);
assertType('*NEVER*', $array);
}

function neverVariable(int $i): void
{
if ($i !== $i) {
assertType('*NEVER*', $i);
assertType('false', $i === null);
assertType('true', $i !== null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1275,4 +1275,42 @@ public function testBug14847(): void
]);
}

public function testBug14281(): void
{
$this->analyse([__DIR__ . '/data/bug-14281.php'], [
[
'Strict comparison using === between null and null will always evaluate to true.',
15,
],
[
'Strict comparison using === between 0 and null will always evaluate to false.',
17,
],
[
'Strict comparison using === between *NEVER* and null will always evaluate to false.',
20,
],
[
'Strict comparison using === between *NEVER* and null will always evaluate to false.',
21,
],
[
'Strict comparison using === between *NEVER* and null will always evaluate to false.',
22,
],
[
'Strict comparison using !== between int and int will always evaluate to false.',
27,
],
[
'Strict comparison using === between *NEVER* and null will always evaluate to false.',
29,
],
[
'Strict comparison using !== between *NEVER* and null will always evaluate to true.',
30,
],
]);
}

}
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14281.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types = 1);

namespace Bug14281Rule;

function test(): void
{
$array = [
null,
0,
'some-string',
new \stdClass(),
['some' => 'value'],
];

assert($array[0] === null);
// $array[1] is 0, so this assertion collapses the array to never
assert($array[1] === null);
// offset access on the never array stays never (instead of *ERROR*),
// so the comparisons below are reported as impossible
assert($array[2] === null);
assert($array[3] === null);
assert($array[4] === null);
}

function neverOperand(int $i): void
{
if ($i !== $i) {
// $i is never here, so the comparisons are reported as impossible
$a = ($i === null);
$b = ($i !== null);
}
}
Loading