From 7de329c63c1a311ef09c436af8b568f3989a23c8 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 4 Aug 2025 10:46:15 +0200 Subject: [PATCH] ForbidCheckedExceptionInCallableRuleTest: Split test methods into individual test cases. To support PHPStan's enhanced dead code detection in closures (phpstan/phpstan-src#4148). --- .../code.php | 134 +++++++++++++++++- 1 file changed, 127 insertions(+), 7 deletions(-) diff --git a/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php b/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php index a9ccb48..786ba72 100644 --- a/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php +++ b/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php @@ -41,14 +41,23 @@ public function allowThrowInBaseClass(callable $callable): void class FirstClassCallableTest extends BaseCallableTest { - public function testDeclarations(): void + public function testDeclarations1(): void { $this->noop(...); + } + public function testDeclarations2(): void + { $this->throws(...); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! + } + public function testDeclarations3(): void + { // $this?->throws(...); // https://github.com/phpstan/phpstan/issues/9746 + } + public function testDeclarations4(): void + { throwing_function(...); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! } @@ -62,27 +71,51 @@ public function testExplicitExecution2(): void (throwing_function(...))(); } - public function testPassedCallbacks(): void + public function testPassedCallbacksA1(): void { $this->immediateThrow(null, $this->throws(...)); + } + public function testPassedCallbacksA2(): void + { array_map($this->throws(...), []); + } + public function testPassedCallbacksA3(): void + { array_map(throwing_function(...), []); + } + public function testPassedCallbacksA4(): void + { $this->allowThrow(42, $this->throws(...)); + } + public function testPassedCallbacksA5(): void + { $this->allowThrow(42, throwing_function(...)); + } + public function testPassedCallbacksA6(): void + { $this->immediateThrow( $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! function () {}, ); + } + public function testPassedCallbacksA7(): void + { $this->allowThrowInBaseClass(throwing_function(...)); + } + public function testPassedCallbacksA8(): void + { $this->allowThrowInInterface(throwing_function(...)); + } + public function testPassedCallbacksA9(): void + { $this->denied($this->throws(...)); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! } @@ -124,20 +157,29 @@ public function allowThrow(int $dummy, callable $callable): void class ClosureTest extends BaseCallableTest { - public function testDeclarations(): void + public function testDeclarationsB1(): void { $fn = function () { throw new CheckedException(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }; + } + public function testDeclarationsB2(): void + { $fn2 = function () { $this->throws(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }; + } + public function testDeclarationsB3(): void + { $fn3 = function () { $this->noop(); // implicit throw is ignored }; + } + public function testDeclarationsB4(): void + { $fn4 = function (callable $c) { $c(); // implicit throw is ignored (https://github.com/phpstan/phpstan/issues/9779) }; @@ -150,58 +192,91 @@ public function testExplicitExecution(): void })(); } - public function testPassedCallbacks(): void + public function testPassedCallbacks1(): void { $this->immediateThrow(function () { throw new CheckedException(); }); + } + public function testPassedCallbacks2(): void + { $self = $this; // self is unknown variable in scope of the closure $self->immediateThrow(function () { throw new CheckedException(); }); + } + public function testPassedCallbacks3(): void + { array_map(function () { throw new CheckedException(); }, []); + } + public function testPassedCallbacks4(): void + { array_map(function () { $this->throws(); }, []); + } + public function testPassedCallbacks5(): void + { $this->allowThrow(function () { $this->throws(); }); + } + public function testPassedCallbacks6(): void + { $this->immediateThrow( function () {}, function () { throw new CheckedException(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }, ); + } + public function testPassedCallbacks7(): void + { $this->immediateThrow( denied: function () {}, ); + } + public function testPassedCallbacks8(): void + { $this->immediateThrow( denied: function () { throw new CheckedException(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }, ); + } + public function testPassedCallbacks9(): void + { $this->allowThrowInBaseClass(function () { $this->throws(); }); + } + public function testPassedCallbacks10(): void + { $this->allowThrowInInterface(function () { $this->throws(); }); + } + public function testPassedCallbacks11(): void + { $this->denied(function () { throw new CheckedException(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }); + } + public function testPassedCallbacks12(): void + { $this?->denied(function () { $this->throws(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in closure! }); @@ -250,14 +325,23 @@ public function __construct($callable) new self(fn () => throw new CheckedException()); } - public function testDeclarations(): void + public function testDeclarationsC1(): void { $fn = fn () => throw new CheckedException(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in arrow function! + } + public function testDeclarationsC2(): void + { $fn2 = fn () => $this->throws(); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in arrow function! + } + public function testDeclarationsC3(): void + { $fn3 = fn () => $this->noop(); // implicit throw is ignored + } + public function testDeclarationsC4(): void + { $fn4 = fn (callable $c) => $c(); // implicit throw is ignored (https://github.com/phpstan/phpstan/issues/9779) } @@ -266,22 +350,43 @@ public function testExplicitExecution(): void (fn () => throw new CheckedException())(); } - public function testPassedCallbacks(): void + public function testPassedCallbacksC1(): void { $this->immediateThrow(fn () => throw new CheckedException()); + } + public function testPassedCallbacksC2(): void + { array_map(fn () => throw new CheckedException(), []); + } + public function testPassedCallbacksC3(): void + { array_map(fn () => $this->throws(), []); + } + public function testPassedCallbacksC4(): void + { $this->allowThrow(fn () => $this->throws()); + } + public function testPassedCallbacksC5(): void + { $this->allowThrowInBaseClass(fn () => $this->throws()); + } + public function testPassedCallbacksC6(): void + { $this->allowThrowInInterface(fn () => $this->throws()); + } + public function testPassedCallbacksC7(): void + { $this->denied(fn () => throw new CheckedException()); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in arrow function! + } + public function testPassedCallbacksC8(): void + { $this?->denied(fn () => throw new CheckedException()); // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in arrow function! } @@ -323,7 +428,7 @@ public function allowThrow(callable $callable): void class ArgumentSwappingTest { - public function test() + public function testD1(): void { $this->call( $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! @@ -331,32 +436,47 @@ public function test() $this->throws(...), $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! ); + } + public function testD2(): void + { $this->call( second: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! first: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! third: $this->throws(...), ); + } + public function testD3(): void + { $this->call( forth: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! first: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! ); + } + public function testD4(): void + { $this->call( $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! forth: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! second: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! third: $this->throws(...), ); + } + public function testD5(): void + { $this->call( $this->noop(...), $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! forth: $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable! third: $this->noop(...), ); + } + public function testD6(): void + { // this is not yet supported, the rule do not see this as argument pass $this->call(... [ 'third' => $this->throws(...), // error: Throwing checked exception ForbidCheckedExceptionInCallableRule\CheckedException in first-class-callable!