Skip to content

Commit

Permalink
Type declaration for iterable|object (#1313)
Browse files Browse the repository at this point in the history
Signed-off-by: Nathanael Esayeas <nathanael.esayeas@protonmail.com>
  • Loading branch information
ghostwriter committed Jul 29, 2023
2 parents 82f2bde + 5853857 commit c9c2f9f
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 9 deletions.
7 changes: 7 additions & 0 deletions library/Mockery/Reflector.php
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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(
Expand Down
37 changes: 28 additions & 9 deletions tests/Bootstrap.php
@@ -1,4 +1,6 @@
<?php

declare(strict_types=1);
/**
* Mockery
*
Expand Down Expand Up @@ -30,18 +32,18 @@ function isAbsolutePath($path)
return ($path[0] === DIRECTORY_SEPARATOR) || (preg_match($windowsPattern, $path) === 1);
}

$root = realpath(dirname(dirname(__FILE__)));
$composerVendorDirectory = getenv("COMPOSER_VENDOR_DIR") ?: "vendor";
$root = realpath(dirname(dirname(__FILE__)));
$composerVendorDirectory = getenv('COMPOSER_VENDOR_DIR') ?: 'vendor';

if (!isAbsolutePath($composerVendorDirectory)) {
if (! isAbsolutePath($composerVendorDirectory)) {
$composerVendorDirectory = $root . DIRECTORY_SEPARATOR . $composerVendorDirectory;
}

/**
* Check that composer installation was done
*/
$autoloadPath = $composerVendorDirectory . DIRECTORY_SEPARATOR . 'autoload.php';
if (!file_exists($autoloadPath)) {
if (! file_exists($autoloadPath)) {
throw new Exception(
'Please run "php composer.phar install" in root directory '
. 'to setup unit test dependencies before running the tests'
Expand All @@ -65,8 +67,25 @@ function isAbsolutePath($path)
*/
unset($root, $autoloadPath, $hamcrestPath, $composerVendorDirectory);

// $mocksDirectory = __DIR__ . '/_mocks/';
// if (! file_exists($mocksDirectory)) {
// mkdir($mocksDirectory, 0777, true);
// }
// Mockery::setLoader(new Mockery\Loader\RequireLoader($mocksDirectory));
$dev = false;

if ($dev) {
$mocksDirectory = __DIR__ . '/_mocks/';
if (! file_exists($mocksDirectory)) {
mkdir($mocksDirectory, 0777, true);
}

Mockery::setLoader(new Mockery\Loader\RequireLoader($mocksDirectory));

function vdd()
{
var_dump(func_get_args());

$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
if (array_key_exists('file', $trace[1]) && array_key_exists('line', $trace[1])) {
echo sprintf(PHP_EOL . '// dd() called from: %s:%s' . PHP_EOL, $trace[1]['file'], $trace[1]['line']), PHP_EOL;
}

exit(42);
}
}
64 changes: 64 additions & 0 deletions tests/Mockery/ReflectorTest.php
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Mockery\Tests\Mockery;

use Generator;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\Reflector;
use ReflectionClass;

/**
* @coversDefaultClass \Mockery\Reflector
*/
class ReflectorTest extends MockeryTestCase
{
/**
* @covers \Mockery\Reflector::getTypeHint
* @dataProvider typeHintDataProvider
*/
public function testGetTypeHint(string $class, string $expectedTypeHint): void
{
$refClass = new ReflectionClass($class);
$refMethod = $refClass->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
{
}
}
58 changes: 58 additions & 0 deletions tests/Unit/PHP82/Php82LanguageFeaturesTest.php
Expand Up @@ -4,6 +4,7 @@

use Generator;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\Reflector;
use ReflectionType;

/**
Expand Down Expand Up @@ -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
{
}
}

/**
Expand Down

0 comments on commit c9c2f9f

Please sign in to comment.