From 75dc57c95b90be224b170b7392e986cd9fa937b5 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 14 Mar 2026 19:38:03 +0100 Subject: [PATCH 1/2] Reproduce error --- ...CheckedExceptionInMethodThrowsRuleTest.php | 5 +++++ .../Exceptions/data/bug-array-offset.php | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php index aee538052e..ce90cd5ada 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php @@ -70,4 +70,9 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/missing-exception-method-throws.php'], $errors); } + public function testBugArrayOffset(): void + { + $this->analyse([__DIR__ . '/data/bug-array-offset.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php b/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php new file mode 100644 index 0000000000..3b93a92890 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php @@ -0,0 +1,22 @@ +getParameter('shipmonkDeadCode')['debug']['usagesOf']; + } + +} From 5b10196f5e8bb63569aa57dee0300472e2dde10a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 14 Mar 2026 19:58:09 +0100 Subject: [PATCH 2/2] Fix and tests --- .../ExprHandler/ArrayDimFetchHandler.php | 3 ++- src/Analyser/ExprHandler/AssignHandler.php | 2 +- src/Analyser/ExprHandler/IssetHandler.php | 3 ++- src/Analyser/NodeScopeResolver.php | 2 +- ...CheckedExceptionInMethodThrowsRuleTest.php | 19 ++++++++++++++- .../Exceptions/data/bug-array-offset.php | 23 +++++++++++++++++++ 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php index 224bf4142d..beb6d991a0 100644 --- a/src/Analyser/ExprHandler/ArrayDimFetchHandler.php +++ b/src/Analyser/ExprHandler/ArrayDimFetchHandler.php @@ -18,6 +18,7 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -99,7 +100,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { $throwPoints = array_merge($throwPoints, $nodeScopeResolver->processExprNode( $stmt, - new MethodCall($expr->var, 'offsetGet'), + new MethodCall(new TypeExpr($varType), 'offsetGet'), $scope, $storage, new NoopNodeCallback(), diff --git a/src/Analyser/ExprHandler/AssignHandler.php b/src/Analyser/ExprHandler/AssignHandler.php index 284ec47619..76c40b1ae3 100644 --- a/src/Analyser/ExprHandler/AssignHandler.php +++ b/src/Analyser/ExprHandler/AssignHandler.php @@ -478,7 +478,7 @@ public function processAssignVar( ) { $throwPoints = array_merge($throwPoints, $nodeScopeResolver->processExprNode( $stmt, - new MethodCall($originalVar->var, 'offsetSet'), + new MethodCall(new TypeExpr($setVarType), 'offsetSet'), $scope, $storage, new NoopNodeCallback(), diff --git a/src/Analyser/ExprHandler/IssetHandler.php b/src/Analyser/ExprHandler/IssetHandler.php index 581ee9af78..d794bea909 100644 --- a/src/Analyser/ExprHandler/IssetHandler.php +++ b/src/Analyser/ExprHandler/IssetHandler.php @@ -17,6 +17,7 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\NoopNodeCallback; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\ObjectType; @@ -101,7 +102,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $throwPoints = array_merge($throwPoints, $nodeScopeResolver->processExprNode( $stmt, - new MethodCall($var->var, 'offsetExists'), + new MethodCall(new TypeExpr($varType), 'offsetExists'), $scope, $storage, new NoopNodeCallback(), diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8a112cda54..79b05cd0f8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2054,7 +2054,7 @@ public function processStmtNode( if (!$varType->isArray()->yes() && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($varType)->no()) { $throwPoints = array_merge($throwPoints, $this->processExprNode( $stmt, - new MethodCall($var->var, 'offsetUnset'), + new MethodCall(new TypeExpr($varType), 'offsetUnset'), $scope, $storage, new NoopNodeCallback(), diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php index ce90cd5ada..c3e548bf0e 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInMethodThrowsRuleTest.php @@ -72,7 +72,24 @@ public function testRule(): void public function testBugArrayOffset(): void { - $this->analyse([__DIR__ . '/data/bug-array-offset.php'], []); + $this->analyse([__DIR__ . '/data/bug-array-offset.php'], [ + [ + "Method BugArrayOffset\Foo::__construct() throws checked exception BugArrayOffset\ParameterNotFoundException but it's missing from the PHPDoc @throws tag.", + 19, + ], + [ + "Method BugArrayOffset\Foo2::__construct() throws checked exception BugArrayOffset\ParameterNotFoundException but it's missing from the PHPDoc @throws tag.", + 27, + ], + [ + "Method BugArrayOffset\Foo3::__construct() throws checked exception BugArrayOffset\ParameterNotFoundException but it's missing from the PHPDoc @throws tag.", + 35, + ], + [ + "Method BugArrayOffset\Foo4::__construct() throws checked exception BugArrayOffset\ParameterNotFoundException but it's missing from the PHPDoc @throws tag.", + 43, + ], + ]); } } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php b/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php index 3b93a92890..35bf9543e8 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-array-offset.php @@ -18,5 +18,28 @@ public function __construct(Container $container) { $container->getParameter('shipmonkDeadCode')['debug']['usagesOf']; } +} +class Foo2 +{ + public function __construct(Container $container) + { + isset($container->getParameter('shipmonkDeadCode')['debug']['usagesOf']); + } +} + +class Foo3 +{ + public function __construct(Container $container) + { + unset($container->getParameter('shipmonkDeadCode')['debug']['usagesOf']); + } +} + +class Foo4 +{ + public function __construct(Container $container) + { + $container->getParameter('shipmonkDeadCode')['debug']['usagesOf'] = 42; + } }