Skip to content

Commit cba95b2

Browse files
rvanvelzenjiripudil
authored andcommitted
Cover more situations and add tests
1 parent c6e8ce5 commit cba95b2

File tree

4 files changed

+98
-1
lines changed

4 files changed

+98
-1
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
use Traversable;
107107
use function array_key_exists;
108108
use function array_map;
109+
use function array_values;
109110
use function count;
110111
use function explode;
111112
use function get_class;
@@ -792,6 +793,15 @@ static function (string $variance): TemplateTypeVariance {
792793

793794
$classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName);
794795
if ($classReflection->isGeneric()) {
796+
$templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes());
797+
for ($i = count($genericTypes), $templateTypesCount = count($templateTypes); $i < $templateTypesCount; $i++) {
798+
$templateType = $templateTypes[$i];
799+
if (!$templateType instanceof TemplateType || $templateType->getDefault() === null) {
800+
continue;
801+
}
802+
$genericTypes[] = $templateType->getDefault();
803+
}
804+
795805
if (in_array($mainTypeClassName, [
796806
Traversable::class,
797807
IteratorAggregate::class,

src/Type/Generic/TemplateTypeMap.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Type\NeverType;
66
use PHPStan\Type\Type;
77
use PHPStan\Type\TypeCombinator;
8+
use PHPStan\Type\TypeTraverser;
89
use PHPStan\Type\TypeUtils;
910
use function array_key_exists;
1011
use function count;
@@ -211,7 +212,10 @@ public function resolveToBounds(): self
211212
if ($this->resolvedToBounds !== null) {
212213
return $this->resolvedToBounds;
213214
}
214-
return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::resolveToBounds($type));
215+
return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TypeTraverser::map(
216+
$type,
217+
static fn (Type $type, callable $traverse): Type => $type instanceof TemplateType ? $traverse($type->getDefault() ?? $type->getBound()) : $traverse($type),
218+
));
215219
}
216220

217221
/**

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ private static function findTestFiles(): iterable
208208
yield __DIR__ . '/../Rules/Classes/data/mixin-trait-use.php';
209209

210210
yield __DIR__ . '/data/bug-4801.php';
211+
yield __DIR__ . '/data/template-default.php';
211212
}
212213

213214
/**
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace TemplateDefault;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T1 = true
9+
* @template T2 = true
10+
*/
11+
class Test
12+
{
13+
}
14+
15+
/**
16+
* @param Test<false> $one
17+
* @param Test<false, false> $two
18+
* @param Test<false, false, false> $three
19+
*/
20+
function foo(Test $one, Test $two, Test $three)
21+
{
22+
assertType('TemplateDefault\\Test<false, true>', $one);
23+
assertType('TemplateDefault\\Test<false, false>', $two);
24+
assertType('TemplateDefault\\Test<false, false, false>', $three);
25+
}
26+
27+
28+
/**
29+
* @template S = false
30+
* @template T = false
31+
*/
32+
class Builder
33+
{
34+
/**
35+
* @phpstan-self-out self<true, T>
36+
*/
37+
public function one(): void
38+
{
39+
}
40+
41+
/**
42+
* @phpstan-self-out self<S, true>
43+
*/
44+
public function two(): void
45+
{
46+
}
47+
48+
/**
49+
* @return ($this is self<true, true> ? void : never)
50+
*/
51+
public function execute(): void
52+
{
53+
}
54+
}
55+
56+
function () {
57+
$qb = new Builder();
58+
assertType('TemplateDefault\\Builder<false, false>', $qb);
59+
$qb->one();
60+
assertType('TemplateDefault\\Builder<true, false>', $qb);
61+
$qb->two();
62+
assertType('TemplateDefault\\Builder<true, true>', $qb);
63+
assertType('void', $qb->execute());
64+
};
65+
66+
function () {
67+
$qb = new Builder();
68+
assertType('TemplateDefault\\Builder<false, false>', $qb);
69+
$qb->two();
70+
assertType('TemplateDefault\\Builder<false, true>', $qb);
71+
$qb->one();
72+
assertType('TemplateDefault\\Builder<true, true>', $qb);
73+
assertType('void', $qb->execute());
74+
};
75+
76+
function () {
77+
$qb = new Builder();
78+
assertType('TemplateDefault\\Builder<false, false>', $qb);
79+
$qb->one();
80+
assertType('TemplateDefault\\Builder<true, false>', $qb);
81+
assertType('*NEVER*', $qb->execute());
82+
};

0 commit comments

Comments
 (0)