Skip to content

Commit 8848e2b

Browse files
author
moellekenl
committed
add regression tests for bug #4192 + handle ClosureType and non-common CallableType comparison in unions
1 parent a69f7ca commit 8848e2b

5 files changed

Lines changed: 107 additions & 0 deletions

File tree

src/Type/TypeCombinator.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,20 @@ private static function isAlreadyNormalized(array $alreadyNormalized, Type $a, T
507507
*/
508508
private static function compareTypesInUnion(Type $a, Type $b): ?array
509509
{
510+
if (
511+
(
512+
$a instanceof ClosureType
513+
|| ($a instanceof CallableType && !$a->isCommonCallable())
514+
)
515+
&& (
516+
$b instanceof ClosureType
517+
|| ($b instanceof CallableType && !$b->isCommonCallable())
518+
)
519+
&& !$a->equals($b)
520+
) {
521+
return null;
522+
}
523+
510524
if ($a instanceof IntegerRangeType) {
511525
$type = $a->tryUnion($b);
512526
if ($type !== null) {

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ private static function findTestFiles(): iterable
280280
yield __DIR__ . '/../Rules/Variables/data/bug-14124.php';
281281
yield __DIR__ . '/../Rules/Variables/data/bug-14124b.php';
282282
yield __DIR__ . '/../Rules/Arrays/data/bug-14308.php';
283+
yield __DIR__ . '/../Rules/Methods/data/bug-4192.php';
283284
}
284285

285286
/**

tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ public function testNamedArguments(): void
198198
]);
199199
}
200200

201+
public function testBug4192(): void
202+
{
203+
$this->analyse([__DIR__ . '/../Methods/data/bug-4192.php'], []);
204+
}
205+
201206
public static function dataBug3566(): array
202207
{
203208
return [

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,6 +1992,15 @@ public function testBug4188(): void
19921992
$this->analyse([__DIR__ . '/data/bug-4188.php'], []);
19931993
}
19941994

1995+
public function testBug4192(): void
1996+
{
1997+
$this->checkThisOnly = false;
1998+
$this->checkNullables = true;
1999+
$this->checkUnionTypes = true;
2000+
2001+
$this->analyse([__DIR__ . '/data/bug-4192.php'], []);
2002+
}
2003+
19952004
public function testOnlyRelevantUnableToResolveTemplateType(): void
19962005
{
19972006
$this->checkThisOnly = false;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug4192;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template TKey of array-key
9+
* @template T
10+
*/
11+
class Arrayy
12+
{
13+
14+
/** @var array<TKey, T> */
15+
private array $array;
16+
17+
/**
18+
* @param array<TKey, T> $array
19+
*/
20+
public function __construct(array $array)
21+
{
22+
$this->array = $array;
23+
}
24+
25+
/**
26+
* @param \Closure|null $closure
27+
* @phpstan-param null|(\Closure(T,TKey): bool)|(\Closure(T): bool)|(\Closure(TKey): bool) $closure
28+
*/
29+
public function filter($closure = null, int $flag = \ARRAY_FILTER_USE_BOTH): void
30+
{
31+
if (!$closure) {
32+
return;
33+
}
34+
35+
if ($flag === \ARRAY_FILTER_USE_KEY) {
36+
/** @phpstan-var \Closure(TKey): bool $closure */
37+
$closure = $closure;
38+
$generator = function () use ($closure): void {
39+
foreach ($this->array as $key => $value) {
40+
assertType('bool', $closure($key));
41+
}
42+
};
43+
$generator();
44+
} elseif ($flag === \ARRAY_FILTER_USE_BOTH) {
45+
/** @phpstan-var \Closure(T,TKey): bool $closure */
46+
$closure = $closure;
47+
$generator = function () use ($closure): void {
48+
foreach ($this->array as $key => $value) {
49+
assertType('bool', $closure($value, $key));
50+
}
51+
};
52+
$generator();
53+
} else {
54+
/** @phpstan-var \Closure(T): bool $closure */
55+
$closure = $closure;
56+
$generator = function () use ($closure): void {
57+
foreach ($this->array as $key => $value) {
58+
assertType('bool', $closure($value));
59+
}
60+
};
61+
$generator();
62+
}
63+
}
64+
65+
}
66+
67+
(new Arrayy([0 => 1, 1 => 2, 2 => 3, 3 => 4, 7 => 7]))->filter(
68+
static function ($value): bool {
69+
return $value % 2 !== 0;
70+
},
71+
);
72+
73+
(new Arrayy([0 => 1, 1 => 2, 2 => 3, 3 => 4, 7 => 7]))->filter(
74+
static function ($key, $value): bool {
75+
return ($value % 2 !== 0) && (($key & 2) !== 0);
76+
},
77+
\ARRAY_FILTER_USE_BOTH,
78+
);

0 commit comments

Comments
 (0)