Skip to content

Commit 11268e5

Browse files
committed
Do not generalize class-level @template type in method call
1 parent 4d5bf20 commit 11268e5

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

src/Reflection/ResolvedFunctionVariant.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance
176176
$references = $type->getReferencedTemplateTypes($positionVariance);
177177

178178
$objectCb = function (Type $type, callable $traverse) use ($references): Type {
179-
if ($type instanceof TemplateType && !$type->isArgument()) {
179+
if (
180+
$type instanceof TemplateType
181+
&& !$type->isArgument()
182+
&& $type->getScope()->getFunctionName() !== null
183+
) {
180184
$newType = $this->resolvedTemplateTypeMap->getType($type->getName());
181185
if ($newType === null || $newType instanceof ErrorType) {
182186
return $traverse($type);

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public function dataFileAsserts(): iterable
210210
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816.php');
211211

212212
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2816-2.php');
213+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10473.php');
213214

214215
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3985.php');
215216

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace Bug10473;
4+
5+
use ArrayAccess;
6+
use function PHPStan\Testing\assertType;
7+
8+
/**
9+
* @template TRow of array<string, mixed>
10+
*/
11+
class Rows
12+
{
13+
14+
/**
15+
* @param list<TRow> $rowsData
16+
*/
17+
public function __construct(private array $rowsData)
18+
{}
19+
20+
/**
21+
* @return Row<TRow>|NULL
22+
*/
23+
public function getByIndex(int $index): ?Row
24+
{
25+
return isset($this->rowsData[$index])
26+
? new Row($this->rowsData[$index])
27+
: NULL;
28+
}
29+
}
30+
31+
/**
32+
* @template TRow of array<string, mixed>
33+
* @implements ArrayAccess<key-of<TRow>, value-of<TRow>>
34+
*/
35+
class Row implements ArrayAccess
36+
{
37+
38+
/**
39+
* @param TRow $data
40+
*/
41+
public function __construct(private array $data)
42+
{}
43+
44+
/**
45+
* @param key-of<TRow> $key
46+
*/
47+
public function offsetExists($key): bool
48+
{
49+
return isset($this->data[$key]);
50+
}
51+
52+
/**
53+
* @template TKey of key-of<TRow>
54+
* @param TKey $key
55+
* @return TRow[TKey]
56+
*/
57+
public function offsetGet($key): mixed
58+
{
59+
return $this->data[$key];
60+
}
61+
62+
public function offsetSet($key, mixed $value): void
63+
{
64+
$this->data[$key] = $value;
65+
}
66+
67+
public function offsetUnset($key): void
68+
{
69+
unset($this->data[$key]);
70+
}
71+
72+
/**
73+
* @return TRow
74+
*/
75+
public function toArray(): array
76+
{
77+
return $this->data;
78+
}
79+
80+
}
81+
82+
class Foo
83+
{
84+
85+
/** @param Rows<array{foo: int<0, max>}> $rows */
86+
public function doFoo(Rows $rows): void
87+
{
88+
assertType('Bug10473\Rows<array{foo: int<0, max>}>', $rows);
89+
90+
$row = $rows->getByIndex(0);
91+
92+
if ($row !== NULL) {
93+
assertType('Bug10473\Row<array{foo: int<0, max>}>', $row);
94+
$fooFromRow = $row['foo'];
95+
96+
assertType('int<0, max>', $fooFromRow);
97+
assertType('array{foo: int<0, max>}', $row->toArray());
98+
}
99+
100+
}
101+
102+
}

0 commit comments

Comments
 (0)