File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -113,7 +113,13 @@ public function describe(VerbosityLevel $level): string
113113
114114 public function isResolvable (): bool
115115 {
116- return !TypeUtils::containsTemplateType ($ this ->subject ) && !TypeUtils::containsTemplateType ($ this ->target );
116+ if (!TypeUtils::containsTemplateType ($ this ->subject ) && !TypeUtils::containsTemplateType ($ this ->target )) {
117+ return true ;
118+ }
119+
120+ $ isSuperType = $ this ->target ->isSuperTypeOf ($ this ->subject );
121+
122+ return $ isSuperType ->yes () || $ isSuperType ->no ();
117123 }
118124
119125 protected function getResult (): Type
Original file line number Diff line number Diff line change 1+ <?php // lint >= 8.0
2+
3+ declare (strict_types = 1 );
4+
5+ namespace Bug11894Nsrt ;
6+
7+ use function PHPStan \Testing \assertType ;
8+
9+ /**
10+ * @template T
11+ * @param T $a
12+ * @return (T is string ? string : T)
13+ */
14+ function conditionalReturn (mixed $ a ): mixed
15+ {
16+ if (!is_string ($ a )) {
17+ return $ a ;
18+ }
19+ return trim ($ a );
20+ }
21+
22+ /**
23+ * @template T of string|null
24+ * @param T $a
25+ */
26+ function testNarrowedToString (mixed $ a ): void
27+ {
28+ if (!is_string ($ a )) {
29+ return ;
30+ }
31+ assertType ('string ' , conditionalReturn ($ a ));
32+ }
33+
34+ /**
35+ * @template T of int|null
36+ * @param T $a
37+ */
38+ function testNarrowedToNonMatchingType (mixed $ a ): void
39+ {
40+ if (!is_int ($ a )) {
41+ return ;
42+ }
43+ assertType ('T of int (function Bug11894Nsrt\testNarrowedToNonMatchingType(), argument) ' , conditionalReturn ($ a ));
44+ }
45+
46+ /**
47+ * @template T of string|int
48+ * @param T $a
49+ */
50+ function testNotFullyNarrowable (mixed $ a ): void
51+ {
52+ assertType ('string|T of int (function Bug11894Nsrt\testNotFullyNarrowable(), argument) ' , conditionalReturn ($ a ));
53+ }
54+
55+ abstract class ConditionalArrayKeys
56+ {
57+ /**
58+ * @template TKey of array-key
59+ * @template TArray of array<TKey, mixed>
60+ * @param TArray $array
61+ * @return (TArray is non-empty-array ? non-empty-list<TKey> : list<TKey>)
62+ */
63+ abstract public function arrayKeys (array $ array ): array ;
64+
65+ /** @param non-empty-array<int, int> $nonEmpty */
66+ public function testMaybeStaysUnresolved (array $ nonEmpty ): void
67+ {
68+ assertType ('non-empty-list<int> ' , $ this ->arrayKeys ($ nonEmpty ));
69+ }
70+ }
Original file line number Diff line number Diff line change 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+ $ x = rand (0 , 1 ) ? CustomResponse::class : null ;
34+ assertType ('Bug8048Nsrt\CustomResponse|null ' , (new ApiService ())->request ($ x ));
35+ };
Original file line number Diff line number Diff line change @@ -2871,4 +2871,9 @@ public function testBug3842(): void
28712871 $ this ->analyse ([__DIR__ . '/../../Analyser/nsrt/bug-3842.php ' ], []);
28722872 }
28732873
2874+ public function testBug11894 (): void
2875+ {
2876+ $ this ->analyse ([__DIR__ . '/data/bug-11894.php ' ], []);
2877+ }
2878+
28742879}
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace Bug11894 ;
4+
5+ /**
6+ * @template T of string|null
7+ * @param T $a
8+ */
9+ function test (mixed $ a ): mixed
10+ {
11+ if (!is_string ($ a )) {
12+ return $ a ;
13+ }
14+
15+ return conditionalReturn ($ a );
16+ }
17+
18+ /**
19+ * @template T
20+ * @param T $a
21+ * @return (T is string ? string : T)
22+ */
23+ function conditionalReturn (mixed $ a ): mixed
24+ {
25+ if (!is_string ($ a )) {
26+ return $ a ;
27+ }
28+
29+ return trim ($ a );
30+ }
31+
32+ /**
33+ * @template T of string|null
34+ * @param T $a
35+ */
36+ function testNegated (mixed $ a ): mixed
37+ {
38+ if (!is_string ($ a )) {
39+ return $ a ;
40+ }
41+
42+ return conditionalReturnNegated ($ a );
43+ }
44+
45+ /**
46+ * @template T
47+ * @param T $a
48+ * @return (T is not string ? T : string)
49+ */
50+ function conditionalReturnNegated (mixed $ a ): mixed
51+ {
52+ if (!is_string ($ a )) {
53+ return $ a ;
54+ }
55+
56+ return trim ($ a );
57+ }
58+
59+ /**
60+ * @template T of int|null
61+ * @param T $a
62+ */
63+ function testNoRelation (mixed $ a ): mixed
64+ {
65+ if (!is_int ($ a )) {
66+ return $ a ;
67+ }
68+
69+ return conditionalReturn ($ a );
70+ }
71+
72+ /**
73+ * @template T of string|int
74+ * @param T $a
75+ */
76+ function testMaybeRelation (mixed $ a ): mixed
77+ {
78+ return conditionalReturn ($ a );
79+ }
Original file line number Diff line number Diff line change @@ -4062,4 +4062,20 @@ public function testBug14549(): void
40624062 ]);
40634063 }
40644064
4065+ public function testBug11894 (): void
4066+ {
4067+ $ this ->checkThisOnly = false ;
4068+ $ this ->checkNullables = true ;
4069+ $ this ->checkUnionTypes = true ;
4070+ $ this ->analyse ([__DIR__ . '/data/bug-11894.php ' ], []);
4071+ }
4072+
4073+ public function testBug8048 (): void
4074+ {
4075+ $ this ->checkThisOnly = false ;
4076+ $ this ->checkNullables = true ;
4077+ $ this ->checkUnionTypes = true ;
4078+ $ this ->analyse ([__DIR__ . '/data/bug-8048.php ' ], []);
4079+ }
4080+
40654081}
Original file line number Diff line number Diff line change @@ -1015,4 +1015,10 @@ public function testPipeOperator(): void
10151015 ]);
10161016 }
10171017
1018+ public function testBug11894 (): void
1019+ {
1020+ $ this ->checkThisOnly = false ;
1021+ $ this ->analyse ([__DIR__ . '/data/bug-11894.php ' ], []);
1022+ }
1023+
10181024}
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace Bug11894Methods ;
4+
5+ class Converter
6+ {
7+ /**
8+ * @template T
9+ * @param T $a
10+ * @return (T is string ? string : T)
11+ */
12+ public function conditionalReturn (mixed $ a ): mixed
13+ {
14+ if (!is_string ($ a )) {
15+ return $ a ;
16+ }
17+ return trim ($ a );
18+ }
19+
20+ /**
21+ * @template T
22+ * @param T $a
23+ * @return (T is string ? string : T)
24+ */
25+ public static function conditionalReturnStatic (mixed $ a ): mixed
26+ {
27+ if (!is_string ($ a )) {
28+ return $ a ;
29+ }
30+ return trim ($ a );
31+ }
32+ }
33+
34+ class Consumer
35+ {
36+ /**
37+ * @template T of string|null
38+ * @param T $a
39+ */
40+ public function testMethod (mixed $ a ): mixed
41+ {
42+ if (!is_string ($ a )) {
43+ return $ a ;
44+ }
45+
46+ $ c = new Converter ();
47+ return $ c ->conditionalReturn ($ a );
48+ }
49+
50+ /**
51+ * @template T of string|null
52+ * @param T $a
53+ */
54+ public function testStaticMethod (mixed $ a ): mixed
55+ {
56+ if (!is_string ($ a )) {
57+ return $ a ;
58+ }
59+
60+ return Converter::conditionalReturnStatic ($ a );
61+ }
62+
63+ /**
64+ * @template T of string|int
65+ * @param T $a
66+ */
67+ public function testMaybeMethod (mixed $ a ): mixed
68+ {
69+ $ c = new Converter ();
70+ return $ c ->conditionalReturn ($ a );
71+ }
72+
73+ /**
74+ * @template T of string|int
75+ * @param T $a
76+ */
77+ public function testMaybeStaticMethod (mixed $ a ): mixed
78+ {
79+ return Converter::conditionalReturnStatic ($ a );
80+ }
81+ }
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types=1 );
2+
3+ namespace Bug8048 ;
4+
5+ interface CustomResponseInterface {}
6+
7+ class CustomResponse implements CustomResponseInterface {}
8+
9+ class ApiService
10+ {
11+ /**
12+ * @template T of CustomResponseInterface
13+ *
14+ * @param class-string<T>|null $responseType
15+ *
16+ * @return ($responseType is class-string<T> ? T : null)
17+ */
18+ public function request (?string $ responseType = null ): ?CustomResponseInterface
19+ {
20+ if ($ responseType === null ) {
21+ return null ;
22+ }
23+
24+ return new CustomResponse ();
25+ }
26+ }
27+
28+ function (): void {
29+ (new ApiService ())->request (null );
30+ (new ApiService ())->request (CustomResponse::class);
31+ $ x = rand (0 , 1 ) ? CustomResponse::class : null ;
32+ (new ApiService ())->request ($ x );
33+ };
You can’t perform that action at this time.
0 commit comments