Skip to content

Commit ea9daa5

Browse files
committed
Fix isset() falsey branch not narrowing array type for dim fetches
- In the falsey branch of isset($a['key']), narrow the array variable by removing union members where the key is definitely present with a non-null value - Preserve variable certainty when the array variable might not be defined (using IssetExpr to restore maybe certainty) - Update tagged-unions test expectation to reflect improved narrowing precision - New regression test in tests/PHPStan/Analyser/nsrt/bug-9908.php Closes phpstan/phpstan#9908
1 parent 106fc93 commit ea9daa5

File tree

4 files changed

+92
-2
lines changed

4 files changed

+92
-2
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ parameters:
8787
-
8888
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
8989
identifier: phpstanApi.instanceofType
90-
count: 4
90+
count: 5
9191
path: src/Analyser/TypeSpecifier.php
9292

9393
-

src/Analyser/TypeSpecifier.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,52 @@ public function specifyTypesInCondition(
920920
return $exprType;
921921
}
922922

923+
if (
924+
$issetExpr instanceof ArrayDimFetch
925+
&& $issetExpr->dim !== null
926+
) {
927+
$varType = $scope->getType($issetExpr->var);
928+
if (!$varType instanceof MixedType) {
929+
$dimType = $scope->getType($issetExpr->dim);
930+
931+
if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) {
932+
$types = $varType instanceof UnionType ? $varType->getTypes() : [$varType];
933+
$typesToRemove = [];
934+
foreach ($types as $innerType) {
935+
$hasOffset = $innerType->hasOffsetValueType($dimType);
936+
if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) {
937+
continue;
938+
}
939+
940+
$typesToRemove[] = $innerType;
941+
}
942+
943+
if ($typesToRemove !== []) {
944+
$typeToRemove = TypeCombinator::union(...$typesToRemove);
945+
$result = $this->create(
946+
$issetExpr->var,
947+
$typeToRemove,
948+
TypeSpecifierContext::createFalse(),
949+
$scope,
950+
)->setRootExpr($expr);
951+
952+
if ($scope->hasExpressionType($issetExpr->var)->maybe()) {
953+
$result = $result->unionWith(
954+
$this->create(
955+
new IssetExpr($issetExpr->var),
956+
new NullType(),
957+
TypeSpecifierContext::createTruthy(),
958+
$scope,
959+
)->setRootExpr($expr),
960+
);
961+
}
962+
963+
return $result;
964+
}
965+
}
966+
}
967+
}
968+
923969
return new SpecifiedTypes();
924970
}
925971

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9908;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function test(): void
10+
{
11+
$a = [];
12+
if (rand() % 2) {
13+
$a = ['bar' => 'string'];
14+
}
15+
16+
if (isset($a['bar'])) {
17+
$a['bar'] = 1;
18+
}
19+
20+
assertType("array{}|array{bar: 1}", $a);
21+
}
22+
23+
/**
24+
* @param array{bar?: int} $foo
25+
*/
26+
public function sayHello(array $foo): void
27+
{
28+
echo 'Hello' . print_r($foo, true);
29+
}
30+
31+
public function test2(): void
32+
{
33+
$a = [];
34+
if (rand() % 2) {
35+
$a = ['bar' => 'string'];
36+
}
37+
38+
if (isset($a['bar'])) {
39+
$a['bar'] = 1;
40+
}
41+
42+
$this->sayHello($a);
43+
}
44+
}

tests/PHPStan/Analyser/nsrt/tagged-unions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function doFoo4(array $foo)
5555
if (isset($foo['C'])) {
5656
assertType("array{A: string, C: 1}", $foo);
5757
} else {
58-
assertType("array{A: int, B: 1}|array{A: string, C: 1}", $foo); // could be array{A: int, B: 1}
58+
assertType("array{A: int, B: 1}", $foo);
5959
}
6060

6161
assertType('array{A: int, B: 1}|array{A: string, C: 1}', $foo);

0 commit comments

Comments
 (0)