Skip to content

Commit 5920c98

Browse files
committed
Correctly infer template type from various callables
1 parent ca25c55 commit 5920c98

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

src/Type/CallableType.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\Reflection\Native\NativeParameterReflection;
1919
use PHPStan\Reflection\ParameterReflection;
2020
use PHPStan\Reflection\ParametersAcceptor;
21+
use PHPStan\Reflection\ParametersAcceptorSelector;
2122
use PHPStan\Reflection\PassedByReference;
2223
use PHPStan\Reflection\Php\DummyParameter;
2324
use PHPStan\ShouldNotHappenException;
@@ -405,10 +406,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
405406

406407
private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap
407408
{
408-
$typeMap = TemplateTypeMap::createEmpty();
409+
$parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
410+
$parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false);
409411
$args = $parametersAcceptor->getParameters();
410412
$returnType = $parametersAcceptor->getReturnType();
411413

414+
$typeMap = TemplateTypeMap::createEmpty();
412415
foreach ($this->getParameters() as $i => $param) {
413416
$paramType = $param->getType();
414417
if (isset($args[$i])) {

src/Type/ClosureType.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\Reflection\Native\NativeParameterReflection;
2424
use PHPStan\Reflection\ParameterReflection;
2525
use PHPStan\Reflection\ParametersAcceptor;
26+
use PHPStan\Reflection\ParametersAcceptorSelector;
2627
use PHPStan\Reflection\PassedByReference;
2728
use PHPStan\Reflection\Php\ClosureCallUnresolvedMethodPrototypeReflection;
2829
use PHPStan\Reflection\Php\DummyParameter;
@@ -524,10 +525,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
524525

525526
private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap
526527
{
527-
$typeMap = TemplateTypeMap::createEmpty();
528+
$parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
529+
$parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false);
528530
$args = $parametersAcceptor->getParameters();
529531
$returnType = $parametersAcceptor->getReturnType();
530532

533+
$typeMap = TemplateTypeMap::createEmpty();
531534
foreach ($this->getParameters() as $i => $param) {
532535
$paramType = $param->getType();
533536
if (isset($args[$i])) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12691;
4+
5+
use Closure;
6+
use function PHPStan\Testing\assertType;
7+
8+
/**
9+
* @template T
10+
*/
11+
class Option
12+
{
13+
/**
14+
* @param T $value
15+
*/
16+
public function __construct(
17+
public mixed $value,
18+
) {
19+
}
20+
21+
/**
22+
* @template S
23+
* @param callable(T): S $callable
24+
* @return self<S>
25+
*/
26+
public function map($callable): self
27+
{
28+
return new self($callable($this->value));
29+
}
30+
31+
/**
32+
* @template S
33+
* @param Closure(T): S $callable
34+
* @return self<S>
35+
*/
36+
public function mapClosure($callable): self
37+
{
38+
return new self($callable($this->value));
39+
}
40+
}
41+
42+
/**
43+
* @param Option<non-empty-array<int>> $ints
44+
*/
45+
function doFoo(Option $ints): void {
46+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->map(array_values(...)));
47+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->map('array_values'));
48+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->map(static fn ($value) => array_values($value)));
49+
};
50+
51+
/**
52+
* @param Option<non-empty-array<int>> $ints
53+
*/
54+
function doFooClosure(Option $ints): void {
55+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->mapClosure(array_values(...)));
56+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->mapClosure(static fn ($value) => array_values($value)));
57+
};
58+
59+
/**
60+
* @template T
61+
* @param array<T> $a
62+
* @return ($a is non-empty-array ? non-empty-list<T> : list<T>)
63+
*/
64+
function myArrayValues(array $a): array {
65+
66+
}
67+
68+
/**
69+
* @param Option<non-empty-array<int>> $ints
70+
*/
71+
function doBar(Option $ints): void {
72+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->mapClosure(myArrayValues(...)));
73+
assertType('Bug12691\\Option<non-empty-list<int>>', $ints->mapClosure(static fn ($value) => myArrayValues($value)));
74+
};

0 commit comments

Comments
 (0)