Skip to content

Commit

Permalink
[feature] support intersection type support
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Aug 31, 2022
1 parent 319fb96 commit d8b7878
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
code-coverage:
uses: zenstruck/.github/.github/workflows/php-coverage-codecov.yml@main
with:
php: 8
php: 8.1
phpunit: simple-phpunit

composer-validate:
Expand Down
36 changes: 32 additions & 4 deletions src/Callback/Argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,18 @@ public function __construct(\ReflectionParameter $parameter)
$this->reflectionType = $parameter->getType();
}

public function __toString(): string
{
return (string) $this->type();
}

public function type(): ?string
{
if (isset($this->type)) {
return $this->type;
}

return $this->type = $this->hasType() ? \implode('|', $this->types()) : null;
return $this->type = $this->hasType() ? \implode($this->isIntersectionType() ? '&' : '|', $this->types()) : null;
}

/**
Expand Down Expand Up @@ -101,12 +106,22 @@ function(\ReflectionNamedType $type) {

public function hasType(): bool
{
return !empty($this->types());
return (bool) $this->reflectionType;
}

public function isNamedType(): bool
{
return $this->reflectionType instanceof \ReflectionNamedType;
}

public function isUnionType(): bool
{
return \count($this->types()) > 1;
return $this->reflectionType instanceof \ReflectionUnionType;
}

public function isIntersectionType(): bool
{
return $this->reflectionType instanceof \ReflectionIntersectionType;
}

public function isOptional(): bool
Expand All @@ -129,11 +144,24 @@ public function defaultValue()
*/
public function supports(string $type, int $options = self::EXACT|self::COVARIANCE): bool
{
if (!$this->hasType()) {
if (!$this->reflectionType) {
// no type-hint so any type is supported
return true;
}

if ($this->reflectionType instanceof \ReflectionIntersectionType) {
foreach ($this->reflectionType->getTypes() as $refType) {
$arg = clone $this;
$arg->reflectionType = $refType;

if (!$arg->supports($type)) {
return false;
}
}

return true;
}

if ('null' === \mb_strtolower($type) && $this->parameter->allowsNull()) {
return true;
}
Expand Down
105 changes: 105 additions & 0 deletions tests/ArgumentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace Zenstruck\Callback\Tests;

use PHPUnit\Framework\TestCase;
use Zenstruck\Callback;

/**
* @author Kevin Bond <[email protected]>
*/
final class ArgumentTest extends TestCase
{
/**
* @test
* @requires PHP >= 8.0
*/
public function union_type(): void
{
eval('$callback = fn(int|string $arg) => null;');
$arg = Callback::createFor($callback)->argument(0);

$this->assertSame('string|int', $arg->type());
$this->assertSame('string|int', (string) $arg);
$this->assertTrue($arg->hasType());
$this->assertFalse($arg->isNamedType());
$this->assertTrue($arg->isUnionType());
$this->assertFalse($arg->isIntersectionType());
}

/**
* @test
*/
public function named_type(): void
{
$arg = Callback::createFor(function(string $foo) {})->argument(0);

$this->assertSame('string', $arg->type());
$this->assertSame('string', (string) $arg);
$this->assertTrue($arg->hasType());
$this->assertTrue($arg->isNamedType());
$this->assertFalse($arg->isUnionType());
$this->assertFalse($arg->isIntersectionType());
}

/**
* @test
*/
public function no_type(): void
{
$arg = Callback::createFor(function($foo) {})->argument(0);

$this->assertNull($arg->type());
$this->assertSame('', (string) $arg);
$this->assertFalse($arg->hasType());
$this->assertFalse($arg->isNamedType());
$this->assertFalse($arg->isUnionType());
$this->assertFalse($arg->isIntersectionType());
}

/**
* @test
* @requires PHP >= 8.1
*/
public function intersection_type(): void
{
eval('$callback = fn(\Countable&\Iterator $arg) => null;');
$arg = Callback::createFor($callback)->argument(0);

$this->assertSame('Countable&Iterator', $arg->type());
$this->assertSame('Countable&Iterator', (string) $arg);
$this->assertTrue($arg->hasType());
$this->assertFalse($arg->isNamedType());
$this->assertFalse($arg->isUnionType());
$this->assertTrue($arg->isIntersectionType());
}

/**
* @test
* @requires PHP >= 8.1
*/
public function supports_intersection(): void
{
eval('$callback = fn(\Countable&\IteratorAggregate $arg) => null;');
$arg = Callback::createFor($callback)->argument(0);

$this->assertFalse($arg->supports('string'));
$this->assertFalse($arg->supports(\get_class(new class() implements \Countable {
public function count(): int
{
return 0;
}
})));
$this->assertTrue($arg->supports(\get_class(new class() implements \Countable, \IteratorAggregate {
public function count(): int
{
return 0;
}

public function getIterator(): \Traversable
{
return new \ArrayIterator();
}
})));
}
}

0 comments on commit d8b7878

Please sign in to comment.