diff --git a/src/Type/PHPUnit/Assert/AssertTypeSpecifyingExtensionHelper.php b/src/Type/PHPUnit/Assert/AssertTypeSpecifyingExtensionHelper.php index 2276b51..3ea36eb 100644 --- a/src/Type/PHPUnit/Assert/AssertTypeSpecifyingExtensionHelper.php +++ b/src/Type/PHPUnit/Assert/AssertTypeSpecifyingExtensionHelper.php @@ -13,7 +13,9 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Instanceof_; use PhpParser\Node\Name; +use PhpParser\Node\Param; use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Stmt; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\Analyser\TypeSpecifier; @@ -274,6 +276,28 @@ private static function getExpressionResolvers(): array 'ObjectHasAttribute' => static function (Scope $scope, Arg $property, Arg $object): FuncCall { return new FuncCall(new Name('property_exists'), [$object, $property]); }, + 'ContainsOnlyInstancesOf' => static function (Scope $scope, Arg $className, Arg $haystack): Expr { + return new Expr\BinaryOp\BooleanOr( + new Expr\Instanceof_($haystack->value, new Name('Traversable')), + new Identical( + $haystack->value, + new FuncCall(new Name('array_filter'), [ + $haystack, + new Arg(new Expr\Closure([ + 'static' => true, + 'params' => [ + new Param(new Expr\Variable('_')), + ], + 'stmts' => [ + new Stmt\Return_( + new FuncCall(new Name('is_a'), [new Arg(new Expr\Variable('_')), $className]) + ), + ], + ])), + ]) + ) + ); + }, ]; } diff --git a/tests/Type/PHPUnit/data/assert-function.php b/tests/Type/PHPUnit/data/assert-function.php index 117179a..af87a92 100644 --- a/tests/Type/PHPUnit/data/assert-function.php +++ b/tests/Type/PHPUnit/data/assert-function.php @@ -4,6 +4,7 @@ use function PHPStan\Testing\assertType; use function PHPUnit\Framework\assertArrayHasKey; +use function PHPUnit\Framework\assertContainsOnlyInstancesOf; use function PHPUnit\Framework\assertEmpty; use function PHPUnit\Framework\assertInstanceOf; use function PHPUnit\Framework\assertObjectHasAttribute; @@ -43,4 +44,13 @@ public function testEmpty($a): void assertType("0|0.0|''|'0'|array{}|Countable|EmptyIterator|false|null", $a); } + public function containsOnlyInstancesOf(array $a, \Traversable $b): void + { + assertContainsOnlyInstancesOf(\stdClass::class, $a); + assertType('array', $a); + + assertContainsOnlyInstancesOf(\stdClass::class, $b); + assertType('Traversable', $b); + } + }