Skip to content

Commit 2f2bd5c

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents a1b68d2 + 5037bd5 commit 2f2bd5c

File tree

2 files changed

+118
-4
lines changed

2 files changed

+118
-4
lines changed

src/Type/Php/ArrayFilterFunctionReturnTypeHelper.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,22 @@ private function processKeyAndItemType(MutatingScope $scope, Type $keyType, Type
260260
return [new NeverType(), new NeverType(), false];
261261
}
262262

263-
$scope = $scope->filterByTruthyValue($expr);
263+
$truthyScope = $scope->filterByTruthyValue($expr);
264+
265+
$optional = !$booleanResult->isTrue()->yes();
266+
if ($optional) {
267+
$falseyScope = $scope->filterByFalseyValue($expr);
268+
$falseyItemType = $itemVarName !== null ? $falseyScope->getVariableType($itemVarName) : $itemType;
269+
$falseyKeyType = $keyVarName !== null ? $falseyScope->getVariableType($keyVarName) : $keyType;
270+
if ($falseyItemType instanceof NeverType || $falseyKeyType instanceof NeverType) {
271+
$optional = false;
272+
}
273+
}
264274

265275
return [
266-
$keyVarName !== null ? $scope->getVariableType($keyVarName) : $keyType,
267-
$itemVarName !== null ? $scope->getVariableType($itemVarName) : $itemType,
268-
!$booleanResult->isTrue()->yes(),
276+
$keyVarName !== null ? $truthyScope->getVariableType($keyVarName) : $keyType,
277+
$itemVarName !== null ? $truthyScope->getVariableType($itemVarName) : $itemType,
278+
$optional,
269279
];
270280
}
271281

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php // lint >= 8.1
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug11730;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Foo {
10+
public bool $active = false;
11+
}
12+
13+
/** @return ($value is Foo ? true : false) */
14+
function isFoo(mixed $value): bool {
15+
return $value instanceof Foo;
16+
}
17+
18+
/** @phpstan-assert-if-true Foo $value */
19+
function checkFoo(mixed $value): bool {
20+
return $value instanceof Foo;
21+
}
22+
23+
$data = [new Foo, new Foo];
24+
25+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, isFoo(...)));
26+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, checkFoo(...)));
27+
28+
/** @param array{Foo|int, Foo|int} $data */
29+
function doFoo ($data) {
30+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, isFoo(...)));
31+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, checkFoo(...)));
32+
}
33+
34+
class Asserter {
35+
/** @return ($value is Foo ? true : false) */
36+
function isFoo(mixed $value): bool {
37+
return $value instanceof Foo;
38+
}
39+
40+
/** @phpstan-assert-if-true Foo $value */
41+
function checkFoo(mixed $value): bool {
42+
return $value instanceof Foo;
43+
}
44+
45+
/** @return ($value is Foo ? true : false) */
46+
static function isFooStatic(mixed $value): bool {
47+
return $value instanceof Foo;
48+
}
49+
50+
/** @phpstan-assert-if-true Foo $value */
51+
static function checkFooStatic(mixed $value): bool {
52+
return $value instanceof Foo;
53+
}
54+
}
55+
56+
function doBar(): void {
57+
$data = [new Foo, new Foo];
58+
$asserter = new Asserter;
59+
60+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, $asserter->isFoo(...)));
61+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, $asserter->checkFoo(...)));
62+
63+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, Asserter::isFooStatic(...)));
64+
assertType('array{Bug11730\Foo, Bug11730\Foo}', array_filter($data, Asserter::checkFooStatic(...)));
65+
}
66+
67+
/** @param array{Foo|int, Foo|int} $data */
68+
function doBaz(array $data): void {
69+
$asserter = new Asserter;
70+
71+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, $asserter->isFoo(...)));
72+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, $asserter->checkFoo(...)));
73+
74+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, Asserter::isFooStatic(...)));
75+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, Asserter::checkFooStatic(...)));
76+
}
77+
78+
class CallableAsserterConditionalReturn {
79+
/** @return ($value is Foo ? true : false) */
80+
function __invoke(mixed $value): bool {
81+
return $value instanceof Foo;
82+
}
83+
}
84+
85+
$data = [new Foo, new Foo];
86+
assertType('array<0|1, Bug11730\Foo>', array_filter($data, new CallableAsserterConditionalReturn())); // could be array{Foo, Foo}
87+
88+
class CallableAssertIfTrue {
89+
/** @phpstan-assert-if-true Foo $value */
90+
function __invoke(mixed $value): bool {
91+
return $value instanceof Foo;
92+
}
93+
}
94+
95+
$data = [new Foo, new Foo];
96+
assertType('array<0|1, Bug11730\Foo>', array_filter($data, new CallableAssertIfTrue())); // could be array{Foo, Foo}
97+
98+
/** @phpstan-assert-if-true =Foo $value */
99+
function checkFooAndMore(mixed $value): bool {
100+
return $value instanceof Foo && $value->active;
101+
}
102+
103+
$data = [new Foo, new Foo];
104+
assertType('array{0?: Bug11730\Foo, 1?: Bug11730\Foo}', array_filter($data, checkFooAndMore(...)));

0 commit comments

Comments
 (0)