Skip to content

Commit 56d7b27

Browse files
herndlmondrejmirtes
authored andcommitted
Improve all* handling
1 parent 11cc4ba commit 56d7b27

File tree

2 files changed

+99
-30
lines changed

2 files changed

+99
-30
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

+74-23
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
use PhpParser\Node\Expr\FuncCall;
2424
use PhpParser\Node\Expr\Instanceof_;
2525
use PhpParser\Node\Expr\StaticCall;
26+
use PhpParser\Node\Expr\Variable;
2627
use PhpParser\Node\Name;
28+
use PhpParser\Node\Param;
2729
use PhpParser\Node\Scalar\LNumber;
2830
use PhpParser\Node\Scalar\String_;
31+
use PhpParser\Node\Stmt\Return_;
2932
use PHPStan\Analyser\Scope;
3033
use PHPStan\Analyser\SpecifiedTypes;
3134
use PHPStan\Analyser\TypeSpecifier;
@@ -56,7 +59,6 @@
5659
use function count;
5760
use function key;
5861
use function lcfirst;
59-
use function reset;
6062
use function substr;
6163

6264
class AssertTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
@@ -133,36 +135,25 @@ public function specifyTypes(
133135
$scope
134136
);
135137
}
138+
139+
if (substr($staticMethodReflection->getName(), 0, 3) === 'all') {
140+
return $this->handleAll(
141+
$staticMethodReflection->getName(),
142+
$node,
143+
$scope
144+
);
145+
}
146+
136147
$expression = self::createExpression($scope, $staticMethodReflection->getName(), $node->getArgs());
137148
if ($expression === null) {
138149
return new SpecifiedTypes([], []);
139150
}
140-
$specifiedTypes = $this->typeSpecifier->specifyTypesInCondition(
151+
152+
return $this->typeSpecifier->specifyTypesInCondition(
141153
$scope,
142154
$expression,
143155
TypeSpecifierContext::createTruthy()
144156
);
145-
146-
if (substr($staticMethodReflection->getName(), 0, 3) === 'all') {
147-
if (count($specifiedTypes->getSureTypes()) > 0) {
148-
$sureTypes = $specifiedTypes->getSureTypes();
149-
reset($sureTypes);
150-
$exprString = key($sureTypes);
151-
$sureType = $sureTypes[$exprString];
152-
return $this->arrayOrIterable(
153-
$scope,
154-
$sureType[0],
155-
static function () use ($sureType): Type {
156-
return $sureType[1];
157-
}
158-
);
159-
}
160-
if (count($specifiedTypes->getSureNotTypes()) > 0) {
161-
throw new ShouldNotHappenException();
162-
}
163-
}
164-
165-
return $specifiedTypes;
166157
}
167158

168159
/**
@@ -758,6 +749,66 @@ static function (Type $type) use ($valueType): Type {
758749
throw new ShouldNotHappenException();
759750
}
760751

752+
private function handleAll(
753+
string $methodName,
754+
StaticCall $node,
755+
Scope $scope
756+
): SpecifiedTypes
757+
{
758+
$closureItemVariable = new Variable('item');
759+
$closureArgs = $node->getArgs();
760+
$closureArgs[0] = new Arg($closureItemVariable);
761+
762+
$expression = new BooleanAnd(
763+
new FuncCall(new Name('is_iterable'), [$node->getArgs()[0]]),
764+
new Identical(
765+
$node->getArgs()[0]->value,
766+
new FuncCall(
767+
new Name('array_filter'),
768+
[
769+
new Arg($node->getArgs()[0]->value),
770+
new Arg(
771+
new Expr\Closure(
772+
[
773+
'static' => true,
774+
'params' => [new Param($closureItemVariable)],
775+
'stmts' => [
776+
new Return_(self::createExpression($scope, $methodName, $closureArgs)),
777+
],
778+
]
779+
)
780+
),
781+
]
782+
)
783+
)
784+
);
785+
786+
$specifiedTypes = $this->typeSpecifier->specifyTypesInCondition(
787+
$scope,
788+
$expression,
789+
TypeSpecifierContext::createTruthy()
790+
);
791+
792+
if (count($specifiedTypes->getSureTypes()) > 0) {
793+
$sureTypes = $specifiedTypes->getSureTypes();
794+
$exprString = key($sureTypes);
795+
[$exprNode, $type] = $sureTypes[$exprString];
796+
797+
return $this->arrayOrIterable(
798+
$scope,
799+
$exprNode,
800+
static function () use ($type): Type {
801+
return $type->getIterableValueType();
802+
}
803+
);
804+
}
805+
if (count($specifiedTypes->getSureNotTypes()) > 0) {
806+
throw new ShouldNotHappenException();
807+
}
808+
809+
return $specifiedTypes;
810+
}
811+
761812
private function arrayOrIterable(
762813
Scope $scope,
763814
Expr $expr,

tests/Type/WebMozartAssert/data/collection.php

+25-7
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,40 @@
88
class CollectionTest
99
{
1010

11-
public function allString(array $a): void
11+
public function allString(array $a, $b): void
1212
{
1313
Assert::allString($a);
1414
\PHPStan\Testing\assertType('array<string>', $a);
15+
16+
Assert::allString($b);
17+
\PHPStan\Testing\assertType('iterable<string>', $b);
1518
}
1619

17-
public function allStringNotEmpty(array $a): void
20+
public function allStringNotEmpty(array $a, iterable $b, $c): void
1821
{
1922
Assert::allStringNotEmpty($a);
20-
\PHPStan\Testing\assertType('array<string>', $a); // should be array<non-empty-string>
23+
\PHPStan\Testing\assertType('array<non-empty-string>', $a);
24+
25+
Assert::allStringNotEmpty($b);
26+
\PHPStan\Testing\assertType('iterable<non-empty-string>', $b);
27+
28+
Assert::allStringNotEmpty($c);
29+
\PHPStan\Testing\assertType('iterable<non-empty-string>', $c);
2130
}
2231

23-
public function allInteger(array $a, iterable $b, iterable $c): void
32+
public function allInteger(array $a, iterable $b, $c): void
2433
{
2534
Assert::allInteger($a);
2635
\PHPStan\Testing\assertType('array<int>', $a);
2736

2837
Assert::allInteger($b);
2938
\PHPStan\Testing\assertType('iterable<int>', $b);
39+
40+
Assert::allInteger($c);
41+
\PHPStan\Testing\assertType('iterable<int>', $c);
3042
}
3143

32-
public function allInstanceOf(array $a, array $b, array $c): void
44+
public function allInstanceOf(array $a, array $b, array $c, $d): void
3345
{
3446
Assert::allIsInstanceOf($a, stdClass::class);
3547
\PHPStan\Testing\assertType('array<stdClass>', $a);
@@ -39,6 +51,9 @@ public function allInstanceOf(array $a, array $b, array $c): void
3951

4052
Assert::allIsInstanceOf($c, 17);
4153
\PHPStan\Testing\assertType('array', $c);
54+
55+
Assert::allIsInstanceOf($d, new stdClass());
56+
\PHPStan\Testing\assertType('iterable<stdClass>', $d);
4257
}
4358

4459
/**
@@ -76,21 +91,24 @@ public function allNotSame(array $a): void
7691
\PHPStan\Testing\assertType('array{1, -2|2, -3|3}', $a);
7792
}
7893

79-
public function allSubclassOf(array $a, $b): void
94+
public function allSubclassOf(array $a, iterable $b, $c): void
8095
{
8196
Assert::allSubclassOf($a, self::class);
8297
\PHPStan\Testing\assertType('array<class-string<PHPStan\Type\WebMozartAssert\CollectionTest>|PHPStan\Type\WebMozartAssert\CollectionTest>', $a);
8398

8499
Assert::allSubclassOf($b, self::class);
85100
\PHPStan\Testing\assertType('iterable<class-string<PHPStan\Type\WebMozartAssert\CollectionTest>|PHPStan\Type\WebMozartAssert\CollectionTest>', $b);
101+
102+
Assert::allSubclassOf($c, self::class);
103+
\PHPStan\Testing\assertType('iterable<class-string<PHPStan\Type\WebMozartAssert\CollectionTest>|PHPStan\Type\WebMozartAssert\CollectionTest>', $c);
86104
}
87105

88106
/**
89107
* @param array<array{id?: int}> $a
90108
* @param array<int, array<string, mixed>> $b
91109
*
92110
*/
93-
public function allKeyExists(array $a, array $b, array $c): void
111+
public function allKeyExists(array $a, array $b, array $c, $d): void
94112
{
95113
Assert::allKeyExists($a, 'id');
96114
\PHPStan\Testing\assertType('array<array{id: int}>', $a);

0 commit comments

Comments
 (0)