diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index c9de826666..714768a8b9 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -66,13 +66,19 @@ public function specifyTypes( && !$keyType instanceof ConstantStringType ) { if ($context->true()) { - if ($arrayType->isIterableAtLeastOnce()->no()) { - return $this->typeSpecifier->create( + $specifiedTypes = new SpecifiedTypes(); + + if (count($keyType->getConstantScalarTypes()) <= 1) { + $specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create( $array, new NonEmptyArrayType(), $context, $scope, - ); + )); + } + + if ($arrayType->isIterableAtLeastOnce()->no()) { + return $specifiedTypes; } $arrayKeyType = $arrayType->getIterableKeyType(); @@ -82,12 +88,12 @@ public function specifyTypes( $arrayKeyType = TypeCombinator::union($arrayKeyType, $arrayKeyType->toString()); } - $specifiedTypes = $this->typeSpecifier->create( + $specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create( $key, $arrayKeyType, $context, $scope, - ); + )); $arrayDimFetch = new ArrayDimFetch( $array, diff --git a/tests/PHPStan/Analyser/nsrt/bug13674a.php b/tests/PHPStan/Analyser/nsrt/bug13674a.php new file mode 100644 index 0000000000..c1750e89da --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug13674a.php @@ -0,0 +1,29 @@ + $arrayA + * @param list $listA + */ + public function sayHello($arrayA, $listA, int $i): void + { + if (array_key_exists($i, $arrayA)) { + assertType('non-empty-array', $arrayA); + } else { + assertType('array', $arrayA); + } + assertType('array', $arrayA); + + if (array_key_exists($i, $listA)) { + assertType('non-empty-list', $listA); + } else { + assertType('list', $listA); + } + assertType('list', $listA); + } +} diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 471a1a1b01..b18e1aa4f3 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -406,6 +406,16 @@ public function testBug7000(): void ]); } + public function testBug7000b(): void + { + $this->analyse([__DIR__ . '/data/bug-7000b.php'], [ + [ + "Offset 'require'|'require-dev' might not exist on array{require?: array, require-dev?: array}.", + 16, + ], + ]); + } + public function testBug6508(): void { $this->analyse([__DIR__ . '/data/bug-6508.php'], []); diff --git a/tests/PHPStan/Rules/Arrays/data/bug-7000b.php b/tests/PHPStan/Rules/Arrays/data/bug-7000b.php new file mode 100644 index 0000000000..2789458ab3 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-7000b.php @@ -0,0 +1,20 @@ +, require-dev?: array} $composer */ + $composer = array(); + /** @var 'require'|'require-dev' $foo */ + $foo = ''; + foreach (array('require', 'require-dev') as $linkType) { + if (array_key_exists($linkType, $composer)) { + foreach ($composer[$linkType] as $x) {} // should not report error + foreach ($composer[$foo] as $x) {} // should report error. It can be $linkType = 'require', $foo = 'require-dev' + } + } + } +} diff --git a/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php b/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php index 0588be365b..08ee797f08 100644 --- a/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php +++ b/tests/PHPStan/Rules/Arrays/data/slevomat-foreach-array-key-exists-bug.php @@ -15,7 +15,7 @@ public function doFoo(array $percentageIntervals, array $changes): void if ($percentageInterval->isInInterval((float) $changeInPercents)) { $key = $percentageInterval->getFormatted(); if (array_key_exists($key, $intervalResults)) { - assertType('array', $intervalResults); + assertType('non-empty-array', $intervalResults); assertType('array{itemsCount: mixed, interval: mixed}', $intervalResults[$key]); $intervalResults[$key]['itemsCount'] += $itemsCount; assertType('non-empty-array', $intervalResults);