Skip to content

Commit eceb9f6

Browse files
committed
CallableTypeHelper - always use isSuperTypeOf() instead of accepts()
1 parent e038140 commit eceb9f6

File tree

4 files changed

+67
-11
lines changed

4 files changed

+67
-11
lines changed

src/Type/CallableTypeHelper.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Reflection\ParametersAcceptor;
66
use PHPStan\TrinaryLogic;
7+
use PHPStan\Type\Generic\TemplateMixedType;
78
use function array_merge;
89
use function sprintf;
910

@@ -59,7 +60,16 @@ public static function isParametersAcceptorSuperTypeOf(
5960
}
6061

6162
if ($treatMixedAsAny) {
62-
$isSuperType = $theirParameter->getType()->acceptsWithReason($ourParameterType, true);
63+
if (
64+
$ourParameterType instanceof MixedType
65+
&& !$ourParameterType instanceof TemplateMixedType
66+
) {
67+
$isSuperType = new AcceptsResult(TrinaryLogic::createYes(), []);
68+
} elseif ($ourParameterType instanceof BenevolentUnionType) {
69+
$isSuperType = $theirParameter->getType()->acceptsWithReason($ourParameterType, true);
70+
} else {
71+
$isSuperType = new AcceptsResult($theirParameter->getType()->isSuperTypeOf($ourParameterType), []);
72+
}
6373
} else {
6474
$isSuperType = new AcceptsResult($theirParameter->getType()->isSuperTypeOf($ourParameterType), []);
6575
}
@@ -85,7 +95,16 @@ public static function isParametersAcceptorSuperTypeOf(
8595

8696
$theirReturnType = $theirs->getReturnType();
8797
if ($treatMixedAsAny) {
88-
$isReturnTypeSuperType = $ours->getReturnType()->acceptsWithReason($theirReturnType, true);
98+
if (
99+
$theirReturnType instanceof MixedType
100+
&& !$theirReturnType instanceof TemplateMixedType
101+
) {
102+
$isReturnTypeSuperType = new AcceptsResult(TrinaryLogic::createYes(), []);
103+
} elseif ($theirReturnType instanceof BenevolentUnionType) {
104+
$isReturnTypeSuperType = $ours->getReturnType()->acceptsWithReason($theirReturnType, true);
105+
} else {
106+
$isReturnTypeSuperType = new AcceptsResult($ours->getReturnType()->isSuperTypeOf($theirReturnType), []);
107+
}
89108
} else {
90109
$isReturnTypeSuperType = new AcceptsResult($ours->getReturnType()->isSuperTypeOf($theirReturnType), []);
91110
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -772,15 +772,17 @@ public function testDiscussion7124(): void
772772
}
773773

774774
$errors = $this->runAnalyse(__DIR__ . '/data/discussion-7124.php');
775-
$this->assertCount(4, $errors);
776-
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool, int=): bool, Closure(int, bool): bool given.', $errors[0]->getMessage());
777-
$this->assertSame(38, $errors[0]->getLine());
778-
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool, int=): bool, Closure(int): bool given.', $errors[1]->getMessage());
779-
$this->assertSame(45, $errors[1]->getLine());
780-
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(int): bool, Closure(bool): bool given.', $errors[2]->getMessage());
781-
$this->assertSame(52, $errors[2]->getLine());
782-
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool): bool, Closure(int): bool given.', $errors[3]->getMessage());
783-
$this->assertSame(59, $errors[3]->getLine());
775+
$this->assertCount(5, $errors);
776+
$this->assertSame('Parameter #2 $callback of function array_filter expects callable(T): mixed, (callable(K): bool)|(callable(T): bool) given.', $errors[0]->getMessage());
777+
$this->assertSame(30, $errors[0]->getLine());
778+
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool, int=): bool, Closure(int, bool): bool given.', $errors[1]->getMessage());
779+
$this->assertSame(38, $errors[1]->getLine());
780+
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool, int=): bool, Closure(int): bool given.', $errors[2]->getMessage());
781+
$this->assertSame(45, $errors[2]->getLine());
782+
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(int): bool, Closure(bool): bool given.', $errors[3]->getMessage());
783+
$this->assertSame(52, $errors[3]->getLine());
784+
$this->assertSame('Parameter #2 $callback of function Discussion7124\filter expects callable(bool): bool, Closure(int): bool given.', $errors[4]->getMessage());
785+
$this->assertSame(59, $errors[4]->getLine());
784786
}
785787

786788
public function testBug7214(): void

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2910,6 +2910,11 @@ public function testTrickyCallables(): void
29102910
'• Parameter #1 $key of passed callable is required but the parameter of accepting callable is optional. It might be called without it.
29112911
• Type int of parameter #1 $key of passed callable needs to be same or wider than parameter type int|string of accepting callable.',
29122912
],
2913+
[
2914+
'Parameter #1 $cb of method TrickyCallables\AcceptsFooParentCallable::sayHello() expects callable(TrickyCallables\FooParent): void, Closure(TrickyCallables\FooChild): void given.',
2915+
110,
2916+
'Type TrickyCallables\FooChild of parameter #1 $bar of passed callable needs to be same or wider than parameter type TrickyCallables\FooParent of accepting callable.',
2917+
],
29132918
]);
29142919
}
29152920

tests/PHPStan/Rules/Methods/data/tricky-callables.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,33 @@ function (TwoErrorsAtOnce $t): void {
8282
$filter = static fn (int $key): bool => true;
8383
$t->run($filter);
8484
};
85+
86+
class FooParent
87+
{
88+
89+
}
90+
91+
class FooChild extends FooParent
92+
{
93+
94+
}
95+
96+
class AcceptsFooParentCallable
97+
{
98+
99+
/**
100+
* @param callable(FooParent): void $cb
101+
*/
102+
public function sayHello(callable $cb): void
103+
{
104+
$cb(new FooParent());
105+
}
106+
107+
public function doFoo(): void
108+
{
109+
$hw = new self();
110+
$hw->sayHello(function (FooChild $bar): void {
111+
});
112+
}
113+
114+
}

0 commit comments

Comments
 (0)