Skip to content

Commit

Permalink
Allow to Assert::isInstanceOf() to work with generic and anonymous …
Browse files Browse the repository at this point in the history
…class strings
  • Loading branch information
axlon authored and ondrejmirtes committed Jul 5, 2024
1 parent 983a1a7 commit 923bd58
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 22 deletions.
41 changes: 23 additions & 18 deletions src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use ReflectionObject;
use Traversable;
use function array_key_exists;
use function array_map;
use function array_reduce;
use function array_shift;
use function count;
Expand Down Expand Up @@ -407,33 +408,37 @@ private function getExpressionResolvers(): array
},
'isInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
$classType = $scope->getType($class->value);
$classNameStrings = $classType->getConstantStrings();
if (count($classNameStrings) !== 1) {
$classNames = $classType->getObjectClassNames();
if (count($classNames) === 1) {
return new Instanceof_(
$expr->value,
new Name($classNames[0])
);
}
return null;
$classNames = $classType->getObjectTypeOrClassStringObjectType()->getObjectClassNames();

if (count($classNames) !== 0) {
return self::implodeExpr(array_map(static function (string $className) use ($expr): Expr {
return new Instanceof_($expr->value, new Name($className));
}, $classNames), BooleanOr::class);
}

return new Instanceof_(
$expr->value,
new Name($classNameStrings[0]->getValue())
return new FuncCall(
new Name('is_object'),
[$expr]
);
},
'isInstanceOfAny' => function (Scope $scope, Arg $expr, Arg $classes): ?Expr {
return self::buildAnyOfExpr($scope, $expr, $classes, $this->resolvers['isInstanceOf']);
},
'notInstanceOf' => function (Scope $scope, Arg $expr, Arg $class): ?Expr {
$expr = $this->resolvers['isInstanceOf']($scope, $expr, $class);
if ($expr === null) {
return null;
'notInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
$classType = $scope->getType($class->value);
$classNames = $classType->getObjectTypeOrClassStringObjectType()->getObjectClassNames();

if (count($classNames) !== 0) {
$result = self::implodeExpr(array_map(static function (string $className) use ($expr): Expr {
return new Instanceof_($expr->value, new Name($className));
}, $classNames), BooleanOr::class);

if ($result !== null) {
return new BooleanNot($result);
}
}

return new BooleanNot($expr);
return null;
},
'isAOf' => static function (Scope $scope, Arg $expr, Arg $class): Expr {
$exprType = $scope->getType($expr->value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-18.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-117.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-150.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-183.php');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public function testExtension(): void
'Call to static method Webmozart\Assert\Assert::implementsInterface() with mixed and \'WebmozartAssertImpossibleCheck\\\Foo\' will always evaluate to false.',
111,
],
[
'Call to static method Webmozart\Assert\Assert::isInstanceOf() with Exception and class-string<Exception> will always evaluate to true.',
119,
],
]);
}

Expand Down
62 changes: 62 additions & 0 deletions tests/Type/WebMozartAssert/data/bug-183.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

use Webmozart\Assert\Assert;
use function PHPStan\Testing\assertType;

function assertInstanceOfWithString($value, string $className): void
{
Assert::isInstanceOf($value, $className);
assertType('object', $value);
}

/**
* @param class-string $className
*/
function assertInstanceOfWithClassString($value, string $className): void
{
Assert::isInstanceOf($value, $className);
assertType('object', $value);
}

/**
* @param class-string<\Bug183> $className
*/
function assertInstanceOfWithGenericClassString($value, string $className): void
{
Assert::isInstanceOf($value, $className);
assertType('Bug183', $value);
}

/**
* @param class-string<\Bug183Bar<\Bug183>> $className
*/
function assertInstanceOfWithGenericClassStringReferencingGenericClass($value, string $className): void
{
Assert::isInstanceOf($value, $className);
assertType('Bug183Bar', $value);
}

/**
* @param class-string<\Bug183|\Bug183Foo> $className
*/
function assertInstanceOfWithGenericUnionClassString($value, string $className): void
{
Assert::isInstanceOf($value, $className);
assertType('Bug183|Bug183Foo', $value);
}


interface Bug183
{
}

interface Bug183Foo
{
}

/**
* @template T of object
*/
interface Bug183Bar
{
}
2 changes: 1 addition & 1 deletion tests/Type/WebMozartAssert/data/collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function allInstanceOf(array $a, array $b, array $c, $d): void
assertType('array<stdClass>', $b);

Assert::allIsInstanceOf($c, 17);
assertType('array', $c);
assertType('array<object>', $c);

Assert::allIsInstanceOf($d, new stdClass());
assertType('iterable<stdClass>', $d);
Expand Down
6 changes: 3 additions & 3 deletions tests/Type/WebMozartAssert/data/type.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public function isInstanceOf($a, $b, $c, $d): void
assertType('stdClass', $c);

Assert::isInstanceOf($d, 17);
assertType('mixed', $d);
assertType('object', $d);
}

public function isInstanceOfAny($a, $b, $c, $d, $e, $f, $g): void
Expand All @@ -215,10 +215,10 @@ public function isInstanceOfAny($a, $b, $c, $d, $e, $f, $g): void
assertType('mixed', $d);

Assert::isInstanceOfAny($e, [17]);
assertType('mixed', $e);
assertType('object', $e);

Assert::isInstanceOfAny($f, [17, self::class]);
assertType('PHPStan\Type\WebMozartAssert\TypeTest', $f);
assertType('object', $f);

Assert::nullOrIsInstanceOfAny($g, [self::class, new stdClass()]);
assertType('PHPStan\Type\WebMozartAssert\TypeTest|stdClass|null', $g);
Expand Down

0 comments on commit 923bd58

Please sign in to comment.