Skip to content

Commit 1c822e2

Browse files
phpstan-botgithub-actions[bot]claude
authored
Fix phpstan/phpstan#9634: Cannot use "T is never" in conditional return (#5082)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 99d83ed commit 1c822e2

File tree

6 files changed

+102
-0
lines changed

6 files changed

+102
-0
lines changed

src/Type/NeverType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\ShouldNotHappenException;
1515
use PHPStan\TrinaryLogic;
1616
use PHPStan\Type\Enum\EnumCaseObjectType;
17+
use PHPStan\Type\Generic\TemplateType;
1718
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
1819
use PHPStan\Type\Traits\NonGenericTypeTrait;
1920
use PHPStan\Type\Traits\NonRemoveableTypeTrait;
@@ -81,6 +82,10 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
8182
return IsSuperTypeOfResult::createYes();
8283
}
8384

85+
if ($type instanceof TemplateType) {
86+
return IsSuperTypeOfResult::createMaybe();
87+
}
88+
8489
return IsSuperTypeOfResult::createNo();
8590
}
8691

src/Type/NonAcceptingNeverType.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PHPStan\Type;
44

5+
use PHPStan\Type\Generic\TemplateType;
6+
57
/** @api */
68
class NonAcceptingNeverType extends NeverType
79
{
@@ -21,6 +23,10 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
2123
return IsSuperTypeOfResult::createMaybe();
2224
}
2325

26+
if ($type instanceof TemplateType) {
27+
return IsSuperTypeOfResult::createMaybe();
28+
}
29+
2430
return IsSuperTypeOfResult::createNo();
2531
}
2632

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10938;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template TKey
9+
* @template TValue
10+
* @extends \IteratorAggregate<TKey, TValue>
11+
*/
12+
interface Collection extends \IteratorAggregate
13+
{
14+
/**
15+
* @return (TValue is never ? true : bool)
16+
*/
17+
function isEmpty(): bool;
18+
}
19+
20+
/** @param Collection<never, never> $c */
21+
function emptyCollection(Collection $c): void {
22+
assertType('true', $c->isEmpty());
23+
}
24+
25+
/** @param Collection<int, string> $c */
26+
function nonEmptyCollection(Collection $c): void {
27+
assertType('bool', $c->isEmpty());
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9634;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/** @template T */
8+
interface Option {
9+
/** @return self<never> */
10+
static function none(): self;
11+
12+
/** @return T */
13+
function unwrap(): mixed;
14+
15+
/**
16+
* @return (T is never ? false : bool)
17+
*/
18+
function isSome(): bool;
19+
}
20+
21+
/** @param Option<never> $o */
22+
function f(Option $o): void {
23+
assertType('false', $o->isSome());
24+
}
25+
26+
/** @param Option<int> $o */
27+
function g(Option $o): void {
28+
assertType('bool', $o->isSome());
29+
}

tests/PHPStan/Rules/PhpDoc/MethodConditionalReturnTypeRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,14 @@ public function testBug11939(): void
102102
$this->analyse([__DIR__ . '/data/bug-11939.php'], []);
103103
}
104104

105+
public function testBug9634(): void
106+
{
107+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-9634.php'], []);
108+
}
109+
110+
public function testBug10938(): void
111+
{
112+
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10938.php'], []);
113+
}
114+
105115
}

tests/PHPStan/Type/TemplateTypeTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,30 @@ public static function dataIsSuperTypeOf(): array
281281
TrinaryLogic::createMaybe(),
282282
TrinaryLogic::createYes(),
283283
],
284+
23 => [
285+
$templateType('T', null),
286+
new NeverType(),
287+
TrinaryLogic::createYes(), // T isSuperTypeTo *NEVER* - never is bottom type
288+
TrinaryLogic::createMaybe(), // *NEVER* isSuperTypeTo T - T could be never
289+
],
290+
24 => [
291+
$templateType('T', null),
292+
new NonAcceptingNeverType(),
293+
TrinaryLogic::createYes(), // T isSuperTypeTo never - never is bottom type
294+
TrinaryLogic::createMaybe(), // never isSuperTypeTo T - T could be never
295+
],
296+
25 => [
297+
$templateType('T', new ObjectType('DateTime')),
298+
new NeverType(),
299+
TrinaryLogic::createYes(), // (T of DateTime) isSuperTypeTo *NEVER* - never is bottom type
300+
TrinaryLogic::createMaybe(), // *NEVER* isSuperTypeTo (T of DateTime) - T could be never
301+
],
302+
26 => [
303+
$templateType('T', new ObjectType('DateTime')),
304+
new NonAcceptingNeverType(),
305+
TrinaryLogic::createYes(), // (T of DateTime) isSuperTypeTo never - never is bottom type
306+
TrinaryLogic::createMaybe(), // never isSuperTypeTo (T of DateTime) - T could be never
307+
],
284308
];
285309
}
286310

0 commit comments

Comments
 (0)