From 3b14a35486036c4c23b1677a321d03e5a5a13212 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:53:48 +0000 Subject: [PATCH 1/7] 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 https://github.com/phpstan/phpstan/issues/9908 --- phpstan-baseline.neon | 2 +- src/Analyser/TypeSpecifier.php | 46 +++++++++++++++++++ tests/PHPStan/Analyser/nsrt/bug-9908.php | 44 ++++++++++++++++++ tests/PHPStan/Analyser/nsrt/tagged-unions.php | 2 +- 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9908.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f7b1c64ee0..a6d38c503c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -87,7 +87,7 @@ parameters: - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.' identifier: phpstanApi.instanceofType - count: 4 + count: 5 path: src/Analyser/TypeSpecifier.php - diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 3a1f8bd98e..9a577ee53f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -920,6 +920,52 @@ public function specifyTypesInCondition( return $exprType; } + if ( + $issetExpr instanceof ArrayDimFetch + && $issetExpr->dim !== null + ) { + $varType = $scope->getType($issetExpr->var); + if (!$varType instanceof MixedType) { + $dimType = $scope->getType($issetExpr->dim); + + if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { + $types = $varType instanceof UnionType ? $varType->getTypes() : [$varType]; + $typesToRemove = []; + foreach ($types as $innerType) { + $hasOffset = $innerType->hasOffsetValueType($dimType); + if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + continue; + } + + $typesToRemove[] = $innerType; + } + + if ($typesToRemove !== []) { + $typeToRemove = TypeCombinator::union(...$typesToRemove); + $result = $this->create( + $issetExpr->var, + $typeToRemove, + TypeSpecifierContext::createFalse(), + $scope, + )->setRootExpr($expr); + + if ($scope->hasExpressionType($issetExpr->var)->maybe()) { + $result = $result->unionWith( + $this->create( + new IssetExpr($issetExpr->var), + new NullType(), + TypeSpecifierContext::createTruthy(), + $scope, + )->setRootExpr($expr), + ); + } + + return $result; + } + } + } + } + return new SpecifiedTypes(); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-9908.php b/tests/PHPStan/Analyser/nsrt/bug-9908.php new file mode 100644 index 0000000000..d51c4c33f9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9908.php @@ -0,0 +1,44 @@ + 'string']; + } + + if (isset($a['bar'])) { + $a['bar'] = 1; + } + + assertType("array{}|array{bar: 1}", $a); + } + + /** + * @param array{bar?: int} $foo + */ + public function sayHello(array $foo): void + { + echo 'Hello' . print_r($foo, true); + } + + public function test2(): void + { + $a = []; + if (rand() % 2) { + $a = ['bar' => 'string']; + } + + if (isset($a['bar'])) { + $a['bar'] = 1; + } + + $this->sayHello($a); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/tagged-unions.php b/tests/PHPStan/Analyser/nsrt/tagged-unions.php index 9926467123..45e7e6e98b 100644 --- a/tests/PHPStan/Analyser/nsrt/tagged-unions.php +++ b/tests/PHPStan/Analyser/nsrt/tagged-unions.php @@ -55,7 +55,7 @@ public function doFoo4(array $foo) if (isset($foo['C'])) { assertType("array{A: string, C: 1}", $foo); } else { - assertType("array{A: int, B: 1}|array{A: string, C: 1}", $foo); // could be array{A: int, B: 1} + assertType("array{A: int, B: 1}", $foo); } assertType('array{A: int, B: 1}|array{A: string, C: 1}', $foo); From fed2702f718dca6a3c53c6b67e769c15cd522055 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 17 Feb 2026 23:55:14 +0100 Subject: [PATCH 2/7] More tests --- tests/PHPStan/Analyser/nsrt/bug-9908.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-9908.php b/tests/PHPStan/Analyser/nsrt/bug-9908.php index d51c4c33f9..992829f8e9 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9908.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9908.php @@ -41,4 +41,17 @@ public function test2(): void $this->sayHello($a); } + + /** + * @param array{a: string, b: string}|array{a: string|null, c: string}|array{a?: string, d: string} $a + */ + public function moreTests($a): void + { + if (isset($a['a'])) { + assertType("array{a: string, b: string}|array{a: string, c: string}|array{a: string, d: string}", $a); + } else { + // Could be "array{a: null, c: string}|array{d: string}" + assertType("array{a: string|null, c: string}|array{a?: string, d: string}", $a); + } + } } From 432d18aa48ca7786d7c8fa81adfdcb3cfe13831a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 19 Feb 2026 00:57:51 +0100 Subject: [PATCH 3/7] Simplify --- src/Analyser/TypeSpecifier.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9a577ee53f..c2adab7320 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -929,19 +929,20 @@ public function specifyTypesInCondition( $dimType = $scope->getType($issetExpr->dim); if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { - $types = $varType instanceof UnionType ? $varType->getTypes() : [$varType]; + $constantArrays = $varType->getConstantArrays(); $typesToRemove = []; - foreach ($types as $innerType) { - $hasOffset = $innerType->hasOffsetValueType($dimType); - if (!$hasOffset->yes() || !$innerType->getOffsetValueType($dimType)->isNull()->no()) { + foreach ($constantArrays as $constantArray) { + $hasOffset = $constantArray->hasOffsetValueType($dimType); + if (!$hasOffset->yes() || !$constantArray->getOffsetValueType($dimType)->isNull()->no()) { continue; } - $typesToRemove[] = $innerType; + $typesToRemove[] = $constantArray; } if ($typesToRemove !== []) { $typeToRemove = TypeCombinator::union(...$typesToRemove); + $result = $this->create( $issetExpr->var, $typeToRemove, From 24c603ce696153d801101dcc693f166f7bf4d896 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 21:01:00 +0000 Subject: [PATCH 4/7] Add regression test for #10544 Closes https://github.com/phpstan/phpstan/issues/10544 --- tests/PHPStan/Analyser/nsrt/bug-10544.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10544.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10544.php b/tests/PHPStan/Analyser/nsrt/bug-10544.php new file mode 100644 index 0000000000..22b9ed2636 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10544.php @@ -0,0 +1,19 @@ + Date: Mon, 16 Feb 2026 21:01:49 +0000 Subject: [PATCH 5/7] Add regression test for #8724 Closes https://github.com/phpstan/phpstan/issues/8724 --- tests/PHPStan/Analyser/nsrt/bug-8724.php | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8724.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-8724.php b/tests/PHPStan/Analyser/nsrt/bug-8724.php new file mode 100644 index 0000000000..121c26bd49 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8724.php @@ -0,0 +1,28 @@ + Date: Mon, 16 Feb 2026 21:02:38 +0000 Subject: [PATCH 6/7] Add regression test for #5128 Closes https://github.com/phpstan/phpstan/issues/5128 --- tests/PHPStan/Analyser/nsrt/bug-5128.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-5128.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-5128.php b/tests/PHPStan/Analyser/nsrt/bug-5128.php new file mode 100644 index 0000000000..163bace5a3 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-5128.php @@ -0,0 +1,18 @@ + Date: Mon, 16 Feb 2026 21:03:19 +0000 Subject: [PATCH 7/7] Add regression test for #12401 Closes https://github.com/phpstan/phpstan/issues/12401 --- tests/PHPStan/Analyser/nsrt/bug-12401.php | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12401.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-12401.php b/tests/PHPStan/Analyser/nsrt/bug-12401.php new file mode 100644 index 0000000000..14155dc701 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12401.php @@ -0,0 +1,24 @@ +