Skip to content

Commit 266d55e

Browse files
POC
1 parent 682acb4 commit 266d55e

File tree

3 files changed

+72
-11
lines changed

3 files changed

+72
-11
lines changed

src/Rules/RuleLevelHelper.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\AutowiredParameter;
88
use PHPStan\DependencyInjection\AutowiredService;
9+
use PHPStan\Reflection\ParametersAcceptor;
910
use PHPStan\Reflection\ReflectionProvider;
1011
use PHPStan\Type\BenevolentUnionType;
1112
use PHPStan\Type\CallableType;
@@ -16,6 +17,7 @@
1617
use PHPStan\Type\MixedType;
1718
use PHPStan\Type\NeverType;
1819
use PHPStan\Type\NullType;
20+
use PHPStan\Type\SimultaneousTypeTraverser;
1921
use PHPStan\Type\StrictMixedType;
2022
use PHPStan\Type\Type;
2123
use PHPStan\Type\TypeCombinator;
@@ -84,18 +86,21 @@ private function transformCommonType(Type $type): Type
8486
/**
8587
* @return array{Type, bool}
8688
*/
87-
private function transformAcceptedType(Type $acceptingType, Type $acceptedType): array
89+
private function transformAcceptedType(Type $acceptingType, Type $acceptedType, bool $strictTypes): array
8890
{
8991
$checkForUnion = $this->checkUnionTypes;
90-
$acceptedType = TypeTraverser::map($acceptedType, function (Type $acceptedType, callable $traverse) use ($acceptingType, &$checkForUnion): Type {
92+
$acceptedType = SimultaneousTypeTraverser::map($acceptedType, $acceptingType, function (Type $acceptedType, Type $acceptingType,callable $traverse) use (&$checkForUnion, $strictTypes): Type {
9193
if ($acceptedType instanceof CallableType) {
9294
if ($acceptedType->isCommonCallable()) {
9395
return $acceptedType;
9496
}
97+
if (!$acceptingType instanceof ParametersAcceptor) {
98+
return $acceptedType;
99+
}
95100

96101
return new CallableType(
97102
$acceptedType->getParameters(),
98-
$traverse($this->transformCommonType($acceptedType->getReturnType())),
103+
$traverse($this->transformCommonType($acceptedType->getReturnType()), $acceptingType->getReturnType()),
99104
$acceptedType->isVariadic(),
100105
$acceptedType->getTemplateTypeMap(),
101106
$acceptedType->getResolvedTemplateTypeMap(),
@@ -109,9 +114,13 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType):
109114
return $acceptedType;
110115
}
111116

117+
if (!$acceptingType instanceof ParametersAcceptor) {
118+
return $acceptedType;
119+
}
120+
112121
return new ClosureType(
113122
$acceptedType->getParameters(),
114-
$traverse($this->transformCommonType($acceptedType->getReturnType())),
123+
$traverse($this->transformCommonType($acceptedType->getReturnType()), $acceptingType->getReturnType()),
115124
$acceptedType->isVariadic(),
116125
$acceptedType->getTemplateTypeMap(),
117126
$acceptedType->getResolvedTemplateTypeMap(),
@@ -127,21 +136,22 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType):
127136

128137
if (
129138
!$this->checkNullables
130-
&& !$acceptingType instanceof NullType
131-
&& !$acceptedType instanceof NullType
132139
&& !$acceptedType instanceof BenevolentUnionType
140+
&& !$acceptedType instanceof NullType
141+
&& TypeCombinator::containsNull($acceptedType)
142+
&& !TypeCombinator::containsNull($acceptingType)
133143
) {
134-
return $traverse(TypeCombinator::removeNull($acceptedType));
144+
return $traverse(TypeCombinator::removeNull($acceptedType), $acceptingType);
135145
}
136146

137147
if ($this->checkBenevolentUnionTypes) {
138148
if ($acceptedType instanceof BenevolentUnionType) {
139149
$checkForUnion = true;
140-
return $traverse(TypeUtils::toStrictUnion($acceptedType));
150+
return $traverse(TypeUtils::toStrictUnion($acceptedType), $acceptingType);
141151
}
142152
}
143153

144-
return $traverse($this->transformCommonType($acceptedType));
154+
return $traverse($this->transformCommonType($acceptedType), $acceptingType);
145155
});
146156

147157
return [$acceptedType, $checkForUnion];
@@ -150,7 +160,7 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType):
150160
/** @api */
151161
public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTypes): RuleLevelHelperAcceptsResult
152162
{
153-
[$acceptedType, $checkForUnion] = $this->transformAcceptedType($acceptingType, $acceptedType);
163+
[$acceptedType, $checkForUnion] = $this->transformAcceptedType($acceptingType, $acceptedType, $strictTypes);
154164
$acceptingType = $this->transformCommonType($acceptingType);
155165

156166
$accepts = $acceptingType->accepts($acceptedType, $strictTypes);

tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
class ClosureReturnTypeRuleTest extends RuleTestCase
1414
{
1515

16+
private bool $checkNullables = true;
17+
1618
protected function getRule(): Rule
1719
{
18-
return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper(self::createReflectionProvider(), true, false, true, false, false, false, true)));
20+
return new ClosureReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper(self::createReflectionProvider(), $this->checkNullables, false, true, false, false, false, true)));
1921
}
2022

2123
public function testClosureReturnTypeRule(): void
@@ -128,6 +130,13 @@ public function testBug7220(): void
128130
$this->analyse([__DIR__ . '/data/bug-7220.php'], []);
129131
}
130132

133+
public function testBug12008(): void
134+
{
135+
$this->checkNullables = false;
136+
137+
$this->analyse([__DIR__ . '/data/bug-12008.php'], []);
138+
}
139+
131140
public function testBugFunctionMethodConstants(): void
132141
{
133142
$this->analyse([__DIR__ . '/data/bug-anonymous-function-method-constant.php'], []);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12008;
4+
5+
interface ProductOverview {
6+
public function getId(): ?int;
7+
}
8+
9+
/**
10+
* @template T
11+
*/
12+
readonly class Pagination
13+
{
14+
/**
15+
* @param iterable<T> $records
16+
*/
17+
public function __construct(
18+
public iterable $records,
19+
) {
20+
}
21+
}
22+
23+
class HelloWorld
24+
{
25+
private function respondToApiRequest(Closure|null $data): never {
26+
exit;
27+
}
28+
29+
/**
30+
* @param list<ProductOverview> $products
31+
*/
32+
public function run(array $products): never {
33+
$this->respondToApiRequest(function () use ($products) {
34+
return new Pagination(array_map(
35+
fn (ProductOverview $product) => [
36+
'id' => $product->getId(),
37+
],
38+
$products,
39+
));
40+
});
41+
}
42+
}

0 commit comments

Comments
 (0)