diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2db90a78a2..f806ea52c9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1758,13 +1758,13 @@ parameters: - rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.' identifier: phpstanApi.instanceofType - count: 2 + count: 1 path: src/Type/TypeUtils.php - rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. identifier: phpstanApi.instanceofType - count: 3 + count: 4 path: src/Type/TypeUtils.php - diff --git a/src/Internal/CombinationsHelper.php b/src/Internal/CombinationsHelper.php index 48749cac4e..303b8b0a69 100644 --- a/src/Internal/CombinationsHelper.php +++ b/src/Internal/CombinationsHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Internal; +use Traversable; use function array_shift; final class CombinationsHelper @@ -9,7 +10,7 @@ final class CombinationsHelper /** * @param array> $arrays - * @return iterable> + * @return Traversable> */ public static function combinations(array $arrays): iterable { diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 4cec2bf409..c334db96b3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -216,6 +216,8 @@ public function getAllArrays(): array } else { $optionalKeysCombinations = [ [], + array_slice($this->optionalKeys, 0, 1, true), + array_slice($this->optionalKeys, -1, 1, true), $this->optionalKeys, ]; } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index d649cfe0a7..8bcba55c9e 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Internal\CombinationsHelper; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; @@ -9,7 +10,10 @@ use PHPStan\Type\Generic\TemplateBenevolentUnionType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateUnionType; +use function array_filter; +use function array_map; use function array_merge; +use function iterator_to_array; /** * @api @@ -133,17 +137,28 @@ public static function flattenTypes(Type $type): array return $type->getAllArrays(); } + if ($type instanceof IntersectionType && $type->isConstantArray()->yes()) { + $newTypes = []; + foreach ($type->getTypes() as $innerType) { + $newTypes[] = self::flattenTypes($innerType); + } + + return array_filter( + array_map( + static fn (array $types): Type => TypeCombinator::intersect(...$types), + iterator_to_array(CombinationsHelper::combinations($newTypes)), + ), + static fn (Type $type): bool => !$type instanceof NeverType, + ); + } + if ($type instanceof UnionType) { $types = []; foreach ($type->getTypes() as $innerType) { - if ($innerType instanceof ConstantArrayType) { - foreach ($innerType->getAllArrays() as $array) { - $types[] = $array; - } - continue; + $flattenTypes = self::flattenTypes($innerType); + foreach ($flattenTypes as $flattenType) { + $types[] = $flattenType; } - - $types[] = $innerType; } return $types; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e7f26496b8..59c2909771 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -983,7 +983,7 @@ public function testBug7581(): void public function testBug7903(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-7903.php'); - $this->assertCount(23, $errors); + $this->assertCount(28, $errors); } public function testBug7901(): void diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 471a1a1b01..4aa49f16d9 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -385,11 +385,6 @@ public function testBug4747(): void $this->analyse([__DIR__ . '/data/bug-4747.php'], []); } - public function testBug6379(): void - { - $this->analyse([__DIR__ . '/data/bug-6379.php'], []); - } - #[RequiresPhp('>= 8.0')] public function testBug4885(): void { @@ -919,13 +914,6 @@ public function testBug4809(): void $this->analyse([__DIR__ . '/data/bug-4809.php'], []); } - public function testBug11602(): void - { - $this->reportPossiblyNonexistentGeneralArrayOffset = true; - - $this->analyse([__DIR__ . '/data/bug-11602.php'], []); - } - public function testBug12593(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; @@ -1106,6 +1094,28 @@ public function testPR4385Bis(): void $this->analyse([__DIR__ . '/data/pr-4385-bis.php'], []); } + public function testBug7143(): void + { + $this->analyse([__DIR__ . '/data/bug-7143.php'], [ + [ + "Offset 'foo' might not exist on non-empty-array{foo?: string, bar?: string}.", + 12, + ], + [ + "Offset 'bar' might not exist on non-empty-array{foo?: string, bar?: string}.", + 13, + ], + [ + "Offset 'foo' might not exist on non-empty-array{foo?: string, bar?: string, 1?: 1, 2?: 2, 3?: 3, 4?: 4, 5?: 5, 6?: 6, ...}.", + 21, + ], + [ + "Offset 'bar' might not exist on non-empty-array{foo?: string, bar?: string, 1?: 1, 2?: 2, 3?: 3, 4?: 4, 5?: 5, 6?: 6, ...}.", + 22, + ], + ]); + } + public function testBug12805(): void { $this->reportPossiblyNonexistentGeneralArrayOffset = true; diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11602.php b/tests/PHPStan/Rules/Arrays/data/bug-11602.php deleted file mode 100644 index 4e1252e5b4..0000000000 --- a/tests/PHPStan/Rules/Arrays/data/bug-11602.php +++ /dev/null @@ -1,23 +0,0 @@ -