Skip to content

Commit

Permalink
Make ParametersAcceptorSelector::selectSingle() return union-ed condi…
Browse files Browse the repository at this point in the history
…tional types

See phpstan/phpstan/issues/3853#issuecomment-1082351763
  • Loading branch information
rvanvelzen committed Apr 1, 2022
1 parent 2b3d282 commit 42e5efc
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/Reflection/ParametersAcceptorSelector.php
Expand Up @@ -48,7 +48,7 @@ public static function selectSingle(
throw new ShouldNotHappenException('Multiple variants - use selectFromArgs() instead.');
}

return $parametersAcceptors[0];
return new SingleParametersAcceptor($parametersAcceptors[0]);
}

/**
Expand Down
52 changes: 52 additions & 0 deletions src/Reflection/SingleParametersAcceptor.php
@@ -0,0 +1,52 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use PHPStan\Type\ConditionalType;
use PHPStan\Type\ConditionalTypeForParameter;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;

class SingleParametersAcceptor implements ParametersAcceptor
{

public function __construct(private ParametersAcceptor $acceptor)
{
}

public function getTemplateTypeMap(): TemplateTypeMap
{
return $this->acceptor->getTemplateTypeMap();
}

public function getResolvedTemplateTypeMap(): TemplateTypeMap
{
return $this->acceptor->getResolvedTemplateTypeMap();
}

/**
* @return array<int, ParameterReflection>
*/
public function getParameters(): array
{
return $this->acceptor->getParameters();
}

public function isVariadic(): bool
{
return $this->acceptor->isVariadic();
}

public function getReturnType(): Type
{
return TypeTraverser::map($this->acceptor->getReturnType(), function (Type $type, callable $traverse) {
while ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$type = $type->getResult();
}

return $traverse($type);
});
}

}
2 changes: 1 addition & 1 deletion src/Type/ConditionalType.php
Expand Up @@ -91,7 +91,7 @@ private function resolve(): Type
}

if ($this->isResolved()) {
return TypeCombinator::union($this->if, $this->else);
return $this->getResult();
}

return $this;
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Traits/ConditionalTypeTrait.php
Expand Up @@ -288,7 +288,7 @@ public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic
return $otherType->isSmallerThanOrEqual($result);
}

private function getResult(): Type
public function getResult(): Type
{
if ($this->result === null) {
return $this->result = TypeCombinator::union($this->if, $this->else);
Expand Down
Expand Up @@ -11,6 +11,7 @@ public function dataAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php');
}

/**
Expand Down
20 changes: 20 additions & 0 deletions tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php
Expand Up @@ -189,3 +189,23 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
}

}


class ConditionalGetSingle implements DynamicMethodReturnTypeExtension {

public function getClass(): string
{
return \DynamicMethodReturnGetSingleConditional\Foo::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'get';
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

}
@@ -0,0 +1,20 @@
<?php

namespace DynamicMethodReturnGetSingleConditional;

use function PHPStan\Testing\assertType;

abstract class Foo
{
/**
* @return ($input is 1 ? true : false)
*/
abstract public function get(int $input): mixed;

public function doFoo(): void
{
assertType('bool', $this->get(0));
assertType('bool', $this->get(1));
assertType('bool', $this->get(2));
}
}
4 changes: 4 additions & 0 deletions tests/PHPStan/Analyser/dynamic-return-type.neon
Expand Up @@ -27,3 +27,7 @@ services:
class: PHPStan\Tests\FooGetSelf
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Tests\ConditionalGetSingle
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

0 comments on commit 42e5efc

Please sign in to comment.