Skip to content

Commit 092cba3

Browse files
phpstan-botclaude
andcommitted
Use actual reproducer from issue #8048 and add NSRT test
The previous test didn't match the playground reproducer — it used @return T|null instead of the conditional return type that triggers the bug. Replace with the actual reproducer: a conditional return type ($responseType is class-string<T> ? T : null) called with null. Add NSRT test verifying that request(null) resolves to null and request(CustomResponse::class) resolves to CustomResponse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 32f9c23 commit 092cba3

2 files changed

Lines changed: 50 additions & 19 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug8048Nsrt;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface CustomResponseInterface {}
8+
9+
class CustomResponse implements CustomResponseInterface {}
10+
11+
class ApiService
12+
{
13+
/**
14+
* @template T of CustomResponseInterface
15+
*
16+
* @param class-string<T>|null $responseType
17+
*
18+
* @return ($responseType is class-string<T> ? T : null)
19+
*/
20+
public function request(?string $responseType = null): ?CustomResponseInterface
21+
{
22+
if ($responseType === null) {
23+
return null;
24+
}
25+
26+
return new CustomResponse();
27+
}
28+
}
29+
30+
function (): void {
31+
assertType('null', (new ApiService())->request(null));
32+
assertType('Bug8048Nsrt\CustomResponse', (new ApiService())->request(CustomResponse::class));
33+
};
Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
1-
<?php
1+
<?php declare(strict_types=1);
22

33
namespace Bug8048;
44

5-
interface CustomResponseInterface
6-
{
7-
}
5+
interface CustomResponseInterface {}
86

9-
final class CustomResponse implements CustomResponseInterface
10-
{
11-
}
7+
class CustomResponse implements CustomResponseInterface {}
128

13-
final class ApiService
9+
class ApiService
1410
{
1511
/**
1612
* @template T of CustomResponseInterface
17-
* @param class-string<T> $class
18-
* @return T|null
13+
*
14+
* @param class-string<T>|null $responseType
15+
*
16+
* @return ($responseType is class-string<T> ? T : null)
1917
*/
20-
public function request(string $class): ?CustomResponseInterface
18+
public function request(?string $responseType = null): ?CustomResponseInterface
2119
{
20+
if ($responseType === null) {
21+
return null;
22+
}
23+
2224
return new CustomResponse();
2325
}
2426
}
2527

26-
final class Consumer
27-
{
28-
public function test(): void
29-
{
30-
$apiService = new ApiService();
31-
$result = $apiService->request(CustomResponse::class);
32-
}
33-
}
28+
function (): void {
29+
(new ApiService())->request(null);
30+
(new ApiService())->request(CustomResponse::class);
31+
};

0 commit comments

Comments
 (0)