1717use PHPStan \Type \Constant \ConstantBooleanType ;
1818use PHPStan \Type \NullType ;
1919use PHPStan \Type \Type ;
20+ use PHPStan \Type \UnionType as PHPStanUnionType ;
2021use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfoFactory ;
2122use Rector \BetterPhpDocParser \PhpDocManipulator \PhpDocTypeChanger ;
2223use Rector \NodeTypeResolver \Node \AttributeKey ;
2324use Rector \PhpParser \AstResolver ;
2425use Rector \Rector \AbstractRector ;
26+ use Rector \StaticTypeMapper \StaticTypeMapper ;
2527use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
2628use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
2729
@@ -35,7 +37,8 @@ final class DowngradeStandaloneNullTrueFalseReturnTypeRector extends AbstractRec
3537 public function __construct (
3638 private readonly PhpDocInfoFactory $ phpDocInfoFactory ,
3739 private readonly PhpDocTypeChanger $ phpDocTypeChanger ,
38- private readonly AstResolver $ astResolver
40+ private readonly AstResolver $ astResolver ,
41+ private readonly StaticTypeMapper $ staticTypeMapper
3942 ) {
4043 }
4144
@@ -83,6 +86,25 @@ public function run(): mixed
8386 public function refactor (Node $ node ): ?Node
8487 {
8588 $ returnType = $ node ->returnType ;
89+
90+ if ($ returnType instanceof NullableType && $ returnType ->type instanceof Identifier) {
91+ $ innerName = $ this ->getName ($ returnType ->type );
92+ if (in_array ($ innerName , ['false ' , 'true ' ], true )) {
93+ $ phpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
94+ $ unionType = new PHPStanUnionType ([new ConstantBooleanType ($ innerName === 'true ' ), new NullType ()]);
95+ $ this ->phpDocTypeChanger ->changeReturnType ($ node , $ phpDocInfo , $ unionType );
96+ $ node ->returnType = new NullableType (new Identifier ('bool ' ));
97+
98+ return $ node ;
99+ }
100+
101+ return null ;
102+ }
103+
104+ if ($ returnType instanceof UnionType) {
105+ return $ this ->refactorUnionType ($ node , $ returnType );
106+ }
107+
86108 if (! $ returnType instanceof Identifier) {
87109 return null ;
88110 }
@@ -102,6 +124,41 @@ public function refactor(Node $node): ?Node
102124 return $ node ;
103125 }
104126
127+ /**
128+ * @param ClassMethod|Function_|Closure|ArrowFunction $node
129+ */
130+ private function refactorUnionType (Node $ node , UnionType $ unionType ): ?Node
131+ {
132+ $ hasChanged = false ;
133+
134+ $ originalUnionType = clone $ unionType ;
135+ foreach ($ unionType ->types as $ key => $ type ) {
136+ if ($ type instanceof Identifier) {
137+ $ name = $ this ->getName ($ type );
138+ if (! in_array ($ name , ['true ' , 'false ' ], true )) {
139+ continue ;
140+ }
141+
142+ $ unionType ->types [$ key ] = new Identifier ('bool ' );
143+ $ hasChanged = true ;
144+ }
145+ }
146+
147+ if (! $ hasChanged ) {
148+ return null ;
149+ }
150+
151+ $ unionType ->types = array_values (array_unique ($ unionType ->types , SORT_REGULAR ));
152+ $ phpStanType = $ this ->staticTypeMapper ->mapPhpParserNodePHPStanType ($ originalUnionType );
153+
154+ $ phpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
155+ $ this ->phpDocTypeChanger ->changeReturnType ($ node , $ phpDocInfo , $ phpStanType );
156+
157+ $ node ->returnType = $ unionType ;
158+
159+ return $ node ;
160+ }
161+
105162 private function resolveType (Identifier $ identifier ): Type
106163 {
107164 $ nodeName = $ this ->getName ($ identifier );
0 commit comments