Skip to content

Commit 367a316

Browse files
authored
Fix inferring template types on intersection types (#1122)
1 parent 6700eb6 commit 367a316

14 files changed

+232
-11
lines changed

src/Type/ArrayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ public static function castToArrayKeyType(Type $offsetType): Type
363363

364364
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
365365
{
366-
if ($receivedType instanceof UnionType) {
366+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
367367
return $receivedType->inferTemplateTypesOn($this);
368368
}
369369

src/Type/CallableType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public function getReturnType(): Type
222222

223223
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
224224
{
225-
if ($receivedType instanceof UnionType) {
225+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
226226
return $receivedType->inferTemplateTypesOn($this);
227227
}
228228

src/Type/ClosureType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ public function getReturnType(): Type
320320

321321
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
322322
{
323-
if ($receivedType instanceof UnionType) {
323+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
324324
return $receivedType->inferTemplateTypesOn($this);
325325
}
326326

src/Type/Constant/ConstantArrayType.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use PHPStan\Type\Generic\TemplateTypeMap;
2222
use PHPStan\Type\Generic\TemplateTypeVariance;
2323
use PHPStan\Type\IntegerRangeType;
24+
use PHPStan\Type\IntersectionType;
2425
use PHPStan\Type\MixedType;
2526
use PHPStan\Type\NeverType;
2627
use PHPStan\Type\ObjectType;
@@ -765,7 +766,7 @@ public function describe(VerbosityLevel $level): string
765766

766767
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
767768
{
768-
if ($receivedType instanceof UnionType) {
769+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
769770
return $receivedType->inferTemplateTypesOn($this);
770771
}
771772

src/Type/Generic/GenericClassStringType.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Type\ClassStringType;
77
use PHPStan\Type\CompoundType;
88
use PHPStan\Type\Constant\ConstantStringType;
9+
use PHPStan\Type\IntersectionType;
910
use PHPStan\Type\MixedType;
1011
use PHPStan\Type\ObjectType;
1112
use PHPStan\Type\ObjectWithoutClassType;
@@ -121,7 +122,7 @@ public function traverse(callable $cb): Type
121122

122123
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
123124
{
124-
if ($receivedType instanceof UnionType) {
125+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
125126
return $receivedType->inferTemplateTypesOn($this);
126127
}
127128

src/Type/Generic/GenericObjectType.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\TrinaryLogic;
1414
use PHPStan\Type\CompoundType;
1515
use PHPStan\Type\ErrorType;
16+
use PHPStan\Type\IntersectionType;
1617
use PHPStan\Type\ObjectType;
1718
use PHPStan\Type\Type;
1819
use PHPStan\Type\TypeWithClassName;
@@ -211,7 +212,7 @@ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAcce
211212

212213
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
213214
{
214-
if ($receivedType instanceof UnionType) {
215+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
215216
return $receivedType->inferTemplateTypesOn($this);
216217
}
217218

src/Type/Generic/TemplateTypeTrait.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,6 @@ public function isSubTypeOf(Type $type): TrinaryLogic
236236

237237
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
238238
{
239-
if (!$receivedType instanceof TemplateType && $receivedType instanceof UnionType) {
240-
return $receivedType->inferTemplateTypesOn($this);
241-
}
242-
243239
if (
244240
$receivedType instanceof TemplateType
245241
&& $this->getBound()->isSuperTypeOf($receivedType->getBound())->yes()

src/Type/IterableType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ public function isLiteralString(): TrinaryLogic
256256

257257
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
258258
{
259-
if ($receivedType instanceof UnionType) {
259+
if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
260260
return $receivedType->inferTemplateTypesOn($this);
261261
}
262262

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,12 @@ public function dataFileAsserts(): iterable
824824
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6889.php');
825825
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6891.php');
826826
yield from $this->gatherAssertTypes(__DIR__ . '/data/simplexml.php');
827+
828+
if (PHP_VERSION_ID >= 80100) {
829+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6904.php');
830+
}
831+
832+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6917.php');
827833
}
828834

829835
/**
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug6904;
4+
5+
use stdClass;
6+
use function PHPStan\Testing\assertType;
7+
8+
/**
9+
* @template T
10+
*/
11+
interface Collection
12+
{
13+
}
14+
15+
16+
/**
17+
* @template T
18+
*/
19+
interface Selectable
20+
{
21+
/** @return T */
22+
public function first();
23+
}
24+
25+
26+
class HelloWorld
27+
{
28+
/**
29+
* @var Collection<stdClass>&Selectable<stdClass>
30+
*/
31+
public Collection&Selectable $items;
32+
33+
/**
34+
* @param Selectable<TValue> $selectable
35+
* @return TValue
36+
*
37+
* @template TValue
38+
*/
39+
private function matchOne(Selectable $selectable)
40+
{
41+
return $selectable->first();
42+
}
43+
44+
public function run(): void
45+
{
46+
assertType('stdClass', $this->matchOne($this->items));
47+
}
48+
}

0 commit comments

Comments
 (0)