Repository: PHP-CS-Fixer/PHP-CS-Fixer (php)

Issue:
Invalid return generated by `simplified_null_return` with union typehint
## Bug report

### Description

When a function has `null|array` return typehint and the code return `null`, the `simplified_null_return` removes the `null` keyword, which generates an invalid code 

> Compile Error: A function with return type must return a value (did you mean "return null;" instead of "return;"?)

Here is a minimal reproducer

```php
    public function test(): null|array
    {
        if (true) {
            return null;
        }
        
        return [];
    }
```

Php-cs-fixer tries to patch this like

```patch
         if (true) {
-            return null;
+            return;
         }
 ```

### Runtime version

```
/composer/vendor/bin/php-cs-fixer -V
PHP CS Fixer 3.46.0 Three Keys by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.2.14
```

### Used command

```
/srv/api $ /composer/vendor/bin/php-cs-fixer fix --allow-risky yes --verbose --diff -vvv
PHP CS Fixer 3.46.0 Three Keys by Fabien Potencier and Dariusz Ruminski.
PHP runtime: 8.2.14
Loaded config default from "/srv/api/.php-cs-fixer.php".
Using cache file "/srv/api/.php_cs.cache".
 345/345 [████████████████████████████] 100%

   1) src/Command/ImportWorkoutCommand.php (simplified_null_return)
      ---------- begin diff ----------
--- /srv/api/src/Command/ImportWorkoutCommand.php
+++ /srv/api/src/Command/ImportWorkoutCommand.php
@@ -32,7 +32,7 @@
     public function test(): null|array
     {
         if (true) {
-            return null;
+            return;
         }
 
         return [];

      ----------- end diff -----------


Fixed 1 of 345 files in 0.452 seconds, 12.000 MB memory used
```

### Configuration file

<!--
  Provide content of configuration file you used (.php-cs-fixer[.dist].php).
-->

### Code snippet that reproduces the problem

```
    public function test(): null|array
    {
        if (true) {
            return null;
        }
        
        return [];
    }
```

Pull request:
fix: SimplifiedNullReturnFixer - support array return typehint
closes #7718 

Patch:
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index 2ad00b5e7..13db70262 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -21,7 +21,7 @@ parameters:
         -
             message: '#^Method PhpCsFixer\\Tests\\.+::provide.+Cases\(\) return type has no value type specified in iterable type iterable\.$#'
             path: tests
-            count: 1034
+            count: 1025
 
         -
             message: '#Call to static method .+ with .+ will always evaluate to true.$#'
diff --git a/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php b/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php
index bc222aa31..b745b212a 100644
--- a/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php
+++ b/src/Fixer/ReturnNotation/SimplifiedNullReturnFixer.php
@@ -125,7 +125,8 @@ private function isStrictOrNullableReturnTypeFunction(Tokens $tokens, int $retur
         } while ($closingCurlyBraceIndex < $returnIndex);
 
         $possibleVoidIndex = $tokens->getPrevMeaningfulToken($openingCurlyBraceIndex);
-        $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind(T_STRING) && 'void' !== $tokens[$possibleVoidIndex]->getContent();
+        $isStrictReturnType = $tokens[$possibleVoidIndex]->isGivenKind([T_STRING, CT::T_ARRAY_TYPEHINT])
+            && 'void' !== $tokens[$possibleVoidIndex]->getContent();
 
         $nullableTypeIndex = $tokens->getNextTokenOfKind($functionIndex, [[CT::T_NULLABLE_TYPE]]);
         $isNullableReturnType = null !== $nullableTypeIndex && $nullableTypeIndex < $openingCurlyBraceIndex;


Test patch:
diff --git a/tests/Fixer/ReturnNotation/NoUselessReturnFixerTest.php b/tests/Fixer/ReturnNotation/NoUselessReturnFixerTest.php
index d8d6daf26..e5571de57 100644
--- a/tests/Fixer/ReturnNotation/NoUselessReturnFixerTest.php
+++ b/tests/Fixer/ReturnNotation/NoUselessReturnFixerTest.php
@@ -31,6 +31,9 @@ public function testFix(string $expected, ?string $input = null): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFixCases(): iterable
     {
         yield [
diff --git a/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php b/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php
index ba7805237..87b379169 100644
--- a/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php
+++ b/tests/Fixer/ReturnNotation/ReturnAssignmentFixerTest.php
@@ -31,6 +31,9 @@ public function testFixNestedFunctions(string $expected, string $input): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFixNestedFunctionsCases(): iterable
     {
         yield [
@@ -189,6 +192,9 @@ public function testFix(string $expected, string $input): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFixCases(): iterable
     {
         yield [
@@ -819,6 +825,9 @@ public function testDoNotFix(string $expected): void
         $this->doTest($expected);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideDoNotFixCases(): iterable
     {
         yield 'invalid reference stays invalid' => [
@@ -1261,6 +1270,9 @@ public function testDoNotFix80(string $expected): void
         $this->doTest($expected);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideDoNotFix80Cases(): iterable
     {
         yield 'try with non-capturing catch block' => [
@@ -1291,6 +1303,9 @@ public function testRepetitiveFix(string $expected, ?string $input = null): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideRepetitiveFixCases(): iterable
     {
         yield [
@@ -1344,6 +1359,9 @@ public function testFix80(string $expected, ?string $input = null): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFix80Cases(): iterable
     {
         yield 'match' => [
@@ -1394,6 +1412,9 @@ public function testFixPhp83(string $expected, ?string $input = null): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFixPhp83Cases(): iterable
     {
         yield 'anonymous readonly class' => [
diff --git a/tests/Fixer/ReturnNotation/SimplifiedNullReturnFixerTest.php b/tests/Fixer/ReturnNotation/SimplifiedNullReturnFixerTest.php
index a6c4ba3ae..04f803536 100644
--- a/tests/Fixer/ReturnNotation/SimplifiedNullReturnFixerTest.php
+++ b/tests/Fixer/ReturnNotation/SimplifiedNullReturnFixerTest.php
@@ -33,6 +33,9 @@ public function testFix(string $expected, ?string $input = null): void
         $this->doTest($expected, $input);
     }
 
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
     public static function provideFixCases(): iterable
     {
         // check correct statements aren't changed
@@ -90,4 +93,47 @@ public static function provideFixCases(): iterable
             '<?php function foo(): void { return; }',
         ];
     }
+
+    /**
+     * @requires PHP 8.0
+     *
+     * @dataProvider provideFix80Cases
+     */
+    public function testFix80(string $expected, ?string $input = null): void
+    {
+        $this->doTest($expected, $input);
+    }
+
+    /**
+     * @return iterable<array{0: non-empty-string, 1?: non-empty-string}>
+     */
+    public static function provideFix80Cases(): iterable
+    {
+        yield [
+            '<?php
+            function test(): null|int
+            {
+                if (true) { return null; }
+                return 42;
+            }',
+        ];
+
+        yield [
+            '<?php
+            function test(): null|array
+            {
+                if (true) { return null; }
+                return [];
+            }',
+        ];
+
+        yield [
+            '<?php
+            function test(): array|null
+            {
+                if (true) { return null; }
+                return [];
+            }',
+        ];
+    }
 }