diff --git a/library/Mockery/Reflector.php b/library/Mockery/Reflector.php index f6b194ae8..0117cbeec 100644 --- a/library/Mockery/Reflector.php +++ b/library/Mockery/Reflector.php @@ -15,6 +15,8 @@ */ class Reflector { + private const TRAVERSABLE_ARRAY = ['\Traversable', 'array']; + private const ITERABLE = ['iterable']; /** * Determine if the parameter is typed as an array. * @@ -249,6 +251,11 @@ private static function getTypeFromReflectionType(\ReflectionType $type, \Reflec $type->getTypes() ); + $intersect = array_intersect(self::TRAVERSABLE_ARRAY, $types); + if (self::TRAVERSABLE_ARRAY === $intersect) { + $types = self::ITERABLE + array_diff($types, self::TRAVERSABLE_ARRAY); + } + return implode( '|', array_map( diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index cf5273e1a..e6ac5574d 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -1,4 +1,6 @@ getMethods()[0]; + $refParam = $refMethod->getParameters()[0]; + + self::assertSame( + $expectedTypeHint, + Reflector::getTypeHint($refParam) + ); + } + + public static function typeHintDataProvider(): Generator + { + $isPHPLessThan8 = \PHP_VERSION_ID < 80000; + yield from [ + [ParentClass::class, '\Mockery\Tests\Mockery\ParentClass'], + [ChildClass::class, '\Mockery\Tests\Mockery\ParentClass'], + NullableObject::class => [NullableObject::class, $isPHPLessThan8 ? '?object' : 'object|null'], + ]; + } + +} + +class ParentClass +{ + public function __invoke(self $arg): void + { + } +} + +class ChildClass extends ParentClass +{ + public function __invoke(parent $arg): void + { + } +} + +class NullableObject +{ + public function __invoke(?object $arg): void + { + } +} diff --git a/tests/Unit/PHP82/Php82LanguageFeaturesTest.php b/tests/Unit/PHP82/Php82LanguageFeaturesTest.php index ca195269f..260b47942 100644 --- a/tests/Unit/PHP82/Php82LanguageFeaturesTest.php +++ b/tests/Unit/PHP82/Php82LanguageFeaturesTest.php @@ -4,6 +4,7 @@ use Generator; use Mockery\Adapter\Phpunit\MockeryTestCase; +use Mockery\Reflector; use ReflectionType; /** @@ -82,6 +83,63 @@ public static function returnCoVarianceDataProvider(): Generator yield $fixture => [$fixture]; } } + + public function testTypeHintIterableObject(): void + { + $refClass = new \ReflectionClass(IterableObject::class); + $refMethod = $refClass->getMethods()[0]; + $refParam = $refMethod->getParameters()[0]; + + self::assertSame( + 'iterable|object', + Reflector::getTypeHint($refParam) + ); + } + + public function testTypeHintIterableObjectString(): void + { + $refClass = new \ReflectionClass(IterableObjectString::class); + $refMethod = $refClass->getMethods()[0]; + $refParam = $refMethod->getParameters()[0]; + + self::assertSame( + 'iterable|object|string', + Reflector::getTypeHint($refParam) + ); + } + + public function testTypeHintIIterableStdClassString(): void + { + $refClass = new \ReflectionClass(IterableStdClassString::class); + $refMethod = $refClass->getMethods()[0]; + $refParam = $refMethod->getParameters()[0]; + + self::assertSame( + 'iterable|\stdClass|string', + Reflector::getTypeHint($refParam) + ); + } +} + +class IterableObject +{ + public function __invoke(iterable|object $arg): void + { + } +} + +class IterableObjectString +{ + public function __invoke(iterable|object|string $arg): void + { + } +} + +class IterableStdClassString +{ + public function __invoke(iterable|\stdClass|string $arg): void + { + } } /**