Skip to content

Commit 1292afe

Browse files
authored
Fix wrong positives about templates in conditional types
1 parent d943d58 commit 1292afe

File tree

7 files changed

+114
-7
lines changed

7 files changed

+114
-7
lines changed

src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Type\TypeTraverser;
1414
use PHPStan\Type\VerbosityLevel;
1515
use function array_key_exists;
16+
use function count;
1617
use function sprintf;
1718
use function substr;
1819

@@ -24,8 +25,6 @@ class ConditionalReturnTypeRuleHelper
2425
*/
2526
public function check(ParametersAcceptor $acceptor): array
2627
{
27-
$templateTypeMap = $acceptor->getTemplateTypeMap();
28-
2928
$conditionalTypes = [];
3029
$parametersByName = [];
3130
foreach ($acceptor->getParameters() as $parameter) {
@@ -55,7 +54,17 @@ public function check(ParametersAcceptor $acceptor): array
5554
if ($subjectType instanceof StaticType) {
5655
continue;
5756
}
58-
if (!$subjectType instanceof TemplateType || $templateTypeMap->getType($subjectType->getName()) === null) {
57+
$templateTypes = [];
58+
TypeTraverser::map($subjectType, static function (Type $type, callable $traverse) use (&$templateTypes): Type {
59+
if ($type instanceof TemplateType) {
60+
$templateTypes[] = $type;
61+
return $type;
62+
}
63+
64+
return $traverse($type);
65+
});
66+
67+
if (count($templateTypes) === 0) {
5968
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type uses subject type %s which is not part of PHPDoc @template tags.', $subjectType->describe(VerbosityLevel::typeOnly())))->build();
6069
continue;
6170
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,8 @@ public function dataFileAsserts(): iterable
12161216
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
12171217
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
12181218
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
1219+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8609.php');
1220+
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/PhpDoc/data/bug-8609-function.php');
12191221
}
12201222

12211223
/**
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Bug8609TypeInference;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T of bool
9+
*/
10+
class Foo
11+
{
12+
13+
/** @return (T is true ? 'foo' : 'bar') */
14+
public function doFoo(): string
15+
{
16+
17+
}
18+
19+
}
20+
21+
class Bar
22+
{
23+
24+
/**
25+
* @param Foo<true> $f
26+
* @param Foo<false> $g
27+
* @param Foo<bool> $h
28+
* @param Foo $i
29+
*/
30+
public function doFoo(Foo $f, Foo $g, Foo $h, Foo $i): void
31+
{
32+
assertType('\'foo\'', $f->doFoo());
33+
assertType('\'bar\'', $g->doFoo());
34+
assertType('\'bar\'|\'foo\'', $h->doFoo());
35+
assertType('\'bar\'|\'foo\'', $i->doFoo());
36+
}
37+
38+
}

tests/PHPStan/Rules/PhpDoc/FunctionConditionalReturnTypeRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@ public function testRule(): void
6363
]);
6464
}
6565

66+
public function testBug8609(): void
67+
{
68+
$this->analyse([__DIR__ . '/data/bug-8609-function.php'], []);
69+
}
70+
6671
}

tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ public function testRule(): void
2323
'Conditional return type uses subject type stdClass which is not part of PHPDoc @template tags.',
2424
48,
2525
],
26-
[
27-
'Conditional return type uses subject type TAboveClass which is not part of PHPDoc @template tags.',
28-
57,
29-
],
3026
[
3127
'Conditional return type references unknown parameter $j.',
3228
65,
@@ -80,4 +76,9 @@ public function testBug8284(): void
8076
]);
8177
}
8278

79+
public function testBug8609(): void
80+
{
81+
$this->analyse([__DIR__ . '/data/bug-8609.php'], []);
82+
}
83+
8384
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Bug8609Function;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T of list<string>|list<list<string>>
9+
* @param T $bar
10+
*
11+
* @return (T[0] is string ? array{T} : T)
12+
*/
13+
function foo(array $bar) : array{ return is_string($bar[0]) ? [$bar] : $bar; }
14+
15+
function(): void {
16+
assertType('array{array{string, string}}', foo(['foo', 'bar']));
17+
assertType('array{array{string, string}, array{string, string}}', foo([['foo','bar'],['xyz','asd']]));
18+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc\data;
4+
5+
/**
6+
* @template T of int
7+
*/
8+
class A {
9+
/** @var T */
10+
private $value;
11+
12+
/** @param T $value */
13+
public function __construct($value) {
14+
$this->value = $value;
15+
}
16+
17+
/**
18+
* @template C of int
19+
*
20+
* @param C $coefficient
21+
*
22+
* @return (
23+
* T is positive-int
24+
* ? (C is positive-int ? positive-int : negative-int)
25+
* : T is negative-int
26+
* ? (C is positive-int ? negative-int : positive-int)
27+
* : (T is 0 ? 0 : int)
28+
* )
29+
* )
30+
*/
31+
public function multiply(int $coefficient): int {
32+
return $this->value * $coefficient;
33+
}
34+
}

0 commit comments

Comments
 (0)