Skip to content

Commit 923bd58

Browse files
axlonondrejmirtes
authored andcommitted
Allow to Assert::isInstanceOf() to work with generic and anonymous class strings
1 parent 983a1a7 commit 923bd58

File tree

6 files changed

+94
-22
lines changed

6 files changed

+94
-22
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

+23-18
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use ReflectionObject;
5050
use Traversable;
5151
use function array_key_exists;
52+
use function array_map;
5253
use function array_reduce;
5354
use function array_shift;
5455
use function count;
@@ -407,33 +408,37 @@ private function getExpressionResolvers(): array
407408
},
408409
'isInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
409410
$classType = $scope->getType($class->value);
410-
$classNameStrings = $classType->getConstantStrings();
411-
if (count($classNameStrings) !== 1) {
412-
$classNames = $classType->getObjectClassNames();
413-
if (count($classNames) === 1) {
414-
return new Instanceof_(
415-
$expr->value,
416-
new Name($classNames[0])
417-
);
418-
}
419-
return null;
411+
$classNames = $classType->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
412+
413+
if (count($classNames) !== 0) {
414+
return self::implodeExpr(array_map(static function (string $className) use ($expr): Expr {
415+
return new Instanceof_($expr->value, new Name($className));
416+
}, $classNames), BooleanOr::class);
420417
}
421418

422-
return new Instanceof_(
423-
$expr->value,
424-
new Name($classNameStrings[0]->getValue())
419+
return new FuncCall(
420+
new Name('is_object'),
421+
[$expr]
425422
);
426423
},
427424
'isInstanceOfAny' => function (Scope $scope, Arg $expr, Arg $classes): ?Expr {
428425
return self::buildAnyOfExpr($scope, $expr, $classes, $this->resolvers['isInstanceOf']);
429426
},
430-
'notInstanceOf' => function (Scope $scope, Arg $expr, Arg $class): ?Expr {
431-
$expr = $this->resolvers['isInstanceOf']($scope, $expr, $class);
432-
if ($expr === null) {
433-
return null;
427+
'notInstanceOf' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
428+
$classType = $scope->getType($class->value);
429+
$classNames = $classType->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
430+
431+
if (count($classNames) !== 0) {
432+
$result = self::implodeExpr(array_map(static function (string $className) use ($expr): Expr {
433+
return new Instanceof_($expr->value, new Name($className));
434+
}, $classNames), BooleanOr::class);
435+
436+
if ($result !== null) {
437+
return new BooleanNot($result);
438+
}
434439
}
435440

436-
return new BooleanNot($expr);
441+
return null;
437442
},
438443
'isAOf' => static function (Scope $scope, Arg $expr, Arg $class): Expr {
439444
$exprType = $scope->getType($expr->value);

tests/Type/WebMozartAssert/AssertTypeSpecifyingExtensionTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public function dataFileAsserts(): iterable
2222
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-18.php');
2323
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-117.php');
2424
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-150.php');
25+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-183.php');
2526
}
2627

2728
/**

tests/Type/WebMozartAssert/ImpossibleCheckTypeMethodCallRuleTest.php

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ public function testExtension(): void
101101
'Call to static method Webmozart\Assert\Assert::implementsInterface() with mixed and \'WebmozartAssertImpossibleCheck\\\Foo\' will always evaluate to false.',
102102
111,
103103
],
104+
[
105+
'Call to static method Webmozart\Assert\Assert::isInstanceOf() with Exception and class-string<Exception> will always evaluate to true.',
106+
119,
107+
],
104108
]);
105109
}
106110

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
use Webmozart\Assert\Assert;
4+
use function PHPStan\Testing\assertType;
5+
6+
function assertInstanceOfWithString($value, string $className): void
7+
{
8+
Assert::isInstanceOf($value, $className);
9+
assertType('object', $value);
10+
}
11+
12+
/**
13+
* @param class-string $className
14+
*/
15+
function assertInstanceOfWithClassString($value, string $className): void
16+
{
17+
Assert::isInstanceOf($value, $className);
18+
assertType('object', $value);
19+
}
20+
21+
/**
22+
* @param class-string<\Bug183> $className
23+
*/
24+
function assertInstanceOfWithGenericClassString($value, string $className): void
25+
{
26+
Assert::isInstanceOf($value, $className);
27+
assertType('Bug183', $value);
28+
}
29+
30+
/**
31+
* @param class-string<\Bug183Bar<\Bug183>> $className
32+
*/
33+
function assertInstanceOfWithGenericClassStringReferencingGenericClass($value, string $className): void
34+
{
35+
Assert::isInstanceOf($value, $className);
36+
assertType('Bug183Bar', $value);
37+
}
38+
39+
/**
40+
* @param class-string<\Bug183|\Bug183Foo> $className
41+
*/
42+
function assertInstanceOfWithGenericUnionClassString($value, string $className): void
43+
{
44+
Assert::isInstanceOf($value, $className);
45+
assertType('Bug183|Bug183Foo', $value);
46+
}
47+
48+
49+
interface Bug183
50+
{
51+
}
52+
53+
interface Bug183Foo
54+
{
55+
}
56+
57+
/**
58+
* @template T of object
59+
*/
60+
interface Bug183Bar
61+
{
62+
}

tests/Type/WebMozartAssert/data/collection.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public function allInstanceOf(array $a, array $b, array $c, $d): void
7575
assertType('array<stdClass>', $b);
7676

7777
Assert::allIsInstanceOf($c, 17);
78-
assertType('array', $c);
78+
assertType('array<object>', $c);
7979

8080
Assert::allIsInstanceOf($d, new stdClass());
8181
assertType('iterable<stdClass>', $d);

tests/Type/WebMozartAssert/data/type.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public function isInstanceOf($a, $b, $c, $d): void
197197
assertType('stdClass', $c);
198198

199199
Assert::isInstanceOf($d, 17);
200-
assertType('mixed', $d);
200+
assertType('object', $d);
201201
}
202202

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

217217
Assert::isInstanceOfAny($e, [17]);
218-
assertType('mixed', $e);
218+
assertType('object', $e);
219219

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

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

0 commit comments

Comments
 (0)