From 86a07a22055b134bca6de171fabf27e7cecc4d32 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:43:34 +0000 Subject: [PATCH 1/9] 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 https://github.com/phpstan/phpstan/issues/12574 --- src/Analyser/NodeScopeResolver.php | 38 ++++++++++++------- ...nexistentOffsetInArrayDimFetchRuleTest.php | 7 ++++ .../PHPStan/Rules/Arrays/data/bug-12574c.php | 32 ++++++++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12574c.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8a807ea745..c267e716cb 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6776,14 +6776,19 @@ private function unwrapAssign(Expr $expr): Expr private function processSureTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array { foreach ($specifiedTypes->getSureTypes() as $exprString => [$expr, $exprType]) { - if (!$expr instanceof Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } + if ($expr instanceof Variable) { + if (!is_string($expr->name)) { + continue; + } - if ($expr->name === $variableName) { + if ($expr->name === $variableName) { + continue; + } + } elseif ( + !$expr instanceof PropertyFetch + && !$expr instanceof ArrayDimFetch + && !$expr instanceof StaticPropertyFetch + ) { continue; } @@ -6810,14 +6815,19 @@ private function processSureTypesForConditionalExpressionsAfterAssign(Scope $sco private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $scope, string $variableName, array $conditionalExpressions, SpecifiedTypes $specifiedTypes, Type $variableType): array { foreach ($specifiedTypes->getSureNotTypes() as $exprString => [$expr, $exprType]) { - if (!$expr instanceof Variable) { - continue; - } - if (!is_string($expr->name)) { - continue; - } + if ($expr instanceof Variable) { + if (!is_string($expr->name)) { + continue; + } - if ($expr->name === $variableName) { + if ($expr->name === $variableName) { + continue; + } + } elseif ( + !$expr instanceof PropertyFetch + && !$expr instanceof ArrayDimFetch + && !$expr instanceof StaticPropertyFetch + ) { continue; } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 879c86f791..a641163fe1 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -1038,6 +1038,13 @@ public function testBug12574b(): void $this->analyse([__DIR__ . '/data/bug-12574b.php'], []); } + public function testBug12574c(): void + { + $this->reportPossiblyNonexistentGeneralArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-12574c.php'], []); + } + public function testBug12926(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12574c.php b/tests/PHPStan/Rules/Arrays/data/bug-12574c.php new file mode 100644 index 0000000000..ec949ff2cc --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12574c.php @@ -0,0 +1,32 @@ + $worlds */ + public array $worlds = []; +} + +class World +{ + public int $x = 1; +} + + +function hello(Galaxy $a): void +{ + $notEmpty = isset($a->worlds[0]); + if ($notEmpty && $a->worlds[0]->x === 1) { + echo 'hello'; + } +} + +function hello2(Galaxy $a): void +{ + $worlds = $a->worlds; + $notEmpty = isset($worlds[0]); + if ($notEmpty && $worlds[0]->x === 1) { + echo 'hello'; + } +} From 71e33f80e66e4b5363b9234d7f6785b04ff8aa45 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 23:51:12 +0000 Subject: [PATCH 2/9] Add regression test for #11870 Closes https://github.com/phpstan/phpstan/issues/11870 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-11870.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11870.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11870.php b/tests/PHPStan/Analyser/nsrt/bug-11870.php new file mode 100644 index 0000000000..04727d679b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11870.php @@ -0,0 +1,14 @@ + Date: Mon, 16 Feb 2026 23:52:08 +0000 Subject: [PATCH 3/9] Add regression test for #11102 Closes https://github.com/phpstan/phpstan/issues/11102 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-11102.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11102.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11102.php b/tests/PHPStan/Analyser/nsrt/bug-11102.php new file mode 100644 index 0000000000..0f32d86399 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11102.php @@ -0,0 +1,15 @@ + null]; + +$startIsADateTime = $details['start'] instanceof DateTime; + +if ($startIsADateTime) { + assertType('DateTime', $details['start']); +} From 156b1eab259befd5bf80689584d51f9c390373b6 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 23:52:33 +0000 Subject: [PATCH 4/9] Add regression test for #10040 Closes https://github.com/phpstan/phpstan/issues/10040 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-10040.php | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-10040.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-10040.php b/tests/PHPStan/Analyser/nsrt/bug-10040.php new file mode 100644 index 0000000000..35b30ac80e --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10040.php @@ -0,0 +1,34 @@ += 8.0 + +namespace Bug10040; + +use function PHPStan\Testing\assertType; + +class A +{ + public string|null $foo; +} + +/** + * @phpstan-assert-if-true !null $a->foo + */ +function assertStringNotNull(A $a): bool +{ + return true; +} + +$a1 = new A(); +if(assertStringNotNull($a1)){ + assertType('string', $a1->foo); +} + +$a2 = new A(); +$stringIsNotNull = assertStringNotNull($a2); +if($stringIsNotNull){ + assertType('string', $a2->foo); +} + +$a3 = new A(); +if($stringIsNotNull = assertStringNotNull($a3)){ + assertType('string', $a3->foo); +} From 38363437044eb695a430c4c91458382fafb7327d Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 23:53:12 +0000 Subject: [PATCH 5/9] Add regression test for #7716 Closes https://github.com/phpstan/phpstan/issues/7716 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-7716.php | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-7716.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-7716.php b/tests/PHPStan/Analyser/nsrt/bug-7716.php new file mode 100644 index 0000000000..7551bf69de --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-7716.php @@ -0,0 +1,50 @@ + 1; + $hasBar = isset($array['bar']) && $array['bar'] > 1; + + if ($hasFoo) { + assertType('int<2, max>', $array['foo']); + return $array['foo']; + } + + if ($hasBar) { + assertType('int<2, max>', $array['bar']); + return $array['bar']; + } + + return 0; + } + + /** + * @param array{foo?: int, bar?: int} $array + */ + public function sayHello2(array $array): int + { + $hasBar = isset($array['bar']) && $array['bar'] > 1; + $hasFoo = isset($array['foo']) && $array['foo'] > 1; + + if ($hasFoo) { + assertType('int<2, max>', $array['foo']); + return $array['foo']; + } + + if ($hasBar) { + assertType('int<2, max>', $array['bar']); + return $array['bar']; + } + + return 0; + } +} From 0ca189006b89c4f4fed14a9a2a59633875e93801 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 23:53:43 +0000 Subject: [PATCH 6/9] Add regression test for #6991 Closes https://github.com/phpstan/phpstan/issues/6991 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-6991.php | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6991.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-6991.php b/tests/PHPStan/Analyser/nsrt/bug-6991.php new file mode 100644 index 0000000000..4b6248aa2b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6991.php @@ -0,0 +1,29 @@ += 8.0 + +namespace Bug6991; + +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + public int $more = 1; + + public ?string $key = null; +} + +class Other { + public ?HelloWorld $optional = null; +} + +function test(Other $object): int +{ + $key = $object->optional?->key; + + if (!$key) { + return 0; + } + + assertType('Bug6991\HelloWorld', $object->optional); + + return $object->optional->more * 100; +} From 2998d07a056c730ec2cac970a1392b9c28b74821 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 16 Feb 2026 23:54:05 +0000 Subject: [PATCH 7/9] Add regression test for #6120 Closes https://github.com/phpstan/phpstan/issues/6120 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-6120.php | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6120.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-6120.php b/tests/PHPStan/Analyser/nsrt/bug-6120.php new file mode 100644 index 0000000000..1833f550e6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6120.php @@ -0,0 +1,27 @@ += 8.0 + +namespace Bug6120; + +use Generator; +use function PHPStan\Testing\assertType; + +class HelloWorld +{ + /** + * @var Generator $gen + */ + private ?Generator $gen = null; + + public function setGenerator(Generator $gen): void { + $this->gen = $gen; + } + + public function sayHello(): void + { + while ($v = $this->gen?->current()) { + assertType('Generator', $this->gen); + echo $v; + $this->gen->next(); + } + } +} From a538aa4221bafb4e82d088ca31f2f9b3a1d19246 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:25:47 +0000 Subject: [PATCH 8/9] Fix bug-10040.php: put declare(strict_types) after lint comment Co-authored-by: Markus Staab --- tests/PHPStan/Analyser/nsrt/bug-10040.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10040.php b/tests/PHPStan/Analyser/nsrt/bug-10040.php index 35b30ac80e..e9dc2c306d 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10040.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10040.php @@ -1,4 +1,5 @@ -= 8.0 +// lint >= 8.0 + Date: Wed, 18 Feb 2026 11:30:41 +0000 Subject: [PATCH 9/9] Revert "Fix bug-10040.php: put declare(strict_types) after lint comment" This reverts commit a538aa422. Co-authored-by: Markus Staab --- tests/PHPStan/Analyser/nsrt/bug-10040.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-10040.php b/tests/PHPStan/Analyser/nsrt/bug-10040.php index e9dc2c306d..35b30ac80e 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-10040.php +++ b/tests/PHPStan/Analyser/nsrt/bug-10040.php @@ -1,5 +1,4 @@ -// lint >= 8.0 -= 8.0 namespace Bug10040;