Skip to content

Commit 9260927

Browse files
ondrejmirtesclaude
andcommitted
Add array_chunk() throw type extension
PhpStorm stubs now declare @throws \ValueError on array_chunk(), which made every call an explicit throw point. It is thrown only when $length is less than 1, even for an empty array, and PHP 7 only warns. The decision reads the native type of $length, so a PHPDoc-only positive-int keeps the ValueError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dhb4ssXuKVWcVBcNFpsdn7
1 parent 084c97e commit 9260927

3 files changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Php\PhpVersion;
9+
use PHPStan\Reflection\FunctionReflection;
10+
use PHPStan\Type\DynamicFunctionThrowTypeExtension;
11+
use PHPStan\Type\IntegerRangeType;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\VoidType;
14+
use function count;
15+
16+
/**
17+
* array_chunk() throws ValueError only when $length is less than 1, even for an empty array.
18+
*/
19+
#[AutowiredService]
20+
final class ArrayChunkFunctionThrowTypeExtension implements DynamicFunctionThrowTypeExtension
21+
{
22+
23+
public function __construct(private PhpVersion $phpVersion)
24+
{
25+
}
26+
27+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
28+
{
29+
return $functionReflection->getName() === 'array_chunk';
30+
}
31+
32+
public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, Scope $scope): ?Type
33+
{
34+
if (!$this->phpVersion->throwsValueErrorForInternalFunctions()) {
35+
return new VoidType();
36+
}
37+
38+
$args = $funcCall->getArgs();
39+
foreach ($args as $arg) {
40+
if ($arg->unpack || $arg->name !== null) {
41+
return $functionReflection->getThrowType();
42+
}
43+
}
44+
45+
if (count($args) < 2) {
46+
return $functionReflection->getThrowType();
47+
}
48+
49+
if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($scope->getNativeType($args[1]->value))->yes()) {
50+
return new VoidType();
51+
}
52+
53+
return $functionReflection->getThrowType();
54+
}
55+
56+
}

‎tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,56 @@ public function testMinMaxThrowTypeBeforePhp8(): void
957957
]);
958958
}
959959

960+
#[RequiresPhp('>= 8.0.0')]
961+
public function testArrayChunkThrowType(): void
962+
{
963+
$this->analyse([__DIR__ . '/data/array-chunk-throw-type.php'], [
964+
[
965+
'Dead catch - ValueError is never thrown in the try block.',
966+
15,
967+
],
968+
[
969+
'Dead catch - ValueError is never thrown in the try block.',
970+
21,
971+
],
972+
[
973+
'Dead catch - ValueError is never thrown in the try block.',
974+
46,
975+
],
976+
]);
977+
}
978+
979+
#[RequiresPhp('< 8.0.0')]
980+
public function testArrayChunkThrowTypeBeforePhp8(): void
981+
{
982+
$this->analyse([__DIR__ . '/data/array-chunk-throw-type.php'], [
983+
[
984+
'Dead catch - ValueError is never thrown in the try block.',
985+
15,
986+
],
987+
[
988+
'Dead catch - ValueError is never thrown in the try block.',
989+
21,
990+
],
991+
[
992+
'Dead catch - ValueError is never thrown in the try block.',
993+
27,
994+
],
995+
[
996+
'Dead catch - ValueError is never thrown in the try block.',
997+
33,
998+
],
999+
[
1000+
'Dead catch - ValueError is never thrown in the try block.',
1001+
39,
1002+
],
1003+
[
1004+
'Dead catch - ValueError is never thrown in the try block.',
1005+
46,
1006+
],
1007+
]);
1008+
}
1009+
9601010
#[RequiresPhp('>= 8.0.0')]
9611011
public function testPrintfThrowType(): void
9621012
{
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace ArrayChunkThrowType;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param positive-int $phpDocLength
10+
*/
11+
public function doFoo(array $a, int $length, int $phpDocLength): void
12+
{
13+
try {
14+
$b = array_chunk($a, 3);
15+
} catch (\ValueError $e) {
16+
17+
}
18+
19+
try {
20+
$b = array_chunk($a, 2, true);
21+
} catch (\ValueError $e) {
22+
23+
}
24+
25+
try {
26+
$b = array_chunk($a, 0);
27+
} catch (\ValueError $e) {
28+
29+
}
30+
31+
try {
32+
$b = array_chunk($a, $length);
33+
} catch (\ValueError $e) {
34+
35+
}
36+
37+
try {
38+
$b = array_chunk($a, $phpDocLength);
39+
} catch (\ValueError $e) {
40+
41+
}
42+
43+
if ($length > 0) {
44+
try {
45+
$b = array_chunk($a, $length);
46+
} catch (\ValueError $e) {
47+
48+
}
49+
}
50+
}
51+
52+
}

0 commit comments

Comments
 (0)