diff --git a/src/ProxyManager/Generator/Util/ProxiedMethodReturnExpression.php b/src/ProxyManager/Generator/Util/ProxiedMethodReturnExpression.php index f931e2dc..481a1ab2 100644 --- a/src/ProxyManager/Generator/Util/ProxiedMethodReturnExpression.php +++ b/src/ProxyManager/Generator/Util/ProxiedMethodReturnExpression.php @@ -24,6 +24,10 @@ public static function generate(string $returnedValueExpression, ?ReflectionMeth return $returnedValueExpression . ";\nreturn;"; } + if ($originalReturnType instanceof ReflectionNamedType && $originalReturnType->getName() === 'never') { + return $returnedValueExpression . ';'; + } + return 'return ' . $returnedValueExpression . ';'; } } diff --git a/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php b/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php index db3892af..7d086959 100644 --- a/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php +++ b/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php @@ -32,10 +32,12 @@ use ProxyManagerTestAsset\ClassWithPublicStringTypedProperty; use ProxyManagerTestAsset\ClassWithSelfHint; use ProxyManagerTestAsset\EmptyClass; +use ProxyManagerTestAsset\NeverCounter; use ProxyManagerTestAsset\OtherObjectAccessClass; use ProxyManagerTestAsset\VoidCounter; use ReflectionClass; use ReflectionProperty; +use RuntimeException; use stdClass; use function array_key_exists; @@ -1547,7 +1549,46 @@ static function ( self::assertSame($initialCounter + $increment, $proxy->counter); } - private static function isPropertyInitialized($object, ReflectionProperty $property): bool + /** + * @requires PHP 8.1 + * + * @psalm-suppress UnusedVariable this method uses by-ref assignment of properties, and isn't recognized by static analysis + * @psalm-suppress UndefinedClass Class, interface or enum named never does not exist + */ + public function testWillExecuteLogicInANeverMethod(): void + { + $initialCounter = random_int(10, 1000); + + $proxy = (new LazyLoadingGhostFactory())->createProxy( + NeverCounter::class, + static function ( + LazyLoadingInterface $proxy, + string $method, + array $params, + ?Closure & $initializer, + array $properties + ) use ($initialCounter): bool { + $initializer = null; + + $properties['counter'] = $initialCounter; + + return true; + } + ); + + $increment = random_int(1001, 10000); + + try { + $proxy->increment($increment); + $this->fail('RuntimeException expected'); + } catch (RuntimeException $e) { + // Nothing to do + } + + self::assertSame($initialCounter + $increment, $proxy->counter); + } + + private static function isPropertyInitialized(object $object, ReflectionProperty $property): bool { return array_key_exists( ($property->isProtected() ? "\0*\0" : '') diff --git a/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php b/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php index 0bee6630..a840c205 100644 --- a/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php +++ b/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php @@ -27,10 +27,12 @@ use ProxyManagerTestAsset\ClassWithPublicStringTypedProperty; use ProxyManagerTestAsset\ClassWithSelfHint; use ProxyManagerTestAsset\EmptyClass; +use ProxyManagerTestAsset\NeverCounter; use ProxyManagerTestAsset\OtherObjectAccessClass; use ProxyManagerTestAsset\VoidCounter; use ReflectionClass; use ReflectionProperty; +use RuntimeException; use stdClass; use function array_values; @@ -695,6 +697,30 @@ public function testWillExecuteLogicInAVoidMethod(): void self::assertSame($increment, $proxy->counter); } + /** + * @requires PHP 8.1 + * + * @psalm-suppress UndefinedClass Class, interface or enum named never does not exist + */ + public function testWillExecuteLogicInANeverMethod(): void + { + $proxy = (new LazyLoadingValueHolderFactory())->createProxy( + NeverCounter::class, + $this->createInitializer(NeverCounter::class, new NeverCounter()) + ); + + $increment = random_int(100, 1000); + + try { + $proxy->increment($increment); + $this->fail('RuntimeException expected'); + } catch (RuntimeException $e) { + // Do nothing + } + + self::assertSame($increment, $proxy->counter); + } + public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope(): Generator { foreach ((new ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) { diff --git a/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php b/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php index b1b860c7..bcf37bde 100644 --- a/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php +++ b/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php @@ -18,9 +18,11 @@ use ProxyManagerTestAsset\ClassWithPublicStringTypedProperty; use ProxyManagerTestAsset\ClassWithSelfHint; use ProxyManagerTestAsset\EmptyClass; +use ProxyManagerTestAsset\NeverCounter; use ProxyManagerTestAsset\VoidCounter; use ReflectionProperty; use stdClass; +use TypeError; use function array_values; use function assert; @@ -126,6 +128,20 @@ public function testPropertyUnset(NullObjectInterface $proxy, string $publicProp self::assertFalse(isset($proxy->$publicProperty)); } + /** + * @requires PHP 8.1 + */ + public function testNeverReturningMethodCalls(): void + { + $proxy = (new NullObjectFactory())->createProxy(NeverCounter::class); + $method = [$proxy, 'increment']; + + self::assertIsCallable($method); + + $this->expectException(TypeError::class); + $method(random_int(10, 1000)); + } + /** * Generates a list of object | invoked method | parameters | expected result * diff --git a/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php b/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php index f277784f..d3fb41dc 100644 --- a/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php +++ b/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php @@ -15,6 +15,7 @@ use ProxyManagerTestAsset\ClassWithPublicStringNullableTypedProperty; use ProxyManagerTestAsset\ClassWithPublicStringTypedProperty; use ProxyManagerTestAsset\ClassWithSelfHint; +use ProxyManagerTestAsset\NeverCounter; use ProxyManagerTestAsset\OtherObjectAccessClass; use ProxyManagerTestAsset\RemoteProxy\BazServiceInterface; use ProxyManagerTestAsset\RemoteProxy\Foo; @@ -26,6 +27,7 @@ use ProxyManagerTestAsset\RemoteProxy\VariadicArgumentsServiceInterface; use ProxyManagerTestAsset\VoidCounter; use ReflectionClass; +use RuntimeException; use stdClass; use function assert; @@ -403,6 +405,30 @@ public function testWillExecuteLogicInAVoidMethod(): void $proxy->increment($increment); } + /** + * @requires PHP 8.1 + */ + public function testWillExecuteLogicInANeverMethod(): void + { + $adapter = $this->createMock(AdapterInterface::class); + + $increment = random_int(10, 1000); + + $adapter + ->expects(self::once()) + ->method('call') + ->with(NeverCounter::class, 'increment', [$increment]) + ->willThrowException(new RuntimeException()); + + $proxy = clone (new RemoteObjectFactory($adapter)) + ->createProxy(NeverCounter::class); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage(''); + + $proxy->increment($increment); + } + public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope(): Generator { foreach ((new ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) { diff --git a/tests/ProxyManagerTestAsset/NeverCounter.php b/tests/ProxyManagerTestAsset/NeverCounter.php new file mode 100644 index 00000000..ca94f66c --- /dev/null +++ b/tests/ProxyManagerTestAsset/NeverCounter.php @@ -0,0 +1,21 @@ +counter += $amount; + throw new RuntimeException(); + } +}