Skip to content

Commit 86a07a2

Browse files
committed
Fix isset() type narrowing lost when result stored in variable for property access
- Extended processSureTypesForConditionalExpressionsAfterAssign and processSureNotTypesForConditionalExpressionsAfterAssign to store conditional expressions for PropertyFetch, ArrayDimFetch, and StaticPropertyFetch in addition to Variable expressions - Previously, when `$notEmpty = isset($a->worlds[0])` was used in a condition like `if ($notEmpty && $a->worlds[0]->x === 1)`, the type narrowing for `$a->worlds` (HasOffsetType) was discarded because only Variable expressions were stored as conditional expressions - New regression test in tests/PHPStan/Rules/Arrays/data/bug-12574c.php Closes phpstan/phpstan#12574
1 parent 106fc93 commit 86a07a2

File tree

3 files changed

+63
-14
lines changed

3 files changed

+63
-14
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6776,14 +6776,19 @@ private function unwrapAssign(Expr $expr): Expr
67766776
private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array
67776777
{
67786778
foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) {
6779-
if (!$expr instanceof Variable) {
6780-
continue;
6781-
}
6782-
if (!is_string($expr->name)) {
6783-
continue;
6784-
}
6779+
if ($expr instanceof Variable) {
6780+
if (!is_string($expr->name)) {
6781+
continue;
6782+
}
67856783

6786-
if ($expr->name === $variableName) {
6784+
if ($expr->name === $variableName) {
6785+
continue;
6786+
}
6787+
} elseif (
6788+
!$expr instanceof PropertyFetch
6789+
&& !$expr instanceof ArrayDimFetch
6790+
&& !$expr instanceof StaticPropertyFetch
6791+
) {
67876792
continue;
67886793
}
67896794

@@ -6810,14 +6815,19 @@ private function processSureTypesForConditionalExpressionsAfterAssign(Scope $sco
68106815
private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array
68116816
{
68126817
foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) {
6813-
if (!$expr instanceof Variable) {
6814-
continue;
6815-
}
6816-
if (!is_string($expr->name)) {
6817-
continue;
6818-
}
6818+
if ($expr instanceof Variable) {
6819+
if (!is_string($expr->name)) {
6820+
continue;
6821+
}
68196822

6820-
if ($expr->name === $variableName) {
6823+
if ($expr->name === $variableName) {
6824+
continue;
6825+
}
6826+
} elseif (
6827+
!$expr instanceof PropertyFetch
6828+
&& !$expr instanceof ArrayDimFetch
6829+
&& !$expr instanceof StaticPropertyFetch
6830+
) {
68216831
continue;
68226832
}
68236833

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,13 @@ public function testBug12574b(): void
10381038
$this->analyse([__DIR__ . '/data/bug-12574b.php'], []);
10391039
}
10401040

1041+
public function testBug12574c(): void
1042+
{
1043+
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
1044+
1045+
$this->analyse([__DIR__ . '/data/bug-12574c.php'], []);
1046+
}
1047+
10411048
public function testBug12926(): void
10421049
{
10431050
$this->reportPossiblyNonexistentGeneralArrayOffset = true;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12574c;
4+
5+
class Galaxy
6+
{
7+
/** @var list<World> $worlds */
8+
public array $worlds = [];
9+
}
10+
11+
class World
12+
{
13+
public int $x = 1;
14+
}
15+
16+
17+
function hello(Galaxy $a): void
18+
{
19+
$notEmpty = isset($a->worlds[0]);
20+
if ($notEmpty && $a->worlds[0]->x === 1) {
21+
echo 'hello';
22+
}
23+
}
24+
25+
function hello2(Galaxy $a): void
26+
{
27+
$worlds = $a->worlds;
28+
$notEmpty = isset($worlds[0]);
29+
if ($notEmpty && $worlds[0]->x === 1) {
30+
echo 'hello';
31+
}
32+
}

0 commit comments

Comments
 (0)