From d6aaf2282b3e74aa335ed048d6051f9aa1d64fb4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月15日 10:25:22 +0200 Subject: [PATCH 1/7] infer `non-empty-list/array` after `isset($arr[$i])` --- src/Analyser/TypeSpecifier.php | 12 +++++++++ tests/PHPStan/Analyser/nsrt/bug-13674.php | 30 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13674.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4ea8a595f5..e66f05cbf1 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -917,6 +917,18 @@ public function specifyTypesInCondition( && !$scope->getType($var->var) instanceof MixedType ) { $dimType = $scope->getType($var->dim); + $varType = $scope->getType($var->var); + + if ($varType->isArray()->yes()) { + $types = $types->unionWith( + $this->create( + $var->var, + new NonEmptyArrayType(), + $context, + $scope, + )->setRootExpr($expr), + ); + } if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { $types = $types->unionWith( diff --git a/tests/PHPStan/Analyser/nsrt/bug-13674.php b/tests/PHPStan/Analyser/nsrt/bug-13674.php new file mode 100644 index 0000000000..eb3cec9da9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13674.php @@ -0,0 +1,30 @@ + $arrayA + * @param list $listA + */ + public function sayHello($arrayA, $listA, int $i): void + { + if (isset($arrayA[$i])) { + assertType('non-empty-array', $arrayA); + } else { + assertType('array', $arrayA); + } + assertType('array', $arrayA); + + if (isset($listA[$i])) { + assertType('non-empty-list', $listA); + } else { + assertType('list', $listA); + } + assertType('list', $listA); + } +} From 3b710d7d6fac35cbe25aa36c5bb945a142622596 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月15日 10:36:39 +0200 Subject: [PATCH 2/7] adjust test expectations --- tests/PHPStan/Analyser/nsrt/bug-12274.php | 14 +++++++------- tests/PHPStan/Analyser/nsrt/bug-7000.php | 2 +- .../PHPStan/Analyser/nsrt/has-offset-type-bug.php | 2 +- .../Analyser/nsrt/specified-types-closure-use.php | 8 ++++---- tests/PHPStan/Rules/Arrays/data/bug-11679.php | 2 +- tests/PHPStan/Rules/Variables/IssetRuleTest.php | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-12274.php b/tests/PHPStan/Analyser/nsrt/bug-12274.php index b17b11aed8..c22fcc2d72 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12274.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12274.php @@ -40,7 +40,7 @@ function getItemsByModifiedIndex(array $items): array function testKeepListAfterIssetIndex(array $list, int $i): void { if (isset($list[$i])) { - assertType('list', $list); + assertType('non-empty-list', $list); $list[$i] = 21; assertType('non-empty-list', $list); $list[$i+1] = 21; @@ -53,8 +53,8 @@ function testKeepListAfterIssetIndex(array $list, int $i): void function testKeepNestedListAfterIssetIndex(array $nestedList, int $i, int $j): void { if (isset($nestedList[$i][$j])) { - assertType('list>', $nestedList); - assertType('list', $nestedList[$i]); + assertType('non-empty-list>', $nestedList); + assertType('non-empty-list', $nestedList[$i]); $nestedList[$i][$j] = 21; assertType('non-empty-list>', $nestedList); assertType('list', $nestedList[$i]); @@ -66,7 +66,7 @@ function testKeepNestedListAfterIssetIndex(array $nestedList, int $i, int $j): v function testKeepListAfterIssetIndexPlusOne(array $list, int $i): void { if (isset($list[$i])) { - assertType('list', $list); + assertType('non-empty-list', $list); $list[$i+1] = 21; assertType('non-empty-list', $list); } @@ -77,7 +77,7 @@ function testKeepListAfterIssetIndexPlusOne(array $list, int $i): void function testKeepListAfterIssetIndexOnePlus(array $list, int $i): void { if (isset($list[$i])) { - assertType('list', $list); + assertType('non-empty-list', $list); $list[1+$i] = 21; assertType('non-empty-list', $list); } @@ -90,7 +90,7 @@ function testShouldLooseListbyAst(array $list, int $i): void if (isset($list[$i])) { $i++; - assertType('list', $list); + assertType('non-empty-list', $list); $list[1+$i] = 21; assertType('non-empty-array, int>', $list); } @@ -101,7 +101,7 @@ function testShouldLooseListbyAst(array $list, int $i): void function testShouldLooseListbyAst2(array $list, int $i): void { if (isset($list[$i])) { - assertType('list', $list); + assertType('non-empty-list', $list); $list[2+$i] = 21; assertType('non-empty-array, int>', $list); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-7000.php b/tests/PHPStan/Analyser/nsrt/bug-7000.php index a2e536a6da..3ad9a1b2d8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7000.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7000.php @@ -12,7 +12,7 @@ public function doBar(): void $composer = array(); foreach (array('require', 'require-dev') as $linkType) { if (isset($composer[$linkType])) { - assertType('array{require?: array, require-dev?: array}', $composer); + assertType('non-empty-array{require?: array, require-dev?: array}', $composer); foreach ($composer[$linkType] as $x) {} } } diff --git a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php index eacfb06af6..09955bde2e 100644 --- a/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php +++ b/tests/PHPStan/Analyser/nsrt/has-offset-type-bug.php @@ -26,7 +26,7 @@ public function doFoo(array $errorMessages): void continue; } - assertType('array>', $fileErrorsCounts); + assertType('non-empty-array>', $fileErrorsCounts); assertType('int<1, max>', $fileErrorsCounts[$errorMessage]); $fileErrorsCounts[$errorMessage]++; diff --git a/tests/PHPStan/Analyser/nsrt/specified-types-closure-use.php b/tests/PHPStan/Analyser/nsrt/specified-types-closure-use.php index 9cd49e4522..f5f8189ded 100644 --- a/tests/PHPStan/Analyser/nsrt/specified-types-closure-use.php +++ b/tests/PHPStan/Analyser/nsrt/specified-types-closure-use.php @@ -48,10 +48,10 @@ function ($arr) use ($key): void { public function doBuzz(array $arr, string $key): void { if (isset($arr[$key])) { - assertType('array', $arr); + assertType('non-empty-array', $arr); assertType("mixed~null", $arr[$key]); function () use ($arr, $key): void { - assertType('array', $arr); + assertType('non-empty-array', $arr); assertType("mixed~null", $arr[$key]); }; } @@ -60,10 +60,10 @@ function () use ($arr, $key): void { public function doBuzz(array $arr, string $key): void { if (isset($arr[$key])) { - assertType('array', $arr); + assertType('non-empty-array', $arr); assertType("mixed~null", $arr[$key]); function ($key) use ($arr): void { - assertType('array', $arr); + assertType('non-empty-array', $arr); assertType("mixed", $arr[$key]); }; } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11679.php b/tests/PHPStan/Rules/Arrays/data/bug-11679.php index 463362516a..c2badb5b75 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-11679.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-11679.php @@ -33,7 +33,7 @@ public function sayHello(int $index): bool $this->arr[$index]['foo'] = true; assertType('non-empty-array', $this->arr); } - assertType('array', $this->arr); + assertType('non-empty-array', $this->arr); return $this->arr[$index]['foo']; // PHPStan does not realize 'foo' is set } } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 40b837c134..3cacd9c7f5 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -498,7 +498,7 @@ public function testPr4374(): void $this->analyse([__DIR__ . '/data/pr-4374.php'], [ [ - 'Offset string on array in isset() always exists and is not nullable.', + 'Offset string on non-empty-array in isset() always exists and is not nullable.', 23, ], ]); From 1543377e515b711184259154053a61b7679b59a7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月15日 10:47:38 +0200 Subject: [PATCH 3/7] fix --- src/Analyser/TypeSpecifier.php | 24 +++++++++---------- tests/PHPStan/Analyser/nsrt/bug-7000.php | 2 +- .../Rules/Variables/NullCoalesceRuleTest.php | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index e66f05cbf1..65c92748d0 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -917,18 +917,6 @@ public function specifyTypesInCondition( && !$scope->getType($var->var) instanceof MixedType ) { $dimType = $scope->getType($var->dim); - $varType = $scope->getType($var->var); - - if ($varType->isArray()->yes()) { - $types = $types->unionWith( - $this->create( - $var->var, - new NonEmptyArrayType(), - $context, - $scope, - )->setRootExpr($expr), - ); - } if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { $types = $types->unionWith( @@ -941,6 +929,18 @@ public function specifyTypesInCondition( ); } else { $varType = $scope->getType($var->var); + + if ($varType->isArray()->yes() && $dimType->isConstantScalarValue()->no()) { + $types = $types->unionWith( + $this->create( + $var->var, + new NonEmptyArrayType(), + $context, + $scope, + )->setRootExpr($expr), + ); + } + $narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType, $dimType); if ($narrowedKey !== null) { $types = $types->unionWith( diff --git a/tests/PHPStan/Analyser/nsrt/bug-7000.php b/tests/PHPStan/Analyser/nsrt/bug-7000.php index 3ad9a1b2d8..a2e536a6da 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-7000.php +++ b/tests/PHPStan/Analyser/nsrt/bug-7000.php @@ -12,7 +12,7 @@ public function doBar(): void $composer = array(); foreach (array('require', 'require-dev') as $linkType) { if (isset($composer[$linkType])) { - assertType('non-empty-array{require?: array, require-dev?: array}', $composer); + assertType('array{require?: array, require-dev?: array}', $composer); foreach ($composer[$linkType] as $x) {} } } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 318bd3b275..2f8c90da34 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -285,7 +285,7 @@ public function testBug7190(): void { $this->analyse([__DIR__ . '/../Properties/data/bug-7190.php'], [ [ - 'Offset int on array on left side of ?? always exists and is not nullable.', + 'Offset int on non-empty-array on left side of ?? always exists and is not nullable.', 20, ], ]); From 24865c061890007693a82bfe5d72220801e19051 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月15日 11:27:39 +0200 Subject: [PATCH 4/7] make this code more uniform with array_key_exists() handling --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 65c92748d0..99a816e1b6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -930,7 +930,7 @@ public function specifyTypesInCondition( } else { $varType = $scope->getType($var->var); - if ($varType->isArray()->yes() && $dimType->isConstantScalarValue()->no()) { + if ($varType->isArray()->yes() && count($dimType->getConstantScalarTypes()) <= 1) { $types = $types->unionWith( $this->create( $var->var, From f9ed60de7e90b44d5b8ed1b8dcaede60f9440884 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月15日 20:11:28 +0200 Subject: [PATCH 5/7] added test --- tests/PHPStan/Analyser/nsrt/bug-13674.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-13674.php b/tests/PHPStan/Analyser/nsrt/bug-13674.php index eb3cec9da9..76f5bf0dc8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13674.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13674.php @@ -26,5 +26,10 @@ public function sayHello($arrayA, $listA, int $i): void assertType('list', $listA); } assertType('list', $listA); + + if (!isset($listA[$i])) { + return; + } + assertType('non-empty-list', $listA); } } From 004abc795e5d2a5651c9f1e35c0d8d844969e304 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月16日 13:26:14 +0200 Subject: [PATCH 6/7] add assert --- tests/PHPStan/Analyser/nsrt/bug-13674.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-13674.php b/tests/PHPStan/Analyser/nsrt/bug-13674.php index 76f5bf0dc8..7e3c0a5a06 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13674.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13674.php @@ -28,6 +28,7 @@ public function sayHello($arrayA, $listA, int $i): void assertType('list', $listA); if (!isset($listA[$i])) { + assertType('list', $listA); return; } assertType('non-empty-list', $listA); From 0fab134cdb14537e07b5ced046a5ab9398e5b438 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: 2025年10月17日 08:53:09 +0200 Subject: [PATCH 7/7] Update bug-13674.php --- tests/PHPStan/Analyser/nsrt/bug-13674.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-13674.php b/tests/PHPStan/Analyser/nsrt/bug-13674.php index 7e3c0a5a06..cabbb99632 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13674.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13674.php @@ -32,5 +32,8 @@ public function sayHello($arrayA, $listA, int $i): void return; } assertType('non-empty-list', $listA); + + $emptyArray = []; + assertType('false', isset($emptyArray[$i])); } }

AltStyle によって変換されたページ (->オリジナル) /