Skip to content
Open
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
5 changes: 4 additions & 1 deletion src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1947,7 +1947,10 @@ private function optimizeScalarType(Type $type): Type
public function resolveIdenticalType(Type $leftType, Type $rightType): TypeResult
{
if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
return new TypeResult(new ConstantBooleanType(false), []);
// A never-typed operand has no value to compare, so the result is
// undecided. This mirrors how never behaves as a boolean condition and
// keeps always-true/false rules from piling onto already-unreachable code.
return new TypeResult(new BooleanType(), []);
}

if ($leftType instanceof ConstantScalarType && $rightType instanceof ConstantScalarType) {
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('bool', $i === null);
assertType('bool', $i !== null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,6 @@ public static function dataLastMatchArm(): iterable
36,
'Remove remaining cases below this one and this error will disappear too.',
],
[
"Strict comparison using === between *NEVER* and 'ccc' will always evaluate to false.",
38,
],
Comment on lines -689 to -692

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need this errors to stay

[
"Strict comparison using === between 'bbb' and 'bbb' will always evaluate to true.",
46,
Expand Down Expand Up @@ -719,10 +715,6 @@ public static function dataLastMatchArm(): iterable
"Strict comparison using === between 'bbb' and 'bbb' will always evaluate to true.",
36,
],
[
"Strict comparison using === between *NEVER* and 'ccc' will always evaluate to false.",
38,
],
[
"Strict comparison using === between 'bbb' and 'bbb' will always evaluate to true.",
46,
Expand Down Expand Up @@ -1244,4 +1236,22 @@ public function testBug14791(): void
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-14791.php'], []);
}

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.',
16,
],
[
'Strict comparison using !== between int and int will always evaluate to false.',
25,
],
]);
}

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

namespace Bug14281Rule;

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

assert($array[0] === null);
assert($array[1] === null);
// everything below is unreachable, the comparisons must not be reported
assert($array[2] === null);
assert($array[3] === null);
assert($array[4] === null);
}

function neverOperand(int $i): void
{
if ($i !== $i) {
// $i is never here
$a = ($i === null);
$b = ($i !== null);
}
}
Loading