Skip to content

Commit af88720

Browse files
committed
Make ParametersAcceptorSelector::selectSingle() return union-ed conditional types
See phpstan/phpstan/issues/3853#issuecomment-1082351763
1 parent eeedd1c commit af88720

8 files changed

+100
-3
lines changed

src/Reflection/ParametersAcceptorSelector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static function selectSingle(
4848
throw new ShouldNotHappenException('Multiple variants - use selectFromArgs() instead.');
4949
}
5050

51-
return $parametersAcceptors[0];
51+
return new SingleParametersAcceptor($parametersAcceptors[0]);
5252
}
5353

5454
/**
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection;
4+
5+
use PHPStan\Type\ConditionalType;
6+
use PHPStan\Type\ConditionalTypeForParameter;
7+
use PHPStan\Type\Generic\TemplateTypeMap;
8+
use PHPStan\Type\Type;
9+
use PHPStan\Type\TypeTraverser;
10+
11+
class SingleParametersAcceptor implements ParametersAcceptor
12+
{
13+
14+
public function __construct(private ParametersAcceptor $acceptor)
15+
{
16+
}
17+
18+
public function getTemplateTypeMap(): TemplateTypeMap
19+
{
20+
return $this->acceptor->getTemplateTypeMap();
21+
}
22+
23+
public function getResolvedTemplateTypeMap(): TemplateTypeMap
24+
{
25+
return $this->acceptor->getResolvedTemplateTypeMap();
26+
}
27+
28+
/**
29+
* @return array<int, ParameterReflection>
30+
*/
31+
public function getParameters(): array
32+
{
33+
return $this->acceptor->getParameters();
34+
}
35+
36+
public function isVariadic(): bool
37+
{
38+
return $this->acceptor->isVariadic();
39+
}
40+
41+
public function getReturnType(): Type
42+
{
43+
return TypeTraverser::map($this->acceptor->getReturnType(), static function (Type $type, callable $traverse) {
44+
while ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
45+
$type = $type->getResult();
46+
}
47+
48+
return $traverse($type);
49+
});
50+
}
51+
52+
}

src/Type/ConditionalType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ private function resolve(): Type
9191
}
9292

9393
if ($this->isResolved()) {
94-
return TypeCombinator::union($this->if, $this->else);
94+
return $this->getResult();
9595
}
9696

9797
return $this;

src/Type/Traits/ConditionalTypeTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic
288288
return $otherType->isSmallerThanOrEqual($result);
289289
}
290290

291-
private function getResult(): Type
291+
public function getResult(): Type
292292
{
293293
if ($this->result === null) {
294294
return $this->result = TypeCombinator::union($this->if, $this->else);

tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public function dataAsserts(): iterable
1111
{
1212
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types.php');
1313
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php');
14+
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php');
1415
}
1516

1617
/**

tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,23 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
189189
}
190190

191191
}
192+
193+
194+
class ConditionalGetSingle implements DynamicMethodReturnTypeExtension {
195+
196+
public function getClass(): string
197+
{
198+
return \DynamicMethodReturnGetSingleConditional\Foo::class;
199+
}
200+
201+
public function isMethodSupported(MethodReflection $methodReflection): bool
202+
{
203+
return $methodReflection->getName() === 'get';
204+
}
205+
206+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
207+
{
208+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
209+
}
210+
211+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace DynamicMethodReturnGetSingleConditional;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
abstract class Foo
8+
{
9+
/**
10+
* @return ($input is 1 ? true : false)
11+
*/
12+
abstract public function get(int $input): mixed;
13+
14+
public function doFoo(): void
15+
{
16+
assertType('bool', $this->get(0));
17+
assertType('bool', $this->get(1));
18+
assertType('bool', $this->get(2));
19+
}
20+
}

tests/PHPStan/Analyser/dynamic-return-type.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ services:
2727
class: PHPStan\Tests\FooGetSelf
2828
tags:
2929
- phpstan.broker.dynamicMethodReturnTypeExtension
30+
-
31+
class: PHPStan\Tests\ConditionalGetSingle
32+
tags:
33+
- phpstan.broker.dynamicMethodReturnTypeExtension

0 commit comments

Comments
 (0)