@@ -150,6 +150,8 @@ class MutatingScope implements Scope
150150
151151 private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4 ;
152152
153+ private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid ' ;
154+
153155 /** @var Type[] */
154156 private array $ resolvedTypes = [];
155157
@@ -676,6 +678,10 @@ private function getNodeKey(Expr $node): string
676678 $ key .= '/* ' . $ node ->getAttribute ('startFilePos ' ) . '*/ ' ;
677679 }
678680
681+ if ($ node ->getAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME ) === true ) {
682+ $ key .= '/* ' . self ::KEEP_VOID_ATTRIBUTE_NAME . '*/ ' ;
683+ }
684+
679685 return $ key ;
680686 }
681687
@@ -1236,7 +1242,7 @@ private function resolveType(string $exprString, Expr $node): Type
12361242 new VoidType (),
12371243 ]);
12381244 } else {
1239- $ returnType = $ arrowScope ->getType ($ node ->expr );
1245+ $ returnType = $ arrowScope ->getKeepVoidType ($ node ->expr );
12401246 if ($ node ->returnType !== null ) {
12411247 $ returnType = TypehintHelper::decideType ($ this ->getFunctionType ($ node ->returnType , false , false ), $ returnType );
12421248 }
@@ -1492,6 +1498,9 @@ private function resolveType(string $exprString, Expr $node): Type
14921498 $ matchScope = $ this ;
14931499 foreach ($ node ->arms as $ arm ) {
14941500 if ($ arm ->conds === null ) {
1501+ if ($ node ->hasAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME )) {
1502+ $ arm ->body ->setAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME , $ node ->getAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME ));
1503+ }
14951504 $ types [] = $ matchScope ->getType ($ arm ->body );
14961505 continue ;
14971506 }
@@ -1521,6 +1530,9 @@ private function resolveType(string $exprString, Expr $node): Type
15211530
15221531 if (!$ filteringExprType ->isFalse ()->yes ()) {
15231532 $ truthyScope = $ matchScope ->filterByTruthyValue ($ filteringExpr );
1533+ if ($ node ->hasAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME )) {
1534+ $ arm ->body ->setAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME , $ node ->getAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME ));
1535+ }
15241536 $ types [] = $ truthyScope ->getType ($ arm ->body );
15251537 }
15261538
@@ -1946,7 +1958,7 @@ private function resolveType(string $exprString, Expr $node): Type
19461958 }
19471959 }
19481960
1949- return $ parametersAcceptor ->getReturnType ();
1961+ return $ this -> transformVoidToNull ( $ parametersAcceptor ->getReturnType (), $ node );
19501962 }
19511963
19521964 return new MixedType ();
@@ -1986,6 +1998,25 @@ private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type
19861998 return $ type ;
19871999 }
19882000
2001+ private function transformVoidToNull (Type $ type , Node $ node ): Type
2002+ {
2003+ if ($ node ->getAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME ) === true ) {
2004+ return $ type ;
2005+ }
2006+
2007+ return TypeTraverser::map ($ type , static function (Type $ type , callable $ traverse ): Type {
2008+ if ($ type instanceof UnionType || $ type instanceof IntersectionType) {
2009+ return $ traverse ($ type );
2010+ }
2011+
2012+ if ($ type ->isVoid ()->yes ()) {
2013+ return new NullType ();
2014+ }
2015+
2016+ return $ type ;
2017+ });
2018+ }
2019+
19892020 /**
19902021 * @param callable(Type): ?bool $typeCallback
19912022 */
@@ -2173,6 +2204,14 @@ public function getNativeType(Expr $expr): Type
21732204 return $ this ->promoteNativeTypes ()->getType ($ expr );
21742205 }
21752206
2207+ public function getKeepVoidType (Expr $ node ): Type
2208+ {
2209+ $ clonedNode = clone $ node ;
2210+ $ clonedNode ->setAttribute (self ::KEEP_VOID_ATTRIBUTE_NAME , true );
2211+
2212+ return $ this ->getType ($ clonedNode );
2213+ }
2214+
21762215 /**
21772216 * @api
21782217 * @deprecated Use getNativeType()
@@ -5103,7 +5142,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
51035142 $ normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments ($ parametersAcceptor , $ methodCall );
51045143 }
51055144 if ($ normalizedMethodCall === null ) {
5106- return $ parametersAcceptor ->getReturnType ();
5145+ return $ this -> transformVoidToNull ( $ parametersAcceptor ->getReturnType (), $ methodCall );
51075146 }
51085147
51095148 $ resolvedTypes = [];
@@ -5142,10 +5181,10 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
51425181 }
51435182
51445183 if (count ($ resolvedTypes ) > 0 ) {
5145- return TypeCombinator::union (...$ resolvedTypes );
5184+ return $ this -> transformVoidToNull ( TypeCombinator::union (...$ resolvedTypes), $ methodCall );
51465185 }
51475186
5148- return $ parametersAcceptor ->getReturnType ();
5187+ return $ this -> transformVoidToNull ( $ parametersAcceptor ->getReturnType (), $ methodCall );
51495188 }
51505189
51515190 /** @api */
0 commit comments